# Work Management Service API Documentation **Base URL:** `/api/work` (via gateway) or `http://localhost:8083` (direct) **Service Port:** 8083 ## Overview The Work Management Service handles all project, task, and work order management for the Coppertone.tech platform. It provides: - Project management with approval workflow - Task tracking with assignments and time tracking - Work orders with IPFS document integration - Role-based access control (RBAC) ## Authentication All endpoints require a valid JWT token in the Authorization header: ``` Authorization: Bearer ``` The token is validated against the auth-service and must contain: - `user_id` / `userId`: User identifier - `roles`: Array of user roles ## Role-Based Access | Role | Projects | Tasks | Work Orders | |------|----------|-------|-------------| | `SUPERUSER` | Full access | Full access | Full access | | `ADMIN` | Full access | Full access | Full access | | `STAFF` | Full access | Full access | Full access | | `CLIENT` | Own projects only | Tasks on own projects | No access | **Note:** `SUPERUSER` role automatically grants full access to all endpoints. --- ## Health Check ``` GET /healthz ``` **Response:** ``` 200 OK ok ``` --- ## Projects ### List Projects Retrieve a list of approved projects. CLIENTs see only their own projects. ``` GET /projects ``` **Headers:** ``` Authorization: Bearer ``` **Required Role:** Any authenticated user **Response (200 OK):** ```json [ { "id": 1, "name": "Website Redesign", "description": "Complete redesign of corporate website", "status": "IN_PROGRESS", "clientId": 5, "ipfsMetadataCid": "QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco", "startDate": "2024-01-15", "endDate": "2024-03-15", "requestedBy": 5, "approvedBy": 2, "approvalStatus": "APPROVED", "approvalDate": "2024-01-10T14:30:00Z", "rejectionReason": null, "createdAt": "2024-01-05T10:00:00Z", "updatedAt": "2024-01-20T09:15:00Z" } ] ``` **Authorization Logic:** - `STAFF/ADMIN/SUPERUSER`: See all approved projects - `CLIENT`: See only projects where `client_id` matches their user ID --- ### Get Project by ID Retrieve a single project by ID. ``` GET /projects/{id} ``` **Headers:** ``` Authorization: Bearer ``` **Required Role:** Any authenticated user (with access) **Path Parameters:** | Parameter | Type | Description | |-----------|------|-------------| | `id` | integer | Project ID | **Response (200 OK):** ```json { "id": 1, "name": "Website Redesign", "description": "Complete redesign of corporate website", "status": "IN_PROGRESS", "clientId": 5, "ipfsMetadataCid": "QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco", "startDate": "2024-01-15", "endDate": "2024-03-15", "requestedBy": 5, "approvedBy": 2, "approvalStatus": "APPROVED", "approvalDate": "2024-01-10T14:30:00Z", "rejectionReason": null, "createdAt": "2024-01-05T10:00:00Z", "updatedAt": "2024-01-20T09:15:00Z" } ``` **Error Responses:** | Status | Condition | Response | |--------|-----------|----------| | 400 | Invalid ID | `Invalid project ID` | | 403 | No access | `Forbidden: you do not have access to this project` | | 404 | Not found | `Project not found` | **Authorization Logic:** - User must be: project owner (`client_id`), requester (`requested_by`), or `STAFF/ADMIN/SUPERUSER` --- ### Create Project Create a new project. Projects created by STAFF/ADMIN are auto-approved. ``` POST /projects ``` **Headers:** ``` Authorization: Bearer ``` **Required Role:** `STAFF`, `ADMIN`, or `SUPERUSER` **Request Body:** ```json { "name": "New Mobile App", "description": "iOS and Android app development", "status": "PLANNING", "clientId": 5, "ipfsMetadataCid": "QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco", "startDate": "2024-02-01", "endDate": "2024-06-30" } ``` | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `name` | string | Yes | - | Project name | | `description` | string | No | - | Project description | | `status` | string | No | `PLANNING` | Project status | | `clientId` | integer | No | - | Assigned client user ID | | `ipfsMetadataCid` | string | No | - | IPFS CID for metadata | | `startDate` | string | No | - | Planned start date (YYYY-MM-DD) | | `endDate` | string | No | - | Planned end date (YYYY-MM-DD) | **Valid Status Values:** - `PLANNING` - `IN_PROGRESS` - `ON_HOLD` - `COMPLETED` - `CANCELLED` **Response (201 Created):** ```json { "id": 2, "name": "New Mobile App", "description": "iOS and Android app development", "status": "PLANNING", "clientId": 5, "ipfsMetadataCid": "QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco", "startDate": "2024-02-01", "endDate": "2024-06-30", "requestedBy": 2, "approvedBy": 2, "approvalStatus": "APPROVED", "approvalDate": "2024-01-25T12:00:00Z", "rejectionReason": null, "createdAt": "2024-01-25T12:00:00Z", "updatedAt": "2024-01-25T12:00:00Z" } ``` **Error Responses:** | Status | Condition | Response | |--------|-----------|----------| | 400 | Missing name | `Project name is required` | | 403 | Insufficient role | `Insufficient permissions` | --- ### Update Project Update an existing project. ``` PUT /projects/{id} ``` **Headers:** ``` Authorization: Bearer ``` **Required Role:** `STAFF`, `ADMIN`, or `SUPERUSER` (or project owner) **Path Parameters:** | Parameter | Type | Description | |-----------|------|-------------| | `id` | integer | Project ID | **Request Body:** ```json { "name": "Updated Project Name", "description": "Updated description", "status": "IN_PROGRESS", "clientId": 5, "ipfsMetadataCid": "QmNewCid...", "startDate": "2024-02-01", "endDate": "2024-07-31" } ``` **Response (200 OK):** ```json { "id": 1, "name": "Updated Project Name", ... } ``` **Error Responses:** | Status | Condition | Response | |--------|-----------|----------| | 403 | No permission | `Forbidden: you do not have permission to update this project` | | 404 | Not found | `Project not found` | --- ### Delete Project Delete a project. Only STAFF/ADMIN can delete projects. ``` DELETE /projects/{id} ``` **Headers:** ``` Authorization: Bearer ``` **Required Role:** `STAFF`, `ADMIN`, or `SUPERUSER` **Path Parameters:** | Parameter | Type | Description | |-----------|------|-------------| | `id` | integer | Project ID | **Response (204 No Content)** **Error Responses:** | Status | Condition | Response | |--------|-----------|----------| | 403 | No permission | `Forbidden: only STAFF or ADMIN can delete projects` | | 404 | Not found | `Project not found` | --- ## Project Requests (Client Workflow) CLIENTs can request new projects, which require STAFF/ADMIN approval. ### List My Project Requests Retrieve all project requests made by the current user. ``` GET /project-requests ``` **Headers:** ``` Authorization: Bearer ``` **Required Role:** Any authenticated user **Response (200 OK):** ```json [ { "id": 3, "name": "New Feature Request", "description": "We need a new dashboard feature", "status": "PENDING_APPROVAL", "clientId": 5, "requestedBy": 5, "approvedBy": null, "approvalStatus": "PENDING", "approvalDate": null, "rejectionReason": null, "createdAt": "2024-01-25T10:00:00Z", "updatedAt": "2024-01-25T10:00:00Z" } ] ``` --- ### Create Project Request Submit a new project request for approval. ``` POST /project-requests ``` **Headers:** ``` Authorization: Bearer ``` **Required Role:** Any authenticated user **Request Body:** ```json { "name": "Custom Integration Project", "description": "We need to integrate our CRM with the platform. This should include bidirectional sync and real-time updates." } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | Yes | Project name | | `description` | string | Yes | Detailed description of the project request | **Response (201 Created):** ```json { "id": 4, "name": "Custom Integration Project", "description": "We need to integrate our CRM with the platform...", "status": "PENDING_APPROVAL", "clientId": 5, "requestedBy": 5, "approvalStatus": "PENDING", "createdAt": "2024-01-25T14:30:00Z", "updatedAt": "2024-01-25T14:30:00Z" } ``` **Error Responses:** | Status | Condition | Response | |--------|-----------|----------| | 400 | Missing name | `Project name is required` | | 400 | Missing description | `Project description is required to help us understand your needs` | --- ### Get Project Request Retrieve a specific project request. ``` GET /project-requests/{id} ``` **Headers:** ``` Authorization: Bearer ``` **Required Role:** Request owner, or `STAFF/ADMIN/SUPERUSER` **Response (200 OK):** ```json { "id": 4, "name": "Custom Integration Project", "description": "...", "status": "PENDING_APPROVAL", "approvalStatus": "PENDING", ... } ``` --- ### Cancel Project Request Cancel a pending project request. ``` DELETE /project-requests/{id} ``` **Headers:** ``` Authorization: Bearer ``` **Required Role:** Request owner only (cannot cancel after approval) **Response (204 No Content)** **Error Responses:** | Status | Condition | Response | |--------|-----------|----------| | 403 | Not owner | `Forbidden: you can only cancel your own requests` | | 403 | Already processed | `Cannot cancel: request has already been processed` | | 404 | Not found | `Project request not found` | --- ## Project Approval (Admin Workflow) ### List Pending Projects Retrieve all projects pending approval. ``` GET /projects/pending ``` **Headers:** ``` Authorization: Bearer ``` **Required Role:** `STAFF`, `ADMIN`, or `SUPERUSER` **Response (200 OK):** ```json [ { "id": 4, "name": "Custom Integration Project", "description": "...", "status": "PENDING_APPROVAL", "clientId": 5, "requestedBy": 5, "approvalStatus": "PENDING", "createdAt": "2024-01-25T14:30:00Z", "updatedAt": "2024-01-25T14:30:00Z" } ] ``` --- ### Approve or Reject Project Approve or reject a pending project request. ``` POST /projects/approve/{id} ``` **Headers:** ``` Authorization: Bearer ``` **Required Role:** `STAFF`, `ADMIN`, or `SUPERUSER` **Path Parameters:** | Parameter | Type | Description | |-----------|------|-------------| | `id` | integer | Project ID | **Request Body (Approve):** ```json { "action": "approve" } ``` **Request Body (Reject):** ```json { "action": "reject", "reason": "The requested scope is too broad. Please break this into smaller projects." } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `action` | string | Yes | `approve` or `reject` | | `reason` | string | If rejecting | Reason for rejection | **Response (200 OK - Approved):** ```json { "message": "Project approved successfully", "project": { "id": 4, "name": "Custom Integration Project", "status": "PLANNING", "approvalStatus": "APPROVED", "approvedBy": 2, "approvalDate": "2024-01-26T09:00:00Z", ... } } ``` **Response (200 OK - Rejected):** ```json { "message": "Project rejected", "project": { "id": 4, "name": "Custom Integration Project", "status": "CANCELLED", "approvalStatus": "REJECTED", "rejectionReason": "The requested scope is too broad...", ... } } ``` **Error Responses:** | Status | Condition | Response | |--------|-----------|----------| | 400 | Invalid action | `Invalid action. Must be 'approve' or 'reject'` | | 400 | Missing reason | `Rejection reason is required` | | 400 | Not pending | `Project is not pending approval` | | 404 | Not found | `Project not found` | --- ## Tasks ### List Tasks Retrieve tasks, optionally filtered by project. ``` GET /tasks GET /tasks?project_id=1 ``` **Headers:** ``` Authorization: Bearer ``` **Required Role:** Any authenticated user **Query Parameters:** | Parameter | Type | Description | |-----------|------|-------------| | `project_id` | integer | Optional. Filter tasks by project | **Response (200 OK):** ```json [ { "id": 1, "projectId": 1, "title": "Design Homepage Mockup", "description": "Create wireframes and visual design for homepage", "status": "IN_PROGRESS", "assigneeId": 3, "dueDate": "2024-02-01", "completedAt": null, "priority": 2, "estimatedHours": 8.0, "actualHours": 3.5, "createdAt": "2024-01-20T10:00:00Z", "updatedAt": "2024-01-25T14:00:00Z" } ] ``` **Authorization Logic:** - `STAFF/ADMIN/SUPERUSER`: See all tasks (optionally filtered) - `CLIENT`: See only tasks for projects they own --- ### Get Task by ID Retrieve a single task. ``` GET /tasks/{id} ``` **Headers:** ``` Authorization: Bearer ``` **Required Role:** Any authenticated user (with access to parent project) **Response (200 OK):** ```json { "id": 1, "projectId": 1, "title": "Design Homepage Mockup", "description": "Create wireframes and visual design for homepage", "status": "IN_PROGRESS", "assigneeId": 3, "dueDate": "2024-02-01", "completedAt": null, "priority": 2, "estimatedHours": 8.0, "actualHours": 3.5, "createdAt": "2024-01-20T10:00:00Z", "updatedAt": "2024-01-25T14:00:00Z" } ``` **Error Responses:** | Status | Condition | Response | |--------|-----------|----------| | 403 | No access | `Forbidden: you do not have access to this task` | | 404 | Not found | `Task not found` | --- ### Create Task Create a new task for a project. ``` POST /tasks ``` **Headers:** ``` Authorization: Bearer ``` **Required Role:** `STAFF`, `ADMIN`, or `SUPERUSER` **Request Body:** ```json { "projectId": 1, "title": "Implement API Endpoints", "description": "Create REST endpoints for user management", "status": "TODO", "assigneeId": 3, "dueDate": "2024-02-15", "priority": 1, "estimatedHours": 16.0 } ``` | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `projectId` | integer | Yes | - | Parent project ID | | `title` | string | Yes | - | Task title | | `description` | string | No | - | Task description | | `status` | string | No | `TODO` | Task status | | `assigneeId` | integer | No | - | Assigned user ID | | `dueDate` | string | No | - | Due date (YYYY-MM-DD) | | `priority` | integer | No | 0 | Priority (higher = more important) | | `estimatedHours` | float | No | - | Estimated hours to complete | **Valid Status Values:** - `TODO` - `IN_PROGRESS` - `REVIEW` - `COMPLETED` - `BLOCKED` **Response (201 Created):** ```json { "id": 5, "projectId": 1, "title": "Implement API Endpoints", "status": "TODO", ... } ``` **Error Responses:** | Status | Condition | Response | |--------|-----------|----------| | 400 | Missing fields | `Task title and project_id are required` | | 403 | No permission | `Insufficient permissions` | --- ### Update Task Update an existing task. ``` PUT /tasks/{id} ``` **Headers:** ``` Authorization: Bearer ``` **Required Role:** `STAFF`, `ADMIN`, or `SUPERUSER` **Request Body:** ```json { "title": "Implement API Endpoints", "description": "Updated description", "status": "COMPLETED", "assigneeId": 3, "dueDate": "2024-02-15", "priority": 1, "estimatedHours": 16.0, "actualHours": 14.5 } ``` **Special Behavior:** - Setting `status` to `COMPLETED` automatically sets `completedAt` timestamp - Setting `status` to anything else clears `completedAt` **Response (200 OK):** ```json { "id": 5, "projectId": 1, "title": "Implement API Endpoints", "status": "COMPLETED", "completedAt": "2024-02-14T16:30:00Z", ... } ``` --- ### Delete Task Delete a task. ``` DELETE /tasks/{id} ``` **Headers:** ``` Authorization: Bearer ``` **Required Role:** `STAFF`, `ADMIN`, or `SUPERUSER` **Response (204 No Content)** --- ## Work Orders Work orders are internal documents for tracking billable work. **STAFF/ADMIN only.** ### List Work Orders Retrieve work orders, optionally filtered by project. ``` GET /workorders GET /workorders?project_id=1 ``` **Headers:** ``` Authorization: Bearer ``` **Required Role:** `STAFF`, `ADMIN`, or `SUPERUSER` **Query Parameters:** | Parameter | Type | Description | |-----------|------|-------------| | `project_id` | integer | Optional. Filter by project | **Response (200 OK):** ```json [ { "id": 1, "projectId": 1, "title": "Initial Setup and Configuration", "description": "Server setup, environment configuration, CI/CD pipeline", "orderNumber": "WO-2024-001", "ipfsDocumentCid": "QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco", "createdBy": 2, "createdAt": "2024-01-20T10:00:00Z", "updatedAt": "2024-01-20T10:00:00Z" } ] ``` --- ### Get Work Order by ID ``` GET /workorders/{id} ``` **Headers:** ``` Authorization: Bearer ``` **Required Role:** `STAFF`, `ADMIN`, or `SUPERUSER` **Response (200 OK):** ```json { "id": 1, "projectId": 1, "title": "Initial Setup and Configuration", "description": "Server setup, environment configuration, CI/CD pipeline", "orderNumber": "WO-2024-001", "ipfsDocumentCid": "QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco", "createdBy": 2, "createdAt": "2024-01-20T10:00:00Z", "updatedAt": "2024-01-20T10:00:00Z" } ``` --- ### Create Work Order ``` POST /workorders ``` **Headers:** ``` Authorization: Bearer ``` **Required Role:** `STAFF`, `ADMIN`, or `SUPERUSER` **Request Body:** ```json { "projectId": 1, "title": "Database Migration", "description": "Migrate from PostgreSQL 14 to 16", "orderNumber": "WO-2024-002", "ipfsDocumentCid": "QmNewCid...", "createdBy": 2 } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `projectId` | integer | Yes | Parent project ID | | `title` | string | Yes | Work order title | | `orderNumber` | string | Yes | Unique order number | | `description` | string | No | Work order description | | `ipfsDocumentCid` | string | No | IPFS CID for attached document | | `createdBy` | integer | No | User ID who created the work order | **Response (201 Created):** ```json { "id": 2, "projectId": 1, "title": "Database Migration", "orderNumber": "WO-2024-002", ... } ``` **Error Responses:** | Status | Condition | Response | |--------|-----------|----------| | 400 | Missing fields | `Work order title, project_id, and order_number are required` | | 409 | Duplicate order number | `Work order number already exists` | --- ### Update Work Order ``` PUT /workorders/{id} ``` **Headers:** ``` Authorization: Bearer ``` **Required Role:** `STAFF`, `ADMIN`, or `SUPERUSER` **Request Body:** ```json { "title": "Updated Title", "description": "Updated description", "orderNumber": "WO-2024-002", "ipfsDocumentCid": "QmUpdatedCid..." } ``` **Response (200 OK)** --- ### Delete Work Order ``` DELETE /workorders/{id} ``` **Headers:** ``` Authorization: Bearer ``` **Required Role:** `STAFF`, `ADMIN`, or `SUPERUSER` **Response (204 No Content)** --- ## CORS Configuration Same as auth-service: | Header | Value | |--------|-------| | `Access-Control-Allow-Origin` | Configured via `CORS_ALLOW_ORIGIN` env var | | `Access-Control-Allow-Methods` | `GET, POST, PUT, DELETE, OPTIONS` | | `Access-Control-Allow-Headers` | `Content-Type, Authorization` | | `Access-Control-Allow-Credentials` | `true` | --- ## Environment Variables | Variable | Required | Default | Description | |----------|----------|---------|-------------| | `JWT_SECRET` | Yes | - | Secret key for JWT validation (must match auth-service) | | `DB_HOST` | Yes | - | PostgreSQL host | | `DB_USER` | Yes | - | PostgreSQL user | | `DB_PASSWORD` | Yes | - | PostgreSQL password | | `DB_NAME` | Yes | - | PostgreSQL database name | | `DB_SSL_MODE` | No | `require` | SSL mode | | `DB_SCHEMA` | No | `public` | Schema: `dev`, `testing`, `prod` | | `CORS_ALLOW_ORIGIN` | No | `http://localhost:8090` | Allowed CORS origin | --- ## Database Tables ### projects ```sql CREATE TABLE projects ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, description TEXT, status VARCHAR(50) DEFAULT 'PLANNING', client_id INTEGER REFERENCES users(id), ipfs_metadata_cid VARCHAR(100), start_date DATE, end_date DATE, requested_by INTEGER REFERENCES users(id), approved_by INTEGER REFERENCES users(id), approval_status VARCHAR(20) DEFAULT 'APPROVED', approval_date TIMESTAMP, rejection_reason TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); ``` ### tasks ```sql CREATE TABLE tasks ( id SERIAL PRIMARY KEY, project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE, title VARCHAR(255) NOT NULL, description TEXT, status VARCHAR(50) DEFAULT 'TODO', assignee_id INTEGER REFERENCES users(id), due_date DATE, completed_at TIMESTAMP, priority INTEGER DEFAULT 0, estimated_hours DECIMAL(5,2), actual_hours DECIMAL(5,2), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); ``` ### work_orders ```sql CREATE TABLE work_orders ( id SERIAL PRIMARY KEY, project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE, title VARCHAR(255) NOT NULL, description TEXT, order_number VARCHAR(50) NOT NULL UNIQUE, ipfs_document_cid VARCHAR(100), created_by INTEGER REFERENCES users(id), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); ```