# Copper Tone Technologies API Documentation This document provides comprehensive documentation for all backend API endpoints and frontend integration patterns. ## Table of Contents - [Overview](#overview) - [Authentication](#authentication) - [Backend Services](#backend-services) - [Auth Service (Port 8082)](#auth-service-port-8082) - [Work Management Service (Port 8083)](#work-management-service-port-8083) - [Payment Service (Port 8084)](#payment-service-port-8084) - [Blog Service (Port 8085)](#blog-service-port-8085) - [IPFS/P2P Service (Port 8086)](#ipfsp2p-service-port-8086) - [Forum Service (Port 8087)](#forum-service-port-8087) - [Frontend Stores](#frontend-stores) - [Error Handling](#error-handling) - [Environment Variables](#environment-variables) --- ## Overview ### Architecture ``` ┌─────────────────────────────────────────────────────────────────┐ │ Frontend (Vue 3 PWA) │ │ Port 8090 │ └─────────────────────────────────────────────────────────────────┘ │ Nginx Reverse Proxy │ ┌─────────────────────┼─────────────────────┐ │ │ │ ▼ ▼ ▼ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ /api/auth/* │ │ /api/work/* │ │ /api/payment/*│ │ Auth Service │ │ Work Mgmt │ │ Payment │ │ Port 8082 │ │ Port 8083 │ │ Port 8084 │ └───────────────┘ └───────────────┘ └───────────────┘ │ │ │ └─────────────────────┼─────────────────────┘ │ ┌─────────────────┐ │ PostgreSQL │ │ Port 5432 │ └─────────────────┘ Additional Services: ┌───────────────┐ ┌───────────────┐ │ /api/blog/* │ │ /api/ipfs/* │ │ Blog Service │ │ IPFS/P2P │ │ Port 8085 │ │ Port 8086 │ └───────────────┘ └───────────────┘ ``` ### Base URLs | Environment | Base URL | |-------------|----------| | Development | `http://localhost:8090/api` | | Production | `https://coppertone.tech/api` | ### Response Format All API responses are JSON. Successful responses return the requested data directly. Error responses follow this format: ```json { "error": "Error message description" } ``` Or for validation errors: ```json { "field": "fieldName", "message": "Validation error message" } ``` --- ## Authentication ### JWT Token All protected endpoints require a JWT token in the Authorization header: ``` Authorization: Bearer ``` ### Token Payload ```json { "user_id": 1, "userId": 1, "email": "user@example.com", "roles": ["CLIENT", "STAFF", "ADMIN"], "exp": 1700000000 } ``` ### Roles | Role | Description | Permissions | |------|-------------|-------------| | `CLIENT` | Customer/end-user | View own projects, invoices; manage own identities | | `STAFF` | Internal team member | Create/edit work items, invoices, blogs | | `ADMIN` | Administrator | Full access including user management | ### Getting a Token **Email/Password Login:** ```bash curl -X POST http://localhost:8090/api/auth/login-email-password \ -H "Content-Type: application/json" \ -d '{"email":"user@example.com","password":"Password123"}' ``` **Blockchain Login:** ```bash curl -X POST http://localhost:8090/api/auth/login-blockchain \ -H "Content-Type: application/json" \ -d '{ "address":"0x...", "signature":"0x...", "message":"Sign this message to login" }' ``` --- ## Backend Services ### Auth Service (Port 8082) Base path: `/api/auth` #### Public Endpoints ##### POST `/register-email-password` Register a new user with email and password. **Request:** ```json { "email": "user@example.com", "password": "SecurePass123", "name": "John Doe" } ``` **Password Requirements:** - 8-72 characters - At least 1 uppercase letter - At least 1 lowercase letter - At least 1 number **Response (201):** ```json { "message": "User registered successfully", "userId": 1 } ``` **Notes:** - First registered user automatically gets ADMIN role - All subsequent users get CLIENT role --- ##### POST `/register-blockchain` Register with Ethereum wallet. **Request:** ```json { "address": "0x1234...", "signature": "0xabcd...", "message": "Sign this message to register", "name": "John Doe" } ``` **Response (201):** ```json { "message": "User registered successfully", "userId": 1 } ``` --- ##### POST `/login-email-password` Login with email and password. **Request:** ```json { "email": "user@example.com", "password": "SecurePass123" } ``` **Response (200):** ```json { "token": "eyJhbGciOiJIUzI1NiIs..." } ``` --- ##### POST `/login-blockchain` Login with Ethereum signature. **Request:** ```json { "address": "0x1234...", "signature": "0xabcd...", "message": "Sign this message to login" } ``` **Response (200):** ```json { "token": "eyJhbGciOiJIUzI1NiIs..." } ``` --- ##### GET `/healthz` Health check endpoint. **Response (200):** ``` ok ``` --- #### Protected Endpoints ##### GET `/profile` Get current user's profile. **Headers:** `Authorization: Bearer ` **Response (200):** ```json { "id": 1, "name": "John Doe", "email": "user@example.com", "roles": ["CLIENT"], "createdAt": "2024-01-01T00:00:00Z" } ``` --- ##### GET `/identities` Get user's linked identities. **Headers:** `Authorization: Bearer ` **Response (200):** ```json [ { "id": 1, "userId": 1, "type": "email_password", "identifier": "user@example.com", "isPrimaryLogin": true, "createdAt": "2024-01-01T00:00:00Z" } ] ``` --- ##### POST `/link-identity` Link a new identity to account. **Headers:** `Authorization: Bearer ` **Request (email):** ```json { "type": "email_password", "email": "another@example.com", "password": "SecurePass123" } ``` **Request (blockchain):** ```json { "type": "blockchain_address", "address": "0x1234...", "signature": "0xabcd...", "message": "Sign to link identity" } ``` **Response (201):** ```json { "message": "Identity linked successfully" } ``` --- ##### POST `/unlink-identity` Remove an identity from account. **Headers:** `Authorization: Bearer ` **Request:** ```json { "identityId": 2 } ``` **Response (200):** ```json { "message": "Identity unlinked successfully" } ``` **Constraints:** - Cannot remove last identity - If removing primary, another identity is auto-promoted --- #### Admin Endpoints ##### GET `/admin/users` Get all users (ADMIN only). **Headers:** `Authorization: Bearer ` **Response (200):** ```json [ { "id": 1, "name": "John Doe", "email": "user@example.com", "roles": ["ADMIN"], "createdAt": "2024-01-01T00:00:00Z" } ] ``` --- ##### POST `/admin/users/promote-role` Grant a role to a user (ADMIN only). **Headers:** `Authorization: Bearer ` **Request:** ```json { "userId": 2, "role": "STAFF" } ``` **Response (200):** ```json { "message": "Successfully granted STAFF role to user 2" } ``` --- ##### POST `/admin/users/demote-role` Remove a role from a user (ADMIN only). **Headers:** `Authorization: Bearer ` **Request:** ```json { "userId": 2, "role": "STAFF" } ``` **Response (200):** ```json { "message": "Successfully removed STAFF role from user 2" } ``` **Constraints:** - Cannot remove user's last role - Admin cannot remove own ADMIN role --- ### Work Management Service (Port 8083) Base path: `/api/work` #### Project Endpoints ##### GET `/projects` List all projects. **Headers:** `Authorization: Bearer ` **Authorization:** - STAFF/ADMIN: See all projects - CLIENT: See only their own projects **Response (200):** ```json [ { "id": 1, "name": "Website Redesign", "description": "Complete website overhaul", "status": "IN_PROGRESS", "clientId": 2, "ipfsMetadataCid": null, "startDate": "2024-01-01", "endDate": "2024-03-01", "createdAt": "2024-01-01T00:00:00Z", "updatedAt": "2024-01-15T00:00:00Z" } ] ``` --- ##### POST `/projects` Create a new project (STAFF/ADMIN). **Headers:** `Authorization: Bearer ` **Request:** ```json { "name": "New Project", "description": "Project description", "status": "PLANNING", "clientId": 2, "startDate": "2024-02-01", "endDate": "2024-04-01" } ``` **Status Values:** `PLANNING`, `IN_PROGRESS`, `ON_HOLD`, `COMPLETED`, `ARCHIVED` **Response (201):** Created project object --- ##### GET `/projects/{id}` Get project by ID. **Headers:** `Authorization: Bearer ` **Response (200):** Project object --- ##### PUT `/projects/{id}` Update a project (STAFF/ADMIN). **Headers:** `Authorization: Bearer ` **Request:** Same as POST **Response (200):** Updated project object --- ##### DELETE `/projects/{id}` Delete a project (STAFF/ADMIN only). **Headers:** `Authorization: Bearer ` **Response:** 204 No Content --- #### Task Endpoints ##### GET `/tasks` List tasks. **Headers:** `Authorization: Bearer ` **Query Parameters:** - `project_id` (optional): Filter by project **Response (200):** ```json [ { "id": 1, "projectId": 1, "title": "Design mockups", "description": "Create UI mockups", "status": "IN_PROGRESS", "assigneeId": 3, "dueDate": "2024-01-15", "completedAt": null, "priority": 1, "estimatedHours": 8, "actualHours": null, "createdAt": "2024-01-01T00:00:00Z", "updatedAt": "2024-01-10T00:00:00Z" } ] ``` --- ##### POST `/tasks` Create a task (STAFF/ADMIN). **Headers:** `Authorization: Bearer ` **Request:** ```json { "projectId": 1, "title": "New Task", "description": "Task description", "status": "TODO", "assigneeId": 3, "dueDate": "2024-02-01", "priority": 2, "estimatedHours": 4 } ``` **Status Values:** `TODO`, `IN_PROGRESS`, `COMPLETED` **Response (201):** Created task object --- ##### GET `/tasks/{id}` Get task by ID. **Response (200):** Task object --- ##### PUT `/tasks/{id}` Update a task (STAFF/ADMIN). **Note:** Setting status to "COMPLETED" auto-sets `completedAt` **Response (200):** Updated task object --- ##### DELETE `/tasks/{id}` Delete a task (STAFF/ADMIN). **Response:** 204 No Content --- #### Work Order Endpoints ##### GET `/workorders` List work orders (STAFF/ADMIN). **Headers:** `Authorization: Bearer ` **Query Parameters:** - `project_id` (optional): Filter by project **Response (200):** ```json [ { "id": 1, "projectId": 1, "title": "Initial Setup", "description": "Server configuration", "orderNumber": "WO-2024-001", "ipfsDocumentCid": null, "createdBy": 3, "createdAt": "2024-01-01T00:00:00Z", "updatedAt": "2024-01-01T00:00:00Z" } ] ``` --- ##### POST `/workorders` Create work order (STAFF/ADMIN). **Request:** ```json { "projectId": 1, "title": "Work Order Title", "description": "Description", "orderNumber": "WO-2024-002" } ``` **Response (201):** Created work order object --- ##### GET `/workorders/{id}` Get work order by ID (STAFF/ADMIN). --- ##### PUT `/workorders/{id}` Update work order (STAFF/ADMIN). --- ##### DELETE `/workorders/{id}` Delete work order (STAFF/ADMIN). --- ### Payment Service (Port 8084) Base path: `/api/payment` #### Invoice Endpoints ##### GET `/invoices` List invoices. **Headers:** `Authorization: Bearer ` **Query Parameters:** - `client_id` (optional, STAFF/ADMIN only): Filter by client **Authorization:** - STAFF/ADMIN: See all or filtered - CLIENT: See only their own invoices **Response (200):** ```json [ { "id": 1, "invoiceNumber": "INV-2024-001", "projectId": 1, "clientId": 2, "amount": 1500.00, "currency": "USD", "status": "ISSUED", "dueDate": "2024-02-01", "issuedDate": "2024-01-01", "paidDate": null, "blockchainTxHash": null, "ipfsDocumentCid": null, "notes": "Initial deposit", "createdAt": "2024-01-01T00:00:00Z", "updatedAt": "2024-01-01T00:00:00Z" } ] ``` --- ##### POST `/invoices` Create invoice (STAFF/ADMIN). **Request:** ```json { "invoiceNumber": "INV-2024-002", "projectId": 1, "clientId": 2, "amount": 2500.00, "currency": "USD", "status": "DRAFT", "dueDate": "2024-03-01", "notes": "Final payment" } ``` **Status Values:** `DRAFT`, `ISSUED`, `PAID`, `CANCELLED`, `OVERDUE` **Response (201):** Created invoice object --- ##### GET `/invoices/{id}` Get invoice by ID. **Authorization:** Owner (client) or STAFF/ADMIN --- ##### PUT `/invoices/{id}` Update invoice (STAFF/ADMIN). --- ##### DELETE `/invoices/{id}` Delete invoice (STAFF/ADMIN). --- #### Payment Endpoints ##### GET `/payments` List payments. **Query Parameters:** - `invoice_id` (optional): Filter by invoice **Response (200):** ```json [ { "id": 1, "invoiceId": 1, "amount": 1500.00, "currency": "USD", "paymentMethod": "CREDIT_CARD", "status": "COMPLETED", "transactionId": "pi_xxx", "blockchainTxHash": null, "blockchainNetwork": null, "paymentProcessor": "stripe", "processorFee": 45.00, "processedAt": "2024-01-15T10:30:00Z", "createdAt": "2024-01-15T10:25:00Z", "updatedAt": "2024-01-15T10:30:00Z" } ] ``` --- ##### POST `/payments` Record a payment (STAFF/ADMIN). **Request:** ```json { "invoiceId": 1, "amount": 1500.00, "currency": "USD", "paymentMethod": "BANK_TRANSFER", "status": "COMPLETED", "transactionId": "TXN123", "processedAt": "2024-01-15T10:30:00Z" } ``` **Payment Methods:** `CREDIT_CARD`, `BANK_TRANSFER`, `CRYPTO`, `CHECK`, `CASH` **Status Values:** `PENDING`, `COMPLETED`, `FAILED`, `REFUNDED` --- ##### GET `/payments/{id}` Get payment by ID. --- #### Stripe Integration ##### POST `/invoices/create-payment-intent` Create Stripe payment intent. **Request:** ```json { "invoiceId": 1 } ``` **Response (200):** ```json { "clientSecret": "pi_xxx_secret_xxx", "paymentId": 1 } ``` **Frontend Usage:** ```javascript const { clientSecret } = await invoicesStore.createPaymentIntent(invoiceId) // Use clientSecret with Stripe.js to complete payment ``` --- ##### POST `/webhooks/stripe` Stripe webhook handler (no auth, signature verified). **Handled Events:** - `payment_intent.succeeded` → Payment COMPLETED, Invoice PAID - `payment_intent.payment_failed` → Payment FAILED - `charge.refunded` → Payment REFUNDED --- ### Blog Service (Port 8085) Base path: `/api/blog` The blog service supports two types of blogs: - **SITE blogs**: Official content created by STAFF/ADMIN with approval workflow - **USER blogs**: Community content created by any authenticated user #### Blog Status Lifecycle ``` SITE Blogs (STAFF): DRAFT → PENDING_REVIEW → APPROVED/REJECTED → PUBLISHED → ARCHIVED SITE Blogs (ADMIN): DRAFT → PUBLISHED (can skip review) → ARCHIVED USER Blogs: PUBLISHED immediately (community content) ``` #### Blog Object ```json { "id": 1, "slug": "getting-started", "title": "Getting Started with Our Services", "author": "John Doe", "authorId": 1, "date": "2024-01-01T00:00:00Z", "tags": ["guide", "introduction"], "short_description": "Learn how to get started...", "content": "# Full markdown content...", "status": "PUBLISHED", "blogType": "SITE", "verified": true, "verifiedBy": 1, "verifiedAt": "2024-01-01T00:00:00Z", "reviewedBy": 1, "reviewedAt": "2024-01-01T00:00:00Z", "reviewNotes": "Approved - great content!", "publishedAt": "2024-01-01T00:00:00Z", "promotedAt": null, "promotedBy": null, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- #### Public Endpoints (Site Blogs) ##### GET `/blogs` List published SITE blogs (official content). **Response (200):** Array of blog objects with blogType="SITE" and status="PUBLISHED" --- ##### GET `/blogs/{slug}` Get a published SITE blog by slug. **Response (200):** Blog object **Response (404):** Blog not found --- #### Public Endpoints (Community Blogs) ##### GET `/community/blogs` List published USER blogs (community content). **Response (200):** Array of blog objects with blogType="USER" and status="PUBLISHED" --- ##### GET `/community/blogs/{slug}` Get a published community blog by slug. **Response (200):** Blog object **Response (404):** Community blog not found --- #### User Endpoints (Authenticated) ##### GET `/community/my-blogs` List current user's community blogs (all statuses). **Auth:** Required (any role) **Response (200):** Array of user's blog objects --- ##### POST `/community/blogs` Create a new community blog (published immediately). **Auth:** Required (any role) **Request:** ```json { "slug": "my-tutorial", "title": "My Tutorial", "author": "Optional Author Name", "tags": ["tutorial", "guide"], "short_description": "Brief description...", "content": "# Full content in markdown..." } ``` Note: Slug is prefixed with "community-" automatically. **Response (201):** Created blog object --- ##### PUT `/community/blogs/{slug}` Update own community blog. **Auth:** Required (author only) **Response (200):** Success message **Response (403):** Not your blog --- ##### DELETE `/community/blogs/{slug}` Delete own community blog. **Auth:** Required (author only) **Response (200):** Success message **Response (403):** Not your blog --- #### STAFF/ADMIN Endpoints (Site Blogs) ##### GET `/admin/blogs` List all SITE blogs with optional filters. **Auth:** Required (STAFF sees own, ADMIN sees all) **Query Parameters:** - `status`: Filter by status (DRAFT, PENDING_REVIEW, APPROVED, PUBLISHED, REJECTED, ARCHIVED) - `author_id`: Filter by author (ADMIN only) **Response (200):** Array of blog objects --- ##### GET `/admin/blogs/pending` List SITE blogs pending review. **Auth:** Required (ADMIN only) **Response (200):** Array of blogs with status="PENDING_REVIEW" --- ##### POST `/admin/blogs` Create a new SITE blog. **Auth:** Required (STAFF, ADMIN) **Request:** ```json { "slug": "new-post", "title": "New Blog Post", "author": "Jane Doe", "date": "2024-01-01T00:00:00Z", "tags": ["news", "update"], "short_description": "Brief description...", "content": "# Full content in markdown..." } ``` **Response (201):** Created blog object (status=DRAFT) --- ##### PUT `/admin/blogs/{slug}` Update a SITE blog. **Auth:** Required (STAFF can update own DRAFT/REJECTED, ADMIN can update any) **Response (200):** Success message --- ##### POST `/admin/blogs/{slug}/submit` Submit blog for admin review. **Auth:** Required (STAFF, ADMIN) **Response (200):** Success message, status changes to PENDING_REVIEW --- ##### POST `/admin/blogs/{slug}/review` Review a pending blog. **Auth:** Required (ADMIN only) **Request:** ```json { "action": "approve|reject|request_changes", "notes": "Feedback for author (required for reject/request_changes)" } ``` **Response (200):** Success message --- ##### POST `/admin/blogs/{slug}/publish` Publish an approved blog. **Auth:** Required (ADMIN only) **Response (200):** Success message, status changes to PUBLISHED --- ##### POST `/admin/blogs/{slug}/unpublish` Unpublish a blog (archive it). **Auth:** Required (ADMIN only) **Response (200):** Success message, status changes to ARCHIVED --- ##### DELETE `/admin/blogs/{slug}` Delete a SITE blog. **Auth:** Required (ADMIN only) **Response (200):** Success message --- #### ADMIN Community Management Endpoints ##### GET `/admin/community/blogs` List all community blogs. **Auth:** Required (ADMIN only) **Query Parameters:** - `status`: Filter by status **Response (200):** Array of blog objects with blogType="USER" --- ##### POST `/admin/community/blogs/{slug}/verify` Verify a community blog/tutorial (adds verified badge). **Auth:** Required (ADMIN only) **Response (200):** Success message --- ##### POST `/admin/community/blogs/{slug}/unverify` Remove verification from a community blog. **Auth:** Required (ADMIN only) **Response (200):** Success message --- ##### POST `/admin/community/blogs/{slug}/promote` Promote a community blog to SITE blog. **Auth:** Required (ADMIN only) **Response (200):** Success message, blogType changes to SITE --- ##### POST `/admin/community/blogs/{slug}/archive` Archive a community blog (hide from public). **Auth:** Required (ADMIN only) **Response (200):** Success message, status changes to ARCHIVED --- ##### DELETE `/admin/community/blogs/{slug}` Delete any community blog. **Auth:** Required (ADMIN only) **Response (200):** Success message --- ### IPFS/P2P Service (Port 8086) Base path: `/api/ipfs` All endpoints are public (no authentication). ##### GET `/health` Service health check. **Response (200):** ```json { "status": "healthy", "peerId": "12D3KooW...", "connectedPeers": 3, "listenAddresses": [ "/ip4/10.89.0.3/tcp/4002/ws", "/ip4/127.0.0.1/tcp/4002/ws" ] } ``` --- ##### GET `/peer-info` Get this node's peer info. **Response (200):** ```json { "peerId": "12D3KooW...", "addresses": [ "/ip4/10.89.0.3/tcp/4001/p2p/12D3KooW...", "/ip4/10.89.0.3/tcp/4002/ws/p2p/12D3KooW..." ] } ``` --- ##### GET `/peers` List connected peers. **Response (200):** ```json { "count": 3, "peers": ["12D3KooWA...", "12D3KooWB...", "12D3KooWC..."] } ``` --- ##### POST `/connect` Connect to a peer. **Request:** ```json { "peerAddr": "/ip4/192.168.1.100/tcp/4001/p2p/12D3KooW..." } ``` **Response (200):** ```json { "status": "connected", "peerId": "12D3KooW..." } ``` --- ### Forum Service (Port 8087) Base path: `/api/forum` The forum service provides Q&A functionality where any user can ask questions and anyone can answer. #### Question Object ```json { "id": 1, "title": "How do I configure IPFS?", "content": "I'm trying to set up IPFS for my project...", "authorId": 1, "authorName": "JohnDoe", "tags": ["ipfs", "configuration"], "upvotes": 10, "downvotes": 2, "answerCount": 3, "viewCount": 150, "acceptedAnswerId": 5, "status": "ANSWERED", "createdAt": "2024-01-01T00:00:00Z", "updatedAt": "2024-01-01T00:00:00Z" } ``` #### Answer Object ```json { "id": 5, "questionId": 1, "content": "You need to configure the bootstrap nodes...", "authorId": 2, "authorName": "JaneDoe", "upvotes": 15, "downvotes": 0, "isAccepted": true, "isVerified": true, "verifiedBy": 1, "verifiedAt": "2024-01-02T00:00:00Z", "createdAt": "2024-01-01T12:00:00Z", "updatedAt": "2024-01-01T12:00:00Z" } ``` --- #### Public Endpoints ##### GET `/questions` List questions with optional filtering. **Query Parameters:** - `tag`: Filter by tag - `status`: Filter by status (OPEN, ANSWERED, CLOSED) - `sort`: Sort by "newest" (default), "votes", or "unanswered" - `limit`: Max results (default 20, max 100) - `offset`: Pagination offset **Response (200):** Array of question objects --- ##### GET `/questions/{id}` Get a question with all its answers. **Response (200):** ```json { "question": { /* question object */ }, "answers": [ /* array of answer objects */ ] } ``` --- #### Authenticated Endpoints ##### POST `/questions` Create a new question. **Auth:** Required (any role) **Request:** ```json { "title": "How do I configure IPFS?", "content": "I'm trying to set up IPFS...", "tags": ["ipfs", "configuration"] } ``` **Response (201):** Created question object --- ##### PUT `/questions/{id}` Update a question. **Auth:** Required (author or ADMIN) **Response (200):** Success message --- ##### DELETE `/questions/{id}` Delete a question. **Auth:** Required (author or ADMIN) **Response (200):** Success message --- ##### POST `/questions/{id}/answers` Create an answer to a question. **Auth:** Required (any role) **Request:** ```json { "content": "You need to configure the bootstrap nodes..." } ``` **Response (201):** Created answer object --- ##### PUT `/answers/{id}` Update an answer. **Auth:** Required (author or ADMIN) **Response (200):** Success message --- ##### DELETE `/answers/{id}` Delete an answer. **Auth:** Required (author or ADMIN) **Response (200):** Success message --- ##### POST `/questions/{id}/vote` Vote on a question. **Auth:** Required (any role) **Request:** ```json { "voteType": 1 } ``` Note: `voteType` is 1 for upvote, -1 for downvote **Response (200):** Success message --- ##### POST `/answers/{id}/vote` Vote on an answer. **Auth:** Required (any role) **Request:** ```json { "voteType": 1 } ``` **Response (200):** Success message --- ##### POST `/answers/{id}/accept` Accept an answer (question author only). **Auth:** Required (question author) **Response (200):** Success message --- #### Admin Endpoints ##### POST `/answers/{id}/verify` Verify an answer as correct. **Auth:** Required (ADMIN only) **Response (200):** Success message --- ##### POST `/questions/{id}/close` Close a question (prevents new answers). **Auth:** Required (ADMIN only) **Response (200):** Success message --- ## Frontend Stores The frontend uses Pinia stores to interact with the API. All stores are located in `frontend/src/stores/`. ### Auth Store (`auth.ts`) ```typescript import { useAuthStore } from '@/stores/auth' const authStore = useAuthStore() // State authStore.user // Current user object authStore.token // JWT token authStore.loading // Loading state authStore.error // Error message // Computed authStore.isAuthenticated // boolean authStore.isAdmin // boolean authStore.isStaff // boolean authStore.isClient // boolean // Actions await authStore.loginEmailPassword(email, password) await authStore.loginBlockchain(address, signature, message) await authStore.registerEmailPassword(email, password, name) await authStore.registerBlockchain(address, signature, message, name) await authStore.fetchProfile() authStore.logout() ``` ### Projects Store (`projects.ts`) ```typescript import { useProjectsStore } from '@/stores/projects' const projectsStore = useProjectsStore() // State projectsStore.projects // Project[] projectsStore.currentProject // Project | null projectsStore.loading // boolean projectsStore.error // string | null // Computed projectsStore.activeProjects // Projects not completed/archived projectsStore.completedProjects // Completed projects projectsStore.inProgressProjects // In-progress projects projectsStore.planningProjects // Planning projects projectsStore.projectCount // Total count projectsStore.projectsByStatus // Record projectsStore.projectsByClient // Record // Actions await projectsStore.fetchProjects() await projectsStore.fetchProject(id) await projectsStore.createProject({ name, description, status, clientId }) await projectsStore.updateProject(id, { name, description, status }) await projectsStore.deleteProject(id) ``` ### Tasks Store (`tasks.ts`) ```typescript import { useTasksStore } from '@/stores/tasks' const tasksStore = useTasksStore() // State tasksStore.tasks // Task[] tasksStore.currentTask // Task | null tasksStore.loading // boolean tasksStore.error // string | null // Computed tasksStore.completedTasks // Completed tasks tasksStore.pendingTasks // Not completed tasks tasksStore.inProgressTasks // In-progress tasks tasksStore.todoTasks // Todo tasks tasksStore.taskCount // Total count tasksStore.tasksByProject // Record tasksStore.tasksByStatus // Record // Actions await tasksStore.fetchTasks(projectId?) // Optional filter by project await tasksStore.fetchTask(id) await tasksStore.createTask({ projectId, title, description, status }) await tasksStore.updateTask(id, { title, status }) await tasksStore.deleteTask(id) ``` ### Invoices Store (`invoices.ts`) ```typescript import { useInvoicesStore } from '@/stores/invoices' const invoicesStore = useInvoicesStore() // State invoicesStore.invoices // Invoice[] invoicesStore.currentInvoice // Invoice | null invoicesStore.payments // Payment[] invoicesStore.loading // boolean invoicesStore.error // string | null // Actions await invoicesStore.fetchInvoices(clientId?) await invoicesStore.fetchInvoice(id) await invoicesStore.createInvoice({ invoiceNumber, clientId, amount, ... }) await invoicesStore.updateInvoice(id, { amount, status, ... }) await invoicesStore.deleteInvoice(id) await invoicesStore.fetchPayments(invoiceId?) await invoicesStore.createPaymentIntent(invoiceId) // Returns { clientSecret, paymentId } await invoicesStore.createStripePayment(invoiceId, amount) // Returns clientSecret await invoicesStore.recordPayment({ invoiceId, amount, paymentMethod, transactionHash? }) ``` ### Users Store (`users.ts`) ```typescript import { useUsersStore } from '@/stores/users' const usersStore = useUsersStore() // State usersStore.users // User[] usersStore.loading // boolean usersStore.error // string | null // Actions await usersStore.fetchUsers() await usersStore.promoteRole(userId, role) await usersStore.demoteRole(userId, role) ``` --- ## Error Handling ### HTTP Status Codes | Code | Meaning | |------|---------| | 200 | Success (GET, PUT) | | 201 | Created (POST) | | 204 | No Content (DELETE) | | 400 | Bad Request - Invalid input | | 401 | Unauthorized - Missing/invalid token | | 403 | Forbidden - Insufficient permissions | | 404 | Not Found | | 409 | Conflict - Duplicate value | | 500 | Server Error | ### Frontend Error Handling ```typescript import { useToast } from '@/composables/useToast' const toast = useToast() try { await projectsStore.createProject(newProject) toast.success('Project created successfully') } catch (err) { toast.error(projectsStore.error || 'Failed to create project') } ``` --- ## Environment Variables ### Backend Services | Variable | Required | Default | Description | |----------|----------|---------|-------------| | `JWT_SECRET` | Yes | - | JWT signing key (min 32 chars) | | `DB_HOST` | Yes | - | PostgreSQL host | | `DB_USER` | Yes | - | Database username | | `DB_PASSWORD` | Yes | - | Database password | | `DB_NAME` | Yes | - | Database name | | `DB_SSL_MODE` | No | `require` | SSL mode (disable, require, verify-ca, verify-full) | | `CORS_ALLOW_ORIGIN` | No | `http://localhost:8090` | Allowed CORS origin | | `STRIPE_SECRET_KEY` | No | - | Stripe secret key (payment-service) | | `STRIPE_WEBHOOK_SECRET` | No | - | Stripe webhook secret | ### Frontend | Variable | Default | Description | |----------|---------|-------------| | `VITE_API_BASE_URL` | `/api` | API base URL (usually not needed with nginx proxy) | --- ## Summary | Service | Port | Endpoints | Auth Required | |---------|------|-----------|---------------| | Auth | 8082 | 12 | 5 public, 5 protected, 2 admin | | Work Management | 8083 | 15 | All protected | | Payment | 8084 | 10 | 1 public (webhook), rest protected | | Blog | 8085 | 6 | 3 public (read), 3 protected (write) | | IPFS/P2P | 8086 | 4 | All public | | **Total** | - | **47** | - |