diff --git a/app.ts b/app.ts index 7d80e095..28200841 100644 --- a/app.ts +++ b/app.ts @@ -3,10 +3,10 @@ import { handleZodError } from "@/api"; import { applyToHono } from "@/bull-board.ts"; import { configureLoggers } from "@/loggers"; import { sentry } from "@/sentry"; -import { swaggerUI } from "@hono/swagger-ui"; import { OpenAPIHono } from "@hono/zod-openapi"; /* import { prometheus } from "@hono/prometheus"; */ import { getLogger } from "@logtape/logtape"; +import { apiReference } from "@scalar/hono-api-reference"; import { inspect } from "bun"; import chalk from "chalk"; import { cors } from "hono/cors"; @@ -148,7 +148,15 @@ export const appFactory = async (): Promise> => { contact: pkg.author, }, }); - app.get("/docs", swaggerUI({ url: "/openapi.json" })); + app.get( + "/docs", + apiReference({ + theme: "deepSpace", + hideClientButton: true, + pageTitle: "Versia Server API", + url: "/openapi.json", + }), + ); applyToHono(app); app.options("*", (context) => { diff --git a/bun.lock b/bun.lock index 4136dc71..fa1c195e 100644 --- a/bun.lock +++ b/bun.lock @@ -13,12 +13,12 @@ "@clerc/plugin-version": "^0.44.0", "@hackmd/markdown-it-task-lists": "^2.1.4", "@hono/prometheus": "^1.0.1", - "@hono/swagger-ui": "^0.5.1", "@hono/zod-openapi": "0.19.2", "@hono/zod-validator": "^0.4.3", "@inquirer/confirm": "^5.1.8", "@logtape/file": "^0.9.0", "@logtape/logtape": "^0.9.0", + "@scalar/hono-api-reference": "^0.7.2", "@sentry/bun": "^9.8.0", "@versia/client": "workspace:*", "@versia/federation": "^0.2.1", @@ -271,8 +271,6 @@ "@hono/prometheus": ["@hono/prometheus@1.0.1", "", { "peerDependencies": { "hono": ">=3.*", "prom-client": "^15.0.0" } }, "sha512-PjMbjAppCgbvRP2aLxqJc1XJLxfmg4dLsS5R5ITt7qCf9Ab/xSRul/LHNVvYK2/ECi3BOPprSnlSizksBJmXBQ=="], - "@hono/swagger-ui": ["@hono/swagger-ui@0.5.1", "", { "peerDependencies": { "hono": "*" } }, "sha512-XpUCfszLJ9b1rtFdzqOSHfdg9pfBiC2J5piEjuSanYpDDTIwpMz0ciiv5N3WWUaQpz9fEgH8lttQqL41vIFuDA=="], - "@hono/zod-openapi": ["@hono/zod-openapi@0.19.2", "", { "dependencies": { "@asteasolutions/zod-to-openapi": "^7.1.0", "@hono/zod-validator": "^0.4.1" }, "peerDependencies": { "hono": ">=4.3.6", "zod": "3.*" } }, "sha512-lkFa6wdQVgY7d7/m++Ixr3hvKCF5Y+zjTIPM37fex5ylCfX53A/W28gZRDuFZx3aR+noKob7lHfwdk9dURLzxw=="], "@hono/zod-validator": ["@hono/zod-validator@0.4.3", "", { "peerDependencies": { "hono": ">=3.9.0", "zod": "^3.19.1" } }, "sha512-xIgMYXDyJ4Hj6ekm9T9Y27s080Nl9NXHcJkOvkXPhubOLj8hZkOL8pDnnXfvCf5xEE8Q4oMFenQUZZREUY2gqQ=="], @@ -465,6 +463,14 @@ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.36.0", "", { "os": "win32", "cpu": "x64" }, "sha512-aRXd7tRZkWLqGbChgcMMDEHjOKudo1kChb1Jt1IfR8cY/KIpgNviLeJy5FUb9IpSuQj8dU2fAYNMPW/hLKOSTw=="], + "@scalar/core": ["@scalar/core@0.2.2", "", { "dependencies": { "@scalar/types": "0.1.2" } }, "sha512-jT6vfz37yQnqVjj8kXYEmV2cZvODW1A0PXjxZ9DzKqjm9tIssNwP4vvcdD1FSuiMcj+rgxAxOjIYMI+ybI/9RQ=="], + + "@scalar/hono-api-reference": ["@scalar/hono-api-reference@0.7.2", "", { "dependencies": { "@scalar/core": "0.2.2" }, "peerDependencies": { "hono": "^4.0.0" } }, "sha512-CnxRjGfAWPGkV0D5TEwogvn7JSx/f9+ag6vQ6g25GigSDyj/UkxYbZqwe/QOV/+2EWruY3ypOvPuNMf7nEQhdQ=="], + + "@scalar/openapi-types": ["@scalar/openapi-types@0.1.9", "", {}, "sha512-HQQudOSQBU7ewzfnBW9LhDmBE2XOJgSfwrh5PlUB7zJup/kaRkBGNgV2wMjNz9Af/uztiU/xNrO179FysmUT+g=="], + + "@scalar/types": ["@scalar/types@0.1.2", "", { "dependencies": { "@scalar/openapi-types": "0.1.9", "@unhead/schema": "^1.11.11", "zod": "^3.23.8" } }, "sha512-5kCLQRwAYWt1ds110EaUb9yonc3KoQYNyo4YUCigJLOnoNugbqkEX0zRudGevItiuk+xg4uOYd30r3C+6xAasA=="], + "@selderee/plugin-htmlparser2": ["@selderee/plugin-htmlparser2@0.11.0", "", { "dependencies": { "domhandler": "^5.0.3", "selderee": "^0.11.0" } }, "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ=="], "@sentry/bun": ["@sentry/bun@9.8.0", "", { "dependencies": { "@sentry/core": "9.8.0", "@sentry/node": "9.8.0", "@sentry/opentelemetry": "9.8.0" } }, "sha512-3JSaxyEomfZYLp1mwvwugj308QeZ4fYRzjAOQcWZqHFKbr6pMFKTvZKfEKIoAEXsNfka33+VpPdltlMgGe9auw=="], @@ -543,6 +549,8 @@ "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], + "@unhead/schema": ["@unhead/schema@1.11.20", "", { "dependencies": { "hookable": "^5.5.3", "zhead": "^2.2.4" } }, "sha512-0zWykKAaJdm+/Y7yi/Yds20PrUK7XabLe9c3IRcjnwYmSWY6z0Cr19VIs3ozCj8P+GhR+/TI2mwtGlueCEYouA=="], + "@versia/client": ["@versia/client@workspace:packages/client"], "@versia/federation": ["@versia/federation@0.2.1", "", { "dependencies": { "magic-regexp": "^0.8.0", "mime-types": "^2.1.35", "zod": "^3.24.1", "zod-validation-error": "^3.4.0" } }, "sha512-FTo3VGNJBGmCi0ZEQMzqFZBbcfbX81kmg0UgY4cKamr1dJWgEf72IAZnEDgrBffFjYtreLGdEjFkkcq3JfS8oQ=="], @@ -1339,6 +1347,8 @@ "yoctocolors-cjs": ["yoctocolors-cjs@2.1.2", "", {}, "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA=="], + "zhead": ["zhead@2.2.4", "", {}, "sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag=="], + "zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="], "zod-to-json-schema": ["zod-to-json-schema@3.24.5", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="], diff --git a/package.json b/package.json index a83fe7b8..5dc2f886 100644 --- a/package.json +++ b/package.json @@ -85,12 +85,12 @@ "@clerc/plugin-version": "^0.44.0", "@hackmd/markdown-it-task-lists": "^2.1.4", "@hono/prometheus": "^1.0.1", - "@hono/swagger-ui": "^0.5.1", "@hono/zod-openapi": "0.19.2", "@hono/zod-validator": "^0.4.3", "@inquirer/confirm": "^5.1.8", "@logtape/file": "^0.9.0", "@logtape/logtape": "^0.9.0", + "@scalar/hono-api-reference": "^0.7.2", "@sentry/bun": "^9.8.0", "@versia/client": "workspace:*", "@versia/federation": "^0.2.1", diff --git a/packages/client/schemas/account-warning.ts b/packages/client/schemas/account-warning.ts index 4bdd730d..2f070eb1 100644 --- a/packages/client/schemas/account-warning.ts +++ b/packages/client/schemas/account-warning.ts @@ -49,7 +49,7 @@ export const AccountWarning = z example: "2025-01-04T14:11:00Z", }), }) - .openapi({ + .openapi("AccountWarning", { description: "Moderation warning against a particular account.", externalDocs: { url: "https://docs.joinmastodon.org/entities/AccountWarning", diff --git a/packages/client/schemas/account.ts b/packages/client/schemas/account.ts index c44a9577..9cda795a 100644 --- a/packages/client/schemas/account.ts +++ b/packages/client/schemas/account.ts @@ -5,44 +5,46 @@ import { iso631, zBoolean } from "./common.ts"; import { CustomEmoji } from "./emoji.ts"; import { Role } from "./versia.ts"; -export const Field = z.object({ - name: z - .string() - .trim() - .min(1) - .max(config.validation.accounts.max_field_name_characters) - .openapi({ - description: "The key of a given field’s key-value pair.", - example: "Freak level", - externalDocs: { - url: "https://docs.joinmastodon.org/entities/Account/#name", - }, - }), - value: z - .string() - .trim() - .min(1) - .max(config.validation.accounts.max_field_value_characters) - .openapi({ - description: "The value associated with the name key.", - example: "

High

", - externalDocs: { - url: "https://docs.joinmastodon.org/entities/Account/#value", - }, - }), - verified_at: z - .string() - .datetime() - .nullable() - .openapi({ - description: - "Timestamp of when the server verified a URL value for a rel=“me” link.", - example: null, - externalDocs: { - url: "https://docs.joinmastodon.org/entities/Account/#verified_at", - }, - }), -}); +export const Field = z + .object({ + name: z + .string() + .trim() + .min(1) + .max(config.validation.accounts.max_field_name_characters) + .openapi({ + description: "The key of a given field’s key-value pair.", + example: "Freak level", + externalDocs: { + url: "https://docs.joinmastodon.org/entities/Account/#name", + }, + }), + value: z + .string() + .trim() + .min(1) + .max(config.validation.accounts.max_field_value_characters) + .openapi({ + description: "The value associated with the name key.", + example: "

High

", + externalDocs: { + url: "https://docs.joinmastodon.org/entities/Account/#value", + }, + }), + verified_at: z + .string() + .datetime() + .nullable() + .openapi({ + description: + "Timestamp of when the server verified a URL value for a rel=“me” link.", + example: null, + externalDocs: { + url: "https://docs.joinmastodon.org/entities/Account/#verified_at", + }, + }), + }) + .openapi("AccountField"); export const Source = z .object({ @@ -107,7 +109,7 @@ export const Source = z description: "Metadata about the account.", }), }) - .openapi({ + .openapi("AccountSource", { description: "An extra attribute that contains source values to be used with API methods that verify credentials and update credentials.", externalDocs: { @@ -116,304 +118,310 @@ export const Source = z }); // Because Account has some recursive references, we need to define it like this -const BaseAccount = z.object({ - id: z - .string() - .uuid() - .openapi({ - description: "The account ID in the database.", - example: "9e84842b-4db6-4a9b-969d-46ab408278da", - externalDocs: { - url: "https://docs.joinmastodon.org/entities/Account/#id", - }, - }), - username: z - .string() - .min(3) - .trim() - .max(config.validation.accounts.max_username_characters) - .regex( - /^[a-z0-9_-]+$/, - "Username can only contain letters, numbers, underscores and hyphens", - ) - .refine( - (s) => - !config.validation.filters.username.some((filter) => - filter.test(s), - ), - "Username contains blocked words", - ) - .refine( - (s) => - !config.validation.accounts.disallowed_usernames.some((u) => - u.test(s), - ), - "Username is disallowed", - ) - .openapi({ - description: "The username of the account, not including domain.", - example: "lexi", - externalDocs: { - url: "https://docs.joinmastodon.org/entities/Account/#username", - }, - }), - acct: z - .string() - .min(1) - .trim() - .regex(userAddressValidator, "Invalid user address") - .openapi({ +const BaseAccount = z + .object({ + id: z + .string() + .uuid() + .openapi({ + description: "The account ID in the database.", + example: "9e84842b-4db6-4a9b-969d-46ab408278da", + externalDocs: { + url: "https://docs.joinmastodon.org/entities/Account/#id", + }, + }), + username: z + .string() + .min(3) + .trim() + .max(config.validation.accounts.max_username_characters) + .regex( + /^[a-z0-9_-]+$/, + "Username can only contain letters, numbers, underscores and hyphens", + ) + .refine( + (s) => + !config.validation.filters.username.some((filter) => + filter.test(s), + ), + "Username contains blocked words", + ) + .refine( + (s) => + !config.validation.accounts.disallowed_usernames.some((u) => + u.test(s), + ), + "Username is disallowed", + ) + .openapi({ + description: + "The username of the account, not including domain.", + example: "lexi", + externalDocs: { + url: "https://docs.joinmastodon.org/entities/Account/#username", + }, + }), + acct: z + .string() + .min(1) + .trim() + .regex(userAddressValidator, "Invalid user address") + .openapi({ + description: + "The Webfinger account URI. Equal to username for local users, or username@domain for remote users.", + example: "lexi@beta.versia.social", + externalDocs: { + url: "https://docs.joinmastodon.org/entities/Account/#acct", + }, + }), + url: z + .string() + .url() + .openapi({ + description: "The location of the user’s profile page.", + example: "https://beta.versia.social/@lexi", + externalDocs: { + url: "https://docs.joinmastodon.org/entities/Account/#url", + }, + }), + display_name: z + .string() + .min(3) + .trim() + .max(config.validation.accounts.max_displayname_characters) + .refine( + (s) => + !config.validation.filters.displayname.some((filter) => + filter.test(s), + ), + "Display name contains blocked words", + ) + .openapi({ + description: "The profile’s display name.", + example: "Lexi :flower:", + externalDocs: { + url: "https://docs.joinmastodon.org/entities/Account/#display_name", + }, + }), + note: z + .string() + .min(0) + .max(config.validation.accounts.max_bio_characters) + .trim() + .refine( + (s) => + !config.validation.filters.bio.some((filter) => + filter.test(s), + ), + "Bio contains blocked words", + ) + .openapi({ + description: "The profile’s bio or description.", + example: "

ermmm what the meow meow

", + externalDocs: { + url: "https://docs.joinmastodon.org/entities/Account/#note", + }, + }), + avatar: z + .string() + .url() + .openapi({ + description: + "An image icon that is shown next to statuses and in the profile.", + example: + "https://cdn.versia.social/avatars/cff9aea0-0000-43fe-8b5e-e7c7ea69a488/lexi.webp", + externalDocs: { + url: "https://docs.joinmastodon.org/entities/Account/#avatar", + }, + }), + avatar_static: z + .string() + .url() + .openapi({ + description: + "A static version of the avatar. Equal to avatar if its value is a static image; different if avatar is an animated GIF.", + example: + "https://cdn.versia.social/avatars/cff9aea0-0000-43fe-8b5e-e7c7ea69a488/lexi.webp", + externalDocs: { + url: "https://docs.joinmastodon.org/entities/Account/#avatar_static", + }, + }), + header: z + .string() + .url() + .openapi({ + description: + "An image banner that is shown above the profile and in profile cards.", + example: + "https://cdn.versia.social/headers/a049f8e3-878c-4faa-ae4c-a6bcceddbd9d/femboy_2.webp", + externalDocs: { + url: "https://docs.joinmastodon.org/entities/Account/#header", + }, + }), + header_static: z + .string() + .url() + .openapi({ + description: + "A static version of the header. Equal to header if its value is a static image; different if header is an animated GIF.", + example: + "https://cdn.versia.social/headers/a049f8e3-878c-4faa-ae4c-a6bcceddbd9d/femboy_2.webp", + externalDocs: { + url: "https://docs.joinmastodon.org/entities/Account/#header_static", + }, + }), + locked: zBoolean.openapi({ description: - "The Webfinger account URI. Equal to username for local users, or username@domain for remote users.", - example: "lexi@beta.versia.social", - externalDocs: { - url: "https://docs.joinmastodon.org/entities/Account/#acct", - }, - }), - url: z - .string() - .url() - .openapi({ - description: "The location of the user’s profile page.", - example: "https://beta.versia.social/@lexi", - externalDocs: { - url: "https://docs.joinmastodon.org/entities/Account/#url", - }, - }), - display_name: z - .string() - .min(3) - .trim() - .max(config.validation.accounts.max_displayname_characters) - .refine( - (s) => - !config.validation.filters.displayname.some((filter) => - filter.test(s), - ), - "Display name contains blocked words", - ) - .openapi({ - description: "The profile’s display name.", - example: "Lexi :flower:", - externalDocs: { - url: "https://docs.joinmastodon.org/entities/Account/#display_name", - }, - }), - note: z - .string() - .min(0) - .max(config.validation.accounts.max_bio_characters) - .trim() - .refine( - (s) => - !config.validation.filters.bio.some((filter) => filter.test(s)), - "Bio contains blocked words", - ) - .openapi({ - description: "The profile’s bio or description.", - example: "

ermmm what the meow meow

", - externalDocs: { - url: "https://docs.joinmastodon.org/entities/Account/#note", - }, - }), - avatar: z - .string() - .url() - .openapi({ - description: - "An image icon that is shown next to statuses and in the profile.", - example: - "https://cdn.versia.social/avatars/cff9aea0-0000-43fe-8b5e-e7c7ea69a488/lexi.webp", - externalDocs: { - url: "https://docs.joinmastodon.org/entities/Account/#avatar", - }, - }), - avatar_static: z - .string() - .url() - .openapi({ - description: - "A static version of the avatar. Equal to avatar if its value is a static image; different if avatar is an animated GIF.", - example: - "https://cdn.versia.social/avatars/cff9aea0-0000-43fe-8b5e-e7c7ea69a488/lexi.webp", - externalDocs: { - url: "https://docs.joinmastodon.org/entities/Account/#avatar_static", - }, - }), - header: z - .string() - .url() - .openapi({ - description: - "An image banner that is shown above the profile and in profile cards.", - example: - "https://cdn.versia.social/headers/a049f8e3-878c-4faa-ae4c-a6bcceddbd9d/femboy_2.webp", - externalDocs: { - url: "https://docs.joinmastodon.org/entities/Account/#header", - }, - }), - header_static: z - .string() - .url() - .openapi({ - description: - "A static version of the header. Equal to header if its value is a static image; different if header is an animated GIF.", - example: - "https://cdn.versia.social/headers/a049f8e3-878c-4faa-ae4c-a6bcceddbd9d/femboy_2.webp", - externalDocs: { - url: "https://docs.joinmastodon.org/entities/Account/#header_static", - }, - }), - locked: zBoolean.openapi({ - description: "Whether the account manually approves follow requests.", - example: false, - externalDocs: { - url: "https://docs.joinmastodon.org/entities/Account/#locked", - }, - }), - fields: z - .array(Field) - .max(config.validation.accounts.max_field_count) - .openapi({ - description: - "Additional metadata attached to a profile as name-value pairs.", - externalDocs: { - url: "https://docs.joinmastodon.org/entities/Account/#fields", - }, - }), - emojis: z.array(CustomEmoji).openapi({ - description: - "Custom emoji entities to be used when rendering the profile.", - externalDocs: { - url: "https://docs.joinmastodon.org/entities/Account/#emojis", - }, - }), - bot: zBoolean.openapi({ - description: - "Indicates that the account may perform automated actions, may not be monitored, or identifies as a robot.", - example: false, - externalDocs: { - url: "https://docs.joinmastodon.org/entities/Account/#bot", - }, - }), - group: z.literal(false).openapi({ - description: "Indicates that the account represents a Group actor.", - example: false, - externalDocs: { - url: "https://docs.joinmastodon.org/entities/Account/#group", - }, - }), - discoverable: zBoolean.nullable().openapi({ - description: - "Whether the account has opted into discovery features such as the profile directory.", - example: true, - externalDocs: { - url: "https://docs.joinmastodon.org/entities/Account/#discoverable", - }, - }), - noindex: zBoolean - .nullable() - .optional() - .openapi({ - description: - "Whether the local user has opted out of being indexed by search engines.", + "Whether the account manually approves follow requests.", example: false, externalDocs: { - url: "https://docs.joinmastodon.org/entities/Account/#noindex", + url: "https://docs.joinmastodon.org/entities/Account/#locked", }, }), - suspended: zBoolean.optional().openapi({ - description: - "An extra attribute returned only when an account is suspended.", - example: false, - externalDocs: { - url: "https://docs.joinmastodon.org/entities/Account/#suspended", - }, - }), - limited: zBoolean.optional().openapi({ - description: - "An extra attribute returned only when an account is silenced. If true, indicates that the account should be hidden behind a warning screen.", - example: false, - externalDocs: { - url: "https://docs.joinmastodon.org/entities/Account/#limited", - }, - }), - created_at: z - .string() - .datetime() - .openapi({ - description: "When the account was created.", - example: "2024-10-15T22:00:00.000Z", + fields: z + .array(Field) + .max(config.validation.accounts.max_field_count) + .openapi({ + description: + "Additional metadata attached to a profile as name-value pairs.", + externalDocs: { + url: "https://docs.joinmastodon.org/entities/Account/#fields", + }, + }), + emojis: z.array(CustomEmoji).openapi({ + description: + "Custom emoji entities to be used when rendering the profile.", externalDocs: { - url: "https://docs.joinmastodon.org/entities/Account/#created_at", + url: "https://docs.joinmastodon.org/entities/Account/#emojis", }, }), - // TODO - last_status_at: z - .literal(null) - .openapi({ - description: "When the most recent status was posted.", - example: null, + bot: zBoolean.openapi({ + description: + "Indicates that the account may perform automated actions, may not be monitored, or identifies as a robot.", + example: false, externalDocs: { - url: "https://docs.joinmastodon.org/entities/Account/#last_status_at", - }, - }) - .nullable(), - statuses_count: z - .number() - .int() - .nonnegative() - .openapi({ - description: "How many statuses are attached to this account.", - example: 42, - externalDocs: { - url: "https://docs.joinmastodon.org/entities/Account/#statuses_count", + url: "https://docs.joinmastodon.org/entities/Account/#bot", }, }), - followers_count: z - .number() - .int() - .nonnegative() - .openapi({ - description: "The reported followers of this profile.", - example: 6, + group: z.literal(false).openapi({ + description: "Indicates that the account represents a Group actor.", + example: false, externalDocs: { - url: "https://docs.joinmastodon.org/entities/Account/#followers_count", + url: "https://docs.joinmastodon.org/entities/Account/#group", }, }), - following_count: z - .number() - .int() - .nonnegative() - .openapi({ - description: "The reported follows of this profile.", - example: 23, + discoverable: zBoolean.nullable().openapi({ + description: + "Whether the account has opted into discovery features such as the profile directory.", + example: true, externalDocs: { - url: "https://docs.joinmastodon.org/entities/Account/#following_count", + url: "https://docs.joinmastodon.org/entities/Account/#discoverable", }, }), - /* Versia Server API extension */ - uri: z.string().url().openapi({ - description: - "The location of the user's Versia profile page, as opposed to the local representation.", - example: - "https://beta.versia.social/users/9e84842b-4db6-4a9b-969d-46ab408278da", - }), - source: Source.optional(), - role: z - .object({ - name: z.string(), - }) - .optional(), - /* Versia Server API extension */ - roles: z.array(Role).openapi({ - description: "Roles assigned to the account.", - }), - mute_expires_at: z.string().datetime().nullable().openapi({ - description: "When a timed mute will expire, if applicable.", - example: "2025-03-01T14:00:00.000Z", - }), -}); + noindex: zBoolean + .nullable() + .optional() + .openapi({ + description: + "Whether the local user has opted out of being indexed by search engines.", + example: false, + externalDocs: { + url: "https://docs.joinmastodon.org/entities/Account/#noindex", + }, + }), + suspended: zBoolean.optional().openapi({ + description: + "An extra attribute returned only when an account is suspended.", + example: false, + externalDocs: { + url: "https://docs.joinmastodon.org/entities/Account/#suspended", + }, + }), + limited: zBoolean.optional().openapi({ + description: + "An extra attribute returned only when an account is silenced. If true, indicates that the account should be hidden behind a warning screen.", + example: false, + externalDocs: { + url: "https://docs.joinmastodon.org/entities/Account/#limited", + }, + }), + created_at: z + .string() + .datetime() + .openapi({ + description: "When the account was created.", + example: "2024-10-15T22:00:00.000Z", + externalDocs: { + url: "https://docs.joinmastodon.org/entities/Account/#created_at", + }, + }), + // TODO + last_status_at: z + .literal(null) + .openapi({ + description: "When the most recent status was posted.", + example: null, + externalDocs: { + url: "https://docs.joinmastodon.org/entities/Account/#last_status_at", + }, + }) + .nullable(), + statuses_count: z + .number() + .int() + .nonnegative() + .openapi({ + description: "How many statuses are attached to this account.", + example: 42, + externalDocs: { + url: "https://docs.joinmastodon.org/entities/Account/#statuses_count", + }, + }), + followers_count: z + .number() + .int() + .nonnegative() + .openapi({ + description: "The reported followers of this profile.", + example: 6, + externalDocs: { + url: "https://docs.joinmastodon.org/entities/Account/#followers_count", + }, + }), + following_count: z + .number() + .int() + .nonnegative() + .openapi({ + description: "The reported follows of this profile.", + example: 23, + externalDocs: { + url: "https://docs.joinmastodon.org/entities/Account/#following_count", + }, + }), + /* Versia Server API extension */ + uri: z.string().url().openapi({ + description: + "The location of the user's Versia profile page, as opposed to the local representation.", + example: + "https://beta.versia.social/users/9e84842b-4db6-4a9b-969d-46ab408278da", + }), + source: Source.optional(), + role: z + .object({ + name: z.string(), + }) + .optional(), + /* Versia Server API extension */ + roles: z.array(Role).openapi({ + description: "Roles assigned to the account.", + }), + mute_expires_at: z.string().datetime().nullable().openapi({ + description: "When a timed mute will expire, if applicable.", + example: "2025-03-01T14:00:00.000Z", + }), + }) + .openapi("BaseAccount"); export const Account = BaseAccount.extend({ moved: BaseAccount.nullable() @@ -426,4 +434,4 @@ export const Account = BaseAccount.extend({ url: "https://docs.joinmastodon.org/entities/Account/#moved", }, }), -}); +}).openapi("Account"); diff --git a/packages/client/schemas/appeal.ts b/packages/client/schemas/appeal.ts index 8c10bd6a..caec9851 100644 --- a/packages/client/schemas/appeal.ts +++ b/packages/client/schemas/appeal.ts @@ -13,7 +13,7 @@ export const Appeal = z example: "pending", }), }) - .openapi({ + .openapi("Appeal", { description: "Appeal against a moderation action.", externalDocs: { url: "https://docs.joinmastodon.org/entities/Appeal", diff --git a/packages/client/schemas/application.ts b/packages/client/schemas/application.ts index ff4271d5..784e1dbe 100644 --- a/packages/client/schemas/application.ts +++ b/packages/client/schemas/application.ts @@ -1,65 +1,67 @@ import { z } from "@hono/zod-openapi"; -export const Application = z.object({ - name: z - .string() - .trim() - .min(1) - .max(200) - .openapi({ - description: "The name of your application.", - example: "Test Application", - externalDocs: { - url: "https://docs.joinmastodon.org/entities/Application/#name", - }, - }), - website: z - .string() - .nullable() - .openapi({ - description: "The website associated with your application.", - example: "https://app.example", - externalDocs: { - url: "https://docs.joinmastodon.org/entities/Application/#website", - }, - }), - scopes: z - .array(z.string()) - .default(["read"]) - .openapi({ +export const Application = z + .object({ + name: z + .string() + .trim() + .min(1) + .max(200) + .openapi({ + description: "The name of your application.", + example: "Test Application", + externalDocs: { + url: "https://docs.joinmastodon.org/entities/Application/#name", + }, + }), + website: z + .string() + .nullable() + .openapi({ + description: "The website associated with your application.", + example: "https://app.example", + externalDocs: { + url: "https://docs.joinmastodon.org/entities/Application/#website", + }, + }), + scopes: z + .array(z.string()) + .default(["read"]) + .openapi({ + description: + "The scopes for your application. This is the registered scopes string split on whitespace.", + example: ["read", "write", "push"], + externalDocs: { + url: "https://docs.joinmastodon.org/entities/Application/#scopes", + }, + }), + redirect_uris: z + .array( + z + .string() + .url() + .or(z.literal("urn:ietf:wg:oauth:2.0:oob")) + .openapi({ + description: "URL or 'urn:ietf:wg:oauth:2.0:oob'", + }), + ) + .openapi({ + description: + "The registered redirection URI(s) for your application.", + externalDocs: { + url: "https://docs.joinmastodon.org/entities/Application/#redirect_uris", + }, + }), + redirect_uri: z.string().openapi({ + deprecated: true, description: - "The scopes for your application. This is the registered scopes string split on whitespace.", - example: ["read", "write", "push"], + "The registered redirection URI(s) for your application. May contain \\n characters when multiple redirect URIs are registered.", externalDocs: { - url: "https://docs.joinmastodon.org/entities/Application/#scopes", + url: "https://docs.joinmastodon.org/entities/Application/#redirect_uri", }, }), - redirect_uris: z - .array( - z - .string() - .url() - .or(z.literal("urn:ietf:wg:oauth:2.0:oob")) - .openapi({ - description: "URL or 'urn:ietf:wg:oauth:2.0:oob'", - }), - ) - .openapi({ - description: - "The registered redirection URI(s) for your application.", - externalDocs: { - url: "https://docs.joinmastodon.org/entities/Application/#redirect_uris", - }, - }), - redirect_uri: z.string().openapi({ - deprecated: true, - description: - "The registered redirection URI(s) for your application. May contain \\n characters when multiple redirect URIs are registered.", - externalDocs: { - url: "https://docs.joinmastodon.org/entities/Application/#redirect_uri", - }, - }), -}); + }) + .openapi("Application"); export const CredentialApplication = Application.extend({ client_id: z.string().openapi({ @@ -81,4 +83,4 @@ export const CredentialApplication = Application.extend({ url: "https://docs.joinmastodon.org/entities/CredentialApplication/#client_secret_expires_at", }, }), -}); +}).openapi("CredentialApplication"); diff --git a/packages/client/schemas/attachment.ts b/packages/client/schemas/attachment.ts index cbea78f3..5e500b66 100644 --- a/packages/client/schemas/attachment.ts +++ b/packages/client/schemas/attachment.ts @@ -67,7 +67,7 @@ export const Attachment = z example: "UFBWY:8_0Jxv4mx]t8t64.%M-:IUWGWAt6M}", }), }) - .openapi({ + .openapi("Attachment", { description: "Represents a file or media attachment that can be added to a status.", externalDocs: { diff --git a/packages/client/schemas/card.ts b/packages/client/schemas/card.ts index 0b367b78..4bcbe171 100644 --- a/packages/client/schemas/card.ts +++ b/packages/client/schemas/card.ts @@ -1,31 +1,33 @@ import { z } from "@hono/zod-openapi"; import { Account } from "./account.ts"; -export const PreviewCardAuthor = z.object({ - name: z.string().openapi({ - description: "The original resource author’s name.", - example: "The Doubleclicks", - externalDocs: { - url: "https://docs.joinmastodon.org/entities/PreviewCardAuthor/#name", - }, - }), - url: z - .string() - .url() - .openapi({ - description: "A link to the author of the original resource.", - example: "https://www.youtube.com/user/thedoubleclicks", +export const PreviewCardAuthor = z + .object({ + name: z.string().openapi({ + description: "The original resource author’s name.", + example: "The Doubleclicks", externalDocs: { - url: "https://docs.joinmastodon.org/entities/PreviewCardAuthor/#url", + url: "https://docs.joinmastodon.org/entities/PreviewCardAuthor/#name", }, }), - account: Account.nullable().openapi({ - description: "The fediverse account of the author.", - externalDocs: { - url: "https://docs.joinmastodon.org/entities/PreviewCardAuthor/#account", - }, - }), -}); + url: z + .string() + .url() + .openapi({ + description: "A link to the author of the original resource.", + example: "https://www.youtube.com/user/thedoubleclicks", + externalDocs: { + url: "https://docs.joinmastodon.org/entities/PreviewCardAuthor/#url", + }, + }), + account: Account.nullable().openapi({ + description: "The fediverse account of the author.", + externalDocs: { + url: "https://docs.joinmastodon.org/entities/PreviewCardAuthor/#account", + }, + }), + }) + .openapi("PreviewCardAuthor"); export const PreviewCard = z .object({ @@ -150,7 +152,7 @@ export const PreviewCard = z }, }), }) - .openapi({ + .openapi("PreviewCard", { description: "Represents a rich preview card that is generated using OpenGraph tags from a URL.", externalDocs: { diff --git a/packages/client/schemas/common.ts b/packages/client/schemas/common.ts index 41bc0b7b..ceb54d56 100644 --- a/packages/client/schemas/common.ts +++ b/packages/client/schemas/common.ts @@ -3,7 +3,9 @@ import ISO6391 from "iso-639-1"; export const Id = z.string().uuid(); -export const iso631 = z.enum(ISO6391.getAllCodes() as [string, ...string[]]); +export const iso631 = z + .enum(ISO6391.getAllCodes() as [string, ...string[]]) + .openapi("ISO631"); export const zBoolean = z .string() diff --git a/packages/client/schemas/context.ts b/packages/client/schemas/context.ts index e29a4b85..16019f4d 100644 --- a/packages/client/schemas/context.ts +++ b/packages/client/schemas/context.ts @@ -16,7 +16,7 @@ export const Context = z }, }), }) - .openapi({ + .openapi("Context", { description: "Represents the tree around a given status. Used for reconstructing threads of statuses.", externalDocs: { diff --git a/packages/client/schemas/emoji.ts b/packages/client/schemas/emoji.ts index cc96a686..1ea67570 100644 --- a/packages/client/schemas/emoji.ts +++ b/packages/client/schemas/emoji.ts @@ -87,7 +87,7 @@ export const CustomEmoji = z }, }), }) - .openapi({ + .openapi("CustomEmoji", { description: "Represents a custom emoji.", externalDocs: { url: "https://docs.joinmastodon.org/entities/CustomEmoji", diff --git a/packages/client/schemas/extended-description.ts b/packages/client/schemas/extended-description.ts index 261dbf1e..87087475 100644 --- a/packages/client/schemas/extended-description.ts +++ b/packages/client/schemas/extended-description.ts @@ -22,7 +22,7 @@ export const ExtendedDescription = z }, }), }) - .openapi({ + .openapi("ExtendedDescription", { description: "Represents an extended description for the instance, to be shown on its about page.", externalDocs: { diff --git a/packages/client/schemas/familiar-followers.ts b/packages/client/schemas/familiar-followers.ts index d4de1c7b..6e8e442e 100644 --- a/packages/client/schemas/familiar-followers.ts +++ b/packages/client/schemas/familiar-followers.ts @@ -17,7 +17,7 @@ export const FamiliarFollowers = z }, }), }) - .openapi({ + .openapi("FamiliarFollowers", { description: "Represents a subset of your follows who also follow some other user.", externalDocs: { diff --git a/packages/client/schemas/filters.ts b/packages/client/schemas/filters.ts index edc75200..75055d27 100644 --- a/packages/client/schemas/filters.ts +++ b/packages/client/schemas/filters.ts @@ -18,7 +18,7 @@ export const FilterStatus = z }, }), }) - .openapi({ + .openapi("FilterStatus", { description: "Represents a status ID that, if matched, should cause the filter action to be taken.", externalDocs: { @@ -51,7 +51,7 @@ export const FilterKeyword = z }, }), }) - .openapi({ + .openapi("FilterKeyword", { description: "Represents a keyword that, if matched, should cause the filter action to be taken.", externalDocs: { @@ -133,7 +133,7 @@ export const Filter = z }, }), }) - .openapi({ + .openapi("Filter", { description: "Represents a user-defined filter for determining which statuses should not be shown to the user.", externalDocs: { @@ -171,7 +171,7 @@ export const FilterResult = z }, }), }) - .openapi({ + .openapi("FilterResult", { description: "Represents a filter whose keywords matched a given status.", externalDocs: { diff --git a/packages/client/schemas/instance-v1.ts b/packages/client/schemas/instance-v1.ts index 88906383..d00008e4 100644 --- a/packages/client/schemas/instance-v1.ts +++ b/packages/client/schemas/instance-v1.ts @@ -132,7 +132,7 @@ export const InstanceV1 = z /* Versia Server API extension */ sso: SSOConfig, }) - .openapi({ + .openapi("InstanceV1", { description: "Represents the software instance of Versia Server running on this domain.", externalDocs: { diff --git a/packages/client/schemas/instance.ts b/packages/client/schemas/instance.ts index a9d535c4..2e688524 100644 --- a/packages/client/schemas/instance.ts +++ b/packages/client/schemas/instance.ts @@ -18,7 +18,7 @@ const InstanceIcon = z example: "36x36", }), }) - .openapi({ + .openapi("InstanceIcon", { externalDocs: { url: "https://docs.joinmastodon.org/entities/InstanceIcon", }, @@ -375,7 +375,7 @@ export const Instance = z /* Versia Server API extension */ sso: SSOConfig, }) - .openapi({ + .openapi("Instance", { externalDocs: { url: "https://docs.joinmastodon.org/entities/Instance", }, diff --git a/packages/client/schemas/marker.ts b/packages/client/schemas/marker.ts index e9a6cdb7..b6fb269c 100644 --- a/packages/client/schemas/marker.ts +++ b/packages/client/schemas/marker.ts @@ -17,7 +17,7 @@ export const Marker = z example: "2025-01-12T13:11:00Z", }), }) - .openapi({ + .openapi("Marker", { description: "Represents the last read position within a user's timelines.", externalDocs: { diff --git a/packages/client/schemas/notification.ts b/packages/client/schemas/notification.ts index 0ff40935..e82e466d 100644 --- a/packages/client/schemas/notification.ts +++ b/packages/client/schemas/notification.ts @@ -62,7 +62,7 @@ export const Notification = z "Moderation warning that caused the notification. Attached when type of the notification is moderation_warning.", }), }) - .openapi({ + .openapi("Notification", { description: "Represents a notification of an event relevant to the user.", externalDocs: { diff --git a/packages/client/schemas/poll.ts b/packages/client/schemas/poll.ts index 92a0c35b..e4d126fd 100644 --- a/packages/client/schemas/poll.ts +++ b/packages/client/schemas/poll.ts @@ -31,7 +31,7 @@ export const PollOption = z }, }), }) - .openapi({ + .openapi("PollOption", { externalDocs: { url: "https://docs.joinmastodon.org/entities/Poll/#Option", }, @@ -130,7 +130,7 @@ export const Poll = z }, }), }) - .openapi({ + .openapi("Poll", { description: "Represents a poll attached to a status.", externalDocs: { url: "https://docs.joinmastodon.org/entities/Poll", diff --git a/packages/client/schemas/preferences.ts b/packages/client/schemas/preferences.ts index 65e104c1..e4c999f1 100644 --- a/packages/client/schemas/preferences.ts +++ b/packages/client/schemas/preferences.ts @@ -42,7 +42,7 @@ export const Preferences = z }, }), }) - .openapi({ + .openapi("Preferences", { description: "Represents a user's preferences.", externalDocs: { url: "https://docs.joinmastodon.org/entities/Preferences", diff --git a/packages/client/schemas/privacy-policy.ts b/packages/client/schemas/privacy-policy.ts index dee471ec..ffa92021 100644 --- a/packages/client/schemas/privacy-policy.ts +++ b/packages/client/schemas/privacy-policy.ts @@ -21,7 +21,7 @@ export const PrivacyPolicy = z }, }), }) - .openapi({ + .openapi("PrivacyPolicy", { description: "Represents the privacy policy of the instance.", externalDocs: { url: "https://docs.joinmastodon.org/entities/PrivacyPolicy", diff --git a/packages/client/schemas/pushsubscription.ts b/packages/client/schemas/pushsubscription.ts index 0e58643d..62cc35bc 100644 --- a/packages/client/schemas/pushsubscription.ts +++ b/packages/client/schemas/pushsubscription.ts @@ -80,7 +80,7 @@ export const WebPushSubscription = z description: "The streaming server’s VAPID key.", }), }) - .openapi({}); + .openapi("WebPushSubscription"); export const WebPushSubscriptionInput = z .object({ diff --git a/packages/client/schemas/relationship.ts b/packages/client/schemas/relationship.ts index b3570495..fa8486c0 100644 --- a/packages/client/schemas/relationship.ts +++ b/packages/client/schemas/relationship.ts @@ -65,7 +65,7 @@ export const Relationship = z example: "they also like Kerbal Space Program", }), }) - .openapi({ + .openapi("Relationship", { description: "Represents the relationship between accounts, such as following / blocking / muting / etc.", externalDocs: { diff --git a/packages/client/schemas/report.ts b/packages/client/schemas/report.ts index 4d4049ec..3ab7b1e2 100644 --- a/packages/client/schemas/report.ts +++ b/packages/client/schemas/report.ts @@ -50,7 +50,7 @@ export const Report = z description: "The account that was reported.", }), }) - .openapi({ + .openapi("Report", { description: "Reports filed against users and/or statuses, to be taken action on by moderators.", externalDocs: { diff --git a/packages/client/schemas/rule.ts b/packages/client/schemas/rule.ts index 0a8db603..030c0822 100644 --- a/packages/client/schemas/rule.ts +++ b/packages/client/schemas/rule.ts @@ -15,7 +15,7 @@ export const Rule = z example: "Please, we beg you.", }), }) - .openapi({ + .openapi("Rule", { description: "Represents a rule that server users should follow.", externalDocs: { url: "https://docs.joinmastodon.org/entities/Rule", diff --git a/packages/client/schemas/search.ts b/packages/client/schemas/search.ts index e3286479..f9e4200d 100644 --- a/packages/client/schemas/search.ts +++ b/packages/client/schemas/search.ts @@ -15,7 +15,7 @@ export const Search = z description: "Hashtags which match the given query", }), }) - .openapi({ + .openapi("Search", { description: "Represents the results of a search.", externalDocs: { url: "https://docs.joinmastodon.org/entities/Search", diff --git a/packages/client/schemas/status.ts b/packages/client/schemas/status.ts index 7aa4a7c1..0ac6575d 100644 --- a/packages/client/schemas/status.ts +++ b/packages/client/schemas/status.ts @@ -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 status’s 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: "

hello world

", - 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 status’s 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 status’s 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: "

hello world

", + 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 status’s 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"); diff --git a/packages/client/schemas/tag.ts b/packages/client/schemas/tag.ts index 4cc9eac6..8255fb65 100644 --- a/packages/client/schemas/tag.ts +++ b/packages/client/schemas/tag.ts @@ -24,7 +24,7 @@ export const Tag = z }, }), }) - .openapi({ + .openapi("Tag", { externalDocs: { url: "https://docs.joinmastodon.org/entities/Status/#Tag", }, diff --git a/packages/client/schemas/token.ts b/packages/client/schemas/token.ts index 11c942dd..4d718f15 100644 --- a/packages/client/schemas/token.ts +++ b/packages/client/schemas/token.ts @@ -20,7 +20,7 @@ export const Token = z example: 1573979017, }), }) - .openapi({ + .openapi("Token", { description: "Represents an OAuth token used for authenticating with the API and performing actions.", externalDocs: { diff --git a/packages/client/schemas/tos.ts b/packages/client/schemas/tos.ts index a47646a6..f8172b98 100644 --- a/packages/client/schemas/tos.ts +++ b/packages/client/schemas/tos.ts @@ -11,6 +11,6 @@ export const TermsOfService = z example: "

ToS

None, have fun.

", }), }) - .openapi({ + .openapi("TermsOfService", { description: "Represents the ToS of the instance.", }); diff --git a/packages/client/schemas/versia.ts b/packages/client/schemas/versia.ts index bf379f55..9aeb1624 100644 --- a/packages/client/schemas/versia.ts +++ b/packages/client/schemas/versia.ts @@ -45,7 +45,7 @@ export const Role = z example: "https://example.com/role-icon.png", }), }) - .openapi({ + .openapi("Role", { description: "Information about a role in the system, as well as its permissions.", }); @@ -72,7 +72,7 @@ export const NoteReaction = z example: true, }), }) - .openapi({ + .openapi("NoteReaction", { description: "Information about a reaction to a note.", }); @@ -134,6 +134,6 @@ export const Challenge = z example: "1234567890", }), }) - .openapi({ + .openapi("Challenge", { description: "A cryptographic challenge to solve. Used for Captchas.", });