diff --git a/API.md b/API.md new file mode 100644 index 0000000..4ea5f82 --- /dev/null +++ b/API.md @@ -0,0 +1,1163 @@ +# KeepItGoing REST API Documentation + +Complete API documentation for integrating applications with KeepItGoing Server. + +**API Version:** 1.0 +**Base URL:** `https://yourdomain.com/api` + +--- + +## Table of Contents + +- [Authentication](#authentication) +- [Rate Limiting](#rate-limiting) +- [Error Handling](#error-handling) +- [User Management](#user-management) +- [Task Management](#task-management) +- [Tags](#tags) +- [Time Tracking](#time-tracking) +- [Task Sharing](#task-sharing) +- [Sync (Offline-First)](#sync-offline-first) +- [Notifications](#notifications) +- [Common Patterns](#common-patterns) + +--- + +## Authentication + +### JWT Token Authentication + +All authenticated endpoints require the `Authorization` header: + +``` +Authorization: Bearer {access_token} +``` + +### Obtain Access Token + +**POST** `/api/users/token/` + +Authenticates user and returns JWT tokens. User must have verified email and admin approval. + +**Request:** +```json +{ + "username": "user@example.com", + "password": "password123" +} +``` + +**Response (200 OK):** +```json +{ + "refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." +} +``` + +**Rate Limit:** 10 requests/hour + +### Refresh Access Token + +**POST** `/api/users/token/refresh/` + +Refreshes an expired access token using a refresh token. + +**Request:** +```json +{ + "refresh": "{refresh_token}" +} +``` + +**Response (200 OK):** +```json +{ + "access": "new_access_token_here" +} +``` + +**Token Lifetimes:** +- Access token: 60 minutes +- Refresh token: 7 days +- Refresh tokens rotate on each use (old token blacklisted) + +--- + +## Rate Limiting + +All endpoints are rate-limited per user: + +| User Type | Limit | Scope | +|-----------|-------|-------| +| Anonymous | 100/hour | General API access | +| Authenticated | 1000/hour | General API access | +| Login | 10/hour | Token endpoint | +| Registration | 5/hour | Registration endpoint | +| Sync | 100/hour | Sync endpoint | + +**Rate Limit Headers:** +``` +X-RateLimit-Limit: 1000 +X-RateLimit-Remaining: 999 +X-RateLimit-Reset: 1234567890 +``` + +**Rate Limit Exceeded (429):** +```json +{ + "detail": "Request was throttled. Expected available in 120 seconds." +} +``` + +--- + +## Error Handling + +### HTTP Status Codes + +| Code | Meaning | +|------|---------| +| 200 | Successful request | +| 201 | Resource created | +| 204 | Successful deletion (no content) | +| 400 | Invalid request data | +| 401 | Authentication required or invalid | +| 403 | Forbidden (insufficient permissions) | +| 404 | Resource not found | +| 429 | Rate limit exceeded | +| 500 | Server error | + +### Error Response Format + +```json +{ + "detail": "Error message", + "field_name": ["Field-specific error message"] +} +``` + +**Examples:** + +**Validation Error (400):** +```json +{ + "email": ["Enter a valid email address."], + "password": ["This field is required."] +} +``` + +**Authentication Error (401):** +```json +{ + "detail": "Given token not valid for any token type" +} +``` + +--- + +## User Management + +### Register New User + +**POST** `/api/users/register/` (Public) + +Creates new user account and sends verification email. + +**Request:** +```json +{ + "email": "user@example.com", + "username": "username", + "password": "SecurePassword123!", + "password_confirm": "SecurePassword123!", + "first_name": "John", + "last_name": "Doe", + "timezone": "America/New_York" +} +``` + +**Response (201 Created):** +```json +{ + "message": "Registration successful! Please check your email to verify your account.", + "email": "user@example.com", + "email_verified": false, + "is_approved": false +} +``` + +**Rate Limit:** 5 requests/hour + +### Verify Email + +**POST** `/api/users/verify-email/` (Public) + +Verifies user email with token from verification email. + +**Request:** +```json +{ + "token": "uuid-token-from-email" +} +``` + +**Response (200 OK):** +```json +{ + "message": "Email verified successfully.", + "email_verified": true, + "is_approved": false +} +``` + +**Notes:** +- Tokens expire after 24 hours (configurable) +- Tokens are single-use +- Admin approval still required before login + +### Resend Verification Email + +**POST** `/api/users/resend-verification/` (Public) + +Resends verification email if original was lost. + +**Request:** +```json +{ + "email": "user@example.com" +} +``` + +**Response (200 OK):** +```json +{ + "message": "Verification email sent." +} +``` + +### Get User Profile + +**GET** `/api/users/profile/` (Authenticated) + +Returns current user's profile information. + +**Response:** +```json +{ + "id": "uuid-string", + "email": "user@example.com", + "username": "username", + "first_name": "John", + "last_name": "Doe", + "timezone": "America/New_York", + "default_reminder_minutes": 30, + "email_notifications": true, + "push_notifications": true, + "created_at": "2024-01-15T10:30:00Z", + "updated_at": "2024-01-20T14:45:00Z" +} +``` + +### Update User Profile + +**PATCH** `/api/users/profile/` (Authenticated) + +Updates current user's profile. All fields optional. + +**Request:** +```json +{ + "first_name": "John", + "last_name": "Doe", + "timezone": "America/Los_Angeles", + "default_reminder_minutes": 15, + "email_notifications": true, + "push_notifications": false +} +``` + +**Response:** Updated user object + +### Change Password + +**POST** `/api/users/change-password/` (Authenticated) + +Changes user's password. + +**Request:** +```json +{ + "old_password": "CurrentPassword123!", + "new_password": "NewPassword123!" +} +``` + +**Response (200 OK):** +```json +{ + "message": "Password changed successfully." +} +``` + +### Device Token Management + +#### Register Device + +**POST** `/api/users/devices/` (Authenticated) + +Registers device for push notifications. + +**Request:** +```json +{ + "platform": "android", + "token": "firebase_device_token", + "device_name": "John's Phone" +} +``` + +**Platform Options:** `android`, `web`, `desktop` + +**Response (201 Created):** +```json +{ + "id": "uuid-string", + "platform": "android", + "token": "firebase_device_token", + "device_name": "John's Phone", + "is_active": true, + "created_at": "2024-01-15T10:30:00Z" +} +``` + +#### List Devices + +**GET** `/api/users/devices/` (Authenticated) + +Returns all registered devices for current user. + +#### Delete Device + +**DELETE** `/api/users/devices/{device_id}/` (Authenticated) + +Removes device token. + +**Response (204 No Content)** + +--- + +## Task Management + +### List Tasks + +**GET** `/api/tasks/` (Authenticated) + +Returns paginated list of tasks owned by or shared with user. + +**Query Parameters:** +- `status` - Filter: `pending`, `in_progress`, `completed`, `cancelled` +- `priority` - Filter: `low`, `medium`, `high`, `urgent` +- `parent` - Filter by parent task ID (shows only subtasks) +- `search` - Search in title and description +- `ordering` - Sort by: `due_date`, `-due_date`, `priority`, `created_at`, `sort_order` +- `page` - Page number (default: 1) + +**Example:** +``` +GET /api/tasks/?status=pending&priority=high&ordering=-due_date +``` + +**Response:** +```json +{ + "count": 45, + "next": "http://domain/api/tasks/?page=2", + "previous": null, + "results": [ + { + "id": "uuid-string", + "title": "Complete project proposal", + "status": "in_progress", + "priority": "high", + "due_date": "2024-02-15", + "due_time": "17:00:00", + "tags": [...], + "subtask_count": 2, + "is_overdue": false, + "sync_id": "uuid-string", + "updated_at": "2024-01-20T14:00:00Z" + } + ] +} +``` + +### Create Task + +**POST** `/api/tasks/` (Authenticated) + +Creates a new task. + +**Request:** +```json +{ + "title": "Complete project proposal", + "description": "Detailed description", + "status": "pending", + "priority": "high", + "due_date": "2024-02-15", + "due_time": "17:00:00", + "reminder_at": "2024-02-15T16:00:00Z", + "recurrence": "none", + "recurrence_end_date": null, + "tag_ids": ["uuid-1", "uuid-2"], + "parent": null, + "sort_order": 0 +} +``` + +**Field Reference:** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| title | string | Yes | Task title (max 500 chars) | +| description | string | No | Detailed description | +| status | enum | No | `pending`, `in_progress`, `completed`, `cancelled` | +| priority | enum | No | `low`, `medium`, `high`, `urgent` | +| due_date | date | No | YYYY-MM-DD | +| due_time | time | No | HH:MM:SS | +| reminder_at | datetime | No | ISO 8601 format | +| recurrence | enum | No | `none`, `daily`, `weekly`, `biweekly`, `monthly`, `yearly`, `custom` | +| recurrence_rule | string | No | RRULE string for custom recurrence | +| recurrence_end_date | date | No | End date for recurring tasks | +| tag_ids | array | No | Array of tag UUIDs | +| parent | uuid | No | Parent task ID (for subtasks) | +| sort_order | integer | No | Display order | + +**Response (201 Created):** +```json +{ + "id": "uuid-string", + "sync_id": "uuid-string", + "title": "Complete project proposal", + "description": "Detailed description", + "status": "pending", + "priority": "high", + "due_date": "2024-02-15", + "due_time": "17:00:00", + "reminder_at": "2024-02-15T16:00:00Z", + "completed_at": null, + "recurrence": "none", + "recurrence_rule": "", + "recurrence_end_date": null, + "tags": [...], + "subtasks": [], + "is_overdue": false, + "total_time_spent": 0, + "created_at": "2024-01-20T14:00:00Z", + "updated_at": "2024-01-20T14:00:00Z" +} +``` + +**Notes:** +- When a recurring task is completed, the next instance is automatically created +- `completed_at` is automatically set when `status` changes to `completed` +- `is_overdue` is calculated based on user's timezone + +### Get Task Details + +**GET** `/api/tasks/{task_id}/` (Authenticated) + +Returns full task details including subtasks. + +**Response:** Task object with nested subtasks array + +### Update Task + +**PATCH** `/api/tasks/{task_id}/` (Authenticated) + +Updates task fields. All fields optional. + +**Request:** +```json +{ + "title": "Updated title", + "status": "completed", + "priority": "medium" +} +``` + +**Response:** Updated task object + +### Delete Task + +**DELETE** `/api/tasks/{task_id}/` (Authenticated) + +Soft-deletes task (marks as deleted, preserves in database for sync). + +**Response (204 No Content)** + +--- + +## Tags + +### List Tags + +**GET** `/api/tasks/tags/` (Authenticated) + +Returns all tags for current user. + +**Response:** +```json +[ + { + "id": "uuid-string", + "name": "work", + "description": "Work-related tasks", + "color": "#3B82F6", + "icon": "briefcase", + "sort_order": 0, + "is_archived": false, + "task_count": 12, + "sync_id": "uuid-string", + "created_at": "2024-01-10T10:00:00Z", + "updated_at": "2024-01-20T14:00:00Z" + } +] +``` + +### Create Tag + +**POST** `/api/tasks/tags/` (Authenticated) + +Creates a new tag. + +**Request:** +```json +{ + "name": "work", + "description": "Work-related tasks", + "color": "#3B82F6", + "icon": "briefcase", + "sort_order": 0, + "is_archived": false +} +``` + +**Response (201 Created):** Tag object + +### Update Tag + +**PATCH** `/api/tasks/tags/{tag_id}/` (Authenticated) + +Updates tag fields. + +### Delete Tag + +**DELETE** `/api/tasks/tags/{tag_id}/` (Authenticated) + +Soft-deletes tag. + +**Response (204 No Content)** + +--- + +## Time Tracking + +### List Time Entries + +**GET** `/api/tasks/time-entries/` (Authenticated) + +Returns time entries for user's tasks. + +**Query Parameters:** +- `task` - Filter by task UUID + +**Response:** +```json +[ + { + "id": "uuid-string", + "task": "task-uuid", + "started_at": "2024-01-20T09:00:00Z", + "ended_at": "2024-01-20T10:30:00Z", + "duration_seconds": 5400, + "notes": "Worked on section 2", + "is_running": false, + "sync_id": "uuid-string" + } +] +``` + +### Create Time Entry + +**POST** `/api/tasks/time-entries/` (Authenticated) + +Creates a time entry (manual entry or closed timer). + +**Request:** +```json +{ + "task": "task-uuid", + "started_at": "2024-01-20T09:00:00Z", + "ended_at": "2024-01-20T10:30:00Z", + "notes": "Worked on section 2" +} +``` + +**Notes:** +- If `ended_at` is omitted, timer is considered running +- `duration_seconds` is automatically calculated from start/end times + +**Response (201 Created):** Time entry object + +### Start Timer (Convenience) + +**POST** `/api/tasks/{task_id}/start-timer/` (Authenticated) + +Starts a timer for specified task. Only one timer can run at a time. + +**Request:** Empty body + +**Response (201 Created):** +```json +{ + "id": "uuid-string", + "task": "task-uuid", + "started_at": "2024-01-20T14:30:00Z", + "ended_at": null, + "is_running": true, + "sync_id": "uuid-string" +} +``` + +**Error (400):** +```json +{ + "error": "You already have a running timer. Stop it first." +} +``` + +### Stop Timer + +**POST** `/api/tasks/time-entries/{entry_id}/stop/` (Authenticated) + +Stops a running timer. + +**Request:** Empty body + +**Response:** +```json +{ + "id": "uuid-string", + "task": "task-uuid", + "started_at": "2024-01-20T14:30:00Z", + "ended_at": "2024-01-20T14:45:00Z", + "duration_seconds": 900, + "is_running": false +} +``` + +### Update Time Entry + +**PATCH** `/api/tasks/time-entries/{entry_id}/` (Authenticated) + +Updates time entry fields. + +### Delete Time Entry + +**DELETE** `/api/tasks/time-entries/{entry_id}/` (Authenticated) + +Soft-deletes time entry. + +**Response (204 No Content)** + +--- + +## Task Sharing + +### List Shares + +**GET** `/api/tasks/shares/` (Authenticated) + +Returns shares where user is owner or recipient. + +**Response:** +```json +[ + { + "id": "uuid-string", + "task": "task-uuid", + "tag": null, + "shared_with_email": "colleague@example.com", + "permission": "viewer", + "sync_id": "uuid-string", + "created_at": "2024-01-20T14:00:00Z" + } +] +``` + +### Create Share + +**POST** `/api/tasks/shares/` (Authenticated) + +Shares a task or tag with another user. + +**Request (share task):** +```json +{ + "task": "task-uuid", + "shared_with_email": "colleague@example.com", + "permission": "viewer" +} +``` + +**Request (share tag):** +```json +{ + "tag": "tag-uuid", + "shared_with_email": "colleague@example.com", + "permission": "editor" +} +``` + +**Permission Levels:** +- `viewer` - Read-only access +- `editor` - Can modify (not yet fully implemented) + +**Validation:** +- Cannot share both task and tag in one request +- Cannot share with yourself +- Recipient email must exist in system +- Must own the task/tag being shared + +**Response (201 Created):** Share object + +### Delete Share + +**DELETE** `/api/tasks/shares/{share_id}/` (Authenticated) + +Removes a share. Owner only. + +**Response (204 No Content)** + +--- + +## Sync (Offline-First) + +The sync system enables offline-first mobile/desktop clients through bidirectional synchronization. + +### Main Sync Endpoint + +**POST** `/api/sync/` (Authenticated) + +Performs bidirectional sync: accepts client changes, returns server changes. + +**Rate Limit:** 100 requests/hour + +**Request:** +```json +{ + "device_id": "unique-device-identifier", + "last_sync_token": "previous-token-or-null", + "changes": { + "tasks": [ + { + "sync_id": "uuid-string", + "title": "Task title", + "status": "completed", + "priority": "high", + "due_date": "2024-02-15", + "due_time": "17:00:00", + "description": "Description", + "tag_sync_ids": ["tag-sync-id-1"], + "parent_sync_id": null, + "sort_order": 0, + "is_deleted": false + } + ], + "tags": [ + { + "sync_id": "uuid-string", + "name": "work", + "description": "Work tasks", + "color": "#3B82F6", + "icon": "briefcase", + "is_deleted": false + } + ], + "time_entries": [ + { + "sync_id": "uuid-string", + "task_sync_id": "task-sync-id", + "started_at": "2024-01-20T09:00:00Z", + "ended_at": "2024-01-20T10:30:00Z", + "notes": "Work notes", + "is_deleted": false + } + ] + } +} +``` + +**Response (200 OK):** +```json +{ + "sync_token": "new-uuid-token", + "server_time": "2024-01-20T14:30:00Z", + "server_changes": { + "tasks": [...], + "tags": [...], + "time_entries": [...] + }, + "conflicts": [ + { + "id": "conflict-uuid", + "entity_type": "task", + "entity_id": "task-uuid", + "local_data": {...}, + "server_data": {...}, + "status": "pending" + } + ] +} +``` + +**Sync Process:** +1. Client submits changes with `device_id` and optional `last_sync_token` +2. Server processes client changes and detects conflicts +3. Server returns new `sync_token`, server changes since last sync, and any conflicts +4. Client applies server changes and handles conflicts +5. Next sync uses returned `sync_token` as `last_sync_token` + +**Full vs Incremental Sync:** +- **Full Sync:** If `last_sync_token` is null/invalid, server returns all data +- **Incremental Sync:** If `last_sync_token` is valid, server returns only changes since that sync + +**Key Sync Concepts:** +- Uses `sync_id` (separate from database `id`) for cross-device consistency +- Includes `is_deleted` flag to propagate deletions +- Soft-delete architecture maintains data for sync +- Timestamp-based conflict detection + +### Get Sync Conflicts + +**GET** `/api/sync/conflicts/` (Authenticated) + +Lists pending conflicts requiring resolution. + +**Response:** +```json +[ + { + "id": "conflict-uuid", + "entity_type": "task", + "entity_id": "task-uuid", + "local_data": { + "title": "Client version", + "status": "completed" + }, + "server_data": { + "title": "Server version", + "status": "in_progress" + }, + "status": "pending", + "created_at": "2024-01-20T14:30:00Z" + } +] +``` + +### Resolve Sync Conflict + +**POST** `/api/sync/conflicts/{conflict_id}/resolve/` (Authenticated) + +Resolves a conflict using one of three strategies. + +**Request (use local/client version):** +```json +{ + "resolution": "local" +} +``` + +**Request (use server version):** +```json +{ + "resolution": "server" +} +``` + +**Request (use merged version):** +```json +{ + "resolution": "merged", + "merged_data": { + "title": "Merged title", + "status": "completed" + } +} +``` + +**Response (200 OK):** +```json +{ + "status": "resolved" +} +``` + +--- + +## Notifications + +### List Notifications + +**GET** `/api/notifications/` (Authenticated) + +Returns notifications for current user. + +**Query Parameters:** +- `is_read` - Filter: `true` or `false` + +**Response:** +```json +[ + { + "id": "notification-uuid", + "notification_type": "reminder", + "title": "Task reminder", + "message": "Complete project proposal is due soon", + "task": "task-uuid", + "task_title": "Complete project proposal", + "is_read": false, + "read_at": null, + "created_at": "2024-01-20T14:00:00Z" + } +] +``` + +**Notification Types:** +- `reminder` - Task reminder +- `due_soon` - Task due soon +- `overdue` - Task overdue +- `shared` - Task/tag shared with user +- `daily_email` - Daily email digest sent + +### Mark Notification as Read + +**POST** `/api/notifications/{notification_id}/read/` (Authenticated) + +Marks single notification as read. + +**Response:** Updated notification object with `is_read: true` + +### Mark All as Read + +**POST** `/api/notifications/read-all/` (Authenticated) + +Marks all unread notifications as read. + +**Response:** +```json +{ + "status": "All notifications marked as read" +} +``` + +### Get Unread Count + +**GET** `/api/notifications/unread-count/` (Authenticated) + +Returns count of unread notifications. + +**Response:** +```json +{ + "unread_count": 5 +} +``` + +### Delete Notification + +**DELETE** `/api/notifications/{notification_id}/` (Authenticated) + +Deletes notification. + +**Response (204 No Content)** + +--- + +## Common Patterns + +### Pagination + +List endpoints return paginated results (50 items per page): + +```json +{ + "count": 100, + "next": "https://domain/api/tasks/?page=2", + "previous": null, + "results": [...] +} +``` + +Use `page` query parameter to navigate: `?page=2` + +### Filtering & Search + +**Available Filters:** +- Tasks: `status`, `priority`, `parent` +- Notifications: `is_read` +- Time Entries: `task` + +**Search:** +- Tasks: Searches `title` and `description` fields +- Use `search` query parameter: `?search=project` + +**Ordering:** +- Use `ordering` parameter with field name +- Prefix with `-` for descending: `?ordering=-due_date` +- Available fields vary by endpoint + +### Timezone Handling + +**User Timezone:** +- Stored as IANA timezone string (e.g., "America/New_York") +- Used for due date calculations and email timing + +**API Timestamps:** +- All timestamps in ISO 8601 format with UTC: `2024-01-20T19:00:00Z` +- Clients should convert to user's timezone for display + +### Recurring Tasks + +**Recurrence Types:** +- `none` - No recurrence +- `daily` - Every day +- `weekly` - Every week (same day) +- `biweekly` - Every 2 weeks +- `monthly` - Every month (same date) +- `yearly` - Every year (same date) +- `custom` - Custom RRULE pattern + +**Behavior:** +- When recurring task marked as completed, next instance automatically created +- New instance has `status: pending` and calculated `due_date` +- Recurrence stops at `recurrence_end_date` if specified + +**Custom RRULE Example:** +```json +{ + "recurrence": "custom", + "recurrence_rule": "FREQ=WEEKLY;BYDAY=MO,WE,FR", + "recurrence_end_date": "2024-12-31" +} +``` + +### Complete Workflow Example + +**1. Register:** +```bash +POST /api/users/register/ +{ + "email": "user@example.com", + "password": "SecurePass123!", + "password_confirm": "SecurePass123!", + "timezone": "America/New_York" +} +``` + +**2. Verify Email:** +```bash +POST /api/users/verify-email/ +{ + "token": "token-from-email" +} +``` + +**3. Wait for Admin Approval** (admin sets `is_approved = true`) + +**4. Login:** +```bash +POST /api/users/token/ +{ + "username": "user@example.com", + "password": "SecurePass123!" +} +``` + +**5. Create Task:** +```bash +POST /api/tasks/ +Authorization: Bearer {access_token} +{ + "title": "Complete project", + "priority": "high", + "due_date": "2024-02-15" +} +``` + +**6. Start Timer:** +```bash +POST /api/tasks/{task_id}/start-timer/ +Authorization: Bearer {access_token} +``` + +**7. Stop Timer:** +```bash +POST /api/tasks/time-entries/{entry_id}/stop/ +Authorization: Bearer {access_token} +``` + +**8. Sync (Mobile App):** +```bash +POST /api/sync/ +Authorization: Bearer {access_token} +{ + "device_id": "device-123", + "last_sync_token": null, + "changes": { ... } +} +``` + +--- + +## Additional Information + +### Security + +- JWT authentication with Bearer tokens +- Access tokens expire after 60 minutes +- Refresh tokens last 7 days and rotate on use +- Email verification required +- Admin approval required for new accounts +- Users can only access their own data (except shared items) + +### Data Limits + +- Max file upload: 5 MB +- Max request body: 5 MB +- Task title: 500 characters max +- Page size: 50 items + +### CORS + +Cross-Origin Resource Sharing (CORS) is configured. Contact admin for allowed origins. + +### API Versioning + +Current version: 1.0 (no version prefix in URLs) + +--- + +## Support + +For API support or questions: +- GitHub Issues: Create an issue in the repository +- Email: keith@firebugit.com + +--- + +**Built by Firebug IT** +**Documentation Version: 1.0** +**Last Updated: 2025-01-10**