1623 lines
32 KiB
Markdown
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** | - |
|