From 896537bf63d28fedad8e74720637856ee40d7d7d Mon Sep 17 00:00:00 2001 From: Jannatul Ferdous Iqra <118426185+iqra1503@users.noreply.github.com> Date: Tue, 10 Mar 2026 03:40:35 +0600 Subject: [PATCH] Fix file-based document updates and add admin document creation --- backend/app/routers/documents.py | 44 ++++++++++++++++++++++-- frontend/src/api/documents.js | 21 +++++++++++ frontend/src/components/DocumentForm.jsx | 37 ++++++++++---------- frontend/src/pages/AdminDashboard.jsx | 13 ++++--- 4 files changed, 88 insertions(+), 27 deletions(-) diff --git a/backend/app/routers/documents.py b/backend/app/routers/documents.py index cb78583..7553114 100644 --- a/backend/app/routers/documents.py +++ b/backend/app/routers/documents.py @@ -213,9 +213,13 @@ def get_document(document_id: int, current_user: User = Depends(get_current_user @router.put('/{document_id}', response_model=DocumentResponse) -def update_document( +async def update_document( document_id: int, - payload: DocumentUpdate, + request: Request, + file: UploadFile | None = File(default=None), + title: str | None = Form(default=None), + description: str | None = Form(default=None), + summary: str | None = Form(default=None), current_user: User = Depends(get_current_user), db: Session = Depends(get_db), ): @@ -226,7 +230,41 @@ def update_document( if current_user.role != 'admin' and document.created_by != current_user.id: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail='Not authorized') - updates = payload.model_dump(exclude_unset=True) + content_type = request.headers.get('content-type', '') + + if content_type.startswith('multipart/form-data'): + updates: dict[str, str] = {} + + if title is not None: + cleaned_title = title.strip() + if not cleaned_title: + raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail='Title cannot be empty') + updates['title'] = cleaned_title + + if description is not None: + updates['description'] = description.strip() + updates['source_type'] = 'typed' + updates['file_name'] = None + updates['file_type'] = None + + if file: + text_content, file_name, file_type = await extract_text_from_upload(file) + updates['description'] = text_content + updates['source_type'] = 'uploaded' + updates['file_name'] = file_name + updates['file_type'] = file_type + + if summary is not None: + cleaned_summary = summary.strip() + updates['summary'] = cleaned_summary + + else: + try: + payload = DocumentUpdate.model_validate(await request.json()) + except ValidationError as exc: + raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail='Invalid JSON payload') from exc + updates = payload.model_dump(exclude_unset=True) + if 'summary' in updates: updates['summary_embedding'] = json.dumps(generate_summary_embedding(updates['summary'])) diff --git a/frontend/src/api/documents.js b/frontend/src/api/documents.js index 8ba4994..06cdcb0 100644 --- a/frontend/src/api/documents.js +++ b/frontend/src/api/documents.js @@ -28,6 +28,27 @@ export const createDocumentApi = async (payload) => { } export const updateDocumentApi = async (id, payload) => { + if (payload.file) { + const formData = new FormData() + if (payload.title !== undefined) { + formData.append('title', payload.title) + } + if (payload.description !== undefined) { + formData.append('description', payload.description || '') + } + if (payload.summary !== undefined) { + formData.append('summary', payload.summary) + } + formData.append('file', payload.file) + + const { data } = await api.put(`/documents/${id}`, formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }) + return data + } + const { data } = await api.put(`/documents/${id}`, payload) return data } diff --git a/frontend/src/components/DocumentForm.jsx b/frontend/src/components/DocumentForm.jsx index 43368f8..3fc6691 100644 --- a/frontend/src/components/DocumentForm.jsx +++ b/frontend/src/components/DocumentForm.jsx @@ -40,6 +40,7 @@ const DocumentForm = ({ initialValues, onSubmit, onCancel, onRefreshSummary }) = } const handleRefreshSummary = async () => { + if (!onRefreshSummary) return setSummaryError('') const sourceDescription = selectedFile ? '' : form.description @@ -85,23 +86,21 @@ const DocumentForm = ({ initialValues, onSubmit, onCancel, onRefreshSummary }) = setForm((prev) => ({ ...prev, title: e.target.value }))} required />