mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 08:28:19 +01:00
feat(api): 🏷️ Port Role and CustomEmoji OpenAPI schemas
Some checks failed
Mirror to Codeberg / Mirror (push) Failing after 1s
Some checks failed
Mirror to Codeberg / Mirror (push) Failing after 1s
This commit is contained in:
parent
7c622730dc
commit
264e2fe8ac
|
|
@ -1,6 +1,7 @@
|
||||||
import { apiRoute, auth, withUserParam } from "@/api";
|
import { apiRoute, auth, withUserParam } from "@/api";
|
||||||
import { createRoute, z } from "@hono/zod-openapi";
|
import { createRoute, z } from "@hono/zod-openapi";
|
||||||
import { Role } from "@versia/kit/db";
|
import { Role } from "@versia/kit/db";
|
||||||
|
import { Role as RoleSchema } from "~/classes/schemas/versia.ts";
|
||||||
|
|
||||||
const route = createRoute({
|
const route = createRoute({
|
||||||
method: "get",
|
method: "get",
|
||||||
|
|
@ -22,7 +23,7 @@ const route = createRoute({
|
||||||
description: "List of roles",
|
description: "List of roles",
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
schema: z.array(Role.schema),
|
schema: z.array(RoleSchema),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { createRoute, z } from "@hono/zod-openapi";
|
||||||
import { Emoji } from "@versia/kit/db";
|
import { Emoji } from "@versia/kit/db";
|
||||||
import { Emojis, RolePermissions } from "@versia/kit/tables";
|
import { Emojis, RolePermissions } from "@versia/kit/tables";
|
||||||
import { and, eq, isNull, or } from "drizzle-orm";
|
import { and, eq, isNull, or } from "drizzle-orm";
|
||||||
|
import { CustomEmoji } from "~/classes/schemas/emoji";
|
||||||
|
|
||||||
const route = createRoute({
|
const route = createRoute({
|
||||||
method: "get",
|
method: "get",
|
||||||
|
|
@ -20,7 +21,7 @@ const route = createRoute({
|
||||||
description: "Emojis",
|
description: "Emojis",
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
schema: z.array(Emoji.schema),
|
schema: z.array(CustomEmoji),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { createRoute, z } from "@hono/zod-openapi";
|
||||||
import { Emoji } from "@versia/kit/db";
|
import { Emoji } from "@versia/kit/db";
|
||||||
import { RolePermissions } from "@versia/kit/tables";
|
import { RolePermissions } from "@versia/kit/tables";
|
||||||
import { ApiError } from "~/classes/errors/api-error";
|
import { ApiError } from "~/classes/errors/api-error";
|
||||||
|
import { CustomEmoji } from "~/classes/schemas/emoji";
|
||||||
import { config } from "~/packages/config-manager";
|
import { config } from "~/packages/config-manager";
|
||||||
import { ErrorSchema } from "~/types/api";
|
import { ErrorSchema } from "~/types/api";
|
||||||
|
|
||||||
|
|
@ -69,7 +70,7 @@ const routeGet = createRoute({
|
||||||
description: "Emoji",
|
description: "Emoji",
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
schema: Emoji.schema,
|
schema: CustomEmoji,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -120,7 +121,7 @@ const routePatch = createRoute({
|
||||||
description: "Emoji modified",
|
description: "Emoji modified",
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
schema: Emoji.schema,
|
schema: CustomEmoji,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { Emoji, Media } from "@versia/kit/db";
|
||||||
import { Emojis, RolePermissions } from "@versia/kit/tables";
|
import { Emojis, RolePermissions } from "@versia/kit/tables";
|
||||||
import { and, eq, isNull, or } from "drizzle-orm";
|
import { and, eq, isNull, or } from "drizzle-orm";
|
||||||
import { ApiError } from "~/classes/errors/api-error";
|
import { ApiError } from "~/classes/errors/api-error";
|
||||||
|
import { CustomEmoji } from "~/classes/schemas/emoji";
|
||||||
import { config } from "~/packages/config-manager";
|
import { config } from "~/packages/config-manager";
|
||||||
import { ErrorSchema } from "~/types/api";
|
import { ErrorSchema } from "~/types/api";
|
||||||
|
|
||||||
|
|
@ -82,7 +83,7 @@ const route = createRoute({
|
||||||
description: "Uploaded emoji",
|
description: "Uploaded emoji",
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
schema: Emoji.schema,
|
schema: CustomEmoji,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { createRoute, z } from "@hono/zod-openapi";
|
||||||
import { Role } from "@versia/kit/db";
|
import { Role } from "@versia/kit/db";
|
||||||
import { RolePermissions } from "@versia/kit/tables";
|
import { RolePermissions } from "@versia/kit/tables";
|
||||||
import { ApiError } from "~/classes/errors/api-error";
|
import { ApiError } from "~/classes/errors/api-error";
|
||||||
|
import { Role as RoleSchema } from "~/classes/schemas/versia.ts";
|
||||||
import { ErrorSchema } from "~/types/api";
|
import { ErrorSchema } from "~/types/api";
|
||||||
|
|
||||||
const routeGet = createRoute({
|
const routeGet = createRoute({
|
||||||
|
|
@ -24,7 +25,7 @@ const routeGet = createRoute({
|
||||||
description: "Role",
|
description: "Role",
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
schema: Role.schema,
|
schema: RoleSchema,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -57,7 +58,7 @@ const routePatch = createRoute({
|
||||||
body: {
|
body: {
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
schema: Role.schema.partial(),
|
schema: RoleSchema.omit({ id: true }).partial(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { apiRoute, auth } from "@/api";
|
||||||
import { createRoute, z } from "@hono/zod-openapi";
|
import { createRoute, z } from "@hono/zod-openapi";
|
||||||
import { Role } from "@versia/kit/db";
|
import { Role } from "@versia/kit/db";
|
||||||
import { ApiError } from "~/classes/errors/api-error";
|
import { ApiError } from "~/classes/errors/api-error";
|
||||||
|
import { Role as RoleSchema } from "~/classes/schemas/versia.ts";
|
||||||
import { RolePermissions } from "~/drizzle/schema";
|
import { RolePermissions } from "~/drizzle/schema";
|
||||||
import { ErrorSchema } from "~/types/api";
|
import { ErrorSchema } from "~/types/api";
|
||||||
|
|
||||||
|
|
@ -19,7 +20,7 @@ const routeGet = createRoute({
|
||||||
description: "List of all roles",
|
description: "List of all roles",
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
schema: z.array(Role.schema),
|
schema: z.array(RoleSchema),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -40,7 +41,7 @@ const routePost = createRoute({
|
||||||
body: {
|
body: {
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
schema: Role.schema.omit({ id: true }),
|
schema: RoleSchema.omit({ id: true }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -50,7 +51,7 @@ const routePost = createRoute({
|
||||||
description: "Role created",
|
description: "Role created",
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
schema: Role.schema,
|
schema: RoleSchema,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
162
bun.lock
162
bun.lock
|
|
@ -4,90 +4,90 @@
|
||||||
"": {
|
"": {
|
||||||
"name": "versia-server",
|
"name": "versia-server",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bull-board/api": "latest",
|
"@bull-board/api": "^6.7.4",
|
||||||
"@bull-board/hono": "latest",
|
"@bull-board/hono": "^6.7.4",
|
||||||
"@hackmd/markdown-it-task-lists": "latest",
|
"@hackmd/markdown-it-task-lists": "^2.1.4",
|
||||||
"@hono/prometheus": "latest",
|
"@hono/prometheus": "^1.0.1",
|
||||||
"@hono/swagger-ui": "latest",
|
"@hono/swagger-ui": "^0.5.0",
|
||||||
"@hono/zod-openapi": "latest",
|
"@hono/zod-openapi": "0.18.3",
|
||||||
"@hono/zod-validator": "latest",
|
"@hono/zod-validator": "^0.4.2",
|
||||||
"@inquirer/confirm": "latest",
|
"@inquirer/confirm": "^5.1.4",
|
||||||
"@inquirer/input": "latest",
|
"@inquirer/input": "^4.1.4",
|
||||||
"@json2csv/plainjs": "latest",
|
"@json2csv/plainjs": "^7.0.6",
|
||||||
"@logtape/logtape": "npm:@jsr/logtape__logtape@latest",
|
"@logtape/logtape": "npm:@jsr/logtape__logtape@0.9.0-dev.114+327c9473",
|
||||||
"@oclif/core": "latest",
|
"@oclif/core": "^4.2.5",
|
||||||
"@sentry/bun": "latest",
|
"@sentry/bun": "^8.53.0",
|
||||||
"@tufjs/canonical-json": "latest",
|
"@tufjs/canonical-json": "^2.0.0",
|
||||||
"@versia/client": "latest",
|
"@versia/client": "^0.1.5",
|
||||||
"@versia/federation": "latest",
|
"@versia/federation": "^0.1.4",
|
||||||
"@versia/kit": "workspace:*",
|
"@versia/kit": "workspace:*",
|
||||||
"altcha-lib": "latest",
|
"altcha-lib": "^1.2.0",
|
||||||
"blurhash": "latest",
|
"blurhash": "^2.0.5",
|
||||||
"bullmq": "latest",
|
"bullmq": "^5.39.1",
|
||||||
"c12": "latest",
|
"c12": "^2.0.1",
|
||||||
"chalk": "latest",
|
"chalk": "^5.4.1",
|
||||||
"cli-progress": "latest",
|
"cli-progress": "^3.12.0",
|
||||||
"cli-table": "latest",
|
"cli-table": "^0.3.11",
|
||||||
"confbox": "latest",
|
"confbox": "^0.1.8",
|
||||||
"drizzle-orm": "latest",
|
"drizzle-orm": "^0.39.1",
|
||||||
"extract-zip": "latest",
|
"extract-zip": "^2.0.1",
|
||||||
"hono": "latest",
|
"hono": "^4.6.20",
|
||||||
"html-to-text": "latest",
|
"html-to-text": "^9.0.5",
|
||||||
"ioredis": "latest",
|
"ioredis": "^5.4.2",
|
||||||
"ip-matching": "latest",
|
"ip-matching": "^2.1.2",
|
||||||
"iso-639-1": "latest",
|
"iso-639-1": "^3.1.4",
|
||||||
"jose": "latest",
|
"jose": "^5.9.6",
|
||||||
"linkify-html": "latest",
|
"linkify-html": "^4.2.0",
|
||||||
"linkify-string": "latest",
|
"linkify-string": "^4.2.0",
|
||||||
"linkifyjs": "latest",
|
"linkifyjs": "^4.2.0",
|
||||||
"magic-regexp": "latest",
|
"magic-regexp": "^0.8.0",
|
||||||
"markdown-it": "latest",
|
"markdown-it": "^14.1.0",
|
||||||
"markdown-it-anchor": "latest",
|
"markdown-it-anchor": "^9.2.0",
|
||||||
"markdown-it-container": "latest",
|
"markdown-it-container": "^4.0.0",
|
||||||
"markdown-it-toc-done-right": "latest",
|
"markdown-it-toc-done-right": "^4.2.0",
|
||||||
"mime-types": "latest",
|
"mime-types": "^2.1.35",
|
||||||
"mitata": "latest",
|
"mitata": "^1.0.33",
|
||||||
"oauth4webapi": "latest",
|
"oauth4webapi": "^3.1.4",
|
||||||
"ora": "latest",
|
"ora": "^8.1.1",
|
||||||
"pg": "latest",
|
"pg": "^8.13.1",
|
||||||
"prom-client": "latest",
|
"prom-client": "^15.1.3",
|
||||||
"qs": "latest",
|
"qs": "^6.14.0",
|
||||||
"sharp": "latest",
|
"sharp": "^0.33.5",
|
||||||
"sonic-channel": "latest",
|
"sonic-channel": "^1.3.1",
|
||||||
"string-comparison": "latest",
|
"string-comparison": "^1.3.0",
|
||||||
"stringify-entities": "latest",
|
"stringify-entities": "^4.0.4",
|
||||||
"strip-ansi": "latest",
|
"strip-ansi": "^7.1.0",
|
||||||
"table": "latest",
|
"table": "^6.9.0",
|
||||||
"unzipit": "latest",
|
"unzipit": "^1.4.3",
|
||||||
"uqr": "latest",
|
"uqr": "^0.1.2",
|
||||||
"web-push": "latest",
|
"web-push": "^3.6.7",
|
||||||
"xss": "latest",
|
"xss": "^1.0.15",
|
||||||
"zod": "latest",
|
"zod": "^3.24.1",
|
||||||
"zod-validation-error": "latest",
|
"zod-validation-error": "^3.4.0",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "latest",
|
"@biomejs/biome": "^1.9.4",
|
||||||
"@types/bun": "latest",
|
"@types/bun": "^1.2.2",
|
||||||
"@types/cli-progress": "latest",
|
"@types/cli-progress": "^3.11.6",
|
||||||
"@types/cli-table": "latest",
|
"@types/cli-table": "^0.3.4",
|
||||||
"@types/html-to-text": "latest",
|
"@types/html-to-text": "^9.0.4",
|
||||||
"@types/jsonld": "latest",
|
"@types/jsonld": "^1.5.15",
|
||||||
"@types/markdown-it-container": "latest",
|
"@types/markdown-it-container": "^2.0.10",
|
||||||
"@types/mime-types": "latest",
|
"@types/mime-types": "^2.1.4",
|
||||||
"@types/pg": "latest",
|
"@types/pg": "^8.11.11",
|
||||||
"@types/qs": "latest",
|
"@types/qs": "^6.9.18",
|
||||||
"@types/web-push": "latest",
|
"@types/web-push": "^3.6.4",
|
||||||
"drizzle-kit": "latest",
|
"drizzle-kit": "^0.30.4",
|
||||||
"markdown-it-image-figures": "latest",
|
"markdown-it-image-figures": "^2.1.1",
|
||||||
"markdown-it-mathjax3": "latest",
|
"markdown-it-mathjax3": "^4.3.2",
|
||||||
"oclif": "latest",
|
"oclif": "^4.17.21",
|
||||||
"ts-prune": "latest",
|
"ts-prune": "^0.10.3",
|
||||||
"typescript": "latest",
|
"typescript": "^5.7.3",
|
||||||
"vitepress": "latest",
|
"vitepress": "^1.6.3",
|
||||||
"vitepress-plugin-tabs": "latest",
|
"vitepress-plugin-tabs": "^0.5.0",
|
||||||
"vitepress-sidebar": "latest",
|
"vitepress-sidebar": "^1.30.2",
|
||||||
"vue": "latest",
|
"vue": "^3.5.13",
|
||||||
"zod-to-json-schema": "latest",
|
"zod-to-json-schema": "^3.24.1",
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "^5.7.2",
|
"typescript": "^5.7.2",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { emojiValidatorWithColons, emojiValidatorWithIdentifiers } from "@/api";
|
import { emojiValidatorWithColons, emojiValidatorWithIdentifiers } from "@/api";
|
||||||
import { proxyUrl } from "@/response";
|
import { proxyUrl } from "@/response";
|
||||||
import { z } from "@hono/zod-openapi";
|
import type { z } from "@hono/zod-openapi";
|
||||||
import type { Emoji as APIEmoji } from "@versia/client/types";
|
|
||||||
import type { CustomEmojiExtension } from "@versia/federation/types";
|
import type { CustomEmojiExtension } from "@versia/federation/types";
|
||||||
import { type Instance, Media, db } from "@versia/kit/db";
|
import { type Instance, Media, db } from "@versia/kit/db";
|
||||||
import { Emojis, type Instances, type Medias } from "@versia/kit/tables";
|
import { Emojis, type Instances, type Medias } from "@versia/kit/tables";
|
||||||
|
|
@ -15,6 +14,7 @@ import {
|
||||||
inArray,
|
inArray,
|
||||||
isNull,
|
isNull,
|
||||||
} from "drizzle-orm";
|
} from "drizzle-orm";
|
||||||
|
import type { CustomEmoji } from "../schemas/emoji.ts";
|
||||||
import { BaseInterface } from "./base.ts";
|
import { BaseInterface } from "./base.ts";
|
||||||
|
|
||||||
type EmojiType = InferSelectModel<typeof Emojis> & {
|
type EmojiType = InferSelectModel<typeof Emojis> & {
|
||||||
|
|
@ -23,16 +23,6 @@ type EmojiType = InferSelectModel<typeof Emojis> & {
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Emoji extends BaseInterface<typeof Emojis, EmojiType> {
|
export class Emoji extends BaseInterface<typeof Emojis, EmojiType> {
|
||||||
public static schema = z.object({
|
|
||||||
id: z.string(),
|
|
||||||
shortcode: z.string(),
|
|
||||||
url: z.string(),
|
|
||||||
visible_in_picker: z.boolean(),
|
|
||||||
category: z.string().optional(),
|
|
||||||
static_url: z.string(),
|
|
||||||
global: z.boolean(),
|
|
||||||
});
|
|
||||||
|
|
||||||
public static $type: EmojiType;
|
public static $type: EmojiType;
|
||||||
public media: Media;
|
public media: Media;
|
||||||
|
|
||||||
|
|
@ -184,18 +174,18 @@ export class Emoji extends BaseInterface<typeof Emojis, EmojiType> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public toApi(): APIEmoji {
|
public toApi(): z.infer<typeof CustomEmoji> {
|
||||||
return {
|
return {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
shortcode: this.data.shortcode,
|
shortcode: this.data.shortcode,
|
||||||
static_url: proxyUrl(this.media.getUrl()).toString(),
|
static_url: proxyUrl(this.media.getUrl()).toString(),
|
||||||
url: proxyUrl(this.media.getUrl()).toString(),
|
url: proxyUrl(this.media.getUrl()).toString(),
|
||||||
visible_in_picker: this.data.visibleInPicker,
|
visible_in_picker: this.data.visibleInPicker,
|
||||||
category: this.data.category ?? undefined,
|
category: this.data.category,
|
||||||
global: this.data.ownerId === null,
|
global: this.data.ownerId === null,
|
||||||
description:
|
description:
|
||||||
this.media.data.content[this.media.getPreferredMimeType()]
|
this.media.data.content[this.media.getPreferredMimeType()]
|
||||||
.description ?? undefined,
|
.description ?? null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import {
|
||||||
inArray,
|
inArray,
|
||||||
} from "drizzle-orm";
|
} from "drizzle-orm";
|
||||||
import { config } from "~/packages/config-manager/index.ts";
|
import { config } from "~/packages/config-manager/index.ts";
|
||||||
|
import { CustomEmoji } from "../schemas/emoji.ts";
|
||||||
import { BaseInterface } from "./base.ts";
|
import { BaseInterface } from "./base.ts";
|
||||||
|
|
||||||
type ReactionType = InferSelectModel<typeof Reactions> & {
|
type ReactionType = InferSelectModel<typeof Reactions> & {
|
||||||
|
|
@ -30,7 +31,7 @@ export class Reaction extends BaseInterface<typeof Reactions, ReactionType> {
|
||||||
public static schema: z.ZodType<APIReaction> = z.object({
|
public static schema: z.ZodType<APIReaction> = z.object({
|
||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
author_id: z.string().uuid(),
|
author_id: z.string().uuid(),
|
||||||
emoji: z.lazy(() => Emoji.schema),
|
emoji: CustomEmoji,
|
||||||
});
|
});
|
||||||
|
|
||||||
public static $type: ReactionType;
|
public static $type: ReactionType;
|
||||||
|
|
@ -169,16 +170,6 @@ export class Reaction extends BaseInterface<typeof Reactions, ReactionType> {
|
||||||
return this.data.id;
|
return this.data.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public toApi(): APIReaction {
|
|
||||||
return {
|
|
||||||
id: this.data.id,
|
|
||||||
author_id: this.data.authorId,
|
|
||||||
emoji: this.hasCustomEmoji()
|
|
||||||
? new Emoji(this.data.emoji as typeof Emoji.$type).toApi()
|
|
||||||
: this.data.emojiText || "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public getUri(baseUrl: URL): URL {
|
public getUri(baseUrl: URL): URL {
|
||||||
return this.data.uri
|
return this.data.uri
|
||||||
? new URL(this.data.uri)
|
? new URL(this.data.uri)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { proxyUrl } from "@/response";
|
import { proxyUrl } from "@/response";
|
||||||
import { z } from "@hono/zod-openapi";
|
import type {
|
||||||
import {
|
VersiaRole as APIRole,
|
||||||
type VersiaRole as APIRole,
|
|
||||||
RolePermission,
|
RolePermission,
|
||||||
} from "@versia/client/types";
|
} from "@versia/client/types";
|
||||||
import { db } from "@versia/kit/db";
|
import { db } from "@versia/kit/db";
|
||||||
|
|
@ -21,16 +20,6 @@ import { BaseInterface } from "./base.ts";
|
||||||
type RoleType = InferSelectModel<typeof Roles>;
|
type RoleType = InferSelectModel<typeof Roles>;
|
||||||
|
|
||||||
export class Role extends BaseInterface<typeof Roles> {
|
export class Role extends BaseInterface<typeof Roles> {
|
||||||
public static schema = z.object({
|
|
||||||
id: z.string().uuid(),
|
|
||||||
name: z.string().min(1).max(128),
|
|
||||||
permissions: z.array(z.nativeEnum(RolePermission)).default([]),
|
|
||||||
priority: z.number().int().default(0),
|
|
||||||
description: z.string().min(0).max(1024).optional(),
|
|
||||||
visible: z.boolean().default(true),
|
|
||||||
icon: z.string().url().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
public static $type: RoleType;
|
public static $type: RoleType;
|
||||||
|
|
||||||
public async reload(): Promise<void> {
|
public async reload(): Promise<void> {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { idValidator } from "@/api";
|
import { idValidator } from "@/api";
|
||||||
import { getBestContentType, urlToContentFormat } from "@/content_types";
|
import { getBestContentType } from "@/content_types";
|
||||||
import { randomString } from "@/math";
|
import { randomString } from "@/math";
|
||||||
import { proxyUrl } from "@/response";
|
import { proxyUrl } from "@/response";
|
||||||
import { sentry } from "@/sentry";
|
import { sentry } from "@/sentry";
|
||||||
|
|
@ -52,7 +52,7 @@ import { type Config, config } from "~/packages/config-manager";
|
||||||
import type { KnownEntity } from "~/types/api.ts";
|
import type { KnownEntity } from "~/types/api.ts";
|
||||||
import { DeliveryJobType, deliveryQueue } from "../queues/delivery.ts";
|
import { DeliveryJobType, deliveryQueue } from "../queues/delivery.ts";
|
||||||
import { PushJobType, pushQueue } from "../queues/push.ts";
|
import { PushJobType, pushQueue } from "../queues/push.ts";
|
||||||
import type { Account } from "../schemas/account.ts";
|
import type { Account, Source } from "../schemas/account.ts";
|
||||||
import { BaseInterface } from "./base.ts";
|
import { BaseInterface } from "./base.ts";
|
||||||
import { Emoji } from "./emoji.ts";
|
import { Emoji } from "./emoji.ts";
|
||||||
import { Instance } from "./instance.ts";
|
import { Instance } from "./instance.ts";
|
||||||
|
|
@ -686,12 +686,12 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
||||||
note: getBestContentType(user.bio).content,
|
note: getBestContentType(user.bio).content,
|
||||||
publicKey: user.public_key.key,
|
publicKey: user.public_key.key,
|
||||||
source: {
|
source: {
|
||||||
language: null,
|
language: "en",
|
||||||
note: "",
|
note: "",
|
||||||
privacy: "public",
|
privacy: "public",
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
fields: [],
|
fields: [],
|
||||||
},
|
} as z.infer<typeof Source>,
|
||||||
};
|
};
|
||||||
|
|
||||||
const userEmojis =
|
const userEmojis =
|
||||||
|
|
@ -888,12 +888,12 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
||||||
privateKey: keys.private_key,
|
privateKey: keys.private_key,
|
||||||
updatedAt: new Date().toISOString(),
|
updatedAt: new Date().toISOString(),
|
||||||
source: {
|
source: {
|
||||||
language: null,
|
language: "en",
|
||||||
note: "",
|
note: "",
|
||||||
privacy: "public",
|
privacy: "public",
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
fields: [],
|
fields: [],
|
||||||
},
|
} as z.infer<typeof Source>,
|
||||||
})
|
})
|
||||||
.returning()
|
.returning()
|
||||||
)[0];
|
)[0];
|
||||||
|
|
@ -1107,6 +1107,7 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
||||||
|
|
||||||
public toApi(isOwnAccount = false): z.infer<typeof Account> {
|
public toApi(isOwnAccount = false): z.infer<typeof Account> {
|
||||||
const user = this.data;
|
const user = this.data;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
username: user.username,
|
username: user.username,
|
||||||
|
|
@ -1177,6 +1178,8 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
||||||
)
|
)
|
||||||
.map((r) => r.toApi()),
|
.map((r) => r.toApi()),
|
||||||
group: false,
|
group: false,
|
||||||
|
// TODO
|
||||||
|
last_status_at: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1235,17 +1238,8 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
||||||
indexable: false,
|
indexable: false,
|
||||||
username: user.username,
|
username: user.username,
|
||||||
manually_approves_followers: this.data.isLocked,
|
manually_approves_followers: this.data.isLocked,
|
||||||
avatar:
|
avatar: this.avatar?.toVersia(),
|
||||||
urlToContentFormat(
|
header: this.header?.toVersia(),
|
||||||
this.getAvatarUrl(config),
|
|
||||||
this.data.source.avatar?.content_type,
|
|
||||||
) ?? undefined,
|
|
||||||
header: this.getHeaderUrl(config)
|
|
||||||
? (urlToContentFormat(
|
|
||||||
this.getHeaderUrl(config) as URL,
|
|
||||||
this.data.source.header?.content_type,
|
|
||||||
) ?? undefined)
|
|
||||||
: undefined,
|
|
||||||
display_name: user.displayName,
|
display_name: user.displayName,
|
||||||
fields: user.fields,
|
fields: user.fields,
|
||||||
public_key: {
|
public_key: {
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ import type { Account as ApiAccount } from "@versia/client/types";
|
||||||
import ISO6391 from "iso-639-1";
|
import ISO6391 from "iso-639-1";
|
||||||
import { config } from "~/packages/config-manager";
|
import { config } from "~/packages/config-manager";
|
||||||
import { zBoolean } from "~/packages/config-manager/config.type";
|
import { zBoolean } from "~/packages/config-manager/config.type";
|
||||||
import { Emoji } from "../database/emoji.ts";
|
import { CustomEmoji } from "./emoji.ts";
|
||||||
import { Role } from "../database/role.ts";
|
import { Role } from "./versia.ts";
|
||||||
|
|
||||||
export const Field = z.object({
|
export const Field = z.object({
|
||||||
name: z
|
name: z
|
||||||
|
|
@ -117,7 +117,7 @@ export const Account = z.object({
|
||||||
.string()
|
.string()
|
||||||
.uuid()
|
.uuid()
|
||||||
.openapi({
|
.openapi({
|
||||||
description: "The account id.",
|
description: "The account ID in the database.",
|
||||||
example: "9e84842b-4db6-4a9b-969d-46ab408278da",
|
example: "9e84842b-4db6-4a9b-969d-46ab408278da",
|
||||||
externalDocs: {
|
externalDocs: {
|
||||||
url: "https://docs.joinmastodon.org/entities/Account/#id",
|
url: "https://docs.joinmastodon.org/entities/Account/#id",
|
||||||
|
|
@ -260,7 +260,7 @@ export const Account = z.object({
|
||||||
url: "https://docs.joinmastodon.org/entities/Account/#fields",
|
url: "https://docs.joinmastodon.org/entities/Account/#fields",
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
emojis: z.array(Emoji.schema).openapi({
|
emojis: z.array(CustomEmoji).openapi({
|
||||||
description:
|
description:
|
||||||
"Custom emoji entities to be used when rendering the profile.",
|
"Custom emoji entities to be used when rendering the profile.",
|
||||||
externalDocs: {
|
externalDocs: {
|
||||||
|
|
@ -384,6 +384,7 @@ export const Account = z.object({
|
||||||
url: "https://docs.joinmastodon.org/entities/Account/#following_count",
|
url: "https://docs.joinmastodon.org/entities/Account/#following_count",
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
/* Versia Server API extension */
|
||||||
uri: z.string().url().openapi({
|
uri: z.string().url().openapi({
|
||||||
description:
|
description:
|
||||||
"The location of the user's Versia profile page, as opposed to the local representation.",
|
"The location of the user's Versia profile page, as opposed to the local representation.",
|
||||||
|
|
@ -396,7 +397,10 @@ export const Account = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
roles: z.array(Role.schema),
|
/* Versia Server API extension */
|
||||||
|
roles: z.array(Role).openapi({
|
||||||
|
description: "Roles assigned to the account.",
|
||||||
|
}),
|
||||||
mute_expires_at: z.string().datetime().nullable().openapi({
|
mute_expires_at: z.string().datetime().nullable().openapi({
|
||||||
description: "When a timed mute will expire, if applicable.",
|
description: "When a timed mute will expire, if applicable.",
|
||||||
example: "2025-03-01T14:00:00.000Z",
|
example: "2025-03-01T14:00:00.000Z",
|
||||||
|
|
|
||||||
25
classes/schemas/context.ts
Normal file
25
classes/schemas/context.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { z } from "@hono/zod-openapi";
|
||||||
|
import { Status } from "./status.ts";
|
||||||
|
|
||||||
|
export const Context = z
|
||||||
|
.object({
|
||||||
|
ancestors: z.array(Status).openapi({
|
||||||
|
description: "Parents in the thread.",
|
||||||
|
externalDocs: {
|
||||||
|
url: "https://docs.joinmastodon.org/entities/Context/#ancestors",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
descendants: z.array(Status).openapi({
|
||||||
|
description: "Children in the thread.",
|
||||||
|
externalDocs: {
|
||||||
|
url: "https://docs.joinmastodon.org/entities/Context/#descendants",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.openapi({
|
||||||
|
description:
|
||||||
|
"Represents the tree around a given status. Used for reconstructing threads of statuses.",
|
||||||
|
externalDocs: {
|
||||||
|
url: "https://docs.joinmastodon.org/entities/Context/#context",
|
||||||
|
},
|
||||||
|
});
|
||||||
82
classes/schemas/emoji.ts
Normal file
82
classes/schemas/emoji.ts
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
import { z } from "@hono/zod-openapi";
|
||||||
|
import { zBoolean } from "~/packages/config-manager/config.type";
|
||||||
|
import { Id } from "./common.ts";
|
||||||
|
|
||||||
|
export const CustomEmoji = z
|
||||||
|
.object({
|
||||||
|
/* Versia Server API extension */
|
||||||
|
id: Id.openapi({
|
||||||
|
description: "ID of the custom emoji in the database.",
|
||||||
|
example: "af9ccd29-c689-477f-aa27-d7d95fd8fb05",
|
||||||
|
}),
|
||||||
|
shortcode: z.string().openapi({
|
||||||
|
description: "The name of the custom emoji.",
|
||||||
|
example: "blobaww",
|
||||||
|
externalDocs: {
|
||||||
|
url: "https://docs.joinmastodon.org/entities/CustomEmoji/#shortcode",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
url: z
|
||||||
|
.string()
|
||||||
|
.url()
|
||||||
|
.openapi({
|
||||||
|
description: "A link to the custom emoji.",
|
||||||
|
example:
|
||||||
|
"https://cdn.versia.social/emojis/images/000/011/739/original/blobaww.png",
|
||||||
|
externalDocs: {
|
||||||
|
url: "https://docs.joinmastodon.org/entities/CustomEmoji/#url",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
static_url: z
|
||||||
|
.string()
|
||||||
|
.url()
|
||||||
|
.openapi({
|
||||||
|
description: "A link to a static copy of the custom emoji.",
|
||||||
|
example:
|
||||||
|
"https://cdn.versia.social/emojis/images/000/011/739/static/blobaww.png",
|
||||||
|
externalDocs: {
|
||||||
|
url: "https://docs.joinmastodon.org/entities/CustomEmoji/#static_url",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
visible_in_picker: z.boolean().openapi({
|
||||||
|
description:
|
||||||
|
"Whether this Emoji should be visible in the picker or unlisted.",
|
||||||
|
example: true,
|
||||||
|
externalDocs: {
|
||||||
|
url: "https://docs.joinmastodon.org/entities/CustomEmoji/#visible_in_picker",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
category: z
|
||||||
|
.string()
|
||||||
|
.nullable()
|
||||||
|
.openapi({
|
||||||
|
description: "Used for sorting custom emoji in the picker.",
|
||||||
|
example: "Blobs",
|
||||||
|
externalDocs: {
|
||||||
|
url: "https://docs.joinmastodon.org/entities/CustomEmoji/#category",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
/* Versia Server API extension */
|
||||||
|
global: zBoolean.openapi({
|
||||||
|
description: "Whether this emoji is visible to all users.",
|
||||||
|
example: false,
|
||||||
|
}),
|
||||||
|
/* Versia Server API extension */
|
||||||
|
description: z
|
||||||
|
.string()
|
||||||
|
.nullable()
|
||||||
|
.openapi({
|
||||||
|
description:
|
||||||
|
"Emoji description for users using screen readers.",
|
||||||
|
example: "A cute blob.",
|
||||||
|
externalDocs: {
|
||||||
|
url: "https://docs.joinmastodon.org/entities/CustomEmoji/#description",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.openapi({
|
||||||
|
description: "Represents a custom emoji.",
|
||||||
|
externalDocs: {
|
||||||
|
url: "https://docs.joinmastodon.org/entities/CustomEmoji",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
import { z } from "@hono/zod-openapi";
|
import { z } from "@hono/zod-openapi";
|
||||||
import type { Status as ApiNote } from "@versia/client/types";
|
import type { Status as ApiNote } from "@versia/client/types";
|
||||||
import { Emoji, Media } from "@versia/kit/db";
|
import { Media } from "@versia/kit/db";
|
||||||
import ISO6391 from "iso-639-1";
|
import ISO6391 from "iso-639-1";
|
||||||
import { zBoolean } from "~/packages/config-manager/config.type.ts";
|
import { zBoolean } from "~/packages/config-manager/config.type.ts";
|
||||||
import { Account } from "./account.ts";
|
import { Account } from "./account.ts";
|
||||||
import { PreviewCard } from "./card.ts";
|
import { PreviewCard } from "./card.ts";
|
||||||
import { Id } from "./common.ts";
|
import { Id } from "./common.ts";
|
||||||
|
import { CustomEmoji } from "./emoji.ts";
|
||||||
import { FilterResult } from "./filters.ts";
|
import { FilterResult } from "./filters.ts";
|
||||||
|
import { NoteReaction } from "./versia.ts";
|
||||||
|
|
||||||
export const Mention = z
|
export const Mention = z
|
||||||
.object({
|
.object({
|
||||||
|
|
@ -168,7 +170,7 @@ export const Poll = z.object({
|
||||||
url: "https://docs.joinmastodon.org/entities/Poll/#options",
|
url: "https://docs.joinmastodon.org/entities/Poll/#options",
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
emojis: z.array(Emoji.schema).openapi({
|
emojis: z.array(CustomEmoji).openapi({
|
||||||
description: "Custom emoji to be used for rendering poll options.",
|
description: "Custom emoji to be used for rendering poll options.",
|
||||||
externalDocs: {
|
externalDocs: {
|
||||||
url: "https://docs.joinmastodon.org/entities/Poll/#emojis",
|
url: "https://docs.joinmastodon.org/entities/Poll/#emojis",
|
||||||
|
|
@ -284,7 +286,7 @@ export const Status = z.object({
|
||||||
url: "https://docs.joinmastodon.org/entities/Status/#edited_at",
|
url: "https://docs.joinmastodon.org/entities/Status/#edited_at",
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
emojis: z.array(Emoji.schema).openapi({
|
emojis: z.array(CustomEmoji).openapi({
|
||||||
description: "Custom emoji to be used when rendering status content.",
|
description: "Custom emoji to be used when rendering status content.",
|
||||||
externalDocs: {
|
externalDocs: {
|
||||||
url: "https://docs.joinmastodon.org/entities/Status/#emojis",
|
url: "https://docs.joinmastodon.org/entities/Status/#emojis",
|
||||||
|
|
@ -455,17 +457,7 @@ export const Status = z.object({
|
||||||
url: "https://docs.joinmastodon.org/entities/Status/#pinned",
|
url: "https://docs.joinmastodon.org/entities/Status/#pinned",
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
emoji_reactions: z.array(
|
reactions: z.array(NoteReaction).openapi({}),
|
||||||
z.object({
|
|
||||||
count: z.number().int().nonnegative(),
|
|
||||||
me: zBoolean,
|
|
||||||
name: z.string(),
|
|
||||||
url: z.string().url().optional(),
|
|
||||||
static_url: z.string().url().optional(),
|
|
||||||
accounts: z.array(Account).optional(),
|
|
||||||
account_ids: z.array(z.string().uuid()).optional(),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
quote: z
|
quote: z
|
||||||
.lazy((): z.ZodType<ApiNote> => Status as z.ZodType<ApiNote>)
|
.lazy((): z.ZodType<ApiNote> => Status as z.ZodType<ApiNote>)
|
||||||
.nullable(),
|
.nullable(),
|
||||||
|
|
|
||||||
77
classes/schemas/versia.ts
Normal file
77
classes/schemas/versia.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
import { z } from "@hono/zod-openapi";
|
||||||
|
import { RolePermission } from "@versia/client/types";
|
||||||
|
import { Id } from "./common.ts";
|
||||||
|
import { config } from "~/packages/config-manager/index.ts";
|
||||||
|
|
||||||
|
/* Versia Server API extension */
|
||||||
|
export const Role = z
|
||||||
|
.object({
|
||||||
|
id: Id.openapi({}).openapi({
|
||||||
|
description: "The role ID in the database.",
|
||||||
|
example: "b4a7e0f0-8f6a-479b-910b-9265c070d5bd",
|
||||||
|
}),
|
||||||
|
name: z.string().min(1).max(128).trim().openapi({
|
||||||
|
description: "The name of the role.",
|
||||||
|
example: "Moderator",
|
||||||
|
}),
|
||||||
|
permissions: z
|
||||||
|
.array(z.nativeEnum(RolePermission))
|
||||||
|
.transform(
|
||||||
|
// Deduplicate permissions
|
||||||
|
(permissions) => Array.from(new Set(permissions)),
|
||||||
|
)
|
||||||
|
.default([])
|
||||||
|
.openapi({
|
||||||
|
description: "The permissions granted to the role.",
|
||||||
|
example: [
|
||||||
|
RolePermission.ManageEmojis,
|
||||||
|
RolePermission.ManageAccounts,
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
priority: z.number().int().default(0).openapi({
|
||||||
|
description:
|
||||||
|
"Role priority. Higher priority roles allow overriding lower priority roles.",
|
||||||
|
example: 100,
|
||||||
|
}),
|
||||||
|
description: z.string().min(0).max(1024).trim().optional().openapi({
|
||||||
|
description: "Short role description.",
|
||||||
|
example: "Allows managing emojis and accounts.",
|
||||||
|
}),
|
||||||
|
visible: z.boolean().default(true).openapi({
|
||||||
|
description: "Whether the role should be shown in the UI.",
|
||||||
|
}),
|
||||||
|
icon: z.string().url().optional().openapi({
|
||||||
|
description: "URL to the role icon.",
|
||||||
|
example: "https://example.com/role-icon.png",
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.openapi({
|
||||||
|
description:
|
||||||
|
"Information about a role in the system, as well as its permissions.",
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Versia Server API extension */
|
||||||
|
export const NoteReaction = z
|
||||||
|
.object({
|
||||||
|
name: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.max(config.validation.max_emoji_shortcode_size)
|
||||||
|
.trim()
|
||||||
|
.openapi({
|
||||||
|
description: "Custom Emoji shortcode or Unicode emoji.",
|
||||||
|
example: "blobfox_coffee",
|
||||||
|
}),
|
||||||
|
count: z.number().int().nonnegative().openapi({
|
||||||
|
description: "Number of users who reacted with this emoji.",
|
||||||
|
example: 5,
|
||||||
|
}),
|
||||||
|
me: z.boolean().optional().openapi({
|
||||||
|
description:
|
||||||
|
"Whether the current authenticated user reacted with this emoji.",
|
||||||
|
example: true,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.openapi({
|
||||||
|
description: "Information about a reaction to a note.",
|
||||||
|
});
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import type { Source as ApiSource } from "@versia/client/types";
|
import type { z } from "@hono/zod-openapi";
|
||||||
import type { ContentFormat, InstanceMetadata } from "@versia/federation/types";
|
import type { ContentFormat, InstanceMetadata } from "@versia/federation/types";
|
||||||
import type { Challenge } from "altcha-lib/types";
|
import type { Challenge } from "altcha-lib/types";
|
||||||
import { relations, sql } from "drizzle-orm";
|
import { relations, sql } from "drizzle-orm";
|
||||||
|
|
@ -14,6 +14,7 @@ import {
|
||||||
uniqueIndex,
|
uniqueIndex,
|
||||||
uuid,
|
uuid,
|
||||||
} from "drizzle-orm/pg-core";
|
} from "drizzle-orm/pg-core";
|
||||||
|
import type { Source } from "~/classes/schemas/account";
|
||||||
|
|
||||||
// biome-ignore lint/nursery/useExplicitType: Type is too complex
|
// biome-ignore lint/nursery/useExplicitType: Type is too complex
|
||||||
const createdAt = () =>
|
const createdAt = () =>
|
||||||
|
|
@ -553,16 +554,7 @@ export const Users = pgTable(
|
||||||
inbox: string;
|
inbox: string;
|
||||||
outbox: string;
|
outbox: string;
|
||||||
}> | null>(),
|
}> | null>(),
|
||||||
source: jsonb("source").notNull().$type<
|
source: jsonb("source").notNull().$type<z.infer<typeof Source>>(),
|
||||||
ApiSource & {
|
|
||||||
avatar?: {
|
|
||||||
content_type: string;
|
|
||||||
};
|
|
||||||
header?: {
|
|
||||||
content_type: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
>(),
|
|
||||||
avatarId: uuid("avatarId").references(() => Medias.id, {
|
avatarId: uuid("avatarId").references(() => Medias.id, {
|
||||||
onDelete: "set null",
|
onDelete: "set null",
|
||||||
onUpdate: "cascade",
|
onUpdate: "cascade",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue