refactor(api): 🏷️ Begin porting all code over to new schemas

This commit is contained in:
Jesse Wierzbinski 2025-02-12 23:25:22 +01:00
parent fda1167234
commit bff1c5f734
No known key found for this signature in database
32 changed files with 171 additions and 264 deletions

View file

@ -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,

View file

@ -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

View file

@ -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}`,
};
}
}

View file

@ -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 servers VAPID key.",
}),
});
public static $type: PushSubscriptionType;
public async reload(): Promise<void> {

View file

@ -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> {

View file

@ -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 ?? [],
};
}
}