Files
thrillwiki_django_no_react/.agent/rules/api-conventions.md
pacnpal 1adba1b804 lol
2026-01-02 07:58:58 -05:00

330 lines
6.1 KiB
Markdown

# 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 <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
```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": <file>,
"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 }
})
```