feat(api): Add OpenAPI visualizer
Some checks failed
CodeQL Scan / Analyze (javascript-typescript) (push) Failing after 53s
Build Docker Images / lint (push) Failing after 10s
Build Docker Images / check (push) Failing after 10s
Build Docker Images / tests (push) Failing after 6s
Build Docker Images / build (server, Dockerfile, ${{ github.repository_owner }}/server) (push) Has been skipped
Build Docker Images / build (worker, Worker.Dockerfile, ${{ github.repository_owner }}/worker) (push) Has been skipped
Deploy Docs to GitHub Pages / build (push) Failing after 5s
Mirror to Codeberg / Mirror (push) Failing after 0s
Deploy Docs to GitHub Pages / Deploy (push) Has been skipped
Nix Build / check (push) Failing after 5s

This commit is contained in:
Jesse Wierzbinski 2025-03-24 15:25:40 +01:00
parent 65e2e19ff1
commit c674a1309c
No known key found for this signature in database
32 changed files with 772 additions and 732 deletions

View file

@ -42,7 +42,7 @@ export const Mention = z
},
}),
})
.openapi({
.openapi("Mention", {
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#Mention",
},
@ -75,282 +75,288 @@ export const StatusSource = z
example: "",
}),
})
.openapi({
.openapi("StatusSource", {
externalDocs: {
url: "https://docs.joinmastodon.org/entities/StatusSource",
},
});
// Because Status has some recursive references, we need to define it like this
const BaseStatus = z.object({
id: Id.openapi({
description: "ID of the status in the database.",
example: "2de861d3-a3dd-42ee-ba38-2c7d3f4af588",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#id",
},
}),
uri: z
.string()
.url()
.openapi({
description: "URI of the status used for federation.",
example:
"https://beta.versia.social/@lexi/2de861d3-a3dd-42ee-ba38-2c7d3f4af588",
const BaseStatus = z
.object({
id: Id.openapi({
description: "ID of the status in the database.",
example: "2de861d3-a3dd-42ee-ba38-2c7d3f4af588",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#uri",
url: "https://docs.joinmastodon.org/entities/Status/#id",
},
}),
url: z
.string()
.url()
.nullable()
.openapi({
description: "A link to the statuss HTML representation.",
example:
"https://beta.versia.social/@lexi/2de861d3-a3dd-42ee-ba38-2c7d3f4af588",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#url",
},
}),
account: Account.openapi({
description: "The account that authored this status.",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#account",
},
}),
in_reply_to_id: Id.nullable().openapi({
description: "ID of the status being replied to.",
example: "c41c9fe9-919a-4d35-a921-d3e79a5c95f8",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#in_reply_to_id",
},
}),
in_reply_to_account_id: Account.shape.id.nullable().openapi({
description:
"ID of the account that authored the status being replied to.",
example: "7b9b3ec6-1013-4cc6-8902-94ad00cf2ccc",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#in_reply_to_account_id",
},
}),
content: z.string().openapi({
description: "HTML-encoded status content.",
example: "<p>hello world</p>",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#content",
},
}),
created_at: z
.string()
.datetime()
.openapi({
description: "The date when this status was created.",
example: "2025-01-07T14:11:00.000Z",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#created_at",
},
}),
edited_at: z
.string()
.datetime()
.nullable()
.openapi({
description: "Timestamp of when the status was last edited.",
example: "2025-01-07T14:11:00.000Z",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#edited_at",
},
}),
emojis: z.array(CustomEmoji).openapi({
description: "Custom emoji to be used when rendering status content.",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#emojis",
},
}),
replies_count: z
.number()
.int()
.nonnegative()
.openapi({
description: "How many replies this status has received.",
example: 1,
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#replies_count",
},
}),
reblogs_count: z
.number()
.int()
.nonnegative()
.openapi({
description: "How many boosts this status has received.",
example: 6,
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#reblogs_count",
},
}),
favourites_count: z
.number()
.int()
.nonnegative()
.openapi({
description: "How many favourites this status has received.",
example: 11,
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#favourites_count",
},
}),
reblogged: zBoolean.optional().openapi({
description:
"If the current token has an authorized user: Have you boosted this status?",
example: false,
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#reblogged",
},
}),
favourited: zBoolean.optional().openapi({
description:
"If the current token has an authorized user: Have you favourited this status?",
example: true,
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#favourited",
},
}),
muted: zBoolean.optional().openapi({
description:
"If the current token has an authorized user: Have you muted notifications for this statuss conversation?",
example: false,
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#muted",
},
}),
sensitive: zBoolean.openapi({
description: "Is this status marked as sensitive content?",
example: false,
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#sensitive",
},
}),
spoiler_text: z.string().openapi({
description:
"Subject or summary line, below which status content is collapsed until expanded.",
example: "lewd text",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#spoiler_text",
},
}),
visibility: z.enum(["public", "unlisted", "private", "direct"]).openapi({
description: "Visibility of this status.",
example: "public",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#visibility",
},
}),
media_attachments: z.array(Attachment).openapi({
description: "Media that is attached to this status.",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#media_attachments",
},
}),
mentions: z.array(Mention).openapi({
description: "Mentions of users within the status content.",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#mentions",
},
}),
tags: z.array(Tag).openapi({
description: "Hashtags used within the status content.",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#tags",
},
}),
card: PreviewCard.nullable().openapi({
description: "Preview card for links included within status content.",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#card",
},
}),
poll: Poll.nullable().openapi({
description: "The poll attached to the status.",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#poll",
},
}),
application: z
.object({
name: z.string().openapi({
description:
"The name of the application that posted this status.",
uri: z
.string()
.url()
.openapi({
description: "URI of the status used for federation.",
example:
"https://beta.versia.social/@lexi/2de861d3-a3dd-42ee-ba38-2c7d3f4af588",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#application-name",
url: "https://docs.joinmastodon.org/entities/Status/#uri",
},
}),
website: z
.string()
.url()
.nullable()
.openapi({
url: z
.string()
.url()
.nullable()
.openapi({
description: "A link to the statuss HTML representation.",
example:
"https://beta.versia.social/@lexi/2de861d3-a3dd-42ee-ba38-2c7d3f4af588",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#url",
},
}),
account: Account.openapi({
description: "The account that authored this status.",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#account",
},
}),
in_reply_to_id: Id.nullable().openapi({
description: "ID of the status being replied to.",
example: "c41c9fe9-919a-4d35-a921-d3e79a5c95f8",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#in_reply_to_id",
},
}),
in_reply_to_account_id: Account.shape.id.nullable().openapi({
description:
"ID of the account that authored the status being replied to.",
example: "7b9b3ec6-1013-4cc6-8902-94ad00cf2ccc",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#in_reply_to_account_id",
},
}),
content: z.string().openapi({
description: "HTML-encoded status content.",
example: "<p>hello world</p>",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#content",
},
}),
created_at: z
.string()
.datetime()
.openapi({
description: "The date when this status was created.",
example: "2025-01-07T14:11:00.000Z",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#created_at",
},
}),
edited_at: z
.string()
.datetime()
.nullable()
.openapi({
description: "Timestamp of when the status was last edited.",
example: "2025-01-07T14:11:00.000Z",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#edited_at",
},
}),
emojis: z.array(CustomEmoji).openapi({
description:
"Custom emoji to be used when rendering status content.",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#emojis",
},
}),
replies_count: z
.number()
.int()
.nonnegative()
.openapi({
description: "How many replies this status has received.",
example: 1,
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#replies_count",
},
}),
reblogs_count: z
.number()
.int()
.nonnegative()
.openapi({
description: "How many boosts this status has received.",
example: 6,
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#reblogs_count",
},
}),
favourites_count: z
.number()
.int()
.nonnegative()
.openapi({
description: "How many favourites this status has received.",
example: 11,
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#favourites_count",
},
}),
reblogged: zBoolean.optional().openapi({
description:
"If the current token has an authorized user: Have you boosted this status?",
example: false,
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#reblogged",
},
}),
favourited: zBoolean.optional().openapi({
description:
"If the current token has an authorized user: Have you favourited this status?",
example: true,
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#favourited",
},
}),
muted: zBoolean.optional().openapi({
description:
"If the current token has an authorized user: Have you muted notifications for this statuss conversation?",
example: false,
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#muted",
},
}),
sensitive: zBoolean.openapi({
description: "Is this status marked as sensitive content?",
example: false,
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#sensitive",
},
}),
spoiler_text: z.string().openapi({
description:
"Subject or summary line, below which status content is collapsed until expanded.",
example: "lewd text",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#spoiler_text",
},
}),
visibility: z
.enum(["public", "unlisted", "private", "direct"])
.openapi({
description: "Visibility of this status.",
example: "public",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#visibility",
},
}),
media_attachments: z.array(Attachment).openapi({
description: "Media that is attached to this status.",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#media_attachments",
},
}),
mentions: z.array(Mention).openapi({
description: "Mentions of users within the status content.",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#mentions",
},
}),
tags: z.array(Tag).openapi({
description: "Hashtags used within the status content.",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#tags",
},
}),
card: PreviewCard.nullable().openapi({
description:
"Preview card for links included within status content.",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#card",
},
}),
poll: Poll.nullable().openapi({
description: "The poll attached to the status.",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#poll",
},
}),
application: z
.object({
name: z.string().openapi({
description:
"The website associated with the application that posted this status.",
"The name of the application that posted this status.",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#application-website",
url: "https://docs.joinmastodon.org/entities/Status/#application-name",
},
}),
})
.optional()
.openapi({
description: "The application used to post this status.",
website: z
.string()
.url()
.nullable()
.openapi({
description:
"The website associated with the application that posted this status.",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#application-website",
},
}),
})
.optional()
.openapi({
description: "The application used to post this status.",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#application",
},
}),
language: iso631.nullable().openapi({
description: "Primary language of this status.",
example: "en",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#application",
url: "https://docs.joinmastodon.org/entities/Status/#language",
},
}),
language: iso631.nullable().openapi({
description: "Primary language of this status.",
example: "en",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#language",
},
}),
text: z
.string()
.nullable()
.openapi({
text: z
.string()
.nullable()
.openapi({
description:
"Plain-text source of a status. Returned instead of content when status is deleted, so the user may redraft from the source text without the client having to reverse-engineer the original text from the HTML content.",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#text",
},
}),
pinned: zBoolean.optional().openapi({
description:
"Plain-text source of a status. Returned instead of content when status is deleted, so the user may redraft from the source text without the client having to reverse-engineer the original text from the HTML content.",
"If the current token has an authorized user: Have you pinned this status? Only appears if the status is pinnable.",
example: true,
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#text",
url: "https://docs.joinmastodon.org/entities/Status/#pinned",
},
}),
pinned: zBoolean.optional().openapi({
description:
"If the current token has an authorized user: Have you pinned this status? Only appears if the status is pinnable.",
example: true,
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#pinned",
},
}),
reactions: z.array(NoteReaction).openapi({}),
bookmarked: zBoolean.optional().openapi({
description:
"If the current token has an authorized user: Have you bookmarked this status?",
example: false,
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#bookmarked",
},
}),
filtered: z
.array(FilterResult)
.optional()
.openapi({
reactions: z.array(NoteReaction).openapi({}),
bookmarked: zBoolean.optional().openapi({
description:
"If the current token has an authorized user: The filter and keywords that matched this status.",
"If the current token has an authorized user: Have you bookmarked this status?",
example: false,
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#filtered",
url: "https://docs.joinmastodon.org/entities/Status/#bookmarked",
},
}),
});
filtered: z
.array(FilterResult)
.optional()
.openapi({
description:
"If the current token has an authorized user: The filter and keywords that matched this status.",
externalDocs: {
url: "https://docs.joinmastodon.org/entities/Status/#filtered",
},
}),
})
.openapi("BaseStatus");
export const Status = BaseStatus.extend({
reblog: BaseStatus.nullable().openapi({
@ -360,50 +366,52 @@ export const Status = BaseStatus.extend({
},
}),
quote: BaseStatus.nullable(),
});
}).openapi("Status");
export const ScheduledStatus = z.object({
id: Id.openapi({
description: "ID of the scheduled status in the database.",
example: "2de861d3-a3dd-42ee-ba38-2c7d3f4af588",
}),
scheduled_at: z.string().datetime().openapi({
description: "When the status will be scheduled.",
example: "2025-01-07T14:11:00.000Z",
}),
media_attachments: Status.shape.media_attachments,
params: z.object({
text: z.string().openapi({
description: "Text to be used as status content.",
example: "Hello, world!",
export const ScheduledStatus = z
.object({
id: Id.openapi({
description: "ID of the scheduled status in the database.",
example: "2de861d3-a3dd-42ee-ba38-2c7d3f4af588",
}),
poll: Status.shape.poll,
media_ids: z
.array(Id)
.nullable()
.openapi({
description:
"IDs of the MediaAttachments that will be attached to the status.",
example: ["1234567890", "1234567891"],
scheduled_at: z.string().datetime().openapi({
description: "When the status will be scheduled.",
example: "2025-01-07T14:11:00.000Z",
}),
media_attachments: Status.shape.media_attachments,
params: z.object({
text: z.string().openapi({
description: "Text to be used as status content.",
example: "Hello, world!",
}),
poll: Status.shape.poll,
media_ids: z
.array(Id)
.nullable()
.openapi({
description:
"IDs of the MediaAttachments that will be attached to the status.",
example: ["1234567890", "1234567891"],
}),
sensitive: Status.shape.sensitive,
spoiler_text: Status.shape.spoiler_text,
visibility: Status.shape.visibility,
in_reply_to_id: Status.shape.in_reply_to_id,
/** Versia Server API Extension */
quote_id: z.string().openapi({
description: "ID of the status being quoted.",
example: "c5d62a13-f340-4e7d-8942-7fd14be688dc",
}),
language: Status.shape.language,
scheduled_at: z.null().openapi({
description:
"When the status will be scheduled. This will be null because the status is only scheduled once.",
example: null,
}),
idempotency: z.string().nullable().openapi({
description: "Idempotency key to prevent duplicate statuses.",
example: "1234567890",
}),
sensitive: Status.shape.sensitive,
spoiler_text: Status.shape.spoiler_text,
visibility: Status.shape.visibility,
in_reply_to_id: Status.shape.in_reply_to_id,
/** Versia Server API Extension */
quote_id: z.string().openapi({
description: "ID of the status being quoted.",
example: "c5d62a13-f340-4e7d-8942-7fd14be688dc",
}),
language: Status.shape.language,
scheduled_at: z.null().openapi({
description:
"When the status will be scheduled. This will be null because the status is only scheduled once.",
example: null,
}),
idempotency: z.string().nullable().openapi({
description: "Idempotency key to prevent duplicate statuses.",
example: "1234567890",
}),
}),
});
})
.openapi("ScheduledStatus");