feat: Add Projects management feature (CRUD)#756
feat: Add Projects management feature (CRUD)#756devin-ai-integration[bot] wants to merge 6 commits into
Conversation
- Add projects table with name, description, client assignment, start date, status - Add CRUD API endpoints at /api/projects with authentication and validation - Add ProjectsPage frontend with table view, create/edit/delete dialogs - Add navigation item in sidebar - Add comprehensive backend tests (all 186 tests passing) - Status options: active, completed, on-hold - Projects can be assigned to existing clients
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
- Extract shared helpers (parseId, dbError, findProject, PROJECT_SELECT) - Use shared SAMPLE_PROJECT fixture and helper functions in tests - Reduce repetitive patterns to address SonarCloud duplication threshold
- Consolidate create/update into single saveMutation - Extract helper functions (statusChip, extractApiError, updateField) - Use typed interfaces and constants to reduce structural similarity
- Create routeTestSetup.js with createTestApp and createMockDb helpers - Use shared helpers in projects test to eliminate boilerplate duplication
- Extract private list/show/create/update/remove helpers in ApiClient - Project endpoints delegate to generic helpers, eliminating duplication
- Validate clientId belongs to authenticated user on create/update (IDOR fix) - Return 400 if clientId references another user's client - Fix parseId guard to use === null instead of !projectId (ID 0 bug) - Add tests for client ownership rejection on POST and PUT
|
| name: Joi.string().trim().min(1).max(255).required(), | ||
| description: Joi.string().trim().max(1000).optional().allow(''), | ||
| clientId: Joi.number().integer().positive().optional().allow(null), | ||
| startDate: Joi.date().iso().optional().allow(null), |
There was a problem hiding this comment.
🔴 Joi date().iso() converts startDate to a Date object, breaking the HTML date input on edit
The projectSchema and updateProjectSchema use Joi.date().iso() for startDate (at backend/src/validation/schemas.js:35 and :43), which converts ISO date strings like "2024-06-01" into JavaScript Date objects (confirmed by running Joi locally). This Date object is then passed as a sqlite3 bind parameter (backend/src/routes/projects.js:83), which will store it in a non-YYYY-MM-DD format (likely the long .toString() representation or epoch value, depending on how sqlite3's native BindParameter IsObject() branch handles it).
When a user later edits a project, the frontend sets the form value directly from the API response: startDate: project.start_date || '' (frontend/src/pages/ProjectsPage.tsx:70). The <input type="date"> at line 155 requires exactly YYYY-MM-DD format — any other format causes the field to appear empty. On re-save, the non-standard stored value is sent back to the backend, where Joi.date().iso() may reject it (if it's not ISO 8601), causing a validation error. This can lead to data loss (date silently cleared) or inability to edit projects that have a start date.
Contrast with work entries (which work correctly)
The existing work entries code uses the same Joi.date().iso() pattern but avoids this issue because WorkEntriesPage.tsx uses a DatePicker component that accepts Date objects: date: new Date(entry.date) (line 109). The projects page instead uses a plain HTML date input that needs a YYYY-MM-DD string.
Prompt for agents
The root cause is that Joi.date().iso() converts date strings to JavaScript Date objects, but the frontend ProjectsPage uses an HTML <input type='date'> that requires YYYY-MM-DD strings.
There are two viable fixes:
1. Schema fix (backend/src/validation/schemas.js lines 35 and 43): Change Joi.date().iso() to Joi.string().pattern(/^\d{4}-\d{2}-\d{2}$/) or Joi.string().isoDate() for the startDate field in both projectSchema and updateProjectSchema. This keeps the value as a string and avoids the Date conversion entirely.
2. Route fix (backend/src/routes/projects.js lines 83 and 127-134): After Joi validation, convert the Date object back to YYYY-MM-DD before passing to sqlite3. For example: startDate instanceof Date ? startDate.toISOString().split('T')[0] : startDate. This approach is used by the work entries frontend at WorkEntriesPage.tsx line 160.
Fix 1 is simpler and more appropriate since sqlite3 DATE columns store text and we want YYYY-MM-DD format throughout the stack.
Was this helpful? React with 👍 or 👎 to provide feedback.



Summary
Adds a full "Projects" management feature with CRUD operations across the backend and frontend.
Backend:
projectstable in SQLite schema (name, description, client_id FK, start_date, status with CHECK constraint, user_email)projectSchema,updateProjectSchema)/api/projectsREST endpoints (GET list, GET /:id, POST, PUT /:id, DELETE /:id) with auth, user-scoping, and client JOINparseId,dbError,findProject,verifyClientOwnership,PROJECT_SELECT)Frontend:
Project,CreateProjectRequest,UpdateProjectRequestTypeScript typesProjectsPagewith MUI table, create/edit dialog (client dropdown, date picker, status selector), delete confirmation/projectsroute and sidebar nav item (AccountTree icon)Tests:
routeTestSetup.js) withcreateTestAppandcreateMockDbReview & Testing Checklist for Human
backend/npm run dev+frontend/npm run dev), log in, and verify the Projects page rendersNotes
client_idFK usesON DELETE SET NULLso deleting a client preserves associated projectsLink to Devin session: https://partner-workshops.devinenterprise.com/sessions/aa7ecae27f684b1ca26e23eb66c1d9d8