Files
web-hosts/domains/coppertone.tech/docs/API.md
2025-12-26 13:38:04 +01:00

1623 lines
32 KiB
Markdown

# 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>
```
### 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 <token>`
**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 <token>`
**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 <token>`
**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 <token>`
**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 <token>`
**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 <token>`
**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 <token>`
**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 <token>`
**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 <token>`
**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 <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):**
```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 <token>`
**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 <token>`
**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 <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):**
```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<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`)
```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<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`)
```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** | - |