life-docs/api.md
2026-03-20 09:32:20 -07:00

999 lines
19 KiB
Markdown

# Life Management System — API Design
## Overview
REST API at `http://localhost:3700/api/*` with Socket.IO WebSocket gateways on the same port. All responses follow consistent JSON format. Pagination via query params. Validation via `class-validator` DTOs.
**Base URL:** `http://localhost:3700/api`
**Swagger:** `http://localhost:3700/api-docs`
## Common Patterns
### Pagination
All list endpoints accept:
```
?page=1&limit=20&sort=createdAt&order=DESC
```
**Response envelope:**
```json
{
"data": [...],
"meta": {
"page": 1,
"limit": 20,
"total": 142,
"totalPages": 8
}
}
```
### Error Response
```json
{
"statusCode": 400,
"error": "Bad Request",
"message": "Validation failed",
"details": [
{ "field": "title", "message": "title should not be empty" }
]
}
```
### Filtering
List endpoints support filtering via query params specific to each entity. Common patterns:
- `?domainId=<uuid>` — Filter by domain
- `?status=todo,in_progress` — Comma-separated enum values
- `?startDate=2026-01-01&endDate=2026-01-31` — Date ranges
---
## Health
### `GET /health`
Health check endpoint (via `@lilith/nestjs-health`).
**Response:**
```json
{
"status": "ok",
"info": {
"database": { "status": "up" },
"redis": { "status": "up" }
}
}
```
---
## Domains Module
### `GET /api/domains`
List all domains.
**Query params:** `?isActive=true`
**Response:** `Domain[]`
```json
[
{
"id": "uuid",
"name": "Christine's Startup",
"type": "client_work",
"slug": "christine",
"color": "#4A90D9",
"icon": "briefcase",
"description": "Primary client contract",
"isActive": true,
"sortOrder": 0,
"config": {},
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z"
}
]
```
### `GET /api/domains/:id`
Get domain by ID.
### `GET /api/domains/slug/:slug`
Get domain by slug (for URL-based lookups).
### `POST /api/domains`
Create domain.
**Body:**
```json
{
"name": "New Domain",
"type": "side_project",
"slug": "new-domain",
"color": "#FF5733",
"icon": "star",
"description": "Optional description",
"config": {}
}
```
### `PATCH /api/domains/:id`
Update domain (partial).
### `DELETE /api/domains/:id`
Delete domain (hard delete — only if no associated entities).
---
## Tasks Module
### `GET /api/tasks`
List tasks with filtering.
**Query params:**
- `?domainId=<uuid>` — Filter by domain
- `?goalId=<uuid>` — Filter by goal
- `?sprintId=<uuid>` — Filter by sprint
- `?parentId=<uuid>` — Filter subtasks of a parent
- `?status=todo,in_progress` — Status filter (comma-separated)
- `?priority=critical,high` — Priority filter
- `?energyLevel=low,medium` — Energy level filter
- `?isQuickWin=true` — Quick wins only
- `?overdue=true` — Overdue tasks only (due_date < today, status not done/cancelled)
- `?scheduledDate=2026-02-24` Scheduled for specific date
- `?dueDateStart=&dueDateEnd=` Due date range
- `?search=<text>` Full-text search on title
- `?tags=urgent,client` Tag filter (comma-separated, AND logic)
- `?includeSubtasks=true` Include subtask tree
**Response:** Paginated `Task[]`
```json
{
"data": [
{
"id": "uuid",
"domainId": "uuid",
"domain": { "id": "uuid", "name": "...", "slug": "...", "color": "..." },
"goalId": null,
"sprintId": null,
"parentId": null,
"title": "Design review for Christine",
"description": "Review the latest mockups",
"status": "todo",
"priority": "high",
"energyLevel": "high",
"estimatedMinutes": 60,
"actualMinutes": null,
"dueDate": "2026-02-24",
"scheduledDate": "2026-02-24",
"isQuickWin": false,
"recurrenceRule": null,
"tags": ["design", "client"],
"sortOrder": 0,
"subtaskCount": 3,
"subtaskCompletedCount": 1,
"createdAt": "...",
"updatedAt": "..."
}
],
"meta": { "page": 1, "limit": 20, "total": 45, "totalPages": 3 }
}
```
### `GET /api/tasks/:id`
Get task by ID with subtasks.
**Response:** `Task` with nested `subtasks: Task[]`
### `POST /api/tasks`
Create task.
**Body:**
```json
{
"domainId": "uuid",
"goalId": null,
"sprintId": null,
"parentId": null,
"title": "New task",
"description": "Description",
"status": "todo",
"priority": "medium",
"energyLevel": "medium",
"estimatedMinutes": 30,
"dueDate": "2026-03-01",
"scheduledDate": null,
"isQuickWin": false,
"recurrenceRule": null,
"tags": ["tag1"]
}
```
### `PATCH /api/tasks/:id`
Update task (partial).
### `PATCH /api/tasks/:id/status`
Quick status transition.
**Body:**
```json
{
"status": "done",
"actualMinutes": 45
}
```
### `DELETE /api/tasks/:id`
Soft delete (sets `deletedAt`).
### `POST /api/tasks/:id/reorder`
Reorder task within its list.
**Body:**
```json
{
"sortOrder": 3
}
```
---
## Goals Module
### `GET /api/goals`
List goals with filtering.
**Query params:**
- `?domainId=<uuid>` Filter by domain
- `?level=yearly,quarterly` Filter by level
- `?status=active` Filter by status
- `?parentId=<uuid>` Children of a specific goal
### `GET /api/goals/tree`
Get hierarchical goal tree.
**Query params:**
- `?domainId=<uuid>` Optional domain filter
**Response:** Nested tree structure:
```json
[
{
"id": "uuid",
"title": "Ship Lilith Platform v1",
"level": "yearly",
"status": "active",
"progressPct": 35,
"children": [
{
"id": "uuid",
"title": "Complete core modules",
"level": "quarterly",
"progressPct": 60,
"children": [
{
"id": "uuid",
"title": "Finish auth system",
"level": "monthly",
"progressPct": 80,
"children": []
}
]
}
]
}
]
```
### `GET /api/goals/:id`
Get goal with children and linked tasks.
### `POST /api/goals`
Create goal.
**Body:**
```json
{
"domainId": "uuid",
"parentId": null,
"title": "Goal title",
"description": "Description",
"level": "quarterly",
"targetDate": "2026-06-30"
}
```
### `PATCH /api/goals/:id`
Update goal.
### `PATCH /api/goals/:id/progress`
Update progress percentage.
**Body:**
```json
{
"progressPct": 75
}
```
### `DELETE /api/goals/:id`
Delete goal (cascading to orphan children re-parents to grandparent or null).
---
## Habits Module
### `GET /api/habits`
List habits.
**Query params:**
- `?domainId=<uuid>`
- `?isActive=true`
- `?frequency=daily`
- `?timeOfDay=morning`
**Response:** `Habit[]` with current streak info.
### `GET /api/habits/due-today`
Get habits due today based on frequency configuration and day of week.
**Response:** `Habit[]` with `checkedInToday: boolean` flag.
### `GET /api/habits/:id`
Get habit with recent check-in history (last 30 days).
### `POST /api/habits`
Create habit.
**Body:**
```json
{
"domainId": "uuid",
"goalId": null,
"name": "Morning meditation",
"description": "10 minutes mindfulness",
"frequency": "daily",
"frequencyConfig": {},
"timeOfDay": "morning",
"estimatedMinutes": 10
}
```
### `PATCH /api/habits/:id`
Update habit.
### `POST /api/habits/:id/check-in`
Record check-in for today.
**Body:**
```json
{
"status": "done",
"qualityRating": 4,
"notes": "Great session today"
}
```
**Response:** Updated `Habit` with new streak values.
### `GET /api/habits/:id/check-ins`
Get check-in history.
**Query params:**
- `?startDate=2026-01-01&endDate=2026-02-28`
**Response:** `HabitCheckIn[]`
### `GET /api/habits/:id/stats`
Get habit statistics.
**Response:**
```json
{
"currentStreak": 14,
"bestStreak": 30,
"completionRate30d": 0.87,
"completionRate7d": 1.0,
"averageQuality": 3.8,
"totalCheckIns": 120,
"checkInsByStatus": { "done": 105, "skipped": 10, "partial": 5 }
}
```
---
## Scheduling Module
### `GET /api/scheduling/time-blocks`
List time blocks.
**Query params:**
- `?date=2026-02-24` Specific date
- `?startDate=&endDate=` Date range
- `?domainId=<uuid>`
### `POST /api/scheduling/time-blocks`
Create time block.
**Body:**
```json
{
"domainId": "uuid",
"taskId": null,
"habitId": null,
"title": "Christine standup",
"date": "2026-02-24",
"startTime": "09:00",
"endTime": "10:30",
"blockType": "work",
"notes": ""
}
```
### `PATCH /api/scheduling/time-blocks/:id`
Update time block.
### `POST /api/scheduling/time-blocks/:id/reorder`
Reorder time block (update start/end times).
**Body:**
```json
{
"startTime": "10:00",
"endTime": "11:30"
}
```
### `DELETE /api/scheduling/time-blocks/:id`
Delete time block.
### `GET /api/scheduling/daily-plan/:date`
Get or initialize daily plan for a date.
**Response:**
```json
{
"id": "uuid",
"date": "2026-02-24",
"energyLevel": "medium",
"focusDomains": ["uuid1", "uuid2"],
"morningIntention": "Focus on Christine deliverables",
"eveningReflection": null,
"moodMorning": 3,
"moodEvening": null,
"timeBlocks": [...],
"createdAt": "...",
"updatedAt": "..."
}
```
### `PUT /api/scheduling/daily-plan/:date`
Create or update daily plan (upsert).
**Body:**
```json
{
"energyLevel": "medium",
"focusDomains": ["uuid1", "uuid2"],
"morningIntention": "Focus on Christine deliverables",
"moodMorning": 3
}
```
### `PATCH /api/scheduling/daily-plan/:date/reflection`
Update evening reflection (separate endpoint for end-of-day flow).
**Body:**
```json
{
"eveningReflection": "Good day, completed all priority tasks",
"moodEvening": 4
}
```
---
## Today Module (Aggregation)
### `GET /api/today`
Unified daily view pulls from all modules for today.
**Response:**
```json
{
"date": "2026-02-24",
"dailyPlan": {
"energyLevel": "medium",
"moodMorning": 3,
"morningIntention": "...",
"focusDomains": [...]
},
"timeBlocks": [...],
"priorityTasks": [...],
"quickWins": [...],
"habitsDueToday": [
{
"habit": { "id": "...", "name": "Morning meditation", ... },
"checkedIn": false,
"checkIn": null
}
],
"domainStats": [
{
"domain": { "id": "...", "name": "Christine's Startup", "color": "#4A90D9" },
"tasksDueToday": 2,
"tasksOverdue": 1,
"incomeThisWeek": "420.00",
"currentStreak": 7,
"sprintProgress": 60
}
],
"overdueTasks": [...],
"completedToday": 5,
"totalToday": 12
}
```
### `GET /api/today/next-action`
Get suggested next action based on energy, priority, time of day.
**Response:**
```json
{
"task": {
"id": "uuid",
"title": "Design review for Christine",
"domain": { "name": "Christine's Startup", "color": "#4A90D9" },
"priority": "high",
"energyLevel": "high",
"estimatedMinutes": 60
},
"score": 65,
"reason": "This is overdue and matches your current energy level"
}
```
---
## Income Module (Encrypted)
### `GET /api/income`
List income entries (decrypted).
**Query params:**
- `?domainId=<uuid>`
- `?startDate=&endDate=`
- `?sourceType=session,tip`
- `?currency=GBP`
### `GET /api/income/summary`
Income summary with aggregation.
**Query params:**
- `?period=month|week|year`
- `?domainId=<uuid>` Optional domain filter
- `?startDate=&endDate=` Custom range
**Response:**
```json
{
"period": "month",
"startDate": "2026-02-01",
"endDate": "2026-02-28",
"total": "3420.00",
"currency": "GBP",
"byDomain": [
{ "domain": { "name": "Escort", "color": "#FF6B6B" }, "total": "2100.00", "count": 8 },
{ "domain": { "name": "OnlyFans", "color": "#00AFF0" }, "total": "820.00", "count": 45 },
{ "domain": { "name": "Christine's Startup", "color": "#4A90D9" }, "total": "500.00", "count": 1 }
],
"bySourceType": [
{ "sourceType": "session", "total": "2100.00", "count": 8 },
{ "sourceType": "subscription", "total": "620.00", "count": 40 },
{ "sourceType": "tip", "total": "200.00", "count": 5 },
{ "sourceType": "invoice", "total": "500.00", "count": 1 }
]
}
```
### `POST /api/income`
Create income entry (encrypted on write).
**Body:**
```json
{
"domainId": "uuid",
"date": "2026-02-24",
"amount": "150.00",
"currency": "GBP",
"sourceType": "session",
"description": "Evening session",
"durationMinutes": 120,
"contactId": "uuid"
}
```
### `PATCH /api/income/:id`
Update income entry.
### `DELETE /api/income/:id`
Delete income entry.
### `GET /api/income/billable`
List billable entries.
**Query params:**
- `?domainId=<uuid>`
- `?isInvoiced=false`
- `?startDate=&endDate=`
### `POST /api/income/billable`
Create billable entry.
### `PATCH /api/income/billable/:id`
Update billable entry (mark as invoiced, etc.).
---
## Health Module (Encrypted)
### `GET /api/health/measurements`
List measurements.
**Query params:**
- `?domainId=<uuid>`
- `?type=weight,waist`
- `?startDate=&endDate=`
### `POST /api/health/measurements`
Create measurement.
**Body:**
```json
{
"domainId": "uuid",
"date": "2026-02-24",
"type": "weight",
"value": 62.5,
"unit": "kg",
"notes": "Morning, before breakfast"
}
```
### `GET /api/health/medical`
List medical entries (decrypted).
**Query params:**
- `?domainId=<uuid>`
- `?type=hrt_dose,appointment`
- `?startDate=&endDate=`
### `POST /api/health/medical`
Create medical entry (encrypted on write).
**Body:**
```json
{
"domainId": "uuid",
"date": "2026-02-24",
"type": "hrt_dose",
"title": "Estradiol injection",
"details": "0.5ml intramuscular, left thigh",
"provider": "Dr. Smith",
"nextDate": "2026-03-10"
}
```
### `PATCH /api/health/medical/:id`
Update medical entry.
---
## Contacts Module (Encrypted)
### `GET /api/contacts`
List contacts (decrypted).
**Query params:**
- `?domainId=<uuid>`
- `?contactType=client,agency`
- `?isActive=true`
- `?search=<name>`
### `GET /api/contacts/:id`
Get contact with interaction history.
### `POST /api/contacts`
Create contact (encrypted on write).
**Body:**
```json
{
"domainId": "uuid",
"name": "James",
"contactType": "client",
"phone": "+447700123456",
"email": "james@example.com",
"notes": "Regular, prefers weekday evenings",
"rating": 4,
"metadata": { "preferences": "..." }
}
```
### `PATCH /api/contacts/:id`
Update contact.
### `DELETE /api/contacts/:id`
Soft delete contact.
---
## Projects Module
### Sprints
#### `GET /api/projects/sprints`
**Query params:** `?domainId=<uuid>&status=active`
#### `GET /api/projects/sprints/:id`
Get sprint with associated tasks.
#### `POST /api/projects/sprints`
**Body:**
```json
{
"domainId": "uuid",
"name": "Sprint 3",
"startDate": "2026-02-24",
"endDate": "2026-03-07",
"goal": "Complete authentication module"
}
```
#### `PATCH /api/projects/sprints/:id`
Update sprint (including retrospective).
### Content Calendar
#### `GET /api/projects/content`
**Query params:**
- `?domainId=<uuid>`
- `?status=idea,planned`
- `?platform=onlyfans`
- `?startDate=&endDate=`
#### `POST /api/projects/content`
**Body:**
```json
{
"domainId": "uuid",
"title": "Lingerie photo set",
"contentType": "photo_set",
"scheduledDate": "2026-03-01",
"platform": "onlyfans",
"status": "planned",
"notes": "Black set, studio lighting"
}
```
#### `PATCH /api/projects/content/:id`
Update content calendar item.
---
## Analytics Module
### `GET /api/analytics/overview`
Cross-domain summary.
**Response:**
```json
{
"period": "month",
"tasks": {
"completed": 45,
"created": 52,
"overdue": 3,
"byDomain": [...]
},
"habits": {
"adherenceRate": 0.82,
"activeStreaks": 5,
"longestStreak": 30,
"byDomain": [...]
},
"goals": {
"active": 12,
"completed": 3,
"averageProgress": 45
},
"income": {
"total": "3420.00",
"currency": "GBP",
"byDomain": [...]
}
}
```
### `GET /api/analytics/domain/:id`
Domain-specific analytics.
### `GET /api/analytics/trends`
Time-series data for charts.
**Query params:**
- `?metric=income|tasks_completed|habit_adherence|time_tracked`
- `?period=day|week|month`
- `?domainId=<uuid>` Optional
- `?startDate=&endDate=`
**Response:**
```json
{
"metric": "income",
"period": "week",
"dataPoints": [
{ "date": "2026-02-03", "value": 820 },
{ "date": "2026-02-10", "value": 1050 },
{ "date": "2026-02-17", "value": 750 },
{ "date": "2026-02-24", "value": 800 }
]
}
```
---
## Chat Module
### `GET /api/chat/conversations`
List conversations (newest first).
**Query params:**
- `?archived=false`
- `?isVoice=false`
**Response:** `Conversation[]` with last message preview.
### `POST /api/chat/conversations`
Start new conversation.
**Body:**
```json
{
"title": "Planning tomorrow",
"model": "qwen3-8b"
}
```
### `GET /api/chat/conversations/:id`
Get conversation with recent messages.
### `GET /api/chat/conversations/:id/messages`
Get messages for a conversation (paginated, oldest first).
### `PATCH /api/chat/conversations/:id`
Update conversation (rename, archive).
### `DELETE /api/chat/conversations/:id`
Delete conversation and all messages.
---
## WebSocket Events
### Chat Gateway (namespace: `/chat`)
**Client → Server:**
| Event | Payload | Description |
|-------|---------|-------------|
| `conversation:join` | `{ conversationId: string }` | Join conversation room |
| `conversation:leave` | `{ conversationId: string }` | Leave conversation room |
| `send_message` | `{ conversationId: string, content: string, model?: string }` | Send user message |
**Server → Client:**
| Event | Payload | Description |
|-------|---------|-------------|
| `message_start` | `{ messageId: string, conversationId: string }` | New assistant message starting |
| `message_chunk` | `{ messageId: string, chunk: string }` | Streaming text chunk |
| `message_end` | `{ messageId: string, model: string, tokenCount: number, durationMs: number }` | Message complete |
| `message_error` | `{ messageId: string, error: string }` | Generation error |
| `typing` | `{ conversationId: string, isTyping: boolean }` | Typing indicator |
### Voice Gateway (namespace: `/voice`)
**Client → Server:**
| Event | Payload | Description |
|-------|---------|-------------|
| `voice:start` | `{ conversationId: string }` | Start voice session |
| `voice:audio` | `{ audio: string (base64), segmentId: number }` | Audio data from mic |
| `voice:interrupt` | `{}` | User speaking interrupt TTS |
| `voice:stop` | `{}` | End voice session |
**Server → Client:**
| Event | Payload | Description |
|-------|---------|-------------|
| `voice:transcription` | `{ text: string, segmentId: number, confidence: number }` | STT result |
| `voice:assistant_text` | `{ text: string, messageId: string }` | Assistant response text |
| `voice:audio_chunk` | `Buffer` (binary) | TTS audio chunk (WAV) |
| `voice:audio_end` | `{ messageId: string }` | TTS playback complete |
| `voice:error` | `{ error: string }` | Voice processing error |
| `voice:status` | `{ state: 'listening' \| 'processing' \| 'speaking' \| 'idle' }` | Voice state change |