From c396a00bbc045639c36cb6e75555079cfa1b8eb5 Mon Sep 17 00:00:00 2001 From: dimitar Date: Wed, 2 Apr 2025 21:33:11 +0200 Subject: [PATCH] document upload redesign --- backend/src/auth/auth.controller.ts | 10 +- backend/src/documents/documents.service.ts | 118 ++++--- .../documentUpload/DocumentUpload.jsx | 305 ++++++++++-------- 3 files changed, 242 insertions(+), 191 deletions(-) diff --git a/backend/src/auth/auth.controller.ts b/backend/src/auth/auth.controller.ts index 44b9d45..4f086c8 100644 --- a/backend/src/auth/auth.controller.ts +++ b/backend/src/auth/auth.controller.ts @@ -181,9 +181,9 @@ export class AuthController { return this.authService.resetPasswordWithToken(token, newPassword); } - @Post("logout") - async logout(@Request() req) { - await this.authService.logout(req.user.userId); - return { message: "Logged out successfully" }; - } + // @Post("logout") + // async logout(@Request() req) { + // await this.authService.logout(req.user.userId); + // return { message: "Logged out successfully" }; + // } } diff --git a/backend/src/documents/documents.service.ts b/backend/src/documents/documents.service.ts index 6558714..3c62bad 100644 --- a/backend/src/documents/documents.service.ts +++ b/backend/src/documents/documents.service.ts @@ -1,8 +1,13 @@ -import { Injectable, Logger, NotFoundException, ForbiddenException } from '@nestjs/common'; -import { PrismaService } from '../prisma/prisma.service'; -import { S3Service } from '../s3/s3.service'; -import { Document, User, Prisma } from '@prisma/client'; -import { EmailService } from '../email/email.service'; +import { + Injectable, + Logger, + NotFoundException, + ForbiddenException, +} from "@nestjs/common"; +import { PrismaService } from "../prisma/prisma.service"; +import { S3Service } from "../s3/s3.service"; +import { Document, User, Prisma } from "@prisma/client"; +import { EmailService } from "../email/email.service"; @Injectable() export class DocumentsService { @@ -13,8 +18,8 @@ export class DocumentsService { private readonly s3Service: S3Service, private readonly emailService: EmailService, ) { - this.logger.log('DocumentsService initialized with EmailService'); - this.logger.debug('EmailService instance:', { + this.logger.log("DocumentsService initialized with EmailService"); + this.logger.debug("EmailService instance:", { exists: !!this.emailService, type: typeof this.emailService, methods: Object.keys(Object.getPrototypeOf(this.emailService)), @@ -31,16 +36,19 @@ export class DocumentsService { }); } - async verifyDocumentAccess(documentId: number, userId: number): Promise { + async verifyDocumentAccess( + documentId: number, + userId: number, + ): Promise { const document = await this.prisma.document.findUnique({ where: { id: documentId }, include: { uploadedBy: true, sharedWith: { where: { - id: userId - } - } + id: userId, + }, + }, }, }); @@ -57,34 +65,34 @@ export class DocumentsService { where: { OR: [ { uploadedById: clientId }, - { + { sharedWith: { some: { - id: clientId - } - } - } - ] + id: clientId, + }, + }, + }, + ], }, include: { uploadedBy: { select: { id: true, name: true, - email: true - } + email: true, + }, }, sharedWith: { select: { id: true, name: true, - email: true - } - } + email: true, + }, + }, }, orderBy: { - createdAt: 'desc' - } + createdAt: "desc", + }, }); } @@ -92,34 +100,34 @@ export class DocumentsService { file: Express.Multer.File, title: string, sharedWithId: number, - uploadedById: number + uploadedById: number, ) { - this.logger.log('=== Starting document upload process ==='); - this.logger.debug('Upload parameters:', { + this.logger.log("=== Starting document upload process ==="); + this.logger.debug("Upload parameters:", { title, sharedWithId, uploadedById, fileName: file.originalname, fileSize: file.size, }); - + try { - this.logger.debug('Uploading file to S3...'); - const s3Key = await this.s3Service.uploadFile(file, 'documents'); + this.logger.debug("Uploading file to S3..."); + const s3Key = await this.s3Service.uploadFile(file, "documents"); this.logger.debug(`File uploaded to S3 successfully with key: ${s3Key}`); - - this.logger.debug('Creating document record in database...'); + + this.logger.debug("Creating document record in database..."); const document = await this.prisma.document.create({ data: { title, s3Key, - status: 'pending', + status: "completed", uploadedBy: { - connect: { id: uploadedById } + connect: { id: uploadedById }, }, sharedWith: { - connect: { id: sharedWithId } - } + connect: { id: sharedWithId }, + }, }, include: { uploadedBy: { @@ -138,7 +146,7 @@ export class DocumentsService { }, }, }); - this.logger.debug('Document record created:', { + this.logger.debug("Document record created:", { id: document.id, title: document.title, uploadedBy: document.uploadedBy, @@ -146,8 +154,8 @@ export class DocumentsService { }); // Send email notifications - this.logger.log('=== Starting email notification process ==='); - + this.logger.log("=== Starting email notification process ==="); + // Notify the user who the document is shared with const sharedUser = document.sharedWith[0]; if (sharedUser) { @@ -157,30 +165,32 @@ export class DocumentsService { name: sharedUser.name, }); try { - this.logger.debug('Calling EmailService.sendDocumentNotification for shared user...'); + this.logger.debug( + "Calling EmailService.sendDocumentNotification for shared user...", + ); await this.emailService.sendDocumentNotification( sharedUser.email, sharedUser.name, document.title, - 'shared' + "shared", ); - this.logger.debug('Shared user notification sent successfully'); + this.logger.debug("Shared user notification sent successfully"); } catch (error) { - this.logger.error('Failed to send notification to shared user:', { + this.logger.error("Failed to send notification to shared user:", { error: error.message, code: error.code, command: error.command, stack: error.stack, }); // Log the email service instance to verify it's properly injected - this.logger.debug('EmailService instance:', { + this.logger.debug("EmailService instance:", { exists: !!this.emailService, type: typeof this.emailService, methods: Object.keys(Object.getPrototypeOf(this.emailService)), }); } } else { - this.logger.warn('No shared user found in the document record'); + this.logger.warn("No shared user found in the document record"); } // Notify the uploader @@ -192,36 +202,38 @@ export class DocumentsService { name: uploader.name, }); try { - this.logger.debug('Calling EmailService.sendDocumentNotification for uploader...'); + this.logger.debug( + "Calling EmailService.sendDocumentNotification for uploader...", + ); await this.emailService.sendDocumentNotification( uploader.email, uploader.name, document.title, - 'uploaded' + "uploaded", ); - this.logger.debug('Uploader notification sent successfully'); + this.logger.debug("Uploader notification sent successfully"); } catch (error) { - this.logger.error('Failed to send notification to uploader:', { + this.logger.error("Failed to send notification to uploader:", { error: error.message, code: error.code, command: error.command, stack: error.stack, }); // Log the email service instance to verify it's properly injected - this.logger.debug('EmailService instance:', { + this.logger.debug("EmailService instance:", { exists: !!this.emailService, type: typeof this.emailService, methods: Object.keys(Object.getPrototypeOf(this.emailService)), }); } } else { - this.logger.warn('No uploader found in the document record'); + this.logger.warn("No uploader found in the document record"); } - this.logger.log('=== Document upload process completed ==='); + this.logger.log("=== Document upload process completed ==="); return document; } catch (error) { - this.logger.error('Error in uploadDocument:', { + this.logger.error("Error in uploadDocument:", { error: error.message, code: error.code, stack: error.stack, diff --git a/frontend/src/components/documentUpload/DocumentUpload.jsx b/frontend/src/components/documentUpload/DocumentUpload.jsx index 032f17a..975237f 100644 --- a/frontend/src/components/documentUpload/DocumentUpload.jsx +++ b/frontend/src/components/documentUpload/DocumentUpload.jsx @@ -1,13 +1,16 @@ -import { useState, useEffect } from 'react'; -import { uploadDocument, getAllUsers, getUserInfo } from '../../services/api'; +import { useState, useEffect } from "react"; +import { motion } from "framer-motion"; +import { FiUpload, FiLoader, FiCheckCircle, FiUsers } from "react-icons/fi"; +import { uploadDocument, getAllUsers, getUserInfo } from "../../services/api"; +import { Card } from "../UI/Card"; function DocumentUpload() { const [file, setFile] = useState(null); - const [title, setTitle] = useState(''); - const [selectedUsers, setSelectedUsers] = useState([]); + const [title, setTitle] = useState(""); + const [selectedUserId, setSelectedUserId] = useState(""); // Changed from array to single value const [availableUsers, setAvailableUsers] = useState([]); - const [status, setStatus] = useState('idle'); // idle, uploading, completed, failed - const [errorMessage, setErrorMessage] = useState(''); + const [status, setStatus] = useState("idle"); + const [errorMessage, setErrorMessage] = useState(""); const [currentUser, setCurrentUser] = useState(null); useEffect(() => { @@ -15,160 +18,196 @@ function DocumentUpload() { getCurrentUser(); }, []); - const fetchUsers = async () => { - try { - const response = await getAllUsers(); - setAvailableUsers(response.data.filter(user => !user.isAdmin)); - } catch (error) { - setErrorMessage('Failed to load users'); - } - }; - const getCurrentUser = async () => { try { const response = await getUserInfo(); - console.log('Current user data:', response.data); // Debug log setCurrentUser(response.data); } catch (error) { - console.error('Failed to get current user:', error); - setErrorMessage('Failed to get current user info'); + console.error("Failed to get current user:", error); + setErrorMessage("Failed to get current user info"); + } + }; + + const fetchUsers = async () => { + try { + const response = await getAllUsers(); + setAvailableUsers(response.data.filter((user) => !user.isAdmin)); + } catch (error) { + setErrorMessage("Failed to load users"); } }; const handleSubmit = async (event) => { event.preventDefault(); - - if (!file || !title || selectedUsers.length === 0 || !currentUser) { - setErrorMessage('Please provide all required information'); + + if (!file || !title || !selectedUserId || !currentUser?.id) { + setErrorMessage("Please provide all required information"); return; } - - setStatus('uploading'); - setErrorMessage(''); - + + // Convert IDs to numbers + const sharedWithId = parseInt(selectedUserId, 10); + const uploadedById = parseInt(currentUser.id, 10); + + if (isNaN(sharedWithId) || isNaN(uploadedById)) { + setErrorMessage("Invalid user IDs"); + return; + } + + setStatus("uploading"); + setErrorMessage(""); + const formData = new FormData(); - formData.append('file', file); - formData.append('title', title); - formData.append('sharedWithId', selectedUsers[0]); // Remove toString() - formData.append('uploadedById', currentUser.id); // Remove toString() - + formData.append("file", file); + formData.append("title", title); + formData.append("sharedWithId", sharedWithId); + formData.append("uploadedById", uploadedById); + // Debug log - console.log('Form Data Contents:', { - file: formData.get('file'), - title: formData.get('title'), - sharedWithId: formData.get('sharedWithId'), - uploadedById: formData.get('uploadedById') + console.log("Form Data:", { + title, + sharedWithId, + uploadedById, + fileName: file.name, }); - + try { - const response = await uploadDocument(formData); - console.log('Upload response:', response); - - setStatus('completed'); - setTitle(''); + await uploadDocument(formData); + setStatus("completed"); + setTitle(""); setFile(null); - setSelectedUsers([]); + setSelectedUserId(""); event.target.reset(); } catch (error) { - console.error('Upload error:', error); - setStatus('failed'); - setErrorMessage(error.response?.data?.message || 'Upload failed'); + console.error("Upload error:", error); + setStatus("failed"); + setErrorMessage(error.response?.data?.message || "Upload failed"); } }; - function handleFileChange(e) { - setFile(e.target.files[0]); - } - - const handleUserSelect = (e) => { - const values = Array.from(e.target.selectedOptions, option => option.value); - console.log('Selected user values:', values); - setSelectedUsers(values); - }; - return ( -
-

Upload Document

- -
-
- - setTitle(e.target.value)} - className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500" - required - /> -
+
+
+
+

+ Upload Document +

+

Share documents with your clients

+
-
- - -
- -
- - -
- - {errorMessage && ( -
- {errorMessage} -
- )} - - + + + {/* Title Input */} +
+ + setTitle(e.target.value)} + className="w-full px-4 py-2 rounded-lg bg-neutral-800/50 border border-neutral-700 + text-white placeholder-neutral-400 focus:outline-none focus:border-primary-500" + required + /> +
- {status === 'completed' && ( -
- Document uploaded successfully! -
- )} - + {/* File Input */} +
+ +
+ +
+
+ + {/* User Selection */} +
+ +
+ + +
+
+ + {/* Error Message */} + {errorMessage && ( +
+ {errorMessage} +
+ )} + + {/* Success Message */} + {status === "completed" && ( +
+ Document uploaded successfully! +
+ )} + + {/* Submit Button */} + + +
+ +
); } -export default DocumentUpload; \ No newline at end of file +export default DocumentUpload;