Swagger UI Ready
This commit is contained in:
@@ -0,0 +1,342 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Packs Site API
|
||||
description: |
|
||||
API for the Packs Site. All endpoints are behind the site's gate (master
|
||||
password) and most require authentication.
|
||||
|
||||
## Authentication
|
||||
|
||||
Requests authenticate with an API token in the `Authorization` header:
|
||||
|
||||
Authorization: Bearer <username> <token>
|
||||
|
||||
Tokens are created on the **API Tokens** settings page. Without a valid
|
||||
token (or a logged-in browser session) API requests get a `401` response.
|
||||
Staff-only endpoints return `403` for non-staff.
|
||||
version: 1.0.0
|
||||
servers:
|
||||
- url: /
|
||||
tags:
|
||||
- name: Files
|
||||
description: Serving stored files
|
||||
- name: Uploads
|
||||
description: Draft/temp upload management
|
||||
- name: Drafts
|
||||
description: Create-page autosave
|
||||
- name: Search
|
||||
description: Autocomplete endpoints
|
||||
- name: Packs
|
||||
description: Pack update-check
|
||||
- name: Stats
|
||||
description: Server statistics
|
||||
paths:
|
||||
/api/files/{file_id}/:
|
||||
get:
|
||||
tags: [Files]
|
||||
summary: Serve a stored file
|
||||
description: Serves any stored file (media, release zips, textures). Use `download=1` to force an attachment.
|
||||
parameters:
|
||||
- name: file_id
|
||||
in: path
|
||||
required: true
|
||||
description: UUID of the stored file.
|
||||
schema: { type: string, format: uuid }
|
||||
- name: download
|
||||
in: query
|
||||
required: false
|
||||
description: Set to `1` to force a download attachment.
|
||||
schema: { type: string }
|
||||
responses:
|
||||
"200":
|
||||
description: The file contents.
|
||||
content:
|
||||
application/octet-stream:
|
||||
schema: { type: string, format: binary }
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"404":
|
||||
description: File not found.
|
||||
/api/uploads/:
|
||||
post:
|
||||
tags: [Uploads]
|
||||
summary: Upload a temporary file
|
||||
description: Uploads a file into the current draft / upload pool. Returns the upload record used by the create-page UI.
|
||||
security:
|
||||
- bearerAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
multipart/form-data:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
file:
|
||||
type: string
|
||||
format: binary
|
||||
description: The file to upload.
|
||||
kind:
|
||||
type: string
|
||||
enum: [thumbnail, version, media]
|
||||
default: media
|
||||
draft_uuid:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Scope the upload to a specific create-page draft (optional).
|
||||
responses:
|
||||
"200":
|
||||
description: Upload accepted.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/TempUpload"
|
||||
"400":
|
||||
description: Invalid file type for the requested kind.
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
/api/uploads/{upload_uuid}/delete/:
|
||||
post:
|
||||
tags: [Uploads]
|
||||
summary: Delete a temporary upload
|
||||
description: Removes a pending upload and its stored file. Only the owner may delete it.
|
||||
security:
|
||||
- bearerAuth: []
|
||||
parameters:
|
||||
- name: upload_uuid
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: string, format: uuid }
|
||||
responses:
|
||||
"200":
|
||||
description: Deleted.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
success: { type: boolean }
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"404":
|
||||
description: Upload not found.
|
||||
/api/draft/:
|
||||
get:
|
||||
tags: [Drafts]
|
||||
summary: Load a draft
|
||||
description: Returns the saved form data and pending uploads for one draft.
|
||||
security:
|
||||
- bearerAuth: []
|
||||
parameters:
|
||||
- name: draft
|
||||
in: query
|
||||
required: true
|
||||
description: The draft UUID.
|
||||
schema: { type: string, format: uuid }
|
||||
responses:
|
||||
"200":
|
||||
description: Draft data + uploads.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/DraftData"
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"404":
|
||||
description: Draft not found or not owned by you.
|
||||
post:
|
||||
tags: [Drafts]
|
||||
summary: Autosave a draft
|
||||
description: Persists the create-page form fields for a draft (debounced autosave).
|
||||
security:
|
||||
- bearerAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [draft]
|
||||
properties:
|
||||
draft:
|
||||
type: string
|
||||
format: uuid
|
||||
data:
|
||||
type: object
|
||||
description: Form fields (title, summary, category, description, tags, version_name, changelog).
|
||||
responses:
|
||||
"200":
|
||||
description: Saved.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
saved: { type: boolean }
|
||||
updated_at: { type: string, format: date-time }
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"404":
|
||||
description: Draft not found or not owned by you.
|
||||
delete:
|
||||
tags: [Drafts]
|
||||
summary: Delete a draft
|
||||
description: Deletes the draft and purges its pending uploads.
|
||||
security:
|
||||
- bearerAuth: []
|
||||
parameters:
|
||||
- name: draft
|
||||
in: query
|
||||
required: true
|
||||
schema: { type: string, format: uuid }
|
||||
responses:
|
||||
"200":
|
||||
description: Deleted.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
success: { type: boolean }
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"404":
|
||||
description: Draft not found or not owned by you.
|
||||
/api/tags/autocomplete/:
|
||||
get:
|
||||
tags: [Search]
|
||||
summary: Tag autocomplete
|
||||
parameters:
|
||||
- name: q
|
||||
in: query
|
||||
required: false
|
||||
description: Partial tag or category name.
|
||||
schema: { type: string }
|
||||
responses:
|
||||
"200":
|
||||
description: Matching tags.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
name: { type: string }
|
||||
category: { type: string }
|
||||
category_name: { type: string }
|
||||
color: { type: string }
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
/api/users/autocomplete/:
|
||||
get:
|
||||
tags: [Search]
|
||||
summary: User autocomplete
|
||||
parameters:
|
||||
- name: q
|
||||
in: query
|
||||
required: false
|
||||
description: Partial username.
|
||||
schema: { type: string }
|
||||
responses:
|
||||
"200":
|
||||
description: Matching usernames.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
username: { type: string }
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
/api/packs/{namespace}/{pack_id}/latest:
|
||||
get:
|
||||
tags: [Packs]
|
||||
summary: Pack update-check
|
||||
description: Newest version for a NeedsOfNature pack id (`namespace:pack_id`), used by the mod's auto-updater.
|
||||
parameters:
|
||||
- name: namespace
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: string }
|
||||
- name: pack_id
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: string }
|
||||
responses:
|
||||
"200":
|
||||
description: Latest version info.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/PackLatest"
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"404":
|
||||
description: Pack not found.
|
||||
/api/stats/:
|
||||
get:
|
||||
tags: [Stats]
|
||||
summary: Server statistics
|
||||
description: One-shot server/performance snapshot. Staff only. Responses are cached ~20s server-side.
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
description: Snapshot payload.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
description: Staff only.
|
||||
components:
|
||||
securitySchemes:
|
||||
bearerAuth:
|
||||
type: apiKey
|
||||
in: header
|
||||
name: Authorization
|
||||
description: "API token authentication: `Bearer <username> <token>`."
|
||||
responses:
|
||||
Unauthorized:
|
||||
description: Missing or invalid credentials.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
detail: { type: string }
|
||||
example: { detail: Unauthorized }
|
||||
schemas:
|
||||
TempUpload:
|
||||
type: object
|
||||
properties:
|
||||
uuid: { type: string, format: uuid }
|
||||
kind: { type: string, enum: [thumbnail, version, media] }
|
||||
filename: { type: string }
|
||||
content_type: { type: string }
|
||||
url: { type: string }
|
||||
DraftData:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
uploads:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/TempUpload"
|
||||
PackLatest:
|
||||
type: object
|
||||
properties:
|
||||
namespace: { type: string }
|
||||
pack_id: { type: string }
|
||||
name: { type: string }
|
||||
version: { type: string }
|
||||
pack_format: { type: integer, nullable: true }
|
||||
created_at: { type: string, format: date-time }
|
||||
download_url: { type: string }
|
||||
@@ -0,0 +1,10 @@
|
||||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
|
||||
app_name = 'api'
|
||||
|
||||
urlpatterns = [
|
||||
path('api/docs/', views.api_docs, name='docs'),
|
||||
path('api/docs/spec/', views.api_docs_spec, name='docs_spec'),
|
||||
]
|
||||
+15
-1
@@ -1,3 +1,17 @@
|
||||
from pathlib import Path
|
||||
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
SPEC_PATH = Path(__file__).parent / 'openapi.yaml'
|
||||
|
||||
|
||||
def api_docs(request):
|
||||
"""Interactive OpenAPI documentation (Swagger UI), gated like the rest."""
|
||||
return render(request, 'api/docs.html')
|
||||
|
||||
|
||||
def api_docs_spec(request):
|
||||
"""The OpenAPI spec served to Swagger UI (YAML, gated)."""
|
||||
with open(SPEC_PATH, 'rb') as fh:
|
||||
return HttpResponse(fh.read(), content_type='application/yaml; charset=utf-8')
|
||||
|
||||
Reference in New Issue
Block a user