refactor(api): 🎨 Refactor emojis into their own class

This commit is contained in:
Jesse Wierzbinski 2024-06-12 18:52:01 -10:00
parent c61f519a34
commit d8cb1d475b
No known key found for this signature in database
11 changed files with 327 additions and 278 deletions

View file

@ -1,11 +1,12 @@
import { Args } from "@oclif/core";
import chalk from "chalk";
import { and, eq, isNull } from "drizzle-orm";
import ora from "ora";
import { BaseCommand } from "~/cli/base";
import { getUrl } from "~/database/entities/attachment";
import { db } from "~/drizzle/db";
import { Emojis } from "~/drizzle/schema";
import { config } from "~/packages/config-manager";
import { Emoji } from "~/packages/database-interface/emoji";
import { MediaBackend } from "~/packages/media-manager";
export default class EmojiAdd extends BaseCommand<typeof EmojiAdd> {
@ -33,13 +34,12 @@ export default class EmojiAdd extends BaseCommand<typeof EmojiAdd> {
const { args } = await this.parse(EmojiAdd);
// Check if emoji already exists
const existingEmoji = await db.query.Emojis.findFirst({
where: (Emojis, { eq, and, isNull }) =>
const existingEmoji = await Emoji.fromSql(
and(
eq(Emojis.shortcode, args.shortcode),
isNull(Emojis.instanceId),
),
});
);
if (existingEmoji) {
this.log(
@ -115,24 +115,12 @@ export default class EmojiAdd extends BaseCommand<typeof EmojiAdd> {
spinner.succeed();
const emoji = await db
.insert(Emojis)
.values({
await Emoji.insert({
shortcode: args.shortcode,
url: getUrl(uploaded.path, config),
visibleInPicker: true,
contentType: uploaded.uploadedFile.type,
})
.returning();
if (!emoji || emoji.length === 0) {
this.log(
`${chalk.red("✗")} Failed to create emoji ${chalk.red(
args.shortcode,
)}`,
);
this.exit(1);
}
});
this.log(
`${chalk.green("✓")} Created emoji ${chalk.green(

View file

@ -6,9 +6,9 @@ import ora from "ora";
import { unzip } from "unzipit";
import { BaseCommand } from "~/cli/base";
import { getUrl } from "~/database/entities/attachment";
import { db } from "~/drizzle/db";
import { Emojis } from "~/drizzle/schema";
import { config } from "~/packages/config-manager";
import { Emoji } from "~/packages/database-interface/emoji";
import { MediaBackend } from "~/packages/media-manager";
type MetaType = {
@ -130,10 +130,7 @@ export default class EmojiImport extends BaseCommand<typeof EmojiImport> {
} as MetaType);
// Get all emojis that already exist
const existingEmojis = await db
.select()
.from(Emojis)
.where(
const existingEmojis = await Emoji.manyFromSql(
and(
isNull(Emojis.instanceId),
inArray(
@ -145,13 +142,16 @@ export default class EmojiImport extends BaseCommand<typeof EmojiImport> {
// Filter out existing emojis
const newEmojis = meta.emojis.filter(
(e) => !existingEmojis.find((ee) => ee.shortcode === e.emoji.name),
(e) =>
!existingEmojis.find(
(ee) => ee.data.shortcode === e.emoji.name,
),
);
existingEmojis.length > 0 &&
this.log(
`${chalk.yellow("⚠")} Emojis with shortcode ${chalk.yellow(
existingEmojis.map((e) => e.shortcode).join(", "),
existingEmojis.map((e) => e.data.shortcode).join(", "),
)} already exist in the database and will not be imported`,
);
@ -212,15 +212,12 @@ export default class EmojiImport extends BaseCommand<typeof EmojiImport> {
continue;
}
await db
.insert(Emojis)
.values({
await Emoji.insert({
shortcode: emoji.emoji.name,
url: getUrl(uploaded.path, config),
visibleInPicker: true,
contentType: uploaded.uploadedFile.type,
})
.execute();
});
successfullyImported.push(emoji);
}

View file

@ -1,11 +1,7 @@
import { emojiValidatorWithColons } from "@/api";
import { proxyUrl } from "@/response";
import type { EntityValidator } from "@lysand-org/federation";
import { type InferSelectModel, and, eq } from "drizzle-orm";
import { db } from "~/drizzle/db";
import { Emojis, Instances } from "~/drizzle/schema";
import type { Emoji as apiEmoji } from "~/types/mastodon/emoji";
import { addInstanceIfNotExists } from "./instance";
import { type InferSelectModel, inArray } from "drizzle-orm";
import { Emojis, type Instances } from "~/drizzle/schema";
import { Emoji } from "~/packages/database-interface/emoji";
export type EmojiWithInstance = InferSelectModel<typeof Emojis> & {
instance: InferSelectModel<typeof Instances> | null;
@ -16,105 +12,16 @@ export type EmojiWithInstance = InferSelectModel<typeof Emojis> & {
* @param text The text to parse
* @returns An array of emojis
*/
export const parseEmojis = async (text: string) => {
export const parseEmojis = async (text: string): Promise<Emoji[]> => {
const matches = text.match(emojiValidatorWithColons);
if (!matches) {
if (!matches || matches.length === 0) {
return [];
}
const emojis = await db.query.Emojis.findMany({
where: (emoji, { eq, or }) =>
or(
...matches
.map((match) => match.replace(/:/g, ""))
.map((match) => eq(emoji.shortcode, match)),
return Emoji.manyFromSql(
inArray(
Emojis.shortcode,
matches.map((match) => match.replace(/:/g, "")),
),
with: {
instance: true,
},
});
return emojis;
};
/**
* Gets an emoji from the database, and fetches it from the remote instance if it doesn't exist.
* @param emoji Emoji to fetch
* @param host Host to fetch the emoji from if remote
* @returns The emoji
*/
export const fetchEmoji = async (
emojiToFetch: (typeof EntityValidator.$CustomEmojiExtension)["emojis"][0],
host?: string,
): Promise<EmojiWithInstance> => {
const existingEmoji = await db
.select()
.from(Emojis)
.innerJoin(Instances, eq(Emojis.instanceId, Instances.id))
.where(
and(
eq(Emojis.shortcode, emojiToFetch.name),
host ? eq(Instances.baseUrl, host) : undefined,
),
)
.limit(1);
if (existingEmoji[0]) {
return {
...existingEmoji[0].Emojis,
instance: existingEmoji[0].Instances,
};
}
const foundInstance = host ? await addInstanceIfNotExists(host) : null;
const result = (
await db
.insert(Emojis)
.values({
shortcode: emojiToFetch.name,
url: Object.entries(emojiToFetch.url)[0][1].content,
alt:
Object.entries(emojiToFetch.url)[0][1].description ||
undefined,
contentType: Object.keys(emojiToFetch.url)[0],
visibleInPicker: true,
instanceId: foundInstance?.id,
})
.returning()
)[0];
return {
...result,
instance: foundInstance,
};
};
/**
* Converts the emoji to an APIEmoji object.
* @returns The APIEmoji object.
*/
export const emojiToApi = (emoji: EmojiWithInstance): apiEmoji => {
return {
// @ts-expect-error ID is not in regular Mastodon API
id: emoji.id,
shortcode: emoji.shortcode,
static_url: proxyUrl(emoji.url) ?? "", // TODO: Add static version
url: proxyUrl(emoji.url) ?? "",
visible_in_picker: emoji.visibleInPicker,
category: emoji.category ?? undefined,
};
};
export const emojiToLysand = (
emoji: EmojiWithInstance,
): (typeof EntityValidator.$CustomEmojiExtension)["emojis"][0] => {
return {
name: emoji.shortcode,
url: {
[emoji.contentType]: {
content: emoji.url,
description: emoji.alt || undefined,
},
},
};
);
};

View file

@ -10,7 +10,7 @@ import {
} from "drizzle-orm";
import { db } from "~/drizzle/db";
import { Attachments } from "~/drizzle/schema";
import type { AsyncAttachment as apiAsyncAttachment } from "~/types/mastodon/async_attachment";
import type { AsyncAttachment as APIAsyncAttachment } from "~/types/mastodon/async_attachment";
import type { Attachment as APIAttachment } from "~/types/mastodon/attachment";
import { BaseInterface } from "./base";
@ -21,7 +21,7 @@ export class Attachment extends BaseInterface<typeof Attachments> {
const reloaded = await Attachment.fromId(this.data.id);
if (!reloaded) {
throw new Error("Failed to reload role");
throw new Error("Failed to reload attachment");
}
this.data = reloaded.data;
@ -83,7 +83,7 @@ export class Attachment extends BaseInterface<typeof Attachments> {
const updated = await Attachment.fromId(this.data.id);
if (!updated) {
throw new Error("Failed to update role");
throw new Error("Failed to update attachment");
}
this.data = updated.data;
@ -111,13 +111,13 @@ export class Attachment extends BaseInterface<typeof Attachments> {
await db.insert(Attachments).values(data).returning()
)[0];
const role = await Attachment.fromId(inserted.id);
const attachment = await Attachment.fromId(inserted.id);
if (!role) {
throw new Error("Failed to insert role");
if (!attachment) {
throw new Error("Failed to insert attachment");
}
return role;
return attachment;
}
get id() {
@ -169,7 +169,7 @@ export class Attachment extends BaseInterface<typeof Attachments> {
};
}
public toApi(): APIAttachment | apiAsyncAttachment {
public toApi(): APIAttachment | APIAsyncAttachment {
return {
id: this.data.id,
type: this.getMastodonType(),

View file

@ -0,0 +1,192 @@
import { proxyUrl } from "@/response";
import type { EntityValidator } from "@lysand-org/federation";
import {
type InferInsertModel,
type SQL,
and,
desc,
eq,
inArray,
} from "drizzle-orm";
import type { EmojiWithInstance } from "~/database/entities/emoji";
import { addInstanceIfNotExists } from "~/database/entities/instance";
import { db } from "~/drizzle/db";
import { Emojis, Instances } from "~/drizzle/schema";
import type { Emoji as APIEmoji } from "~/types/mastodon/emoji";
import { BaseInterface } from "./base";
export class Emoji extends BaseInterface<typeof Emojis, EmojiWithInstance> {
async reload(): Promise<void> {
const reloaded = await Emoji.fromId(this.data.id);
if (!reloaded) {
throw new Error("Failed to reload emoji");
}
this.data = reloaded.data;
}
public static async fromId(id: string | null): Promise<Emoji | null> {
if (!id) {
return null;
}
return await Emoji.fromSql(eq(Emojis.id, id));
}
public static async fromIds(ids: string[]): Promise<Emoji[]> {
return await Emoji.manyFromSql(inArray(Emojis.id, ids));
}
public static async fromSql(
sql: SQL<unknown> | undefined,
orderBy: SQL<unknown> | undefined = desc(Emojis.id),
): Promise<Emoji | null> {
const found = await db.query.Emojis.findFirst({
where: sql,
orderBy,
with: {
instance: true,
},
});
if (!found) {
return null;
}
return new Emoji(found);
}
public static async manyFromSql(
sql: SQL<unknown> | undefined,
orderBy: SQL<unknown> | undefined = desc(Emojis.id),
limit?: number,
offset?: number,
extra?: Parameters<typeof db.query.Emojis.findMany>[0],
): Promise<Emoji[]> {
const found = await db.query.Emojis.findMany({
where: sql,
orderBy,
limit,
offset,
with: { ...extra?.with, instance: true },
});
return found.map((s) => new Emoji(s));
}
async update(
newEmoji: Partial<EmojiWithInstance>,
): Promise<EmojiWithInstance> {
await db.update(Emojis).set(newEmoji).where(eq(Emojis.id, this.id));
const updated = await Emoji.fromId(this.data.id);
if (!updated) {
throw new Error("Failed to update emoji");
}
this.data = updated.data;
return updated.data;
}
save(): Promise<EmojiWithInstance> {
return this.update(this.data);
}
async delete(ids: string[]): Promise<void>;
async delete(): Promise<void>;
async delete(ids?: unknown): Promise<void> {
if (Array.isArray(ids)) {
await db.delete(Emojis).where(inArray(Emojis.id, ids));
} else {
await db.delete(Emojis).where(eq(Emojis.id, this.id));
}
}
public static async insert(
data: InferInsertModel<typeof Emojis>,
): Promise<Emoji> {
const inserted = (await db.insert(Emojis).values(data).returning())[0];
const emoji = await Emoji.fromId(inserted.id);
if (!emoji) {
throw new Error("Failed to insert emoji");
}
return emoji;
}
public static async fetchFromRemote(
emojiToFetch: (typeof EntityValidator.$CustomEmojiExtension)["emojis"][0],
host?: string,
): Promise<Emoji> {
const existingEmoji = await db
.select()
.from(Emojis)
.innerJoin(Instances, eq(Emojis.instanceId, Instances.id))
.where(
and(
eq(Emojis.shortcode, emojiToFetch.name),
host ? eq(Instances.baseUrl, host) : undefined,
),
)
.limit(1);
if (existingEmoji[0]) {
const found = await Emoji.fromId(existingEmoji[0].Emojis.id);
if (!found) {
throw new Error("Failed to fetch emoji");
}
return found;
}
const foundInstance = host ? await addInstanceIfNotExists(host) : null;
return await Emoji.fromLysand(emojiToFetch, foundInstance?.id ?? null);
}
get id() {
return this.data.id;
}
public toApi(): APIEmoji {
return {
// @ts-expect-error ID is not in regular Mastodon API
id: this.id,
shortcode: this.data.shortcode,
static_url: proxyUrl(this.data.url) ?? "", // TODO: Add static version
url: proxyUrl(this.data.url) ?? "",
visible_in_picker: this.data.visibleInPicker,
category: this.data.category ?? undefined,
};
}
public toLysand(): (typeof EntityValidator.$CustomEmojiExtension)["emojis"][0] {
return {
name: this.data.shortcode,
url: {
[this.data.contentType]: {
content: this.data.url,
description: this.data.alt || undefined,
},
},
};
}
public static fromLysand(
emoji: (typeof EntityValidator.$CustomEmojiExtension)["emojis"][0],
instanceId: string | null,
): Promise<Emoji> {
return Emoji.insert({
shortcode: emoji.name,
url: Object.entries(emoji.url)[0][1].content,
alt: Object.entries(emoji.url)[0][1].description || undefined,
contentType: Object.keys(emoji.url)[0],
visibleInPicker: true,
instanceId: instanceId,
});
}
}

View file

@ -21,13 +21,7 @@ import {
type Application,
applicationToApi,
} from "~/database/entities/application";
import {
type EmojiWithInstance,
emojiToApi,
emojiToLysand,
fetchEmoji,
parseEmojis,
} from "~/database/entities/emoji";
import { parseEmojis } from "~/database/entities/emoji";
import { localObjectUri } from "~/database/entities/federation";
import {
type StatusWithRelations,
@ -49,6 +43,7 @@ import type { Attachment as apiAttachment } from "~/types/mastodon/attachment";
import type { Status as apiStatus } from "~/types/mastodon/status";
import { Attachment } from "./attachment";
import { BaseInterface } from "./base";
import { Emoji } from "./emoji";
import { User } from "./user";
/**
@ -256,7 +251,7 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
visibility: apiStatus["visibility"];
isSensitive: boolean;
spoilerText: string;
emojis?: EmojiWithInstance[];
emojis?: Emoji[];
uri?: string;
mentions?: User[];
/** List of IDs of database Attachment objects */
@ -341,7 +336,7 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
visibility?: apiStatus["visibility"];
isSensitive?: boolean;
spoilerText?: string;
emojis?: EmojiWithInstance[];
emojis?: Emoji[];
uri?: string;
mentions?: User[];
/** List of IDs of database Attachment objects */
@ -410,9 +405,7 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
return this;
}
public async recalculateDatabaseEmojis(
emojis: EmojiWithInstance[],
): Promise<void> {
public async recalculateDatabaseEmojis(emojis: Emoji[]): Promise<void> {
// Fuse and deduplicate
const fusedEmojis = emojis.filter(
(emoji, index, self) =>
@ -546,14 +539,16 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
for (const emoji of note.extensions?.["org.lysand:custom_emojis"]
?.emojis ?? []) {
const resolvedEmoji = await fetchEmoji(emoji).catch((e) => {
const resolvedEmoji = await Emoji.fetchFromRemote(emoji).catch(
(e) => {
dualLogger.logError(
LogLevel.Error,
"Federation.StatusResolver",
e,
);
return null;
});
},
);
if (resolvedEmoji) {
emojis.push(resolvedEmoji);
@ -721,7 +716,7 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
: null,
card: null,
content: replacedContent,
emojis: data.emojis.map((emoji) => emojiToApi(emoji)),
emojis: data.emojis.map((emoji) => new Emoji(emoji).toApi()),
favourited: data.liked,
favourites_count: data.likeCount,
media_attachments: (data.attachments ?? []).map(
@ -816,7 +811,9 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
| "direct",
extensions: {
"org.lysand:custom_emojis": {
emojis: status.emojis.map((emoji) => emojiToLysand(emoji)),
emojis: status.emojis.map((emoji) =>
new Emoji(emoji).toLysand(),
),
},
// TODO: Add polls and reactions
},

View file

@ -20,11 +20,6 @@ import {
sql,
} from "drizzle-orm";
import { htmlToText } from "html-to-text";
import {
emojiToApi,
emojiToLysand,
fetchEmoji,
} from "~/database/entities/emoji";
import { objectToInboxRequest } from "~/database/entities/federation";
import { addInstanceIfNotExists } from "~/database/entities/instance";
import {
@ -46,6 +41,7 @@ import { type Config, config } from "~/packages/config-manager";
import type { Account as apiAccount } from "~/types/mastodon/account";
import type { Mention as apiMention } from "~/types/mastodon/mention";
import { BaseInterface } from "./base";
import { Emoji } from "./emoji";
import type { Note } from "./note";
import { Role } from "./role";
@ -273,11 +269,9 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
const instance = await addInstanceIfNotExists(data.uri);
const emojis = [];
for (const emoji of userEmojis) {
emojis.push(await fetchEmoji(emoji));
}
const emojis = await Promise.all(
userEmojis.map((emoji) => Emoji.fromLysand(emoji, instance.id)),
);
const user = await User.fromLysand(data, instance);
@ -580,7 +574,7 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
followers_count: user.followerCount,
following_count: user.followingCount,
statuses_count: user.statusCount,
emojis: user.emojis.map((emoji) => emojiToApi(emoji)),
emojis: user.emojis.map((emoji) => new Emoji(emoji).toApi()),
fields: user.fields.map((field) => ({
name: htmlToText(getBestContentType(field.key).content),
value: getBestContentType(field.value).content,
@ -695,7 +689,9 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
},
extensions: {
"org.lysand:custom_emojis": {
emojis: user.emojis.map((emoji) => emojiToLysand(emoji)),
emojis: user.emojis.map((emoji) =>
new Emoji(emoji).toLysand(),
),
},
},
};

View file

@ -11,10 +11,11 @@ import type { MediaBackend } from "media-manager";
import { LocalMediaBackend, S3MediaBackend } from "media-manager";
import { z } from "zod";
import { getUrl } from "~/database/entities/attachment";
import { type EmojiWithInstance, parseEmojis } from "~/database/entities/emoji";
import { parseEmojis } from "~/database/entities/emoji";
import { contentToHtml } from "~/database/entities/status";
import { db } from "~/drizzle/db";
import { EmojiToUser, RolePermissions } from "~/drizzle/schema";
import type { Emoji } from "~/packages/database-interface/emoji";
import { User } from "~/packages/database-interface/user";
export const meta = applyConfig({
@ -222,7 +223,7 @@ export default (app: Hono) =>
self.isDiscoverable = discoverable;
}
const fieldEmojis: EmojiWithInstance[] = [];
const fieldEmojis: Emoji[] = [];
if (fields_attributes) {
self.fields = [];
@ -280,7 +281,11 @@ export default (app: Hono) =>
const displaynameEmojis = await parseEmojis(sanitizedDisplayName);
const noteEmojis = await parseEmojis(self.note);
self.emojis = [...displaynameEmojis, ...noteEmojis, ...fieldEmojis];
self.emojis = [
...displaynameEmojis,
...noteEmojis,
...fieldEmojis,
].map((e) => e.data);
// Deduplicate emojis
self.emojis = self.emojis.filter(

View file

@ -1,9 +1,9 @@
import { applyConfig, auth } from "@/api";
import { jsonResponse } from "@/response";
import { and, eq, isNull, or } from "drizzle-orm";
import type { Hono } from "hono";
import { emojiToApi } from "~/database/entities/emoji";
import { db } from "~/drizzle/db";
import { RolePermissions } from "~/drizzle/schema";
import { Emojis, RolePermissions } from "~/drizzle/schema";
import { Emoji } from "~/packages/database-interface/emoji";
export const meta = applyConfig({
allowedMethods: ["GET"],
@ -28,22 +28,16 @@ export default (app: Hono) =>
async (context) => {
const { user } = context.req.valid("header");
const emojis = await db.query.Emojis.findMany({
where: (emoji, { isNull, and, eq, or }) =>
const emojis = await Emoji.manyFromSql(
and(
isNull(emoji.instanceId),
isNull(Emojis.instanceId),
or(
isNull(emoji.ownerId),
user ? eq(emoji.ownerId, user.id) : undefined,
isNull(Emojis.ownerId),
user ? eq(Emojis.ownerId, user.id) : undefined,
),
),
with: {
instance: true,
},
});
);
return jsonResponse(
await Promise.all(emojis.map((emoji) => emojiToApi(emoji))),
);
return jsonResponse(emojis.map((emoji) => emoji.toApi()));
},
);

View file

@ -12,10 +12,10 @@ import { eq } from "drizzle-orm";
import type { Hono } from "hono";
import { z } from "zod";
import { getUrl } from "~/database/entities/attachment";
import { emojiToApi } from "~/database/entities/emoji";
import { db } from "~/drizzle/db";
import { Emojis, RolePermissions } from "~/drizzle/schema";
import { config } from "~/packages/config-manager";
import { Emoji } from "~/packages/database-interface/emoji";
import { MediaBackend } from "~/packages/media-manager";
export const meta = applyConfig({
@ -83,12 +83,7 @@ export default (app: Hono) =>
return errorResponse("Unauthorized", 401);
}
const emoji = await db.query.Emojis.findFirst({
where: (emoji, { eq }) => eq(emoji.id, id),
with: {
instance: true,
},
});
const emoji = await Emoji.fromId(id);
if (!emoji) {
return errorResponse("Emoji not found", 404);
@ -97,7 +92,7 @@ export default (app: Hono) =>
// Check if user is admin
if (
!user.hasPermission(RolePermissions.ManageEmojis) &&
emoji.ownerId !== user.data.id
emoji.data.ownerId !== user.data.id
) {
return jsonResponse(
{
@ -114,7 +109,7 @@ export default (app: Hono) =>
config,
);
await mediaBackend.deleteFileByUrl(emoji.url);
await mediaBackend.deleteFileByUrl(emoji.data.url);
await db.delete(Emojis).where(eq(Emojis.id, id));
@ -156,6 +151,8 @@ export default (app: Hono) =>
);
}
const modified = structuredClone(emoji.data);
if (form.element) {
// Check of emoji is an image
let contentType =
@ -188,35 +185,22 @@ export default (app: Hono) =>
url = form.element;
}
emoji.url = getUrl(url, config);
emoji.contentType = contentType;
modified.url = getUrl(url, config);
modified.contentType = contentType;
}
const newEmoji = (
await db
.update(Emojis)
.set({
shortcode: form.shortcode ?? emoji.shortcode,
alt: form.alt ?? emoji.alt,
url: emoji.url,
ownerId: form.global ? null : user.id,
contentType: emoji.contentType,
category: form.category ?? emoji.category,
})
.where(eq(Emojis.id, id))
.returning()
)[0];
modified.shortcode = form.shortcode ?? modified.shortcode;
modified.alt = form.alt ?? modified.alt;
modified.category = form.category ?? modified.category;
modified.ownerId = form.global ? null : user.data.id;
return jsonResponse(
emojiToApi({
...newEmoji,
instance: null,
}),
);
await emoji.update(modified);
return jsonResponse(emoji.toApi());
}
case "GET": {
return jsonResponse(emojiToApi(emoji));
return jsonResponse(emoji.toApi());
}
}
},

View file

@ -8,13 +8,13 @@ import {
import { mimeLookup } from "@/content_types";
import { errorResponse, jsonResponse } from "@/response";
import { zValidator } from "@hono/zod-validator";
import { and, eq, isNull, or } from "drizzle-orm";
import type { Hono } from "hono";
import { z } from "zod";
import { getUrl } from "~/database/entities/attachment";
import { emojiToApi } from "~/database/entities/emoji";
import { db } from "~/drizzle/db";
import { Emojis, RolePermissions } from "~/drizzle/schema";
import { config } from "~/packages/config-manager";
import { Emoji } from "~/packages/database-interface/emoji";
import { MediaBackend } from "~/packages/media-manager";
export const meta = applyConfig({
@ -84,14 +84,13 @@ export default (app: Hono) =>
}
// Check if emoji already exists
const existing = await db.query.Emojis.findFirst({
where: (emoji, { eq, and, isNull, or }) =>
const existing = await Emoji.fromSql(
and(
eq(emoji.shortcode, shortcode),
isNull(emoji.instanceId),
or(eq(emoji.ownerId, user.id), isNull(emoji.ownerId)),
eq(Emojis.shortcode, shortcode),
isNull(Emojis.instanceId),
or(eq(Emojis.ownerId, user.id), isNull(Emojis.ownerId)),
),
});
);
if (existing) {
return errorResponse(
@ -129,10 +128,7 @@ export default (app: Hono) =>
url = element;
}
const emoji = (
await db
.insert(Emojis)
.values({
const emoji = await Emoji.insert({
shortcode,
url: getUrl(url, config),
visibleInPicker: true,
@ -140,15 +136,8 @@ export default (app: Hono) =>
category,
contentType,
alt,
})
.returning()
)[0];
});
return jsonResponse(
emojiToApi({
...emoji,
instance: null,
}),
);
return jsonResponse(emoji.toApi());
},
);