mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-22 12:11:14 -05:00
Add state machine diagrams and code examples for ThrillWiki
- 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.
This commit is contained in:
660
docs/api/state_transitions.md
Normal file
660
docs/api/state_transitions.md
Normal file
@@ -0,0 +1,660 @@
|
||||
# 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"]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user