|
# retoor <retoor@molodetz.nl>
|
|
|
|
from .._shared import endpoint, field
|
|
|
|
GROUP = {
|
|
"slug": "uploads",
|
|
"title": "Uploads",
|
|
"intro": """
|
|
# Uploads
|
|
|
|
Attachment storage. Upload a file - or hand the server a public URL to fetch - to receive an
|
|
attachment record, then reference its `uid` in an `attachment_uids` field when creating a post,
|
|
comment, project, gist, message, or issue - see
|
|
[Posts, Comments, Projects, Gists & News](/docs/content.html). Images and videos embed and
|
|
play inline once posted; other types render as download links. The record's `is_image` and
|
|
`is_video` flags indicate how the file is displayed.
|
|
|
|
You manage your own attachments over the full lifecycle: **list** every file you uploaded, **get**
|
|
one by uid, **rename** its display filename, and **delete** it. The list is the same set of
|
|
attachments that appear on your posts and other content - listing, renaming, or deleting one is
|
|
reflected everywhere it is used.
|
|
|
|
Every endpoint follows the shared [Conventions & Errors](/docs/conventions.html) (auth, content
|
|
negotiation, pagination, status codes); see [Authentication](/docs/authentication.html) for the
|
|
four ways to sign requests.
|
|
""",
|
|
"endpoints": [
|
|
endpoint(
|
|
id="uploads-upload",
|
|
method="POST",
|
|
path="/uploads/upload",
|
|
title="Upload a file",
|
|
summary="Store a file and return its attachment record.",
|
|
auth="user",
|
|
encoding="multipart",
|
|
params=[field("file", "form", "file", True, "", "The file to upload.")],
|
|
notes=[
|
|
"Allowed file types and the size limit are configured by administrators. Images and common video formats (mp4, webm, ogv, mov, m4v) are accepted by default.",
|
|
"Returns `201` on success, `413` if too large, `415` if the type is not allowed.",
|
|
],
|
|
sample_response={
|
|
"uid": "ATTACHMENT_UID",
|
|
"filename": "clip.mp4",
|
|
"url": "/static/uploads/attachments/ab/cd/ATTACHMENT_UID.mp4",
|
|
"size": 20480,
|
|
"is_image": False,
|
|
"is_video": True,
|
|
"mime_type": "video/mp4",
|
|
},
|
|
),
|
|
endpoint(
|
|
id="uploads-upload-url",
|
|
method="POST",
|
|
path="/uploads/upload-url",
|
|
title="Attach a file from a URL",
|
|
summary="Download a public URL on the server and store it as an attachment.",
|
|
auth="user",
|
|
params=[
|
|
field(
|
|
"url",
|
|
"form",
|
|
"string",
|
|
True,
|
|
"https://example.com/photo.png",
|
|
"Public http(s) URL of the file to download and attach.",
|
|
),
|
|
field(
|
|
"filename",
|
|
"form",
|
|
"string",
|
|
False,
|
|
"photo.png",
|
|
"Optional filename with an allowed extension, used when the URL has no clear name.",
|
|
),
|
|
],
|
|
notes=[
|
|
"The server fetches the URL (SSRF-guarded, size-capped) and stores the bytes through the same pipeline as a direct upload; the response is identical to Upload a file.",
|
|
"The file type is taken from the URL path or the response Content-Type. Returns `201` on success, `413` if too large, `415` if the type cannot be resolved to an allowed type, `400` for an unreachable or private address.",
|
|
],
|
|
sample_response={
|
|
"uid": "ATTACHMENT_UID",
|
|
"filename": "photo.png",
|
|
"url": "/static/uploads/attachments/ab/cd/ATTACHMENT_UID.png",
|
|
"size": 20480,
|
|
"is_image": True,
|
|
"is_video": False,
|
|
"mime_type": "image/png",
|
|
},
|
|
),
|
|
endpoint(
|
|
id="uploads-list",
|
|
method="GET",
|
|
path="/uploads",
|
|
title="List your attachments",
|
|
summary="List every attachment you uploaded, newest first, paginated (24 per page).",
|
|
auth="user",
|
|
params=[
|
|
field(
|
|
"page",
|
|
"query",
|
|
"integer",
|
|
False,
|
|
"1",
|
|
"1-based page number.",
|
|
),
|
|
field(
|
|
"linked",
|
|
"query",
|
|
"string",
|
|
False,
|
|
"",
|
|
"Filter: `true` returns only attachments already used on a post/comment/project/gist/issue, `false` returns only orphaned uploads. Omit for all.",
|
|
),
|
|
],
|
|
notes=[
|
|
"Each item carries `uid`, `original_filename`, `mime_type`, `url`, `file_size`, its `target_type`/`target_uid`/`target_url` when linked, and a `linked` flag.",
|
|
],
|
|
sample_response={
|
|
"attachments": [
|
|
{
|
|
"uid": "ATTACHMENT_UID",
|
|
"original_filename": "photo.png",
|
|
"file_size": 20480,
|
|
"mime_type": "image/png",
|
|
"url": "/static/uploads/attachments/ab/cd/ATTACHMENT_UID.png",
|
|
"is_image": True,
|
|
"is_video": False,
|
|
"is_audio": False,
|
|
"linked": True,
|
|
"target_type": "post",
|
|
"target_uid": "POST_UID",
|
|
"target_url": "/posts/POST_SLUG",
|
|
"created_at": "2026-01-01T12:00:00+00:00",
|
|
}
|
|
],
|
|
"pagination": {"page": 1, "per_page": 24, "total": 1, "total_pages": 1},
|
|
"total": 1,
|
|
},
|
|
),
|
|
endpoint(
|
|
id="uploads-get",
|
|
method="GET",
|
|
path="/uploads/{attachment_uid}",
|
|
title="Get one attachment",
|
|
summary="Fetch the metadata of a single attachment you own; administrators may fetch any user's attachment.",
|
|
auth="user",
|
|
params=[
|
|
field(
|
|
"attachment_uid",
|
|
"path",
|
|
"string",
|
|
True,
|
|
"ATTACHMENT_UID",
|
|
"UID of the attachment.",
|
|
)
|
|
],
|
|
notes=["Returns `404` if the attachment does not exist, `403` if it is not yours."],
|
|
sample_response={
|
|
"uid": "ATTACHMENT_UID",
|
|
"original_filename": "photo.png",
|
|
"file_size": 20480,
|
|
"mime_type": "image/png",
|
|
"url": "/static/uploads/attachments/ab/cd/ATTACHMENT_UID.png",
|
|
"is_image": True,
|
|
"is_video": False,
|
|
"is_audio": False,
|
|
"linked": True,
|
|
"target_type": "post",
|
|
"target_uid": "POST_UID",
|
|
"target_url": "/posts/POST_SLUG",
|
|
"created_at": "2026-01-01T12:00:00+00:00",
|
|
},
|
|
),
|
|
endpoint(
|
|
id="uploads-rename",
|
|
method="PATCH",
|
|
path="/uploads/{attachment_uid}",
|
|
title="Rename an attachment",
|
|
summary="Change the display filename of an attachment you own; administrators may rename any user's attachment.",
|
|
auth="user",
|
|
params=[
|
|
field(
|
|
"attachment_uid",
|
|
"path",
|
|
"string",
|
|
True,
|
|
"ATTACHMENT_UID",
|
|
"UID of the attachment.",
|
|
),
|
|
field(
|
|
"filename",
|
|
"form",
|
|
"string",
|
|
True,
|
|
"renamed.png",
|
|
"New display filename.",
|
|
),
|
|
],
|
|
notes=[
|
|
"Only the display filename changes; the stored file and its extension are untouched. The original extension is always preserved, so the file type cannot be altered.",
|
|
"Returns the updated attachment record. `404` if it does not exist, `403` if it is not yours, `400` for an empty filename.",
|
|
],
|
|
sample_response={
|
|
"uid": "ATTACHMENT_UID",
|
|
"original_filename": "renamed.png",
|
|
"file_size": 20480,
|
|
"mime_type": "image/png",
|
|
"url": "/static/uploads/attachments/ab/cd/ATTACHMENT_UID.png",
|
|
"is_image": True,
|
|
"linked": True,
|
|
"target_type": "post",
|
|
"target_uid": "POST_UID",
|
|
"target_url": "/posts/POST_SLUG",
|
|
"created_at": "2026-01-01T12:00:00+00:00",
|
|
},
|
|
),
|
|
endpoint(
|
|
id="uploads-delete",
|
|
method="DELETE",
|
|
path="/uploads/delete/{attachment_uid}",
|
|
title="Delete an attachment",
|
|
summary="Remove an attachment you previously uploaded; administrators may remove any user's attachment.",
|
|
auth="user",
|
|
destructive=True,
|
|
params=[
|
|
field(
|
|
"attachment_uid",
|
|
"path",
|
|
"string",
|
|
True,
|
|
"ATTACHMENT_UID",
|
|
"UID of the attachment (the `uid` returned by Upload a file, Attach a file from a URL, or List your attachments).",
|
|
)
|
|
],
|
|
notes=[
|
|
"Only the owner may delete their own attachment; an administrator may delete any user's. Deleting one you do not own returns `403`.",
|
|
"The attachment is removed everywhere at once: it leaves your attachment list (List your attachments) and disappears from every post, comment, project, gist, message, or issue it was attached to, and its file stops being served under `/static/uploads/`.",
|
|
"Idempotent from the caller's view: an already-removed or unknown uid returns `404`. A successful delete returns `200` with `{\"status\": \"deleted\"}`.",
|
|
"To detach a file from a single post/comment without removing the upload itself, edit that object's attachment list instead - deleting here removes the attachment from every place it is used.",
|
|
],
|
|
sample_response={"status": "deleted"},
|
|
),
|
|
],
|
|
}
|