mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2026-02-05 13:15:17 -05:00
lol
This commit is contained in:
329
.agent/rules/api-conventions.md
Normal file
329
.agent/rules/api-conventions.md
Normal file
@@ -0,0 +1,329 @@
|
||||
# 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 }
|
||||
})
|
||||
```
|
||||
Reference in New Issue
Block a user