Skip to content

feat: Add Projects management feature (CRUD)#756

Open
devin-ai-integration[bot] wants to merge 6 commits into
mainfrom
devin/1778650397-add-projects-management
Open

feat: Add Projects management feature (CRUD)#756
devin-ai-integration[bot] wants to merge 6 commits into
mainfrom
devin/1778650397-add-projects-management

Conversation

@devin-ai-integration
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot commented May 13, 2026

Summary

Adds a full "Projects" management feature with CRUD operations across the backend and frontend.

Backend:

  • projects table in SQLite schema (name, description, client_id FK, start_date, status with CHECK constraint, user_email)
  • Indexes on user_email, client_id, status
  • Joi validation schemas (projectSchema, updateProjectSchema)
  • /api/projects REST endpoints (GET list, GET /:id, POST, PUT /:id, DELETE /:id) with auth, user-scoping, and client JOIN
  • Client ownership validation on create/update to prevent IDOR (follows workEntries pattern)
  • Extracted shared helpers (parseId, dbError, findProject, verifyClientOwnership, PROJECT_SELECT)

Frontend:

  • Project, CreateProjectRequest, UpdateProjectRequest TypeScript types
  • Generic CRUD helpers in ApiClient + project-specific methods
  • ProjectsPage with MUI table, create/edit dialog (client dropdown, date picker, status selector), delete confirmation
  • /projects route and sidebar nav item (AccountTree icon)

Tests:

  • 27 tests for all project endpoints (happy paths, validation, 404s, DB errors, client ownership IDOR rejection)
  • Shared test setup helper (routeTestSetup.js) with createTestApp and createMockDb
  • All 188 tests passing across 9 suites

Review & Testing Checklist for Human

  • Start the dev servers (backend/npm run dev + frontend/npm run dev), log in, and verify the Projects page renders
  • Create a project with all fields filled (name, description, client, start date, status) and confirm it appears in the table
  • Edit a project (change status to "on-hold" or "completed") and verify the status chip updates
  • Delete a project and verify it's removed from the list
  • Create a client, then create a project assigned to that client, then delete the client — verify the project remains with null client

Notes

  • client_id FK uses ON DELETE SET NULL so deleting a client preserves associated projects
  • Status is constrained at DB level to: active, completed, on-hold
  • Client ownership is validated on create/update to prevent cross-user data association (IDOR)
  • SonarCloud Quality Gate now passing

Link to Devin session: https://partner-workshops.devinenterprise.com/sessions/aa7ecae27f684b1ca26e23eb66c1d9d8


Open in Devin Review

- 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-integration
Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

- 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
devin-ai-integration[bot]

This comment was marked as resolved.

- 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
@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown
Contributor Author

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 6 additional findings in Devin Review.

Open in Devin Review

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),
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants