mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2026-02-05 14:55:18 -05:00
330 lines
6.1 KiB
Markdown
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 }
|
|
})
|
|
```
|