mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-22 05:11:09 -05:00
- Created a comprehensive documentation file for state machine diagrams, detailing various states and transitions for models such as EditSubmission, ModerationReport, and Park Status. - Included transition matrices for each state machine to clarify role requirements and guards. - Developed a new document providing code examples for implementing state machines, including adding new state machines to models, defining custom guards, implementing callbacks, and testing state machines. - Added examples for document approval workflows, custom guards, email notifications, and cache invalidation callbacks. - Implemented a test suite for document workflows, covering various scenarios including approval, rejection, and transition logging.
661 lines
10 KiB
Markdown
661 lines
10 KiB
Markdown
# State Transition API Endpoints
|
|
|
|
This document describes the API endpoints for performing state transitions on various models in ThrillWiki.
|
|
|
|
## Overview
|
|
|
|
State transitions are performed via POST requests to specific action endpoints. All transition endpoints:
|
|
|
|
- Require authentication
|
|
- Return the updated object on success
|
|
- Return appropriate error codes on failure
|
|
- Log the transition in the StateLog
|
|
|
|
## Authentication
|
|
|
|
All endpoints require a valid JWT token in the Authorization header:
|
|
|
|
```
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
## Error Responses
|
|
|
|
| Status Code | Meaning |
|
|
|-------------|---------|
|
|
| 400 | Invalid transition (state not allowed) |
|
|
| 401 | Not authenticated |
|
|
| 403 | Permission denied (insufficient role) |
|
|
| 404 | Object not found |
|
|
| 422 | Validation error (missing required fields) |
|
|
|
|
---
|
|
|
|
## EditSubmission Transitions
|
|
|
|
### Approve Submission
|
|
|
|
Approve an edit submission and apply changes to the target object.
|
|
|
|
```http
|
|
POST /api/moderation/submissions/{id}/approve/
|
|
```
|
|
|
|
**Permissions**: MODERATOR or above
|
|
|
|
**Request Body**:
|
|
```json
|
|
{
|
|
"notes": "Optional approval notes"
|
|
}
|
|
```
|
|
|
|
**Response** (200 OK):
|
|
```json
|
|
{
|
|
"id": 123,
|
|
"status": "APPROVED",
|
|
"handled_by": {
|
|
"id": 456,
|
|
"username": "moderator"
|
|
},
|
|
"handled_at": "2025-01-15T10:30:00Z",
|
|
"notes": "Looks good, approved"
|
|
}
|
|
```
|
|
|
|
### Reject Submission
|
|
|
|
Reject an edit submission with a reason.
|
|
|
|
```http
|
|
POST /api/moderation/submissions/{id}/reject/
|
|
```
|
|
|
|
**Permissions**: MODERATOR or above
|
|
|
|
**Request Body**:
|
|
```json
|
|
{
|
|
"reason": "Required rejection reason"
|
|
}
|
|
```
|
|
|
|
**Response** (200 OK):
|
|
```json
|
|
{
|
|
"id": 123,
|
|
"status": "REJECTED",
|
|
"handled_by": {
|
|
"id": 456,
|
|
"username": "moderator"
|
|
},
|
|
"handled_at": "2025-01-15T10:30:00Z",
|
|
"notes": "Rejected: Insufficient evidence"
|
|
}
|
|
```
|
|
|
|
### Escalate Submission
|
|
|
|
Escalate a submission to admin review.
|
|
|
|
```http
|
|
POST /api/moderation/submissions/{id}/escalate/
|
|
```
|
|
|
|
**Permissions**: MODERATOR or above
|
|
|
|
**Request Body**:
|
|
```json
|
|
{
|
|
"reason": "Reason for escalation"
|
|
}
|
|
```
|
|
|
|
**Response** (200 OK):
|
|
```json
|
|
{
|
|
"id": 123,
|
|
"status": "ESCALATED",
|
|
"handled_by": {
|
|
"id": 456,
|
|
"username": "moderator"
|
|
},
|
|
"handled_at": "2025-01-15T10:30:00Z",
|
|
"notes": "Escalated: Needs admin approval for sensitive change"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## ModerationReport Transitions
|
|
|
|
### Start Review
|
|
|
|
Claim a report and start reviewing it.
|
|
|
|
```http
|
|
POST /api/moderation/reports/{id}/start_review/
|
|
```
|
|
|
|
**Permissions**: MODERATOR or above
|
|
|
|
**Request Body**: None
|
|
|
|
**Response** (200 OK):
|
|
```json
|
|
{
|
|
"id": 123,
|
|
"status": "UNDER_REVIEW",
|
|
"assigned_moderator": {
|
|
"id": 456,
|
|
"username": "moderator"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Resolve Report
|
|
|
|
Mark a report as resolved.
|
|
|
|
```http
|
|
POST /api/moderation/reports/{id}/resolve/
|
|
```
|
|
|
|
**Permissions**: MODERATOR or above (must be assigned)
|
|
|
|
**Request Body**:
|
|
```json
|
|
{
|
|
"resolution_action": "CONTENT_REMOVED",
|
|
"resolution_notes": "Description of action taken"
|
|
}
|
|
```
|
|
|
|
**Response** (200 OK):
|
|
```json
|
|
{
|
|
"id": 123,
|
|
"status": "RESOLVED",
|
|
"resolution_action": "CONTENT_REMOVED",
|
|
"resolution_notes": "Description of action taken",
|
|
"resolved_at": "2025-01-15T10:30:00Z"
|
|
}
|
|
```
|
|
|
|
### Dismiss Report
|
|
|
|
Dismiss a report as invalid.
|
|
|
|
```http
|
|
POST /api/moderation/reports/{id}/dismiss/
|
|
```
|
|
|
|
**Permissions**: MODERATOR or above (must be assigned)
|
|
|
|
**Request Body**:
|
|
```json
|
|
{
|
|
"resolution_notes": "Reason for dismissal"
|
|
}
|
|
```
|
|
|
|
**Response** (200 OK):
|
|
```json
|
|
{
|
|
"id": 123,
|
|
"status": "DISMISSED",
|
|
"resolution_notes": "Report did not violate guidelines",
|
|
"resolved_at": "2025-01-15T10:30:00Z"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Park Transitions
|
|
|
|
### Close Temporarily
|
|
|
|
Temporarily close a park.
|
|
|
|
```http
|
|
POST /api/parks/{slug}/close_temporarily/
|
|
```
|
|
|
|
**Permissions**: Authenticated user
|
|
|
|
**Request Body**: None
|
|
|
|
**Response** (200 OK):
|
|
```json
|
|
{
|
|
"id": 123,
|
|
"slug": "example-park",
|
|
"name": "Example Park",
|
|
"status": "CLOSED_TEMP"
|
|
}
|
|
```
|
|
|
|
### Reopen
|
|
|
|
Reopen a temporarily closed park.
|
|
|
|
```http
|
|
POST /api/parks/{slug}/reopen/
|
|
```
|
|
|
|
**Permissions**: Authenticated user
|
|
|
|
**Request Body**: None
|
|
|
|
**Response** (200 OK):
|
|
```json
|
|
{
|
|
"id": 123,
|
|
"slug": "example-park",
|
|
"name": "Example Park",
|
|
"status": "OPERATING"
|
|
}
|
|
```
|
|
|
|
### Close Permanently
|
|
|
|
Permanently close a park.
|
|
|
|
```http
|
|
POST /api/parks/{slug}/close_permanently/
|
|
```
|
|
|
|
**Permissions**: MODERATOR or above
|
|
|
|
**Request Body**:
|
|
```json
|
|
{
|
|
"closing_date": "2025-12-31"
|
|
}
|
|
```
|
|
|
|
**Response** (200 OK):
|
|
```json
|
|
{
|
|
"id": 123,
|
|
"slug": "example-park",
|
|
"name": "Example Park",
|
|
"status": "CLOSED_PERM",
|
|
"closing_date": "2025-12-31"
|
|
}
|
|
```
|
|
|
|
### Demolish
|
|
|
|
Mark a park as demolished.
|
|
|
|
```http
|
|
POST /api/parks/{slug}/demolish/
|
|
```
|
|
|
|
**Permissions**: MODERATOR or above
|
|
|
|
**Request Body**: None
|
|
|
|
**Response** (200 OK):
|
|
```json
|
|
{
|
|
"id": 123,
|
|
"slug": "example-park",
|
|
"name": "Example Park",
|
|
"status": "DEMOLISHED"
|
|
}
|
|
```
|
|
|
|
### Relocate
|
|
|
|
Mark a park as relocated.
|
|
|
|
```http
|
|
POST /api/parks/{slug}/relocate/
|
|
```
|
|
|
|
**Permissions**: MODERATOR or above
|
|
|
|
**Request Body**:
|
|
```json
|
|
{
|
|
"new_location_notes": "Optional notes about new location"
|
|
}
|
|
```
|
|
|
|
**Response** (200 OK):
|
|
```json
|
|
{
|
|
"id": 123,
|
|
"slug": "example-park",
|
|
"name": "Example Park",
|
|
"status": "RELOCATED"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Ride Transitions
|
|
|
|
### Open
|
|
|
|
Open a ride (from CLOSED_TEMP, SBNO, or UNDER_CONSTRUCTION).
|
|
|
|
```http
|
|
POST /api/parks/{park_slug}/rides/{ride_slug}/open/
|
|
```
|
|
|
|
**Permissions**: Authenticated user (CLOSED_TEMP), MODERATOR+ (SBNO)
|
|
|
|
**Request Body**: None
|
|
|
|
**Response** (200 OK):
|
|
```json
|
|
{
|
|
"id": 123,
|
|
"slug": "example-ride",
|
|
"name": "Example Coaster",
|
|
"status": "OPERATING"
|
|
}
|
|
```
|
|
|
|
### Close Temporarily
|
|
|
|
Temporarily close a ride.
|
|
|
|
```http
|
|
POST /api/parks/{park_slug}/rides/{ride_slug}/close_temporarily/
|
|
```
|
|
|
|
**Permissions**: Authenticated user
|
|
|
|
**Request Body**: None
|
|
|
|
**Response** (200 OK):
|
|
```json
|
|
{
|
|
"id": 123,
|
|
"slug": "example-ride",
|
|
"name": "Example Coaster",
|
|
"status": "CLOSED_TEMP"
|
|
}
|
|
```
|
|
|
|
### Mark SBNO
|
|
|
|
Mark a ride as Standing But Not Operating.
|
|
|
|
```http
|
|
POST /api/parks/{park_slug}/rides/{ride_slug}/mark_sbno/
|
|
```
|
|
|
|
**Permissions**: MODERATOR or above
|
|
|
|
**Request Body**: None
|
|
|
|
**Response** (200 OK):
|
|
```json
|
|
{
|
|
"id": 123,
|
|
"slug": "example-ride",
|
|
"name": "Example Coaster",
|
|
"status": "SBNO"
|
|
}
|
|
```
|
|
|
|
### Mark Closing
|
|
|
|
Mark a ride as scheduled for closure.
|
|
|
|
```http
|
|
POST /api/parks/{park_slug}/rides/{ride_slug}/mark_closing/
|
|
```
|
|
|
|
**Permissions**: MODERATOR or above
|
|
|
|
**Request Body**:
|
|
```json
|
|
{
|
|
"closing_date": "2025-12-31",
|
|
"post_closing_status": "DEMOLISHED"
|
|
}
|
|
```
|
|
|
|
**Valid post_closing_status values**:
|
|
- `SBNO` - Will become Standing But Not Operating
|
|
- `CLOSED_PERM` - Will be permanently closed
|
|
- `DEMOLISHED` - Will be demolished
|
|
- `RELOCATED` - Will be relocated
|
|
|
|
**Response** (200 OK):
|
|
```json
|
|
{
|
|
"id": 123,
|
|
"slug": "example-ride",
|
|
"name": "Example Coaster",
|
|
"status": "CLOSING",
|
|
"closing_date": "2025-12-31",
|
|
"post_closing_status": "DEMOLISHED"
|
|
}
|
|
```
|
|
|
|
### Close Permanently
|
|
|
|
Permanently close a ride.
|
|
|
|
```http
|
|
POST /api/parks/{park_slug}/rides/{ride_slug}/close_permanently/
|
|
```
|
|
|
|
**Permissions**: MODERATOR or above
|
|
|
|
**Request Body**:
|
|
```json
|
|
{
|
|
"closing_date": "2025-12-31"
|
|
}
|
|
```
|
|
|
|
**Response** (200 OK):
|
|
```json
|
|
{
|
|
"id": 123,
|
|
"slug": "example-ride",
|
|
"name": "Example Coaster",
|
|
"status": "CLOSED_PERM",
|
|
"closing_date": "2025-12-31"
|
|
}
|
|
```
|
|
|
|
### Demolish
|
|
|
|
Mark a ride as demolished.
|
|
|
|
```http
|
|
POST /api/parks/{park_slug}/rides/{ride_slug}/demolish/
|
|
```
|
|
|
|
**Permissions**: MODERATOR or above
|
|
|
|
**Request Body**: None
|
|
|
|
**Response** (200 OK):
|
|
```json
|
|
{
|
|
"id": 123,
|
|
"slug": "example-ride",
|
|
"name": "Example Coaster",
|
|
"status": "DEMOLISHED"
|
|
}
|
|
```
|
|
|
|
### Relocate
|
|
|
|
Mark a ride as relocated.
|
|
|
|
```http
|
|
POST /api/parks/{park_slug}/rides/{ride_slug}/relocate/
|
|
```
|
|
|
|
**Permissions**: MODERATOR or above
|
|
|
|
**Request Body**:
|
|
```json
|
|
{
|
|
"new_park_slug": "destination-park",
|
|
"notes": "Optional relocation notes"
|
|
}
|
|
```
|
|
|
|
**Response** (200 OK):
|
|
```json
|
|
{
|
|
"id": 123,
|
|
"slug": "example-ride",
|
|
"name": "Example Coaster",
|
|
"status": "RELOCATED"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Transition History
|
|
|
|
### Get Object History
|
|
|
|
Get the state transition history for any object.
|
|
|
|
```http
|
|
GET /api/moderation/history/{content_type}/{object_id}/
|
|
```
|
|
|
|
**Permissions**: MODERATOR or above
|
|
|
|
**Response** (200 OK):
|
|
```json
|
|
{
|
|
"count": 3,
|
|
"results": [
|
|
{
|
|
"id": 1,
|
|
"timestamp": "2025-01-10T08:00:00Z",
|
|
"source_state": "PENDING",
|
|
"state": "ESCALATED",
|
|
"transition": "transition_to_escalated",
|
|
"by": {
|
|
"id": 456,
|
|
"username": "moderator"
|
|
},
|
|
"description": null
|
|
},
|
|
{
|
|
"id": 2,
|
|
"timestamp": "2025-01-15T10:30:00Z",
|
|
"source_state": "ESCALATED",
|
|
"state": "APPROVED",
|
|
"transition": "transition_to_approved",
|
|
"by": {
|
|
"id": 789,
|
|
"username": "admin"
|
|
},
|
|
"description": "Approved after escalation review"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Get All History (Admin)
|
|
|
|
Get all recent transition history across all models.
|
|
|
|
```http
|
|
GET /api/moderation/reports/all_history/
|
|
```
|
|
|
|
**Permissions**: ADMIN or above
|
|
|
|
**Query Parameters**:
|
|
- `page` - Page number (default: 1)
|
|
- `page_size` - Items per page (default: 20)
|
|
- `model` - Filter by model name (optional)
|
|
- `user` - Filter by user ID (optional)
|
|
- `from_date` - Filter from date (optional)
|
|
- `to_date` - Filter to date (optional)
|
|
|
|
**Response** (200 OK):
|
|
```json
|
|
{
|
|
"count": 100,
|
|
"next": "/api/moderation/reports/all_history/?page=2",
|
|
"previous": null,
|
|
"results": [
|
|
{
|
|
"id": 1,
|
|
"content_type": "moderation.editsubmission",
|
|
"object_id": 123,
|
|
"timestamp": "2025-01-15T10:30:00Z",
|
|
"source_state": "PENDING",
|
|
"state": "APPROVED",
|
|
"transition": "transition_to_approved",
|
|
"by": {
|
|
"id": 456,
|
|
"username": "moderator"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Mandatory API Rules
|
|
|
|
1. **Trailing Slashes**: All endpoints MUST include trailing forward slashes
|
|
2. **HTTP Methods**: All transitions use POST
|
|
3. **Authentication**: All endpoints require valid JWT token
|
|
4. **Content-Type**: Request bodies must be `application/json`
|
|
|
|
## Error Response Format
|
|
|
|
All error responses follow this format:
|
|
|
|
```json
|
|
{
|
|
"error": "Error code",
|
|
"message": "Human-readable error message",
|
|
"details": {
|
|
"field_name": ["Specific field errors"]
|
|
}
|
|
}
|
|
```
|
|
|
|
### Example Error Responses
|
|
|
|
**Invalid Transition (400)**:
|
|
```json
|
|
{
|
|
"error": "TRANSITION_NOT_ALLOWED",
|
|
"message": "Cannot transition from APPROVED to REJECTED"
|
|
}
|
|
```
|
|
|
|
**Permission Denied (403)**:
|
|
```json
|
|
{
|
|
"error": "PERMISSION_DENIED",
|
|
"message": "This action requires moderator privileges"
|
|
}
|
|
```
|
|
|
|
**Validation Error (422)**:
|
|
```json
|
|
{
|
|
"error": "VALIDATION_ERROR",
|
|
"message": "Missing required fields",
|
|
"details": {
|
|
"post_closing_status": ["This field is required when marking as CLOSING"]
|
|
}
|
|
}
|