mirror of
https://github.com/versia-pub/server.git
synced 2026-03-13 13:59:16 +01:00
refactor(api): 🏷️ Begin porting all code over to new schemas
This commit is contained in:
parent
fda1167234
commit
bff1c5f734
32 changed files with 171 additions and 264 deletions
|
|
@ -1,8 +1,7 @@
|
|||
import { join } from "node:path";
|
||||
import { mimeLookup } from "@/content_types.ts";
|
||||
import { proxyUrl } from "@/response";
|
||||
import { z } from "@hono/zod-openapi";
|
||||
import type { Attachment as ApiAttachment } from "@versia/client/types";
|
||||
import type { z } from "@hono/zod-openapi";
|
||||
import type { ContentFormat } from "@versia/federation/types";
|
||||
import { db } from "@versia/kit/db";
|
||||
import { Medias } from "@versia/kit/tables";
|
||||
|
|
@ -16,6 +15,7 @@ import {
|
|||
inArray,
|
||||
} from "drizzle-orm";
|
||||
import sharp from "sharp";
|
||||
import type { Attachment as AttachmentSchema } from "~/classes/schemas/attachment.ts";
|
||||
import { MediaBackendType } from "~/packages/config-manager/config.type";
|
||||
import { config } from "~/packages/config-manager/index.ts";
|
||||
import { ApiError } from "../errors/api-error.ts";
|
||||
|
|
@ -26,34 +26,6 @@ import { BaseInterface } from "./base.ts";
|
|||
type MediaType = InferSelectModel<typeof Medias>;
|
||||
|
||||
export class Media extends BaseInterface<typeof Medias> {
|
||||
public static schema: z.ZodType<ApiAttachment> = z.object({
|
||||
id: z.string().uuid(),
|
||||
type: z.enum(["unknown", "image", "gifv", "video", "audio"]),
|
||||
url: z.string().url(),
|
||||
remote_url: z.string().url().nullable(),
|
||||
preview_url: z.string().url().nullable(),
|
||||
text_url: z.string().url().nullable(),
|
||||
meta: z
|
||||
.object({
|
||||
width: z.number().optional(),
|
||||
height: z.number().optional(),
|
||||
fps: z.number().optional(),
|
||||
size: z.string().optional(),
|
||||
duration: z.number().optional(),
|
||||
length: z.string().optional(),
|
||||
aspect: z.number().optional(),
|
||||
original: z.object({
|
||||
width: z.number().optional(),
|
||||
height: z.number().optional(),
|
||||
size: z.string().optional(),
|
||||
aspect: z.number().optional(),
|
||||
}),
|
||||
})
|
||||
.nullable(),
|
||||
description: z.string().nullable(),
|
||||
blurhash: z.string().nullable(),
|
||||
});
|
||||
|
||||
public static $type: MediaType;
|
||||
|
||||
public async reload(): Promise<void> {
|
||||
|
|
@ -446,7 +418,7 @@ export class Media extends BaseInterface<typeof Medias> {
|
|||
*
|
||||
* @returns
|
||||
*/
|
||||
public getMastodonType(): ApiAttachment["type"] {
|
||||
public getMastodonType(): z.infer<typeof AttachmentSchema.shape.type> {
|
||||
const type = this.getPreferredMimeType();
|
||||
|
||||
if (type.startsWith("image/")) {
|
||||
|
|
@ -500,7 +472,7 @@ export class Media extends BaseInterface<typeof Medias> {
|
|||
};
|
||||
}
|
||||
|
||||
public toApiMeta(): ApiAttachment["meta"] {
|
||||
public toApiMeta(): z.infer<typeof AttachmentSchema.shape.meta> {
|
||||
const type = this.getPreferredMimeType();
|
||||
const data = this.data.content[type];
|
||||
const size =
|
||||
|
|
@ -529,7 +501,7 @@ export class Media extends BaseInterface<typeof Medias> {
|
|||
};
|
||||
}
|
||||
|
||||
public toApi(): ApiAttachment {
|
||||
public toApi(): z.infer<typeof AttachmentSchema> {
|
||||
const type = this.getPreferredMimeType();
|
||||
const data = this.data.content[type];
|
||||
|
||||
|
|
@ -545,7 +517,6 @@ export class Media extends BaseInterface<typeof Medias> {
|
|||
preview_url: thumbnailData?.content
|
||||
? proxyUrl(new URL(thumbnailData.content)).toString()
|
||||
: null,
|
||||
text_url: null,
|
||||
meta: this.toApiMeta(),
|
||||
description: data.description || null,
|
||||
blurhash: this.data.blurhash,
|
||||
|
|
|
|||
|
|
@ -5,10 +5,6 @@ import { sanitizedHtmlStrip } from "@/sanitization";
|
|||
import { sentry } from "@/sentry";
|
||||
import type { z } from "@hono/zod-openapi";
|
||||
import { getLogger } from "@logtape/logtape";
|
||||
import type {
|
||||
Attachment as ApiAttachment,
|
||||
Status as ApiStatus,
|
||||
} from "@versia/client/types";
|
||||
import { EntityValidator } from "@versia/federation";
|
||||
import type {
|
||||
ContentFormat,
|
||||
|
|
@ -41,6 +37,7 @@ import {
|
|||
findManyNotes,
|
||||
parseTextMentions,
|
||||
} from "~/classes/functions/status";
|
||||
import type { Status as StatusSchema } from "~/classes/schemas/status.ts";
|
||||
import { config } from "~/packages/config-manager";
|
||||
import { DeliveryJobType, deliveryQueue } from "../queues/delivery.ts";
|
||||
import type { Status } from "../schemas/status.ts";
|
||||
|
|
@ -346,7 +343,7 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
|
|||
public static async fromData(data: {
|
||||
author: User;
|
||||
content: ContentFormat;
|
||||
visibility: ApiStatus["visibility"];
|
||||
visibility: z.infer<typeof StatusSchema.shape.visibility>;
|
||||
isSensitive: boolean;
|
||||
spoilerText: string;
|
||||
emojis?: Emoji[];
|
||||
|
|
@ -420,7 +417,7 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
|
|||
public async updateFromData(data: {
|
||||
author: User;
|
||||
content?: ContentFormat;
|
||||
visibility?: ApiStatus["visibility"];
|
||||
visibility?: z.infer<typeof StatusSchema.shape.visibility>;
|
||||
isSensitive?: boolean;
|
||||
spoilerText?: string;
|
||||
emojis?: Emoji[];
|
||||
|
|
@ -677,7 +674,7 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
|
|||
remote: false,
|
||||
},
|
||||
},
|
||||
visibility: visibility as ApiStatus["visibility"],
|
||||
visibility,
|
||||
isSensitive: note.is_sensitive ?? false,
|
||||
spoilerText: note.subject ?? "",
|
||||
emojis,
|
||||
|
|
@ -811,8 +808,8 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
|
|||
emojis: data.emojis.map((emoji) => new Emoji(emoji).toApi()),
|
||||
favourited: data.liked,
|
||||
favourites_count: data.likeCount,
|
||||
media_attachments: (data.attachments ?? []).map(
|
||||
(a) => new Media(a).toApi() as ApiAttachment,
|
||||
media_attachments: (data.attachments ?? []).map((a) =>
|
||||
new Media(a).toApi(),
|
||||
),
|
||||
mentions: data.mentions.map((mention) => ({
|
||||
id: mention.id,
|
||||
|
|
@ -844,7 +841,7 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
|
|||
spoiler_text: data.spoilerText,
|
||||
tags: [],
|
||||
uri: data.uri || this.getUri().toString(),
|
||||
visibility: data.visibility as ApiStatus["visibility"],
|
||||
visibility: data.visibility,
|
||||
url: data.uri || this.getMastoUri().toString(),
|
||||
bookmarked: false,
|
||||
quote: data.quotingId
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { z } from "@hono/zod-openapi";
|
||||
import type { Notification as APINotification } from "@versia/client/types";
|
||||
import type { z } from "@hono/zod-openapi";
|
||||
import { Note, User, db } from "@versia/kit/db";
|
||||
import { Notifications } from "@versia/kit/tables";
|
||||
import {
|
||||
|
|
@ -10,13 +9,12 @@ import {
|
|||
eq,
|
||||
inArray,
|
||||
} from "drizzle-orm";
|
||||
import type { Notification as NotificationSchema } from "~/classes/schemas/notification.ts";
|
||||
import {
|
||||
transformOutputToUserWithRelations,
|
||||
userExtrasTemplate,
|
||||
userRelations,
|
||||
} from "../functions/user.ts";
|
||||
import { Account } from "../schemas/account.ts";
|
||||
import { Status } from "../schemas/status.ts";
|
||||
import { BaseInterface } from "./base.ts";
|
||||
|
||||
export type NotificationType = InferSelectModel<typeof Notifications> & {
|
||||
|
|
@ -28,37 +26,6 @@ export class Notification extends BaseInterface<
|
|||
typeof Notifications,
|
||||
NotificationType
|
||||
> {
|
||||
public static schema: z.ZodType<APINotification> = z.object({
|
||||
account: Account.nullable(),
|
||||
created_at: z.string(),
|
||||
id: z.string().uuid(),
|
||||
status: Status.optional(),
|
||||
// TODO: Add reactions
|
||||
type: z.enum([
|
||||
"mention",
|
||||
"status",
|
||||
"follow",
|
||||
"follow_request",
|
||||
"reblog",
|
||||
"poll",
|
||||
"favourite",
|
||||
"update",
|
||||
"admin.sign_up",
|
||||
"admin.report",
|
||||
"chat",
|
||||
"pleroma:chat_mention",
|
||||
"pleroma:emoji_reaction",
|
||||
"pleroma:event_reminder",
|
||||
"pleroma:participation_request",
|
||||
"pleroma:participation_accepted",
|
||||
"move",
|
||||
"group_reblog",
|
||||
"group_favourite",
|
||||
"user_approved",
|
||||
]),
|
||||
target: Account.optional(),
|
||||
});
|
||||
|
||||
public async reload(): Promise<void> {
|
||||
const reloaded = await Notification.fromId(this.data.id);
|
||||
|
||||
|
|
@ -215,7 +182,7 @@ export class Notification extends BaseInterface<
|
|||
return this.data.id;
|
||||
}
|
||||
|
||||
public async toApi(): Promise<APINotification> {
|
||||
public async toApi(): Promise<z.infer<typeof NotificationSchema>> {
|
||||
const account = new User(this.data.account);
|
||||
|
||||
return {
|
||||
|
|
@ -226,6 +193,7 @@ export class Notification extends BaseInterface<
|
|||
status: this.data.status
|
||||
? await new Note(this.data.status).toApi(account)
|
||||
: undefined,
|
||||
group_key: `ungrouped-${this.data.id}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { z } from "@hono/zod-openapi";
|
||||
import type {
|
||||
Alerts,
|
||||
PushSubscription as ApiPushSubscription,
|
||||
|
|
@ -21,85 +20,6 @@ export class PushSubscription extends BaseInterface<
|
|||
typeof PushSubscriptions,
|
||||
PushSubscriptionType
|
||||
> {
|
||||
public static schema = z.object({
|
||||
id: z.string().uuid().openapi({
|
||||
example: "24eb1891-accc-43b4-b213-478e37d525b4",
|
||||
description: "The ID of the Web Push subscription in the database.",
|
||||
}),
|
||||
endpoint: z.string().url().openapi({
|
||||
example: "https://yourdomain.example/listener",
|
||||
description: "Where push alerts will be sent to.",
|
||||
}),
|
||||
alerts: z
|
||||
.object({
|
||||
mention: z.boolean().optional().openapi({
|
||||
example: true,
|
||||
description: "Receive mention notifications?",
|
||||
}),
|
||||
favourite: z.boolean().optional().openapi({
|
||||
example: true,
|
||||
description: "Receive favourite notifications?",
|
||||
}),
|
||||
reblog: z.boolean().optional().openapi({
|
||||
example: true,
|
||||
description: "Receive reblog notifications?",
|
||||
}),
|
||||
follow: z.boolean().optional().openapi({
|
||||
example: true,
|
||||
description: "Receive follow notifications?",
|
||||
}),
|
||||
poll: z.boolean().optional().openapi({
|
||||
example: false,
|
||||
description: "Receive poll notifications?",
|
||||
}),
|
||||
follow_request: z.boolean().optional().openapi({
|
||||
example: false,
|
||||
description: "Receive follow request notifications?",
|
||||
}),
|
||||
status: z.boolean().optional().openapi({
|
||||
example: false,
|
||||
description:
|
||||
"Receive new subscribed account notifications?",
|
||||
}),
|
||||
update: z.boolean().optional().openapi({
|
||||
example: false,
|
||||
description: "Receive status edited notifications?",
|
||||
}),
|
||||
"admin.sign_up": z.boolean().optional().openapi({
|
||||
example: false,
|
||||
description:
|
||||
"Receive new user signup notifications? Must have a role with the appropriate permissions.",
|
||||
}),
|
||||
"admin.report": z.boolean().optional().openapi({
|
||||
example: false,
|
||||
description:
|
||||
"Receive new report notifications? Must have a role with the appropriate permissions.",
|
||||
}),
|
||||
})
|
||||
.default({})
|
||||
.openapi({
|
||||
example: {
|
||||
mention: true,
|
||||
favourite: true,
|
||||
reblog: true,
|
||||
follow: true,
|
||||
poll: false,
|
||||
follow_request: false,
|
||||
status: false,
|
||||
update: false,
|
||||
"admin.sign_up": false,
|
||||
"admin.report": false,
|
||||
},
|
||||
description:
|
||||
"Which alerts should be delivered to the endpoint.",
|
||||
}),
|
||||
server_key: z.string().openapi({
|
||||
example:
|
||||
"BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=",
|
||||
description: "The streaming server’s VAPID key.",
|
||||
}),
|
||||
});
|
||||
|
||||
public static $type: PushSubscriptionType;
|
||||
|
||||
public async reload(): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import { z } from "@hono/zod-openapi";
|
||||
import type { Emoji as APIEmoji } from "@versia/client/types";
|
||||
import type { ReactionExtension } from "@versia/federation/types";
|
||||
import { Emoji, Instance, Note, User, db } from "@versia/kit/db";
|
||||
import { type Notes, Reactions, type Users } from "@versia/kit/tables";
|
||||
|
|
@ -12,7 +10,6 @@ import {
|
|||
inArray,
|
||||
} from "drizzle-orm";
|
||||
import { config } from "~/packages/config-manager/index.ts";
|
||||
import { CustomEmoji } from "../schemas/emoji.ts";
|
||||
import { BaseInterface } from "./base.ts";
|
||||
|
||||
type ReactionType = InferSelectModel<typeof Reactions> & {
|
||||
|
|
@ -21,19 +18,7 @@ type ReactionType = InferSelectModel<typeof Reactions> & {
|
|||
note: InferSelectModel<typeof Notes>;
|
||||
};
|
||||
|
||||
export interface APIReaction {
|
||||
id: string;
|
||||
author_id: string;
|
||||
emoji: APIEmoji | string;
|
||||
}
|
||||
|
||||
export class Reaction extends BaseInterface<typeof Reactions, ReactionType> {
|
||||
public static schema: z.ZodType<APIReaction> = z.object({
|
||||
id: z.string().uuid(),
|
||||
author_id: z.string().uuid(),
|
||||
emoji: CustomEmoji,
|
||||
});
|
||||
|
||||
public static $type: ReactionType;
|
||||
|
||||
public async reload(): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { z } from "@hono/zod-openapi";
|
||||
import type { Relationship as APIRelationship } from "@versia/client/types";
|
||||
import { db } from "@versia/kit/db";
|
||||
import { Relationships } from "@versia/kit/tables";
|
||||
import {
|
||||
|
|
@ -11,6 +10,7 @@ import {
|
|||
eq,
|
||||
inArray,
|
||||
} from "drizzle-orm";
|
||||
import type { Relationship as RelationshipSchema } from "~/classes/schemas/relationship";
|
||||
import { BaseInterface } from "./base.ts";
|
||||
import type { User } from "./user.ts";
|
||||
|
||||
|
|
@ -292,7 +292,7 @@ export class Relationship extends BaseInterface<
|
|||
return this.data.id;
|
||||
}
|
||||
|
||||
public toApi(): APIRelationship {
|
||||
public toApi(): z.infer<typeof RelationshipSchema> {
|
||||
return {
|
||||
id: this.data.subjectId,
|
||||
blocked_by: this.data.blockedBy,
|
||||
|
|
@ -308,6 +308,7 @@ export class Relationship extends BaseInterface<
|
|||
requested_by: this.data.requestedBy,
|
||||
requested: this.data.requested,
|
||||
showing_reblogs: this.data.showingReblogs,
|
||||
languages: this.data.languages ?? [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,86 @@
|
|||
import { z } from "@hono/zod-openapi";
|
||||
import { PushSubscription } from "@versia/kit/db";
|
||||
import { Id } from "./common.ts";
|
||||
|
||||
export const WebPushSubscription = z
|
||||
.object({
|
||||
id: Id.openapi({
|
||||
example: "24eb1891-accc-43b4-b213-478e37d525b4",
|
||||
description: "The ID of the Web Push subscription in the database.",
|
||||
}),
|
||||
endpoint: z.string().url().openapi({
|
||||
example: "https://yourdomain.example/listener",
|
||||
description: "Where push alerts will be sent to.",
|
||||
}),
|
||||
alerts: z
|
||||
.object({
|
||||
mention: z.boolean().optional().openapi({
|
||||
example: true,
|
||||
description: "Receive mention notifications?",
|
||||
}),
|
||||
favourite: z.boolean().optional().openapi({
|
||||
example: true,
|
||||
description: "Receive favourite notifications?",
|
||||
}),
|
||||
reblog: z.boolean().optional().openapi({
|
||||
example: true,
|
||||
description: "Receive reblog notifications?",
|
||||
}),
|
||||
follow: z.boolean().optional().openapi({
|
||||
example: true,
|
||||
description: "Receive follow notifications?",
|
||||
}),
|
||||
poll: z.boolean().optional().openapi({
|
||||
example: false,
|
||||
description: "Receive poll notifications?",
|
||||
}),
|
||||
follow_request: z.boolean().optional().openapi({
|
||||
example: false,
|
||||
description: "Receive follow request notifications?",
|
||||
}),
|
||||
status: z.boolean().optional().openapi({
|
||||
example: false,
|
||||
description:
|
||||
"Receive new subscribed account notifications?",
|
||||
}),
|
||||
update: z.boolean().optional().openapi({
|
||||
example: false,
|
||||
description: "Receive status edited notifications?",
|
||||
}),
|
||||
"admin.sign_up": z.boolean().optional().openapi({
|
||||
example: false,
|
||||
description:
|
||||
"Receive new user signup notifications? Must have a role with the appropriate permissions.",
|
||||
}),
|
||||
"admin.report": z.boolean().optional().openapi({
|
||||
example: false,
|
||||
description:
|
||||
"Receive new report notifications? Must have a role with the appropriate permissions.",
|
||||
}),
|
||||
})
|
||||
.default({})
|
||||
.openapi({
|
||||
example: {
|
||||
mention: true,
|
||||
favourite: true,
|
||||
reblog: true,
|
||||
follow: true,
|
||||
poll: false,
|
||||
follow_request: false,
|
||||
status: false,
|
||||
update: false,
|
||||
"admin.sign_up": false,
|
||||
"admin.report": false,
|
||||
},
|
||||
description:
|
||||
"Which alerts should be delivered to the endpoint.",
|
||||
}),
|
||||
server_key: z.string().openapi({
|
||||
example:
|
||||
"BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=",
|
||||
description: "The streaming server’s VAPID key.",
|
||||
}),
|
||||
})
|
||||
.openapi({});
|
||||
|
||||
export const WebPushSubscriptionInput = z
|
||||
.object({
|
||||
|
|
@ -33,7 +114,7 @@ export const WebPushSubscriptionInput = z
|
|||
}),
|
||||
data: z
|
||||
.object({
|
||||
alerts: PushSubscription.schema.shape.alerts,
|
||||
alerts: WebPushSubscription.shape.alerts,
|
||||
})
|
||||
.strict()
|
||||
.default({
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { z } from "@hono/zod-openapi";
|
||||
import type { Status as ApiNote } from "@versia/client/types";
|
||||
import { Media } from "@versia/kit/db";
|
||||
import { zBoolean } from "~/packages/config-manager/config.type.ts";
|
||||
import { Account } from "./account.ts";
|
||||
import { Attachment } from "./attachment.ts";
|
||||
import { PreviewCard } from "./card.ts";
|
||||
import { Id, iso631 } from "./common.ts";
|
||||
import { CustomEmoji } from "./emoji.ts";
|
||||
|
|
@ -223,7 +223,7 @@ export const Status = z.object({
|
|||
url: "https://docs.joinmastodon.org/entities/Status/#visibility",
|
||||
},
|
||||
}),
|
||||
media_attachments: z.array(Media.schema).openapi({
|
||||
media_attachments: z.array(Attachment).openapi({
|
||||
description: "Media that is attached to this status.",
|
||||
externalDocs: {
|
||||
url: "https://docs.joinmastodon.org/entities/Status/#media_attachments",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue