mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 08:28:19 +01:00
refactor(api): 🎨 Refactor complex functions into smaller ones
This commit is contained in:
parent
a1e02d0d78
commit
c61f519a34
|
|
@ -2,7 +2,7 @@ import { idValidator } from "@/api";
|
|||
import { dualLogger } from "@/loggers";
|
||||
import { proxyUrl } from "@/response";
|
||||
import { sanitizedHtmlStrip } from "@/sanitization";
|
||||
import type { EntityValidator } from "@lysand-org/federation";
|
||||
import { EntityValidator } from "@lysand-org/federation";
|
||||
import {
|
||||
type InferInsertModel,
|
||||
type SQL,
|
||||
|
|
@ -33,6 +33,7 @@ import {
|
|||
type StatusWithRelations,
|
||||
contentToHtml,
|
||||
findManyNotes,
|
||||
parseTextMentions,
|
||||
} from "~/database/entities/status";
|
||||
import { db } from "~/drizzle/db";
|
||||
import {
|
||||
|
|
@ -249,90 +250,79 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
|||
return this;
|
||||
}
|
||||
|
||||
static async fromData(
|
||||
author: User,
|
||||
content: typeof EntityValidator.$ContentFormat,
|
||||
visibility: apiStatus["visibility"],
|
||||
isSensitive: boolean,
|
||||
spoilerText: string,
|
||||
emojis: EmojiWithInstance[],
|
||||
uri?: string,
|
||||
mentions?: User[],
|
||||
static async fromData(data: {
|
||||
author: User;
|
||||
content: typeof EntityValidator.$ContentFormat;
|
||||
visibility: apiStatus["visibility"];
|
||||
isSensitive: boolean;
|
||||
spoilerText: string;
|
||||
emojis?: EmojiWithInstance[];
|
||||
uri?: string;
|
||||
mentions?: User[];
|
||||
/** List of IDs of database Attachment objects */
|
||||
mediaAttachments?: string[],
|
||||
replyId?: string,
|
||||
quoteId?: string,
|
||||
application?: Application,
|
||||
): Promise<Note | null> {
|
||||
const htmlContent = await contentToHtml(content, mentions);
|
||||
mediaAttachments?: string[];
|
||||
replyId?: string;
|
||||
quoteId?: string;
|
||||
application?: Application;
|
||||
}): Promise<Note> {
|
||||
const plaintextContent =
|
||||
data.content["text/plain"]?.content ??
|
||||
Object.entries(data.content)[0][1].content;
|
||||
|
||||
// Parse emojis and fuse with existing emojis
|
||||
let foundEmojis = emojis;
|
||||
const parsedMentions = [
|
||||
...(data.mentions ?? []),
|
||||
...(await parseTextMentions(plaintextContent)),
|
||||
// Deduplicate by .id
|
||||
].filter(
|
||||
(mention, index, self) =>
|
||||
index === self.findIndex((t) => t.id === mention.id),
|
||||
);
|
||||
|
||||
if (author.isLocal()) {
|
||||
const parsedEmojis = await parseEmojis(htmlContent);
|
||||
// Fuse and deduplicate
|
||||
foundEmojis = [...emojis, ...parsedEmojis].filter(
|
||||
const parsedEmojis = [
|
||||
...(data.emojis ?? []),
|
||||
...(await parseEmojis(plaintextContent)),
|
||||
// Deduplicate by .id
|
||||
].filter(
|
||||
(emoji, index, self) =>
|
||||
index === self.findIndex((t) => t.id === emoji.id),
|
||||
);
|
||||
}
|
||||
|
||||
const htmlContent = await contentToHtml(data.content, parsedMentions);
|
||||
|
||||
const newNote = await Note.insert({
|
||||
authorId: author.id,
|
||||
authorId: data.author.id,
|
||||
content: htmlContent,
|
||||
contentSource:
|
||||
content["text/plain"]?.content ||
|
||||
content["text/markdown"]?.content ||
|
||||
Object.entries(content)[0][1].content ||
|
||||
data.content["text/plain"]?.content ||
|
||||
data.content["text/markdown"]?.content ||
|
||||
Object.entries(data.content)[0][1].content ||
|
||||
"",
|
||||
contentType: "text/html",
|
||||
visibility,
|
||||
sensitive: isSensitive,
|
||||
spoilerText: await sanitizedHtmlStrip(spoilerText),
|
||||
uri: uri || null,
|
||||
replyId: replyId ?? null,
|
||||
quotingId: quoteId ?? null,
|
||||
applicationId: application?.id ?? null,
|
||||
visibility: data.visibility,
|
||||
sensitive: data.isSensitive,
|
||||
spoilerText: await sanitizedHtmlStrip(data.spoilerText),
|
||||
uri: data.uri || null,
|
||||
replyId: data.replyId ?? null,
|
||||
quotingId: data.quoteId ?? null,
|
||||
applicationId: data.application?.id ?? null,
|
||||
});
|
||||
|
||||
// Connect emojis
|
||||
for (const emoji of foundEmojis) {
|
||||
await db
|
||||
.insert(EmojiToNote)
|
||||
.values({
|
||||
emojiId: emoji.id,
|
||||
noteId: newNote.id,
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
await newNote.recalculateDatabaseEmojis(parsedEmojis);
|
||||
|
||||
// Connect mentions
|
||||
for (const mention of mentions ?? []) {
|
||||
await db
|
||||
.insert(NoteToMentions)
|
||||
.values({
|
||||
noteId: newNote.id,
|
||||
userId: mention.id,
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
await newNote.recalculateDatabaseMentions(parsedMentions);
|
||||
|
||||
// Set attachment parents
|
||||
if (mediaAttachments && mediaAttachments.length > 0) {
|
||||
await db
|
||||
.update(Attachments)
|
||||
.set({
|
||||
noteId: newNote.id,
|
||||
})
|
||||
.where(inArray(Attachments.id, mediaAttachments));
|
||||
}
|
||||
await newNote.recalculateDatabaseAttachments(
|
||||
data.mediaAttachments ?? [],
|
||||
);
|
||||
|
||||
// Send notifications for mentioned local users
|
||||
for (const mention of mentions ?? []) {
|
||||
for (const mention of parsedMentions ?? []) {
|
||||
if (mention.isLocal()) {
|
||||
await db.insert(Notifications).values({
|
||||
accountId: author.id,
|
||||
accountId: data.author.id,
|
||||
notifiedId: mention.id,
|
||||
type: "mention",
|
||||
noteId: newNote.id,
|
||||
|
|
@ -340,61 +330,101 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
|||
}
|
||||
}
|
||||
|
||||
return await Note.fromId(newNote.id, newNote.data.authorId);
|
||||
await newNote.reload();
|
||||
|
||||
return newNote;
|
||||
}
|
||||
|
||||
async updateFromData(
|
||||
content?: typeof EntityValidator.$ContentFormat,
|
||||
visibility?: apiStatus["visibility"],
|
||||
isSensitive?: boolean,
|
||||
spoilerText?: string,
|
||||
emojis: EmojiWithInstance[] = [],
|
||||
mentions: User[] = [],
|
||||
async updateFromData(data: {
|
||||
author?: User;
|
||||
content?: typeof EntityValidator.$ContentFormat;
|
||||
visibility?: apiStatus["visibility"];
|
||||
isSensitive?: boolean;
|
||||
spoilerText?: string;
|
||||
emojis?: EmojiWithInstance[];
|
||||
uri?: string;
|
||||
mentions?: User[];
|
||||
/** List of IDs of database Attachment objects */
|
||||
mediaAttachments: string[] = [],
|
||||
replyId?: string,
|
||||
quoteId?: string,
|
||||
application?: Application,
|
||||
) {
|
||||
const htmlContent = content
|
||||
? await contentToHtml(content, mentions)
|
||||
mediaAttachments?: string[];
|
||||
replyId?: string;
|
||||
quoteId?: string;
|
||||
application?: Application;
|
||||
}): Promise<Note> {
|
||||
const plaintextContent = data.content
|
||||
? data.content["text/plain"]?.content ??
|
||||
Object.entries(data.content)[0][1].content
|
||||
: undefined;
|
||||
|
||||
// Parse emojis and fuse with existing emojis
|
||||
let foundEmojis = emojis;
|
||||
const parsedMentions = [
|
||||
...(data.mentions ?? []),
|
||||
...(plaintextContent
|
||||
? await parseTextMentions(plaintextContent)
|
||||
: []),
|
||||
// Deduplicate by .id
|
||||
].filter(
|
||||
(mention, index, self) =>
|
||||
index === self.findIndex((t) => t.id === mention.id),
|
||||
);
|
||||
|
||||
if (this.author.isLocal() && htmlContent) {
|
||||
const parsedEmojis = await parseEmojis(htmlContent);
|
||||
// Fuse and deduplicate
|
||||
foundEmojis = [...emojis, ...parsedEmojis].filter(
|
||||
const parsedEmojis = [
|
||||
...(data.emojis ?? []),
|
||||
...(plaintextContent ? await parseEmojis(plaintextContent) : []),
|
||||
// Deduplicate by .id
|
||||
].filter(
|
||||
(emoji, index, self) =>
|
||||
index === self.findIndex((t) => t.id === emoji.id),
|
||||
);
|
||||
}
|
||||
|
||||
const newNote = await this.update({
|
||||
const htmlContent = data.content
|
||||
? await contentToHtml(data.content, parsedMentions)
|
||||
: undefined;
|
||||
|
||||
await this.update({
|
||||
content: htmlContent,
|
||||
contentSource: content
|
||||
? content["text/plain"]?.content ||
|
||||
content["text/markdown"]?.content ||
|
||||
Object.entries(content)[0][1].content ||
|
||||
contentSource: data.content
|
||||
? data.content["text/plain"]?.content ||
|
||||
data.content["text/markdown"]?.content ||
|
||||
Object.entries(data.content)[0][1].content ||
|
||||
""
|
||||
: undefined,
|
||||
contentType: "text/html",
|
||||
visibility,
|
||||
sensitive: isSensitive,
|
||||
spoilerText: spoilerText,
|
||||
replyId,
|
||||
quotingId: quoteId,
|
||||
applicationId: application?.id,
|
||||
visibility: data.visibility,
|
||||
sensitive: data.isSensitive,
|
||||
spoilerText: data.spoilerText,
|
||||
replyId: data.replyId,
|
||||
quotingId: data.quoteId,
|
||||
applicationId: data.application?.id,
|
||||
});
|
||||
|
||||
// Connect emojis
|
||||
await this.recalculateDatabaseEmojis(parsedEmojis);
|
||||
|
||||
// Connect mentions
|
||||
await this.recalculateDatabaseMentions(parsedMentions);
|
||||
|
||||
// Set attachment parents
|
||||
await this.recalculateDatabaseAttachments(data.mediaAttachments ?? []);
|
||||
|
||||
await this.reload();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public async recalculateDatabaseEmojis(
|
||||
emojis: EmojiWithInstance[],
|
||||
): Promise<void> {
|
||||
// Fuse and deduplicate
|
||||
const fusedEmojis = emojis.filter(
|
||||
(emoji, index, self) =>
|
||||
index === self.findIndex((t) => t.id === emoji.id),
|
||||
);
|
||||
|
||||
// Connect emojis
|
||||
await db
|
||||
.delete(EmojiToNote)
|
||||
.where(eq(EmojiToNote.noteId, this.data.id));
|
||||
|
||||
for (const emoji of foundEmojis) {
|
||||
for (const emoji of fusedEmojis) {
|
||||
await db
|
||||
.insert(EmojiToNote)
|
||||
.values({
|
||||
|
|
@ -403,13 +433,15 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
|||
})
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
|
||||
public async recalculateDatabaseMentions(mentions: User[]): Promise<void> {
|
||||
// Connect mentions
|
||||
await db
|
||||
.delete(NoteToMentions)
|
||||
.where(eq(NoteToMentions.noteId, this.data.id));
|
||||
|
||||
for (const mention of mentions ?? []) {
|
||||
for (const mention of mentions) {
|
||||
await db
|
||||
.insert(NoteToMentions)
|
||||
.values({
|
||||
|
|
@ -418,9 +450,12 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
|||
})
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
|
||||
public async recalculateDatabaseAttachments(
|
||||
mediaAttachments: string[],
|
||||
): Promise<void> {
|
||||
// Set attachment parents
|
||||
if (mediaAttachments) {
|
||||
await db
|
||||
.update(Attachments)
|
||||
.set({
|
||||
|
|
@ -438,9 +473,6 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
|||
}
|
||||
}
|
||||
|
||||
return await Note.fromId(newNote.id, newNote.authorId);
|
||||
}
|
||||
|
||||
static async resolve(
|
||||
uri?: string,
|
||||
providedNote?: typeof EntityValidator.$Note,
|
||||
|
|
@ -476,10 +508,6 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
|||
throw new Error("No URI or note provided");
|
||||
}
|
||||
|
||||
const foundStatus = await Note.fromSql(
|
||||
eq(Notes.uri, uri ?? providedNote?.uri ?? ""),
|
||||
);
|
||||
|
||||
let note = providedNote || null;
|
||||
|
||||
if (uri) {
|
||||
|
|
@ -494,27 +522,44 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
|||
},
|
||||
});
|
||||
|
||||
note = (await response.json()) as typeof EntityValidator.$Note;
|
||||
note = await new EntityValidator().Note(await response.json());
|
||||
}
|
||||
|
||||
if (!note) {
|
||||
throw new Error("No note was able to be fetched");
|
||||
}
|
||||
|
||||
if (note.type !== "Note") {
|
||||
throw new Error("Invalid object type");
|
||||
}
|
||||
|
||||
if (!note.author) {
|
||||
throw new Error("Invalid object author");
|
||||
}
|
||||
|
||||
const author = await User.resolve(note.author);
|
||||
|
||||
if (!author) {
|
||||
throw new Error("Invalid object author");
|
||||
}
|
||||
|
||||
return await Note.fromLysand(note, author);
|
||||
}
|
||||
|
||||
static async fromLysand(
|
||||
note: typeof EntityValidator.$Note,
|
||||
author: User,
|
||||
): Promise<Note> {
|
||||
const emojis = [];
|
||||
|
||||
for (const emoji of note.extensions?.["org.lysand:custom_emojis"]
|
||||
?.emojis ?? []) {
|
||||
const resolvedEmoji = await fetchEmoji(emoji).catch((e) => {
|
||||
dualLogger.logError(
|
||||
LogLevel.Error,
|
||||
"Federation.StatusResolver",
|
||||
e,
|
||||
);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (resolvedEmoji) {
|
||||
emojis.push(resolvedEmoji);
|
||||
}
|
||||
}
|
||||
|
||||
const attachments = [];
|
||||
|
||||
for (const attachment of note.attachments ?? []) {
|
||||
|
|
@ -534,85 +579,45 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
|||
}
|
||||
}
|
||||
|
||||
const emojis = [];
|
||||
|
||||
for (const emoji of note.extensions?.["org.lysand:custom_emojis"]
|
||||
?.emojis ?? []) {
|
||||
const resolvedEmoji = await fetchEmoji(emoji).catch((e) => {
|
||||
dualLogger.logError(
|
||||
LogLevel.Error,
|
||||
"Federation.StatusResolver",
|
||||
e,
|
||||
);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (resolvedEmoji) {
|
||||
emojis.push(resolvedEmoji);
|
||||
}
|
||||
}
|
||||
|
||||
if (foundStatus) {
|
||||
return await foundStatus.updateFromData(
|
||||
note.content ?? {
|
||||
"text/plain": {
|
||||
content: "",
|
||||
},
|
||||
},
|
||||
note.visibility as apiStatus["visibility"],
|
||||
note.is_sensitive ?? false,
|
||||
note.subject ?? "",
|
||||
emojis,
|
||||
note.mentions
|
||||
? await Promise.all(
|
||||
(note.mentions ?? [])
|
||||
.map((mention) => User.resolve(mention))
|
||||
.filter(
|
||||
(mention) => mention !== null,
|
||||
) as Promise<User>[],
|
||||
)
|
||||
: [],
|
||||
attachments.map((a) => a.id),
|
||||
note.replies_to
|
||||
? (await Note.resolve(note.replies_to))?.data.id
|
||||
: undefined,
|
||||
note.quotes
|
||||
? (await Note.resolve(note.quotes))?.data.id
|
||||
: undefined,
|
||||
);
|
||||
}
|
||||
|
||||
const createdNote = await Note.fromData(
|
||||
const newData = {
|
||||
author,
|
||||
note.content ?? {
|
||||
content: note.content ?? {
|
||||
"text/plain": {
|
||||
content: "",
|
||||
},
|
||||
},
|
||||
note.visibility as apiStatus["visibility"],
|
||||
note.is_sensitive ?? false,
|
||||
note.subject ?? "",
|
||||
visibility: note.visibility as apiStatus["visibility"],
|
||||
isSensitive: note.is_sensitive ?? false,
|
||||
spoilerText: note.subject ?? "",
|
||||
emojis,
|
||||
note.uri,
|
||||
await Promise.all(
|
||||
uri: note.uri,
|
||||
mentions: await Promise.all(
|
||||
(note.mentions ?? [])
|
||||
.map((mention) => User.resolve(mention))
|
||||
.filter((mention) => mention !== null) as Promise<User>[],
|
||||
),
|
||||
attachments.map((a) => a.id),
|
||||
note.replies_to
|
||||
mediaAttachments: attachments.map((a) => a.id),
|
||||
replyId: note.replies_to
|
||||
? (await Note.resolve(note.replies_to))?.data.id
|
||||
: undefined,
|
||||
note.quotes
|
||||
quoteId: note.quotes
|
||||
? (await Note.resolve(note.quotes))?.data.id
|
||||
: undefined,
|
||||
);
|
||||
};
|
||||
|
||||
if (!createdNote) {
|
||||
throw new Error("Failed to create status");
|
||||
// Check if new note already exists
|
||||
|
||||
const foundNote = await Note.fromSql(eq(Notes.uri, note.uri));
|
||||
|
||||
// If it exists, simply update it
|
||||
if (foundNote) {
|
||||
await foundNote.updateFromData(newData);
|
||||
|
||||
return foundNote;
|
||||
}
|
||||
|
||||
return createdNote;
|
||||
// Else, create a new note
|
||||
return await Note.fromData(newData);
|
||||
}
|
||||
|
||||
async delete(ids: string[]): Promise<void>;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { applyConfig, auth, handleZodError, qs } from "@/api";
|
||||
import { applyConfig, auth, handleZodError, jsonOrForm } from "@/api";
|
||||
import { errorResponse, jsonResponse } from "@/response";
|
||||
import { sanitizedHtmlStrip } from "@/sanitization";
|
||||
import { zValidator } from "@hono/zod-validator";
|
||||
|
|
@ -99,7 +99,7 @@ export default (app: Hono) =>
|
|||
app.on(
|
||||
meta.allowedMethods,
|
||||
meta.route,
|
||||
qs(),
|
||||
jsonOrForm(),
|
||||
zValidator("form", schemas.form, handleZodError),
|
||||
auth(meta.auth, meta.permissions),
|
||||
async (context) => {
|
||||
|
|
|
|||
|
|
@ -159,21 +159,18 @@ export default (app: Hono) =>
|
|||
}
|
||||
}
|
||||
|
||||
const newNote = await foundStatus.updateFromData(
|
||||
statusText
|
||||
const newNote = await foundStatus.updateFromData({
|
||||
content: statusText
|
||||
? {
|
||||
[content_type]: {
|
||||
content: statusText,
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
undefined,
|
||||
sensitive,
|
||||
spoiler_text,
|
||||
undefined,
|
||||
undefined,
|
||||
media_ids,
|
||||
);
|
||||
isSensitive: sensitive,
|
||||
spoilerText: spoiler_text,
|
||||
mediaAttachments: media_ids,
|
||||
});
|
||||
|
||||
if (!newNote) {
|
||||
return errorResponse("Failed to update status", 500);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { config } from "config-manager";
|
|||
import type { Hono } from "hono";
|
||||
import ISO6391 from "iso-639-1";
|
||||
import { z } from "zod";
|
||||
import { federateNote, parseTextMentions } from "~/database/entities/status";
|
||||
import { federateNote } from "~/database/entities/status";
|
||||
import { db } from "~/drizzle/db";
|
||||
import { RolePermissions } from "~/drizzle/schema";
|
||||
import { Note } from "~/packages/database-interface/note";
|
||||
|
|
@ -175,26 +175,21 @@ export default (app: Hono) =>
|
|||
}
|
||||
}
|
||||
|
||||
const mentions = await parseTextMentions(status ?? "");
|
||||
|
||||
const newNote = await Note.fromData(
|
||||
user,
|
||||
{
|
||||
const newNote = await Note.fromData({
|
||||
author: user,
|
||||
content: {
|
||||
[content_type]: {
|
||||
content: status ?? "",
|
||||
},
|
||||
},
|
||||
visibility,
|
||||
sensitive ?? false,
|
||||
spoiler_text ?? "",
|
||||
[],
|
||||
undefined,
|
||||
mentions,
|
||||
media_ids,
|
||||
in_reply_to_id ?? undefined,
|
||||
quote_id ?? undefined,
|
||||
application ?? undefined,
|
||||
);
|
||||
isSensitive: sensitive ?? false,
|
||||
spoilerText: spoiler_text ?? "",
|
||||
mediaAttachments: media_ids,
|
||||
replyId: in_reply_to_id ?? undefined,
|
||||
quoteId: quote_id ?? undefined,
|
||||
application: application ?? undefined,
|
||||
});
|
||||
|
||||
if (!newNote) {
|
||||
return errorResponse("Failed to create status", 500);
|
||||
|
|
|
|||
204
utils/api.ts
204
utils/api.ts
|
|
@ -3,7 +3,6 @@ import chalk from "chalk";
|
|||
import { config } from "config-manager";
|
||||
import type { Context } from "hono";
|
||||
import { createMiddleware } from "hono/factory";
|
||||
import type { BodyData } from "hono/utils/body";
|
||||
import { validator } from "hono/validator";
|
||||
import {
|
||||
anyOf,
|
||||
|
|
@ -198,92 +197,8 @@ export const auth = (
|
|||
return checkRouteNeedsAuth(auth, authData, context);
|
||||
});
|
||||
|
||||
/* export const auth = (
|
||||
authData: ApiRouteMetadata["auth"],
|
||||
permissionData?: ApiRouteMetadata["permissions"],
|
||||
) =>
|
||||
validator("header", async (value, context) => {
|
||||
const auth = value.authorization
|
||||
? await getFromHeader(value.authorization)
|
||||
: null;
|
||||
|
||||
const error = errorResponse("Unauthorized", 401);
|
||||
|
||||
// Permissions check
|
||||
if (permissionData) {
|
||||
const userPerms = auth?.user
|
||||
? auth.user.getAllPermissions()
|
||||
: config.permissions.anonymous;
|
||||
|
||||
const requiredPerms =
|
||||
permissionData.methodOverrides?.[
|
||||
context.req.method as HttpVerb
|
||||
] ?? permissionData.required;
|
||||
|
||||
if (!requiredPerms.every((perm) => userPerms.includes(perm))) {
|
||||
const missingPerms = requiredPerms.filter(
|
||||
(perm) => !userPerms.includes(perm),
|
||||
);
|
||||
|
||||
return context.json(
|
||||
{
|
||||
error: `You do not have the required permissions to access this route. Missing: ${missingPerms.join(
|
||||
", ",
|
||||
)}`,
|
||||
},
|
||||
403,
|
||||
error.headers.toJSON(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (auth?.user) {
|
||||
return {
|
||||
user: auth.user as User,
|
||||
token: auth.token as string,
|
||||
application: auth.application as Application | null,
|
||||
};
|
||||
}
|
||||
if (authData.required) {
|
||||
return context.json(
|
||||
{
|
||||
error: "Unauthorized",
|
||||
},
|
||||
401,
|
||||
error.headers.toJSON(),
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
authData.requiredOnMethods?.includes(context.req.method as HttpVerb)
|
||||
) {
|
||||
return context.json(
|
||||
{
|
||||
error: "Unauthorized",
|
||||
},
|
||||
401,
|
||||
error.headers.toJSON(),
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
user: null,
|
||||
token: null,
|
||||
application: null,
|
||||
};
|
||||
}); */
|
||||
|
||||
/**
|
||||
* Middleware to magically unfuck forms
|
||||
* Add it to random Hono routes and hope it works
|
||||
* @returns
|
||||
*/
|
||||
export const qs = () => {
|
||||
return createMiddleware(async (context, next) => {
|
||||
const contentType = context.req.header("content-type");
|
||||
|
||||
if (contentType?.includes("multipart/form-data")) {
|
||||
// Get it as a query format to pass on to qs, then insert back files
|
||||
// Helper function to parse form data
|
||||
async function parseFormData(context: Context) {
|
||||
const formData = await context.req.formData();
|
||||
const urlparams = new URLSearchParams();
|
||||
const files = new Map<string, File>();
|
||||
|
|
@ -306,40 +221,21 @@ export const qs = () => {
|
|||
interpretNumericEntities: true,
|
||||
});
|
||||
|
||||
// @ts-ignore Very bad hack
|
||||
context.req.parseBody = <T extends BodyData = BodyData>() =>
|
||||
Promise.resolve({
|
||||
...parsed,
|
||||
...Object.fromEntries(files),
|
||||
} as T);
|
||||
|
||||
context.req.formData = () =>
|
||||
// @ts-ignore I'm so sorry for this
|
||||
Promise.resolve({
|
||||
...parsed,
|
||||
...Object.fromEntries(files),
|
||||
});
|
||||
// @ts-ignore I'm so sorry for this
|
||||
context.req.bodyCache.formData = {
|
||||
...parsed,
|
||||
...Object.fromEntries(files),
|
||||
return {
|
||||
parsed,
|
||||
files,
|
||||
};
|
||||
} else if (contentType?.includes("application/x-www-form-urlencoded")) {
|
||||
}
|
||||
|
||||
// Helper function to parse urlencoded data
|
||||
async function parseUrlEncoded(context: Context) {
|
||||
const parsed = parse(await context.req.text(), {
|
||||
parseArrays: true,
|
||||
interpretNumericEntities: true,
|
||||
});
|
||||
|
||||
context.req.parseBody = <T extends BodyData = BodyData>() =>
|
||||
Promise.resolve(parsed as T);
|
||||
// @ts-ignore Very bad hack
|
||||
context.req.formData = () => Promise.resolve(parsed);
|
||||
// @ts-ignore I'm so sorry for this
|
||||
context.req.bodyCache.formData = parsed;
|
||||
}
|
||||
await next();
|
||||
});
|
||||
};
|
||||
return parsed;
|
||||
}
|
||||
|
||||
export const qsQuery = () => {
|
||||
return createMiddleware(async (context, next) => {
|
||||
|
|
@ -350,77 +246,49 @@ export const qsQuery = () => {
|
|||
|
||||
// @ts-ignore Very bad hack
|
||||
context.req.query = () => parsed;
|
||||
|
||||
// @ts-ignore I'm so sorry for this
|
||||
context.req.queries = () => parsed;
|
||||
await next();
|
||||
});
|
||||
};
|
||||
|
||||
// Fill in queries, formData and json
|
||||
export const setContextFormDataToObject = (
|
||||
context: Context,
|
||||
setTo: object,
|
||||
): Context => {
|
||||
// @ts-expect-error HACK
|
||||
context.req.bodyCache.formData = setTo;
|
||||
context.req.parseBody = async () =>
|
||||
context.req.bodyCache.formData as FormData;
|
||||
context.req.formData = async () =>
|
||||
context.req.bodyCache.formData as FormData;
|
||||
|
||||
return context;
|
||||
};
|
||||
|
||||
/*
|
||||
* Middleware to magically unfuck forms
|
||||
* Add it to random Hono routes and hope it works
|
||||
* @returns
|
||||
*/
|
||||
export const jsonOrForm = () => {
|
||||
return createMiddleware(async (context, next) => {
|
||||
const contentType = context.req.header("content-type");
|
||||
|
||||
if (contentType?.includes("application/json")) {
|
||||
context.req.parseBody = async <T extends BodyData = BodyData>() =>
|
||||
(await context.req.json()) as T;
|
||||
context.req.bodyCache.formData = await context.req.json();
|
||||
context.req.formData = async () =>
|
||||
context.req.bodyCache.formData as FormData;
|
||||
setContextFormDataToObject(context, await context.req.json());
|
||||
} else if (contentType?.includes("application/x-www-form-urlencoded")) {
|
||||
const parsed = parse(await context.req.text(), {
|
||||
parseArrays: true,
|
||||
interpretNumericEntities: true,
|
||||
});
|
||||
const parsed = await parseUrlEncoded(context);
|
||||
|
||||
context.req.parseBody = <T extends BodyData = BodyData>() =>
|
||||
Promise.resolve(parsed as T);
|
||||
// @ts-ignore Very bad hack
|
||||
context.req.formData = () => Promise.resolve(parsed);
|
||||
// @ts-ignore I'm so sorry for this
|
||||
context.req.bodyCache.formData = parsed;
|
||||
setContextFormDataToObject(context, parsed);
|
||||
} else if (contentType?.includes("multipart/form-data")) {
|
||||
// Get it as a query format to pass on to qs, then insert back files
|
||||
const formData = await context.req.formData();
|
||||
const urlparams = new URLSearchParams();
|
||||
const files = new Map<string, File>();
|
||||
for (const [key, value] of [...formData.entries()]) {
|
||||
if (Array.isArray(value)) {
|
||||
for (const val of value) {
|
||||
urlparams.append(key, val);
|
||||
}
|
||||
} else if (value instanceof File) {
|
||||
if (!files.has(key)) {
|
||||
files.set(key, value);
|
||||
}
|
||||
} else {
|
||||
urlparams.append(key, String(value));
|
||||
}
|
||||
}
|
||||
const { parsed, files } = await parseFormData(context);
|
||||
|
||||
const parsed = parse(urlparams.toString(), {
|
||||
parseArrays: true,
|
||||
interpretNumericEntities: true,
|
||||
});
|
||||
|
||||
// @ts-ignore Very bad hack
|
||||
context.req.parseBody = <T extends BodyData = BodyData>() =>
|
||||
Promise.resolve({
|
||||
...parsed,
|
||||
...Object.fromEntries(files),
|
||||
} as T);
|
||||
|
||||
context.req.formData = () =>
|
||||
// @ts-ignore I'm so sorry for this
|
||||
Promise.resolve({
|
||||
setContextFormDataToObject(context, {
|
||||
...parsed,
|
||||
...Object.fromEntries(files),
|
||||
});
|
||||
// @ts-ignore I'm so sorry for this
|
||||
context.req.bodyCache.formData = {
|
||||
...parsed,
|
||||
...Object.fromEntries(files),
|
||||
};
|
||||
}
|
||||
|
||||
await next();
|
||||
|
|
|
|||
Loading…
Reference in a new issue