Files
2025-12-26 13:38:04 +01:00

32 KiB

Copper Tone Technologies API Documentation

This document provides comprehensive documentation for all backend API endpoints and frontend integration patterns.

Table of Contents


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:

{
  "error": "Error message description"
}

Or for validation errors:

{
  "field": "fieldName",
  "message": "Validation error message"
}

Authentication

JWT Token

All protected endpoints require a JWT token in the Authorization header:

Authorization: Bearer <token>

Token Payload

{
  "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:

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:

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:

{
  "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):

{
  "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:

{
  "address": "0x1234...",
  "signature": "0xabcd...",
  "message": "Sign this message to register",
  "name": "John Doe"
}

Response (201):

{
  "message": "User registered successfully",
  "userId": 1
}

POST /login-email-password

Login with email and password.

Request:

{
  "email": "user@example.com",
  "password": "SecurePass123"
}

Response (200):

{
  "token": "eyJhbGciOiJIUzI1NiIs..."
}

POST /login-blockchain

Login with Ethereum signature.

Request:

{
  "address": "0x1234...",
  "signature": "0xabcd...",
  "message": "Sign this message to login"
}

Response (200):

{
  "token": "eyJhbGciOiJIUzI1NiIs..."
}

GET /healthz

Health check endpoint.

Response (200):

ok

Protected Endpoints

GET /profile

Get current user's profile.

Headers: Authorization: Bearer <token>

Response (200):

{
  "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 <token>

Response (200):

[
  {
    "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 <token>

Request (email):

{
  "type": "email_password",
  "email": "another@example.com",
  "password": "SecurePass123"
}

Request (blockchain):

{
  "type": "blockchain_address",
  "address": "0x1234...",
  "signature": "0xabcd...",
  "message": "Sign to link identity"
}

Response (201):

{
  "message": "Identity linked successfully"
}

POST /unlink-identity

Remove an identity from account.

Headers: Authorization: Bearer <token>

Request:

{
  "identityId": 2
}

Response (200):

{
  "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 <token>

Response (200):

[
  {
    "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 <token>

Request:

{
  "userId": 2,
  "role": "STAFF"
}

Response (200):

{
  "message": "Successfully granted STAFF role to user 2"
}

POST /admin/users/demote-role

Remove a role from a user (ADMIN only).

Headers: Authorization: Bearer <token>

Request:

{
  "userId": 2,
  "role": "STAFF"
}

Response (200):

{
  "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 <token>

Authorization:

  • STAFF/ADMIN: See all projects
  • CLIENT: See only their own projects

Response (200):

[
  {
    "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 <token>

Request:

{
  "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 <token>

Response (200): Project object


PUT /projects/{id}

Update a project (STAFF/ADMIN).

Headers: Authorization: Bearer <token>

Request: Same as POST

Response (200): Updated project object


DELETE /projects/{id}

Delete a project (STAFF/ADMIN only).

Headers: Authorization: Bearer <token>

Response: 204 No Content


Task Endpoints

GET /tasks

List tasks.

Headers: Authorization: Bearer <token>

Query Parameters:

  • project_id (optional): Filter by project

Response (200):

[
  {
    "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 <token>

Request:

{
  "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 <token>

Query Parameters:

  • project_id (optional): Filter by project

Response (200):

[
  {
    "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:

{
  "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 <token>

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):

[
  {
    "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:

{
  "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):

[
  {
    "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:

{
  "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:

{
  "invoiceId": 1
}

Response (200):

{
  "clientSecret": "pi_xxx_secret_xxx",
  "paymentId": 1
}

Frontend Usage:

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

{
  "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:

{
  "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:

{
  "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:

{
  "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):

{
  "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):

{
  "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):

{
  "count": 3,
  "peers": ["12D3KooWA...", "12D3KooWB...", "12D3KooWC..."]
}

POST /connect

Connect to a peer.

Request:

{
  "peerAddr": "/ip4/192.168.1.100/tcp/4001/p2p/12D3KooW..."
}

Response (200):

{
  "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

{
  "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

{
  "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):

{
  "question": { /* question object */ },
  "answers": [ /* array of answer objects */ ]
}

Authenticated Endpoints

POST /questions

Create a new question.

Auth: Required (any role)

Request:

{
  "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:

{
  "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:

{
  "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:

{
  "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)

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)

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<string, Project[]>
projectsStore.projectsByClient    // Record<number, Project[]>

// 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)

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<number, Task[]>
tasksStore.tasksByStatus     // Record<string, Task[]>

// 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)

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)

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

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 -