mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2026-02-05 11:25:19 -05:00
6.1 KiB
6.1 KiB
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)
{
"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)
{
"count": 150,
"next": "/api/v1/parks/?page=2",
"previous": null,
"results": [
{ ... },
{ ... }
]
}
Error Response
{
"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
{
"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 <jwt_token>
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
{
"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
{
"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": <file>,
"caption": "Optional caption"
}
Response
{
"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)
// 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
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 }
})