# Implementation Plan: FP Attachment Library ## Overview This plan implements the FP Attachment Library feature, which allows users to attach existing CVE document library files to FP workflow submissions alongside traditional local file uploads. The implementation adds a new Document Search API endpoint, modifies two existing backend endpoints to handle library document references, and creates a shared AttachmentSourcePicker component used in both the create and edit modals. ## Tasks - [x] 1. Add Document Search API endpoint - [x] 1.1 Add `GET /api/documents/search` route in `backend/routes/ivantiFpWorkflow.js` - Add a new GET route handler for `/documents/search` inside `createIvantiFpWorkflowRouter` - Accept optional `q` query parameter for search term - When `q` is provided, query the `documents` table with `LIKE` matching against `name`, `cve_id`, and `vendor` columns (case-insensitive) - When `q` is empty or missing, return the most recent documents ordered by `uploaded_at DESC` - Limit results to 50 records maximum - Return each record with fields: `id`, `cve_id`, `vendor`, `name`, `type`, `file_size`, `mime_type`, `uploaded_at` - Protect with `requireAuth(db)` middleware - Return 500 with `{ error: 'Database error.' }` on DB failure - _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6_ - [x] 2. Modify backend to handle library document attachments on create - [x] 2.1 Update `POST /api/ivanti/fp-workflow` in `backend/routes/ivantiFpWorkflow.js` to accept `libraryDocIds` - Parse `libraryDocIds` from `req.body` as a JSON-encoded array (default to `[]` if absent) - Return 400 if `libraryDocIds` is not valid JSON - Validate each ID is a positive integer; return 400 identifying any invalid ID - Query the `documents` table for all referenced IDs; return 400 if any ID is not found - Read each library file from disk using `fs.readFileSync(file_path)`; if a file is missing on disk, log a warning and include `{ success: false, error: 'File not found on disk', source: 'library', documentId: id }` in attachment results, skip that file - Combine local file buffers (`req.files`) and library file buffers into a single `formFiles` array passed to `ivantiFormPost` - Record attachment results with `source: "local"` for uploaded files and `source: "library"` plus `documentId` for library files - Use the `name` field from the `documents` record as the `filename` in attachment results for library files - _Requirements: 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7_ - [x] 3. Modify backend to handle library document attachments on edit - [x] 3.1 Update `POST /api/ivanti/fp-workflow/submissions/:id/attachments` in `backend/routes/ivantiFpWorkflow.js` to accept `libraryDocIds` - Apply the same `libraryDocIds` parsing, validation, disk-read, and combined upload logic as task 2.1 - Combine local file buffers and library file buffers into a single `formFiles` array for the Ivanti API call - Record attachment results with `source` and `documentId` fields matching the create endpoint behavior - _Requirements: 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7_ - [x] 4. Checkpoint — Verify backend changes - Ensure all backend changes are syntactically correct and consistent with existing patterns. Ask the user if questions arise. - [x] 5. Create AttachmentSourcePicker component - [x] 5.1 Implement `AttachmentSourcePicker` inline in `frontend/src/components/pages/ReportingPage.js` - Define the component above `FpWorkflowModal` in the file - Accept props: `files`, `onFilesChange`, `libraryDocs`, `onLibraryDocsChange`, `disabled` - Implement a mode toggle with two tab-style buttons: "Local Upload" and "Library" (default to "Local Upload") - In Local Upload mode, render the existing drag-and-drop zone with file input, file validation (extension + size), and file list - In Library mode, render a search input that queries `GET /api/documents/search?q=...` with 300ms debounce using `setTimeout`/`clearTimeout` - Display search results in a scrollable list showing document name, CVE ID, vendor, and file size - Show already-selected library documents as disabled/checked in search results to prevent duplicates - When a search result is clicked, add it to `libraryDocs` via `onLibraryDocsChange` (skip if already selected by `id`) - When a library doc is removed from the attachment list, re-enable it in search results - Render a unified attachment list below the mode-specific UI showing all attachments (local + library) - Each attachment row displays: source badge ("Local" or "Library"), filename, file size, and a remove button (Trash2 icon) - Library attachment rows additionally display CVE ID and vendor name - Disable all interactions when `disabled` prop is true - Style consistently with existing modal components using inline style objects, monospace font, dark theme colors from DESIGN_SYSTEM.md - Handle network errors on search by showing an inline error message in the results area - Show "No documents found" when search returns empty results - _Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 5.1, 5.2, 5.3, 6.1, 6.2, 6.3, 6.4_ - [x] 6. Integrate AttachmentSourcePicker into FpWorkflowModal (create flow) - [x] 6.1 Replace the file upload section in `FpWorkflowModal` with `AttachmentSourcePicker` - Add `libraryDocs` state (`useState([])`) alongside existing `files` state - Reset `libraryDocs` to `[]` when modal opens (in the existing `useEffect` on `open`) - Replace the current drag-and-drop zone and file list section with `` passing `files`, `setFiles`, `libraryDocs`, `setLibraryDocs`, and `disabled={submitting}` - Remove the inline `addFiles`, `removeFile`, `handleDrop`, `handleDragOver` functions and `fileInputRef`/`dropRef` refs (these are now handled inside AttachmentSourcePicker) - On submit, append `libraryDocIds` as a JSON string to the FormData: `formData.append('libraryDocIds', JSON.stringify(libraryDocs.map(d => d.id)))` - Update the progress message to reflect combined attachment count - Update the result view to show source badges on attachment results (use `source` field, default to `"local"` for backward compatibility) - _Requirements: 2.1, 2.2, 2.3, 2.5, 2.6, 2.7_ - [x] 7. Integrate AttachmentSourcePicker into FpEditModal (edit flow) - [x] 7.1 Replace the static "upload in Ivanti" message on the attachments tab with `AttachmentSourcePicker` - Add `libraryDocs` state (`useState([])`) alongside existing `files` state - Reset `libraryDocs` to `[]` when submission changes (in the existing `useEffect` on `submission`) - Keep the existing attachment display section (showing attachments from initial submission) above the picker - Render `` below existing attachments, passing `files`, `setFiles`, `libraryDocs`, `setLibraryDocs`, and `disabled={isApproved}` - Update `handleUploadAttachments` to build FormData with both local files and `libraryDocIds` JSON field - Enable the upload button when either `files.length > 0` or `libraryDocs.length > 0` - Disable the picker when `lifecycle_status === 'approved'` - _Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 3.6_ - [x] 8. Final checkpoint — Verify all changes - Ensure all changes are complete and consistent across backend and frontend. Ensure no hanging or orphaned code. Ask the user if questions arise. ## Notes - No testing tasks included per user request — testing will be done on the dev server - The project uses plain JavaScript (no TypeScript) throughout - All frontend styling uses inline style objects consistent with the existing dark theme design system - The `documents` table already exists — no database migrations are needed - The `libraryDocIds` field is optional in both endpoints, preserving full backward compatibility - Existing `attachment_results_json` entries without a `source` field are treated as `"local"` by the frontend