# ThrillWiki API Conventions Standards for designing and implementing APIs between the Django backend and Nuxt frontend. ## API Base Structure ### URL Patterns ``` /api/v1/ # API root (versioned) /api/v1/parks/ # List/Create parks /api/v1/parks/{slug}/ # Retrieve/Update/Delete park /api/v1/parks/{slug}/rides/ # Nested resource /api/v1/auth/ # Authentication endpoints /api/v1/users/me/ # Current user ``` ### HTTP Methods | Method | Usage | |--------|-------| | GET | Retrieve resource(s) | | POST | Create resource | | PUT | Replace resource entirely | | PATCH | Update resource partially | | DELETE | Remove resource | ## Response Formats ### Success Response (Single Resource) ```json { "id": "uuid", "name": "Cedar Point", "slug": "cedar-point", "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-16T14:20:00Z", ... } ``` ### Success Response (List) ```json { "count": 150, "next": "/api/v1/parks/?page=2", "previous": null, "results": [ { ... }, { ... } ] } ``` ### Error Response ```json { "error": { "code": "validation_error", "message": "Invalid input data", "details": { "name": ["This field is required."], "status": ["Invalid choice."] } } } ``` ### HTTP Status Codes | Code | Meaning | When to Use | |------|---------|-------------| | 200 | OK | Successful GET, PUT, PATCH | | 201 | Created | Successful POST | | 204 | No Content | Successful DELETE | | 400 | Bad Request | Validation errors | | 401 | Unauthorized | Missing/invalid auth | | 403 | Forbidden | Insufficient permissions | | 404 | Not Found | Resource doesn't exist | | 409 | Conflict | Duplicate resource | | 500 | Server Error | Unexpected errors | ## Pagination ### Query Parameters ``` ?page=2 # Page number (default: 1) ?page_size=20 # Items per page (default: 20, max: 100) ``` ### Response ```json { "count": 150, "next": "/api/v1/parks/?page=3", "previous": "/api/v1/parks/?page=1", "results": [ ... ] } ``` ## Filtering & Sorting ### Filtering ``` GET /api/v1/parks/?status=operating GET /api/v1/parks/?country=USA&status=operating GET /api/v1/rides/?park=cedar-point&type=coaster ``` ### Search ``` GET /api/v1/parks/?search=cedar ``` ### Sorting ``` GET /api/v1/parks/?ordering=name # Ascending GET /api/v1/parks/?ordering=-created_at # Descending GET /api/v1/parks/?ordering=-rating,name # Multiple fields ``` ## Authentication ### Token-Based Auth ``` Authorization: Bearer ``` ### Endpoints ``` POST /api/v1/auth/login/ # Get tokens POST /api/v1/auth/register/ # Create account POST /api/v1/auth/refresh/ # Refresh access token POST /api/v1/auth/logout/ # Invalidate tokens GET /api/v1/auth/me/ # Current user info ``` ### Social Auth ``` POST /api/v1/auth/google/ # Google OAuth POST /api/v1/auth/discord/ # Discord OAuth ``` ## Content Submission API ### Submit New Content ``` POST /api/v1/submissions/ { "content_type": "park", "data": { "name": "New Park Name", "city": "Orlando", ... } } ``` ### Response ```json { "id": "submission-uuid", "status": "pending", "content_type": "park", "data": { ... }, "created_at": "2024-01-15T10:30:00Z" } ``` ### Submit Edit to Existing Content ``` POST /api/v1/submissions/ { "content_type": "park", "object_id": "existing-park-uuid", "data": { "description": "Updated description..." } } ``` ## Moderation API ### Get Moderation Queue ``` GET /api/v1/moderation/ GET /api/v1/moderation/?status=pending&type=park ``` ### Review Submission ``` POST /api/v1/moderation/{id}/approve/ POST /api/v1/moderation/{id}/reject/ { "notes": "Reason for rejection..." } ``` ## Nested Resources ### Pattern ``` /api/v1/parks/{slug}/rides/ # List rides in park /api/v1/parks/{slug}/reviews/ # List park reviews /api/v1/parks/{slug}/photos/ # List park photos ``` ### When to Nest vs Flat **Nest when:** - Resource only makes sense in context of parent (park photos) - Need to filter by parent frequently **Use flat with filter when:** - Resource can exist independently - Need to query across parents ``` # Flat with filter GET /api/v1/rides/?park=cedar-point # Or nested GET /api/v1/parks/cedar-point/rides/ ``` ## Geolocation API ### Parks Nearby ``` GET /api/v1/parks/nearby/?lat=41.4821&lng=-82.6822&radius=50 ``` ### Response includes distance ```json { "results": [ { "id": "uuid", "name": "Cedar Point", "distance": 12.5, // in user's preferred units ... } ] } ``` ## File Uploads ### Photo Upload ``` POST /api/v1/photos/ Content-Type: multipart/form-data { "content_type": "park", "object_id": "park-uuid", "image": , "caption": "Optional caption" } ``` ### Response ```json { "id": "photo-uuid", "url": "https://cdn.thrillwiki.com/photos/...", "thumbnail_url": "https://cdn.thrillwiki.com/photos/.../thumb", "status": "pending", // Pending moderation "created_at": "2024-01-15T10:30:00Z" } ``` ## Rate Limiting ### Headers ``` X-RateLimit-Limit: 100 X-RateLimit-Remaining: 95 X-RateLimit-Reset: 1642089600 ``` ### When Limited ``` HTTP/1.1 429 Too Many Requests { "error": { "code": "rate_limit_exceeded", "message": "Too many requests. Try again in 60 seconds." } } ``` ## Frontend Integration ### API Client Setup (Nuxt) ```typescript // composables/useApi.ts export function useApi() { const config = useRuntimeConfig() const authStore = useAuthStore() const api = $fetch.create({ baseURL: config.public.apiBase, headers: authStore.token ? { Authorization: `Bearer ${authStore.token}` } : {}, onResponseError: ({ response }) => { if (response.status === 401) { authStore.logout() navigateTo('/auth/login') } } }) return api } ``` ### Usage in Components ```typescript const api = useApi() // Fetch parks const { data: parks } = await useAsyncData('parks', () => api('/parks/', { params: { status: 'operating' } }) ) // Create submission await api('/submissions/', { method: 'POST', body: { content_type: 'park', data: formData } }) ```