refactor(api): ♻️ Make SDK and client package only use resources in their own package
Some checks failed
CodeQL Scan / Analyze (javascript-typescript) (push) Failing after 1s
Build Docker Images / lint (push) Failing after 7s
Build Docker Images / check (push) Failing after 6s
Build Docker Images / tests (push) Failing after 6s
Deploy Docs to GitHub Pages / build (push) Failing after 0s
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 / Deploy (push) Has been skipped
Mirror to Codeberg / Mirror (push) Failing after 0s
Nix Build / check (push) Failing after 1s
Test Publish / build (client) (push) Failing after 0s
Test Publish / build (sdk) (push) Failing after 0s

This commit is contained in:
Jesse Wierzbinski 2025-05-13 11:51:59 +02:00
parent c0060f1baf
commit 5dfcfc548f
No known key found for this signature in database
25 changed files with 256 additions and 260 deletions

View file

@ -52,17 +52,56 @@ export default apiRoute((app) =>
"json",
z
.object({
display_name: AccountSchema.shape.display_name.openapi({
description: "The display name to use for the profile.",
example: "Lexi",
}),
username: AccountSchema.shape.username.openapi({
description: "The username to use for the profile.",
example: "lexi",
}),
note: AccountSchema.shape.note.openapi({
description: "The account bio. Markdown is supported.",
}),
display_name: AccountSchema.shape.display_name
.openapi({
description:
"The display name to use for the profile.",
example: "Lexi",
})
.max(
config.validation.accounts
.max_displayname_characters,
)
.refine(
(s) =>
!config.validation.filters.displayname.some(
(filter) => filter.test(s),
),
"Display name contains blocked words",
),
username: AccountSchema.shape.username
.openapi({
description: "The username to use for the profile.",
example: "lexi",
})
.max(config.validation.accounts.max_username_characters)
.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",
),
note: AccountSchema.shape.note
.openapi({
description:
"The account bio. Markdown is supported.",
})
.max(config.validation.accounts.max_bio_characters)
.refine(
(s) =>
!config.validation.filters.bio.some((filter) =>
filter.test(s),
),
"Bio contains blocked words",
),
avatar: z
.string()
.url()
@ -150,10 +189,14 @@ export default apiRoute((app) =>
fields_attributes: z
.array(
z.object({
name: AccountSchema.shape.fields.element.shape
.name,
value: AccountSchema.shape.fields.element.shape
.value,
name: AccountSchema.shape.fields.element.shape.name.max(
config.validation.accounts
.max_field_name_characters,
),
value: AccountSchema.shape.fields.element.shape.value.max(
config.validation.accounts
.max_field_value_characters,
),
}),
)
.max(config.validation.accounts.max_field_count),

View file

@ -113,7 +113,9 @@ export default apiRoute((app) => {
"json",
z
.object({
shortcode: CustomEmojiSchema.shape.shortcode,
shortcode: CustomEmojiSchema.shape.shortcode.max(
config.validation.emojis.max_shortcode_characters,
),
element: z
.string()
.url()
@ -136,7 +138,12 @@ export default apiRoute((app) => {
),
),
category: CustomEmojiSchema.shape.category.optional(),
alt: CustomEmojiSchema.shape.description.optional(),
alt: CustomEmojiSchema.shape.description
.unwrap()
.max(
config.validation.emojis.max_description_characters,
)
.optional(),
global: CustomEmojiSchema.shape.global.default(false),
})
.partial(),

View file

@ -45,7 +45,9 @@ export default apiRoute((app) =>
validator(
"json",
z.object({
shortcode: CustomEmojiSchema.shape.shortcode,
shortcode: CustomEmojiSchema.shape.shortcode.max(
config.validation.emojis.max_shortcode_characters,
),
element: z
.string()
.url()
@ -68,7 +70,10 @@ export default apiRoute((app) =>
),
),
category: CustomEmojiSchema.shape.category.optional(),
alt: CustomEmojiSchema.shape.description.optional(),
alt: CustomEmojiSchema.shape.description
.unwrap()
.max(config.validation.emojis.max_description_characters)
.optional(),
global: CustomEmojiSchema.shape.global.default(false),
}),
handleZodError,

View file

@ -8,6 +8,7 @@ import { resolver, validator } from "hono-openapi/zod";
import { z } from "zod";
import { apiRoute, auth, handleZodError } from "@/api";
import { ApiError } from "~/classes/errors/api-error";
import { config } from "~/config";
export default apiRoute((app) => {
app.get(
@ -109,7 +110,10 @@ export default apiRoute((app) => {
description:
"The custom thumbnail of the media to be attached, encoded using multipart form data.",
}),
description: AttachmentSchema.shape.description,
description: AttachmentSchema.shape.description
.unwrap()
.max(config.validation.media.max_description_characters)
.optional(),
focus: z.string().openapi({
description:
"Two floating points (x,y), comma-delimited, ranging from -1.0 to 1.0. Used for media cropping on clients.",

View file

@ -8,6 +8,7 @@ import { resolver, validator } from "hono-openapi/zod";
import { z } from "zod";
import { apiRoute, auth, handleZodError } from "@/api";
import { ApiError } from "~/classes/errors/api-error";
import { config } from "~/config";
export default apiRoute((app) =>
app.post(
@ -67,7 +68,10 @@ export default apiRoute((app) =>
description:
"The custom thumbnail of the media to be attached, encoded using multipart form data.",
}),
description: AttachmentSchema.shape.description.optional(),
description: AttachmentSchema.shape.description
.unwrap()
.max(config.validation.media.max_description_characters)
.optional(),
focus: z
.string()
.optional()

View file

@ -25,10 +25,20 @@ import * as VersiaEntities from "~/packages/sdk/entities";
const schema = z
.object({
status: StatusSourceSchema.shape.text.optional().openapi({
description:
"The text content of the status. If media_ids is provided, this becomes optional. Attaching a poll is optional while status is provided.",
}),
status: StatusSourceSchema.shape.text
.max(config.validation.notes.max_characters)
.refine(
(s) =>
!config.validation.filters.note_content.some((filter) =>
filter.test(s),
),
"Status contains blocked words",
)
.optional()
.openapi({
description:
"The text content of the status. If media_ids is provided, this becomes optional. Attaching a poll is optional while status is provided.",
}),
/* Versia Server API Extension */
content_type: z
.enum(["text/plain", "text/html", "text/markdown"])
@ -54,7 +64,11 @@ const schema = z
}),
language: StatusSchema.shape.language.optional(),
"poll[options]": z
.array(PollOption.shape.title)
.array(
PollOption.shape.title.max(
config.validation.polls.max_option_characters,
),
)
.max(config.validation.polls.max_options)
.optional()
.openapi({

View file

@ -20,10 +20,20 @@ import * as VersiaEntities from "~/packages/sdk/entities";
const schema = z
.object({
status: StatusSourceSchema.shape.text.optional().openapi({
description:
"The text content of the status. If media_ids is provided, this becomes optional. Attaching a poll is optional while status is provided.",
}),
status: StatusSourceSchema.shape.text
.max(config.validation.notes.max_characters)
.refine(
(s) =>
!config.validation.filters.note_content.some((filter) =>
filter.test(s),
),
"Status contains blocked words",
)
.optional()
.openapi({
description:
"The text content of the status. If media_ids is provided, this becomes optional. Attaching a poll is optional while status is provided.",
}),
/* Versia Server API Extension */
content_type: z
.enum(["text/plain", "text/html", "text/markdown"])
@ -49,7 +59,11 @@ const schema = z
}),
language: StatusSchema.shape.language.optional(),
"poll[options]": z
.array(PollOption.shape.title)
.array(
PollOption.shape.title.max(
config.validation.polls.max_option_characters,
),
)
.max(config.validation.polls.max_options)
.optional()
.openapi({

View file

@ -8,6 +8,7 @@ import { resolver, validator } from "hono-openapi/zod";
import { z } from "zod";
import { apiRoute, auth, handleZodError } from "@/api";
import { ApiError } from "~/classes/errors/api-error";
import { config } from "~/config";
export default apiRoute((app) =>
app.post(
@ -76,7 +77,10 @@ export default apiRoute((app) =>
description:
"The custom thumbnail of the media to be attached, encoded using multipart form data.",
}),
description: AttachmentSchema.shape.description.optional(),
description: AttachmentSchema.shape.description
.unwrap()
.max(config.validation.media.max_description_characters)
.optional(),
focus: z
.string()
.optional()

View file

@ -3,6 +3,7 @@ import {
Id,
RolePermission,
Search as SearchSchema,
userAddressRegex,
zBoolean,
} from "@versia/client/schemas";
import { db, Note, User } from "@versia/kit/db";
@ -11,13 +12,7 @@ import { and, eq, inArray, isNull, sql } from "drizzle-orm";
import { describeRoute } from "hono-openapi";
import { resolver, validator } from "hono-openapi/zod";
import { z } from "zod";
import {
apiRoute,
auth,
handleZodError,
parseUserAddress,
userAddressValidator,
} from "@/api";
import { apiRoute, auth, handleZodError, parseUserAddress } from "@/api";
import { ApiError } from "~/classes/errors/api-error";
import { searchManager } from "~/classes/search/search-manager";
import { config } from "~/config.ts";
@ -151,7 +146,7 @@ export default apiRoute((app) =>
if (!type || type === "accounts") {
// Check if q is matching format username@domain.com or @username@domain.com
const accountMatches = q?.trim().match(userAddressValidator);
const accountMatches = q?.trim().match(userAddressRegex);
if (accountMatches) {
// Remove leading @ if it exists
if (accountMatches[0].startsWith("@")) {