From 7b05a34cce8147405b902859415ae26164e73cb0 Mon Sep 17 00:00:00 2001 From: Jesse Wierzbinski Date: Tue, 7 May 2024 03:13:37 +0000 Subject: [PATCH] refactor: :fire: Remove dead code --- bun.lockb | Bin 144460 -> 159180 bytes database/entities/Object.ts | 90 ----- database/entities/Queue.ts | 123 ------- database/entities/Status.ts | 7 - database/entities/User.ts | 24 -- drizzle/types.ts | 39 -- index.ts | 2 +- package.json | 9 +- packages/request-parser/index.ts | 219 ----------- packages/request-parser/package.json | 9 - .../tests/request-parser.test.ts | 180 ---------- packages/server-handler/index.ts | 179 --------- packages/server-handler/package.json | 6 - packages/server-handler/tests.test.ts | 340 ------------------ routes.ts | 6 - server/api/api/v1/accounts/:id/index.ts | 2 +- .../v1/accounts/:id/remove_from_followers.ts | 2 +- server/api/api/v1/accounts/index.ts | 2 +- server/api/api/v1/blocks/index.ts | 2 +- server/api/api/v1/favourites/index.ts | 2 +- server/api/api/v1/follow_requests/index.ts | 2 +- server/api/api/v1/notifications/index.ts | 2 +- server/api/objects/[uuid]/index.ts | 2 +- types/activitypub.ts | 141 -------- types/api.ts | 20 +- types/lysand/Extension.ts | 6 - types/lysand/Object.ts | 177 --------- .../extensions/org.lysand/custom_emojis.ts | 7 - types/lysand/extensions/org.lysand/polls.ts | 14 - .../lysand/extensions/org.lysand/reactions.ts | 8 - utils/api.ts | 18 +- utils/meilisearch.ts | 14 - utils/merge.ts | 17 - utils/module.ts | 31 -- utils/oauth.ts | 2 - utils/temp.ts | 20 -- 36 files changed, 32 insertions(+), 1692 deletions(-) delete mode 100644 database/entities/Object.ts delete mode 100644 database/entities/Queue.ts delete mode 100644 drizzle/types.ts delete mode 100644 packages/request-parser/index.ts delete mode 100644 packages/request-parser/package.json delete mode 100644 packages/request-parser/tests/request-parser.test.ts delete mode 100644 packages/server-handler/index.ts delete mode 100644 packages/server-handler/package.json delete mode 100644 packages/server-handler/tests.test.ts delete mode 100644 types/activitypub.ts delete mode 100644 types/lysand/Extension.ts delete mode 100644 types/lysand/Object.ts delete mode 100644 types/lysand/extensions/org.lysand/custom_emojis.ts delete mode 100644 types/lysand/extensions/org.lysand/polls.ts delete mode 100644 types/lysand/extensions/org.lysand/reactions.ts delete mode 100644 utils/merge.ts delete mode 100644 utils/module.ts delete mode 100644 utils/temp.ts diff --git a/bun.lockb b/bun.lockb index 4747f73c24fad170f2d435fcb1e850cfceaf967f..bcb09639b5b9ab0c516067c83e7d7bcbf264c57b 100755 GIT binary patch delta 33595 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 Pg@GejU15g1df)#Ca9H~R delta 24853 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 diff --git a/database/entities/Object.ts b/database/entities/Object.ts deleted file mode 100644 index 7fb5128c..00000000 --- a/database/entities/Object.ts +++ /dev/null @@ -1,90 +0,0 @@ -import type { InferSelectModel } from "drizzle-orm"; -import type * as Lysand from "lysand-types"; -import { db } from "~drizzle/db"; -import { LysandObjects } from "~drizzle/schema"; -import { findFirstUser } from "./User"; - -export type LysandObject = InferSelectModel; - -/** - * Represents a Lysand object in the database. - */ - -export const createFromObject = async ( - object: Lysand.Entity, - authorUri: string, -) => { - const foundObject = await db.query.LysandObjects.findFirst({ - where: (o, { eq }) => eq(o.remoteId, object.id), - with: { - author: true, - }, - }); - - if (foundObject) { - return foundObject; - } - - const author = await findFirstUser({ - where: (user, { eq }) => eq(user.uri, authorUri), - }); - - return await db.insert(LysandObjects).values({ - authorId: author?.id, - createdAt: new Date(object.created_at).toISOString(), - extensions: object.extensions, - remoteId: object.id, - type: object.type, - uri: object.uri, - // Rest of data (remove id, author, created_at, extensions, type, uri) - extraData: Object.fromEntries( - Object.entries(object).filter( - ([key]) => - ![ - "id", - "author", - "created_at", - "extensions", - "type", - "uri", - ].includes(key), - ), - ), - }); -}; - -export const toLysand = (lyObject: LysandObject): Lysand.Entity => { - return { - id: lyObject.remoteId || lyObject.id, - created_at: new Date(lyObject.createdAt).toISOString(), - type: lyObject.type, - uri: lyObject.uri, - ...(lyObject.extraData as object), - // @ts-expect-error Assume stored JSON is valid - extensions: lyObject.extensions as object, - }; -}; - -export const isPublication = (lyObject: LysandObject): boolean => { - return lyObject.type === "Note" || lyObject.type === "Patch"; -}; - -export const isAction = (lyObject: LysandObject): boolean => { - return [ - "Like", - "Follow", - "Dislike", - "FollowAccept", - "FollowReject", - "Undo", - "Announce", - ].includes(lyObject.type); -}; - -export const isActor = (lyObject: LysandObject): boolean => { - return lyObject.type === "User"; -}; - -export const isExtension = (lyObject: LysandObject): boolean => { - return lyObject.type === "Extension"; -}; diff --git a/database/entities/Queue.ts b/database/entities/Queue.ts deleted file mode 100644 index c44718ea..00000000 --- a/database/entities/Queue.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { config } from "config-manager"; -// import { Worker } from "bullmq"; - -/* export const federationWorker = new Worker( - "federation", - async job => { - await job.updateProgress(0); - - switch (job.name) { - case "federation": { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - const statusId = job.data.id as string; - - const status = await client.status.findUnique({ - where: { id: statusId }, - include: statusAndUserRelations, - }); - - if (!status) return; - - // Only get remote users that follow the author of the status, and the remote mentioned users - const peopleToSendTo = await client.user.findMany({ - where: { - OR: [ - ["public", "unlisted", "private"].includes( - status.visibility - ) - ? { - relationships: { - some: { - subjectId: status.authorId, - following: true, - }, - }, - instanceId: { - not: null, - }, - } - : {}, - // Mentioned users - { - id: { - in: status.mentions.map(m => m.id), - }, - instanceId: { - not: null, - }, - }, - ], - }, - }); - - let peopleDone = 0; - - // Spawn sendToServer job for each user - for (const person of peopleToSendTo) { - await federationQueue.add("sendToServer", { - id: statusId, - user: person, - }); - - peopleDone++; - - await job.updateProgress( - Math.round((peopleDone / peopleToSendTo.length) * 100) - ); - } - break; - } - case "sendToServer": { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - const statusId = job.data.id as string; - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - const user = job.data.user as User; - - const status = await client.status.findUnique({ - where: { id: statusId }, - include: statusAndUserRelations, - }); - - if (!status) return; - - const response = await federateStatusTo( - status, - status.author, - user - ); - - if (response.status !== 200) { - throw new Error( - `Federation error: ${response.status} ${response.statusText}` - ); - } - - break; - } - } - - await job.updateProgress(100); - - return true; - }, - { - connection: { - host: config.redis.queue.host, - port: config.redis.queue.port, - password: config.redis.queue.password, - db: config.redis.queue.database || undefined, - }, - removeOnComplete: { - count: 400, - }, - removeOnFail: { - count: 3000, - }, - } -); */ - -export const addStatusFederationJob = async (statusId: string) => { - /* await federationQueue.add("federation", { - id: statusId, - }); */ -}; diff --git a/database/entities/Status.ts b/database/entities/Status.ts index 9e88a72f..06806f48 100644 --- a/database/entities/Status.ts +++ b/database/entities/Status.ts @@ -647,10 +647,3 @@ export const federateNote = async (note: Note) => { } } }; - -export const isFavouritedBy = async (status: Status, user: UserType) => { - return !!(await db.query.Likes.findFirst({ - where: (like, { and, eq }) => - and(eq(like.likerId, user.id), eq(like.likedId, status.id)), - })); -}; diff --git a/database/entities/User.ts b/database/entities/User.ts index 61e75107..467a1d18 100644 --- a/database/entities/User.ts +++ b/database/entities/User.ts @@ -34,11 +34,6 @@ export type UserWithRelations = UserType & { statusCount: number; }; -export type UserWithRelationsAndRelationships = UserWithRelations & { - relationships: InferSelectModel[]; - relationshipSubjects: InferSelectModel[]; -}; - export const userRelations: { instance: true; emojis: { @@ -99,16 +94,6 @@ export interface AuthData { application: Application | null; } -export const getFromRequest = async (req: Request): Promise => { - // Check auth token - const token = req.headers.get("Authorization")?.split(" ")[1] || ""; - - const { user, application } = - await retrieveUserAndApplicationFromToken(token); - - return { user, token, application }; -}; - export const getFromHeader = async (value: string): Promise => { const token = value.split(" ")[1]; @@ -388,15 +373,6 @@ export const resolveWebFinger = async ( return User.resolve(relevantLink.href); }; -/** - * Parses mentions from a list of URIs - */ -export const parseMentionsUris = async ( - mentions: string[], -): Promise => { - return await User.manyFromSql(inArray(Users.uri, mentions)); -}; - /** * Retrieves a user from a token. * @param access_token The access token to retrieve the user from. diff --git a/drizzle/types.ts b/drizzle/types.ts deleted file mode 100644 index c6f5db2c..00000000 --- a/drizzle/types.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { - BuildQueryResult, - DBQueryConfig, - ExtractTablesWithRelations, -} from "drizzle-orm"; - -import type * as schema from "./schema"; - -type Schema = typeof schema; -type TablesWithRelations = ExtractTablesWithRelations; - -export type IncludeRelation = - DBQueryConfig< - "one" | "many", - boolean, - TablesWithRelations, - TablesWithRelations[TableName] - >["with"]; - -export type IncludeColumns = - DBQueryConfig< - "one" | "many", - boolean, - TablesWithRelations, - TablesWithRelations[TableName] - >["columns"]; - -export type InferQueryModel< - TableName extends keyof TablesWithRelations, - Columns extends IncludeColumns | undefined = undefined, - With extends IncludeRelation | undefined = undefined, -> = BuildQueryResult< - TablesWithRelations, - TablesWithRelations[TableName], - { - columns: Columns; - with: With; - } ->; diff --git a/index.ts b/index.ts index ff785e75..16105825 100644 --- a/index.ts +++ b/index.ts @@ -11,9 +11,9 @@ import { ipBans } from "~middlewares/ip-bans"; import { logger } from "~middlewares/logger"; import { Note } from "~packages/database-interface/note"; import { handleGlitchRequest } from "~packages/glitch-server/main"; -import type { APIRouteExports } from "~packages/server-handler"; import { routes } from "~routes"; import { createServer } from "~server"; +import type { APIRouteExports } from "~types/api"; const timeAtStart = performance.now(); diff --git a/package.json b/package.json index 86dd24fd..752e6767 100644 --- a/package.json +++ b/package.json @@ -32,11 +32,11 @@ "dev": "bun run --hot index.ts", "start": "NODE_ENV=production bun run dist/index.js --prod", "lint": "bunx @biomejs/biome check .", - "prod-build": "bun run build.ts", - "benchmark:timeline": "bun run benchmarks/timelines.ts", + "build": "bun run build.ts", "cloc": "cloc . --exclude-dir node_modules,dist,.output,.nuxt,meta,logs,glitch,glitch-dev --exclude-ext sql,log,pem", "wc": "find server database *.ts docs packages types utils drizzle tests -type f -print0 | wc -m --files0-from=-", - "cli": "bun run cli.ts" + "cli": "bun run cli.ts", + "prune": "ts-prune | grep -v server/ | grep -v dist/ | grep -v '(used in module)'" }, "trustedDependencies": [ "@biomejs/biome", @@ -62,6 +62,7 @@ "@types/pg": "^8.11.5", "bun-types": "latest", "drizzle-kit": "^0.20.14", + "ts-prune": "^0.10.3", "typescript": "latest" }, "peerDependencies": { @@ -71,7 +72,6 @@ "@hackmd/markdown-it-task-lists": "^2.1.4", "@hono/zod-validator": "^0.2.1", "@json2csv/plainjs": "^7.0.6", - "@shikijs/markdown-it": "^1.3.0", "@tufjs/canonical-json": "^2.0.0", "blurhash": "^2.0.5", "bullmq": "^5.7.1", @@ -102,7 +102,6 @@ "oauth4webapi": "^2.4.0", "pg": "^8.11.5", "qs": "^6.12.1", - "request-parser": "workspace:*", "sharp": "^0.33.3", "string-comparison": "^1.3.0", "stringify-entities": "^4.0.4", diff --git a/packages/request-parser/index.ts b/packages/request-parser/index.ts deleted file mode 100644 index 9559978b..00000000 --- a/packages/request-parser/index.ts +++ /dev/null @@ -1,219 +0,0 @@ -import { parse } from "qs"; - -/** - * RequestParser - * Parses Request object into a JavaScript object - * based on the Content-Type header - * @param request Request object - * @returns JavaScript object of type T - */ -export class RequestParser { - constructor(public request: Request) {} - - /** - * Parse request body into a JavaScript object - * @returns JavaScript object of type T - * @throws Error if body is invalid - */ - async toObject() { - switch (await this.determineContentType()) { - case "application/json": - return { - ...(await this.parseJson()), - ...this.parseQuery(), - }; - case "application/x-www-form-urlencoded": - return { - ...(await this.parseFormUrlencoded()), - ...this.parseQuery(), - }; - case "multipart/form-data": - return { - ...(await this.parseFormData()), - ...this.parseQuery(), - }; - default: - return { ...this.parseQuery() } as T; - } - } - - /** - * Determine body content type - * If there is no Content-Type header, automatically - * guess content type. Cuts off after ";" character - * @returns Content-Type header value, or empty string if there is no body - * @throws Error if body is invalid - * @private - */ - private async determineContentType() { - const content_type = this.request.headers.get("Content-Type"); - - if (content_type?.startsWith("application/json")) { - return "application/json"; - } - - if (content_type?.startsWith("application/x-www-form-urlencoded")) { - return "application/x-www-form-urlencoded"; - } - - if (content_type?.startsWith("multipart/form-data")) { - return "multipart/form-data"; - } - - // Check if body is valid JSON - try { - await this.request.clone().json(); - return "application/json"; - } catch { - // This is not JSON - } - - // Check if body is valid FormData - try { - await this.request.clone().formData(); - return "multipart/form-data"; - } catch { - // This is not FormData - } - - if (content_type) { - return content_type.split(";")[0] ?? ""; - } - - if (this.request.body) { - throw new Error("Invalid body"); - } - - // If there is no body, return query parameters - return ""; - } - - /** - * Parse FormData body into a JavaScript object - * @returns JavaScript object of type T - * @private - * @throws Error if body is invalid - */ - private async parseFormData(): Promise> { - const formData = await this.request.clone().formData(); - const result: Partial = {}; - - // Extract the files from the FormData - for (const [key, value] of formData.entries()) { - if (value instanceof Blob) { - result[key as keyof T] = value as T[keyof T]; - } - } - - const formDataWithoutFiles = new FormData(); - for (const [key, value] of formData.entries()) { - if (!(value instanceof Blob)) { - formDataWithoutFiles.append(key, value); - } - } - - // Convert to URLSearchParams and parse as query - const searchParams = new URLSearchParams([ - ...formDataWithoutFiles.entries(), - ] as [string, string][]); - - const parsed = parse(searchParams.toString(), { - parseArrays: true, - interpretNumericEntities: true, - }); - - const casted = castBooleanObject( - parsed as PossiblyRecursiveObject, - ) as Partial; - - return { ...result, ...casted }; - } - - /** - * Parse application/x-www-form-urlencoded body into a JavaScript object - * @returns JavaScript object of type T - * @private - * @throws Error if body is invalid - */ - private async parseFormUrlencoded(): Promise> { - const parsed = parse(await this.request.text(), { - parseArrays: true, - interpretNumericEntities: true, - }); - - return castBooleanObject( - parsed as PossiblyRecursiveObject, - ) as Partial; - } - - /** - * Parse JSON body into a JavaScript object - * @returns JavaScript object of type T - * @private - * @throws Error if body is invalid - */ - private async parseJson(): Promise { - return (await this.request.json()) as T; - } - - /** - * Parse query parameters into a JavaScript object - * @private - * @throws Error if body is invalid - * @returns JavaScript object of type T - */ - parseQuery(): Partial { - const parsed = parse( - new URL(this.request.url).searchParams.toString(), - { - parseArrays: true, - interpretNumericEntities: true, - }, - ); - - return castBooleanObject( - parsed as PossiblyRecursiveObject, - ) as Partial; - } -} - -interface PossiblyRecursiveObject { - [key: string]: - | PossiblyRecursiveObject[] - | PossiblyRecursiveObject - | string - | string[] - | boolean; -} - -// Recursive -const castBooleanObject = (value: PossiblyRecursiveObject | string) => { - if (typeof value === "string") { - return castBoolean(value); - } - - for (const key in value) { - const child = value[key]; - if (Array.isArray(child)) { - value[key] = child.map((v) => castBooleanObject(v)) as string[]; - } else if (typeof child === "object") { - value[key] = castBooleanObject(child); - } else { - value[key] = castBoolean(child as string); - } - } - - return value; -}; - -const castBoolean = (value: string) => { - if (["true"].includes(value)) { - return true; - } - - if (["false"].includes(value)) { - return false; - } - - return value; -}; diff --git a/packages/request-parser/package.json b/packages/request-parser/package.json deleted file mode 100644 index 4697e599..00000000 --- a/packages/request-parser/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "request-parser", - "version": "0.0.0", - "main": "index.ts", - "dependencies": { "qs": "^6.12.1" }, - "devDependencies": { - "@types/qs": "^6.9.15" - } -} diff --git a/packages/request-parser/tests/request-parser.test.ts b/packages/request-parser/tests/request-parser.test.ts deleted file mode 100644 index e0eb2a61..00000000 --- a/packages/request-parser/tests/request-parser.test.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { describe, expect, it, test } from "bun:test"; -import { RequestParser } from ".."; - -describe("RequestParser", () => { - describe("Should parse query parameters correctly", () => { - test("With text parameters", async () => { - const request = new Request( - "http://localhost?param1=value1¶m2=value2", - ); - const result = await new RequestParser(request).parseQuery<{ - param1: string; - param2: string; - }>(); - expect(result).toEqual({ param1: "value1", param2: "value2" }); - }); - - test("With Array", async () => { - const request = new Request( - "http://localhost?test[]=value1&test[]=value2", - ); - const result = await new RequestParser(request).parseQuery<{ - test: string[]; - }>(); - expect(result?.test).toEqual(["value1", "value2"]); - }); - - test("With Array of objects", async () => { - const request = new Request( - "http://localhost?test[][key]=value1&test[][value]=value2", - ); - const result = await new RequestParser(request).parseQuery<{ - test: { key: string; value: string }[]; - }>(); - expect(result?.test).toEqual([{ key: "value1", value: "value2" }]); - }); - - test("With Array of multiple objects", async () => { - const request = new Request( - "http://localhost?test[][key]=value1&test[][value]=value2&test[][key]=value3&test[][value]=value4", - ); - const result = await new RequestParser(request).parseQuery<{ - test: { key: string[]; value: string[] }[]; - }>(); - expect(result?.test).toEqual([ - { key: ["value1", "value3"], value: ["value2", "value4"] }, - ]); - }); - - test("With both at once", async () => { - const request = new Request( - "http://localhost?param1=value1¶m2=value2&test[]=value1&test[]=value2", - ); - const result = await new RequestParser(request).parseQuery<{ - param1: string; - param2: string; - test: string[]; - }>(); - expect(result).toEqual({ - param1: "value1", - param2: "value2", - test: ["value1", "value2"], - }); - }); - }); - - it("should parse JSON body correctly", async () => { - const request = new Request("http://localhost", { - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ param1: "value1", param2: "value2" }), - }); - const result = await new RequestParser(request).toObject<{ - param1: string; - param2: string; - }>(); - expect(result).toEqual({ param1: "value1", param2: "value2" }); - }); - - it("should handle invalid JSON body", async () => { - const request = new Request("http://localhost", { - headers: { "Content-Type": "application/json" }, - body: "invalid json", - }); - const result = new RequestParser(request).toObject<{ - param1: string; - param2: string; - }>(); - expect(result).rejects.toThrow(); - }); - - describe("should parse form data correctly", () => { - test("With basic text parameters", async () => { - const formData = new FormData(); - formData.append("param1", "value1"); - formData.append("param2", "value2"); - const request = new Request("http://localhost", { - method: "POST", - body: formData, - }); - const result = await new RequestParser(request).toObject<{ - param1: string; - param2: string; - }>(); - expect(result).toEqual({ param1: "value1", param2: "value2" }); - }); - - test("With File object", async () => { - const file = new File(["content"], "filename.txt", { - type: "text/plain", - }); - const formData = new FormData(); - formData.append("file", file); - const request = new Request("http://localhost", { - method: "POST", - body: formData, - }); - const result = await new RequestParser(request).toObject<{ - file: File; - }>(); - expect(result?.file).toBeInstanceOf(File); - expect(await result?.file?.text()).toEqual("content"); - }); - - test("With Array", async () => { - const formData = new FormData(); - formData.append("test[]", "value1"); - formData.append("test[]", "value2"); - const request = new Request("http://localhost", { - method: "POST", - body: formData, - }); - const result = await new RequestParser(request).toObject<{ - test: string[]; - }>(); - expect(result?.test).toEqual(["value1", "value2"]); - }); - - test("With all three at once", async () => { - const file = new File(["content"], "filename.txt", { - type: "text/plain", - }); - const formData = new FormData(); - formData.append("param1", "value1"); - formData.append("param2", "value2"); - formData.append("file", file); - formData.append("test[]", "value1"); - formData.append("test[]", "value2"); - const request = new Request("http://localhost", { - method: "POST", - body: formData, - }); - const result = await new RequestParser(request).toObject<{ - param1: string; - param2: string; - file: File; - test: string[]; - }>(); - expect(result).toEqual({ - param1: "value1", - param2: "value2", - file: file, - test: ["value1", "value2"], - }); - }); - - test("URL Encoded", async () => { - const request = new Request("http://localhost", { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - body: "param1=value1¶m2=value2", - }); - const result = await new RequestParser(request).toObject<{ - param1: string; - param2: string; - }>(); - expect(result).toEqual({ param1: "value1", param2: "value2" }); - }); - }); -}); diff --git a/packages/server-handler/index.ts b/packages/server-handler/index.ts deleted file mode 100644 index 10fa5b6f..00000000 --- a/packages/server-handler/index.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { dualLogger } from "@loggers"; -import { errorResponse, jsonResponse, response } from "@response"; -import type { MatchedRoute } from "bun"; -import { type Config, config } from "config-manager"; -import type { Hono } from "hono"; -import type { RouterRoute } from "hono/types"; -import { LogLevel, type LogManager, type MultiLogManager } from "log-manager"; -import { RequestParser } from "request-parser"; -import type { ZodType, z } from "zod"; -import { fromZodError } from "zod-validation-error"; -import type { Application } from "~database/entities/Application"; -import { type AuthData, getFromRequest } from "~database/entities/User"; -import type { User } from "~packages/database-interface/user"; - -type MaybePromise = T | Promise; -export type HttpVerb = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "OPTIONS"; - -export type RouteHandler< - RouteMeta extends APIRouteMetadata, - ZodSchema extends ZodType, -> = ( - req: Request, - matchedRoute: MatchedRoute, - extraData: { - auth: { - // If the route doesn't require authentication, set the type to User | null - // Otherwise set to User - user: RouteMeta["auth"]["required"] extends true - ? User - : User | null; - token: RouteMeta["auth"]["required"] extends true - ? string - : string | null; - application: Application | null; - }; - parsedRequest: z.infer; - configManager: { - getConfig: () => Promise; - }; - }, -) => MaybePromise | MaybePromise; - -export interface APIRouteMetadata { - allowedMethods: HttpVerb[]; - ratelimits: { - max: number; - duration: number; - }; - route: string; - auth: { - required: boolean; - requiredOnMethods?: HttpVerb[]; - oauthPermissions?: string[]; - }; -} - -export interface APIRouteExports { - meta: APIRouteMetadata; - schemas?: { - query?: z.AnyZodObject; - body?: z.AnyZodObject; - }; - default: (app: Hono) => RouterRoute; -} - -export const processRoute = async ( - matchedRoute: MatchedRoute, - request: Request, - logger: LogManager | MultiLogManager, -): Promise => { - if (request.method === "OPTIONS") { - return response(); - } - - const route: APIRouteExports | null = await import( - matchedRoute.filePath - ).catch((e) => { - dualLogger.logError(LogLevel.ERROR, "Server.RouteImport", e as Error); - return null; - }); - - if (!route?.meta) { - return errorResponse("Route not found", 404); - } - - // Check if the request method is allowed - if (!route.meta.allowedMethods.includes(request.method as HttpVerb)) { - return errorResponse("Method not allowed", 405); - } - - const auth: AuthData = await getFromRequest(request); - - if ( - route.meta.auth.required || - route.meta.auth.requiredOnMethods?.includes(request.method as HttpVerb) - ) { - if (!auth.user) { - return errorResponse( - "Unauthorized: access to this method requires an authenticated user", - 401, - ); - } - } - - // Check if Content-Type header is missing if there is a body - if (request.clone().body) { - if (!request.headers.has("Content-Type")) { - return errorResponse( - `Content-Type header is missing but required on method ${request.method}`, - 400, - ); - } - } - - const parsedRequest = await new RequestParser(request.clone()) - .toObject() - .catch(async (err) => { - console.log(err); - await logger.logError( - LogLevel.ERROR, - "Server.RouteRequestParser", - err as Error, - ); - return null; - }); - - if (!parsedRequest) { - return errorResponse( - "The request could not be parsed, it may be malformed", - 400, - ); - } - - const parsingResult = route.schema?.safeParse(parsedRequest); - - if (parsingResult && !parsingResult.success) { - // Return a 422 error with the first error message - return errorResponse(fromZodError(parsingResult.error).toString(), 422); - } - - try { - const output = await route.default(request, matchedRoute, { - auth: { - token: auth?.token ?? null, - user: auth?.user ?? null, - application: auth?.application ?? null, - }, - parsedRequest: parsingResult - ? (parsingResult.data as z.infer) - : parsedRequest, - configManager: { - getConfig: async () => config as Config, - }, - }); - - // If the output is a normal JS object and not a Response, convert it to a jsonResponse - if (!(output instanceof Response)) { - return jsonResponse(output); - } - - return output; - } catch (err) { - await logger.log( - LogLevel.DEBUG, - "Server.RouteHandler", - (err as Error).toString(), - ); - await logger.logError( - LogLevel.ERROR, - "Server.RouteHandler", - err as Error, - ); - - return errorResponse( - `A server error occured: ${(err as Error).message}`, - 500, - ); - } -}; diff --git a/packages/server-handler/package.json b/packages/server-handler/package.json deleted file mode 100644 index ffb0f706..00000000 --- a/packages/server-handler/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "server-handler", - "version": "0.0.0", - "main": "index.ts", - "dependencies": { "zod": "^3.22.4", "zod-validation-error": "^3.2.0" } -} diff --git a/packages/server-handler/tests.test.ts b/packages/server-handler/tests.test.ts deleted file mode 100644 index 30ace68c..00000000 --- a/packages/server-handler/tests.test.ts +++ /dev/null @@ -1,340 +0,0 @@ -import { afterAll, describe, expect, it, mock } from "bun:test"; -import type { MatchedRoute } from "bun"; -import { LogManager } from "log-manager"; -import { z } from "zod"; -import { getTestUsers } from "~tests/utils"; -import { type APIRouteExports, processRoute } from "."; - -describe("Route Processor", () => { - it("should return a Response", async () => { - mock.module( - "./route", - () => - ({ - default: async () => new Response(), - meta: { - allowedMethods: ["GET"], - ratelimits: { - max: 100, - duration: 60, - }, - route: "/route", - auth: { - required: false, - }, - }, - schema: z.object({}), - }) as APIRouteExports, - ); - - const output = await processRoute( - { - filePath: "./route", - } as MatchedRoute, - new Request("https://test.com/route", { - method: "GET", - }), - new LogManager(Bun.file("/dev/null")), - ); - - expect(output).toBeInstanceOf(Response); - }); - - it("should return a 404 when the route does not exist", async () => { - const output = await processRoute( - { - filePath: "./nonexistent-route", - } as MatchedRoute, - new Request("https://test.com/nonexistent-route"), - new LogManager(Bun.file("/dev/null")), - ); - - expect(output.status).toBe(404); - }); - - it("should return a 405 when the request method is not allowed", async () => { - mock.module( - "./route", - () => - ({ - default: async () => new Response(), - meta: { - allowedMethods: ["POST"], - ratelimits: { - max: 100, - duration: 60, - }, - route: "/route", - auth: { - required: false, - }, - }, - schema: z.object({}), - }) as APIRouteExports, - ); - - const output = await processRoute( - { - filePath: "./route", - } as MatchedRoute, - new Request("https://test.com/route", { - method: "GET", - }), - new LogManager(Bun.file("/dev/null")), - ); - - expect(output.status).toBe(405); - }); - - it("should return a 401 when the route requires authentication but no user is authenticated", async () => { - mock.module( - "./route", - () => - ({ - default: async () => new Response(), - meta: { - allowedMethods: ["POST"], - ratelimits: { - max: 100, - duration: 60, - }, - route: "/route", - auth: { - required: true, - }, - }, - schema: z.object({}), - }) as APIRouteExports, - ); - - const output = await processRoute( - { - filePath: "./route", - } as MatchedRoute, - new Request("https://test.com/route", { - method: "POST", - }), - new LogManager(Bun.file("/dev/null")), - ); - - expect(output.status).toBe(401); - }); - - it("should return a 400 when the Content-Type header is missing but there is a body", async () => { - mock.module( - "./route", - () => - ({ - default: async () => new Response(), - meta: { - allowedMethods: ["POST", "PUT", "PATCH"], - ratelimits: { - max: 100, - duration: 60, - }, - route: "/route", - auth: { - required: false, - }, - }, - schema: z.object({}), - }) as APIRouteExports, - ); - - const output = await processRoute( - { - filePath: "./route", - } as MatchedRoute, - new Request("https://test.com/route", { - method: "POST", - body: "test", - }), - new LogManager(Bun.file("/dev/null")), - ); - - expect(output.status).toBe(400); - }); - - it("should return a 400 when the request could not be parsed", async () => { - mock.module( - "./route", - () => - ({ - default: async () => new Response(), - meta: { - allowedMethods: ["POST"], - ratelimits: { - max: 100, - duration: 60, - }, - route: "/route", - auth: { - required: false, - }, - }, - schema: z.object({}), - }) as APIRouteExports, - ); - - const output = await processRoute( - { - filePath: "./route", - } as MatchedRoute, - new Request("https://test.com/route", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: "invalid-json", - }), - new LogManager(Bun.file("/dev/null")), - ); - - expect(output.status).toBe(400); - }); - - it("should return a 422 when the request does not match the schema", async () => { - mock.module( - "./route", - () => - ({ - default: async () => new Response(), - meta: { - allowedMethods: ["POST"], - ratelimits: { - max: 100, - duration: 60, - }, - route: "/route", - auth: { - required: false, - }, - }, - schema: z.object({ - foo: z.string(), - }), - }) as APIRouteExports, - ); - - const output = await processRoute( - { - filePath: "./route", - } as MatchedRoute, - new Request("https://test.com/route", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ bar: "baz" }), - }), - new LogManager(Bun.file("/dev/null")), - ); - - expect(output.status).toBe(422); - }); - - it("should convert any JS objects returned by the route to a Response", async () => { - mock.module( - "./route", - () => - ({ - default: async () => ({ status: 200 }), - meta: { - allowedMethods: ["GET"], - ratelimits: { - max: 100, - duration: 60, - }, - route: "/route", - auth: { - required: false, - }, - }, - schema: z.object({}), - }) as APIRouteExports, - ); - - const output = await processRoute( - { - filePath: "./route", - } as MatchedRoute, - new Request("https://test.com/route", { - method: "GET", - }), - new LogManager(Bun.file("/dev/null")), - ); - - expect(output.status).toBe(200); - }); - - it("should handle route errors", async () => { - mock.module( - "./route", - () => - ({ - default: async () => { - throw new Error("Route error"); - }, - meta: { - allowedMethods: ["GET"], - ratelimits: { - max: 100, - duration: 60, - }, - route: "/route", - auth: { - required: false, - }, - }, - schema: z.object({}), - }) as APIRouteExports, - ); - - const output = await processRoute( - { - filePath: "./route", - } as MatchedRoute, - new Request("https://test.com/route", { - method: "GET", - }), - new LogManager(Bun.file("/dev/null")), - ); - - expect(output.status).toBe(500); - }); - - it("should return the route output when everything is valid", async () => { - mock.module( - "./route", - () => - ({ - default: async () => new Response("OK"), - meta: { - allowedMethods: ["GET"], - ratelimits: { - max: 100, - duration: 60, - }, - route: "/route", - auth: { - required: false, - }, - }, - schema: z.object({}), - }) as APIRouteExports, - ); - - const output = await processRoute( - { - filePath: "./route", - } as MatchedRoute, - new Request("https://test.com/route", { - method: "GET", - }), - new LogManager(Bun.file("/dev/null")), - ); - - expect(output.status).toBe(200); - expect(await output.text()).toBe("OK"); - }); -}); diff --git a/routes.ts b/routes.ts index 7e6f5a60..828479da 100644 --- a/routes.ts +++ b/routes.ts @@ -21,9 +21,3 @@ for (const [route, path] of Object.entries(routes)) { routes = Object.fromEntries(Object.entries(routes).reverse()); export { routes }; - -export const matchRoute = (request: Request) => { - const route = routeMatcher.match(request); - - return route ?? null; -}; diff --git a/server/api/api/v1/accounts/:id/index.ts b/server/api/api/v1/accounts/:id/index.ts index 2f2682f1..24992d99 100644 --- a/server/api/api/v1/accounts/:id/index.ts +++ b/server/api/api/v1/accounts/:id/index.ts @@ -1,4 +1,4 @@ -import { apiRoute, applyConfig, auth, handleZodError, idValidator } from "@api"; +import { applyConfig, auth, handleZodError, idValidator } from "@api"; import { zValidator } from "@hono/zod-validator"; import { errorResponse, jsonResponse } from "@response"; import type { Hono } from "hono"; diff --git a/server/api/api/v1/accounts/:id/remove_from_followers.ts b/server/api/api/v1/accounts/:id/remove_from_followers.ts index a3bd9102..e0fc4180 100644 --- a/server/api/api/v1/accounts/:id/remove_from_followers.ts +++ b/server/api/api/v1/accounts/:id/remove_from_followers.ts @@ -1,4 +1,4 @@ -import { apiRoute, applyConfig, auth, handleZodError, idValidator } from "@api"; +import { applyConfig, auth, handleZodError, idValidator } from "@api"; import { zValidator } from "@hono/zod-validator"; import { errorResponse, jsonResponse } from "@response"; import { and, eq } from "drizzle-orm"; diff --git a/server/api/api/v1/accounts/index.ts b/server/api/api/v1/accounts/index.ts index c8506393..3bb77173 100644 --- a/server/api/api/v1/accounts/index.ts +++ b/server/api/api/v1/accounts/index.ts @@ -1,4 +1,4 @@ -import { apiRoute, applyConfig, auth, handleZodError } from "@api"; +import { applyConfig, auth, handleZodError } from "@api"; import { zValidator } from "@hono/zod-validator"; import { jsonResponse, response } from "@response"; import { tempmailDomains } from "@tempmail"; diff --git a/server/api/api/v1/blocks/index.ts b/server/api/api/v1/blocks/index.ts index f21fe0f7..eb03b8c8 100644 --- a/server/api/api/v1/blocks/index.ts +++ b/server/api/api/v1/blocks/index.ts @@ -1,4 +1,4 @@ -import { apiRoute, applyConfig, auth, handleZodError, idValidator } from "@api"; +import { applyConfig, auth, handleZodError, idValidator } from "@api"; import { zValidator } from "@hono/zod-validator"; import { errorResponse, jsonResponse } from "@response"; import { and, gt, gte, lt, sql } from "drizzle-orm"; diff --git a/server/api/api/v1/favourites/index.ts b/server/api/api/v1/favourites/index.ts index 5b95ed93..3b804b34 100644 --- a/server/api/api/v1/favourites/index.ts +++ b/server/api/api/v1/favourites/index.ts @@ -1,4 +1,4 @@ -import { apiRoute, applyConfig, auth, handleZodError, idValidator } from "@api"; +import { applyConfig, auth, handleZodError, idValidator } from "@api"; import { zValidator } from "@hono/zod-validator"; import { errorResponse, jsonResponse } from "@response"; import { and, gt, gte, lt, sql } from "drizzle-orm"; diff --git a/server/api/api/v1/follow_requests/index.ts b/server/api/api/v1/follow_requests/index.ts index 950d44c0..707a92a3 100644 --- a/server/api/api/v1/follow_requests/index.ts +++ b/server/api/api/v1/follow_requests/index.ts @@ -1,4 +1,4 @@ -import { apiRoute, applyConfig, auth, handleZodError, idValidator } from "@api"; +import { applyConfig, auth, handleZodError, idValidator } from "@api"; import { zValidator } from "@hono/zod-validator"; import { errorResponse, jsonResponse } from "@response"; import { and, gt, gte, lt, sql } from "drizzle-orm"; diff --git a/server/api/api/v1/notifications/index.ts b/server/api/api/v1/notifications/index.ts index 1da9c650..bb0db8e7 100644 --- a/server/api/api/v1/notifications/index.ts +++ b/server/api/api/v1/notifications/index.ts @@ -1,4 +1,4 @@ -import { apiRoute, applyConfig, auth, handleZodError, idValidator } from "@api"; +import { applyConfig, auth, handleZodError, idValidator } from "@api"; import { zValidator } from "@hono/zod-validator"; import { errorResponse, jsonResponse } from "@response"; import { fetchTimeline } from "@timelines"; diff --git a/server/api/objects/[uuid]/index.ts b/server/api/objects/[uuid]/index.ts index 895b6aa0..9d7525b5 100644 --- a/server/api/objects/[uuid]/index.ts +++ b/server/api/objects/[uuid]/index.ts @@ -1,4 +1,4 @@ -import { apiRoute, applyConfig, handleZodError } from "@api"; +import { applyConfig, handleZodError } from "@api"; import { zValidator } from "@hono/zod-validator"; import { errorResponse, jsonResponse } from "@response"; import { and, eq, inArray, sql } from "drizzle-orm"; diff --git a/types/activitypub.ts b/types/activitypub.ts deleted file mode 100644 index ae4d8a83..00000000 --- a/types/activitypub.ts +++ /dev/null @@ -1,141 +0,0 @@ -export type APActivityPubContext = - | "https://www.w3.org/ns/activitystreams" - | { - ostatus: string; - atomUri: string; - inReplyToAtomUri: string; - conversation: string; - sensitive: string; - toot: string; - votersCount: string; - litepub: string; - directMessage: string; - }; - -export interface APActivityPubObject { - id: string; - type: string; - summary?: string; - inReplyTo?: string; - published: string; - url: string; - attributedTo: string; - to: string[]; - cc: string[]; - sensitive?: boolean; - atomUri: string; - inReplyToAtomUri?: string; - conversation: string; - content: string; - contentMap: Record; - attachment: APActivityPubAttachment[]; - tag: APTag[]; - context?: string; - quoteUri?: string; - quoteUrl?: string; - source?: { - content: string; - mediaType: string; - }; -} - -export interface APActivityPubAttachment { - type?: string; - mediaType?: string; - url?: string; - name?: string; -} - -export interface APActivityPubCollection { - id: string; - type: string; - first?: { - type: string; - next: string; - partOf: string; - items: T[]; - }; -} - -export interface APActivityPubNote extends APActivityPubObject { - type: "Note"; -} - -export interface APActivityPubActivity { - "@context": APActivityPubContext[]; - id: string; - type: string; - actor: string; - published: string; - to: string[]; - cc: string[]; - object: APActivityPubNote; -} - -export type APActorContext = - | "https://www.w3.org/ns/activitystreams" - | "https://w3id.org/security/v1" - | Record< - string, - | string - | { "@id": string; "@type": string } - | { "@container": string; "@id": string } - >; - -export interface APActorPublicKey { - id: string; - owner: string; - publicKeyPem: string; -} - -export interface APActorEndpoints { - sharedInbox: string; -} - -export interface APActorIcon { - type: string; - mediaType: string; - url: string; -} - -export interface APActor { - "@context": APActorContext[]; - id: string; - type: string; - following: string; - followers: string; - inbox: string; - outbox: string; - featured: string; - featuredTags: string; - preferredUsername: string; - name: string; - summary: string; - url: string; - manuallyApprovesFollowers: boolean; - discoverable: boolean; - indexable: boolean; - published: string; - memorial: boolean; - devices: string; - publicKey: APActorPublicKey; - tag: APTag[]; - attachment: APAttachment[]; - endpoints: APActorEndpoints; - icon: APActorIcon; -} - -export interface APTag { - type: string; - href: string; - name: string; -} - -export interface APAttachment { - type: string; - mediaType: string; - url: string; - name?: string; - blurhash?: string; - description?: string; -} diff --git a/types/api.ts b/types/api.ts index 7b695f52..48208962 100644 --- a/types/api.ts +++ b/types/api.ts @@ -1,5 +1,10 @@ -export interface APIRouteMeta { - allowedMethods: ("GET" | "POST" | "PUT" | "DELETE" | "PATCH")[]; +import type { Hono } from "hono"; +import type { RouterRoute } from "hono/types"; +import type { z } from "zod"; + +export type HttpVerb = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "OPTIONS"; +export interface APIRouteMetadata { + allowedMethods: HttpVerb[]; ratelimits: { max: number; duration: number; @@ -7,7 +12,16 @@ export interface APIRouteMeta { route: string; auth: { required: boolean; - requiredOnMethods?: ("GET" | "POST" | "PUT" | "DELETE" | "PATCH")[]; + requiredOnMethods?: HttpVerb[]; oauthPermissions?: string[]; }; } + +export interface APIRouteExports { + meta: APIRouteMetadata; + schemas?: { + query?: z.AnyZodObject; + body?: z.AnyZodObject; + }; + default: (app: Hono) => RouterRoute; +} diff --git a/types/lysand/Extension.ts b/types/lysand/Extension.ts deleted file mode 100644 index ebdefa56..00000000 --- a/types/lysand/Extension.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { LysandObjectType } from "./Object"; - -export interface ExtensionType extends LysandObjectType { - type: "Extension"; - extension_type: string; -} diff --git a/types/lysand/Object.ts b/types/lysand/Object.ts deleted file mode 100644 index 86103f1d..00000000 --- a/types/lysand/Object.ts +++ /dev/null @@ -1,177 +0,0 @@ -import type { Emoji } from "./extensions/org.lysand/custom_emojis"; - -export interface LysandObjectType { - type: string; - id: string; // Either a UUID or some kind of time-based UUID-compatible system - uri: string; // URI to the note - created_at: string; - extensions?: { - // Should be in the format - // "organization:extension_name": value - // Example: "org.joinmastodon:spoiler_text": "This is a spoiler!" - "org.lysand:custom_emojis"?: { - emojis: Emoji[]; - }; - "org.lysand:reactions"?: { - reactions: string; - }; - "org.lysand:polls"?: { - poll: { - options: ContentFormat[][]; - votes: number[]; - expires_at: string; - multiple_choice: boolean; - }; - }; - - [key: string]: Record | undefined; - }; -} - -export interface ActorPublicKeyData { - public_key: string; - actor: string; -} - -export interface Collection { - first: string; - last: string; - next?: string; - prev?: string; - items: T[]; -} - -export interface LysandUser extends LysandObjectType { - type: "User"; - bio: ContentFormat[]; - - inbox: string; - outbox: string; - followers: string; - following: string; - liked: string; - disliked: string; - featured: string; - - indexable: boolean; - fields?: { - key: ContentFormat[]; - value: ContentFormat[]; - }[]; - display_name?: string; - public_key?: ActorPublicKeyData; - username: string; - avatar?: ContentFormat[]; - header?: ContentFormat[]; -} - -export interface LysandPublication extends LysandObjectType { - type: "Note" | "Patch"; - author: string; - contents: ContentFormat[]; - mentions: string[]; - replies_to: string[]; - quotes: string[]; - is_sensitive: boolean; - subject: string; - attachments: ContentFormat[][]; -} - -export interface LysandAction extends LysandObjectType { - type: - | "Like" - | "Dislike" - | "Follow" - | "FollowAccept" - | "FollowReject" - | "Announce" - | "Undo" - | "Extension"; - author: string; -} - -/** - * A Note is a publication on the network, such as a post or comment - */ -export interface Note extends LysandPublication { - type: "Note"; -} - -/** - * A Patch is an edit to a Note - */ -export interface Patch extends LysandPublication { - type: "Patch"; - patched_id: string; - patched_at: string; -} - -export interface Like extends LysandAction { - type: "Like"; - object: string; -} - -export interface Dislike extends LysandAction { - type: "Dislike"; - object: string; -} - -export interface Announce extends LysandAction { - type: "Announce"; - object: string; -} - -export interface Undo extends LysandAction { - type: "Undo"; - object: string; -} - -export interface Follow extends LysandAction { - type: "Follow"; - followee: string; -} - -export interface FollowAccept extends LysandAction { - type: "FollowAccept"; - follower: string; -} - -export interface FollowReject extends LysandAction { - type: "FollowReject"; - follower: string; -} - -export interface ServerMetadata extends LysandObjectType { - type: "ServerMetadata"; - name: string; - version?: string; - description?: string; - website?: string; - moderators?: string[]; - admins?: string[]; - logo?: ContentFormat[]; - banner?: ContentFormat[]; - supported_extensions?: string[]; -} - -/** - * Content format is an array of objects that contain the content and the content type. - */ -export interface ContentFormat { - content: string; - content_type: string; - description?: string; - size?: number; - hash?: { - md5?: string; - sha1?: string; - sha256?: string; - sha512?: string; - [key: string]: string | undefined; - }; - blurhash?: string; - fps?: number; - width?: number; - height?: number; - duration?: number; -} diff --git a/types/lysand/extensions/org.lysand/custom_emojis.ts b/types/lysand/extensions/org.lysand/custom_emojis.ts deleted file mode 100644 index f2070357..00000000 --- a/types/lysand/extensions/org.lysand/custom_emojis.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { ContentFormat } from "../../Object"; - -export interface Emoji { - name: string; - url: ContentFormat[]; - alt?: string; -} diff --git a/types/lysand/extensions/org.lysand/polls.ts b/types/lysand/extensions/org.lysand/polls.ts deleted file mode 100644 index ec89ba11..00000000 --- a/types/lysand/extensions/org.lysand/polls.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { ExtensionType } from "../../Extension"; - -export interface OrgLysandPollsVoteType extends ExtensionType { - extension_type: "org.lysand:polls/Vote"; - author: string; - poll: string; - option: number; -} - -export interface OrgLysandPollsVoteResultType extends ExtensionType { - extension_type: "org.lysand:polls/VoteResult"; - poll: string; - votes: number[]; -} diff --git a/types/lysand/extensions/org.lysand/reactions.ts b/types/lysand/extensions/org.lysand/reactions.ts deleted file mode 100644 index 677f52ff..00000000 --- a/types/lysand/extensions/org.lysand/reactions.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { ExtensionType } from "../../Extension"; - -export interface OrgLysandReactionsType extends ExtensionType { - extension_type: "org.lysand:reactions/Reaction"; - author: string; - object: string; - content: string; -} diff --git a/utils/api.ts b/utils/api.ts index 63bd38f6..ebd87632 100644 --- a/utils/api.ts +++ b/utils/api.ts @@ -13,17 +13,12 @@ import { exactly, } from "magic-regexp"; import { parse } from "qs"; -import type { - APIRouteExports, - APIRouteMetadata, - HttpVerb, - RouteHandler, -} from "server-handler"; import type { z } from "zod"; import { fromZodError } from "zod-validation-error"; import type { Application } from "~database/entities/Application"; -import { getFromHeader, getFromRequest } from "~database/entities/User"; +import { getFromHeader } from "~database/entities/User"; import type { User } from "~packages/database-interface/user"; +import type { APIRouteMetadata, HttpVerb } from "~types/api"; export const applyConfig = (routeMeta: APIRouteMetadata) => { const newMeta = routeMeta; @@ -39,15 +34,6 @@ export const applyConfig = (routeMeta: APIRouteMetadata) => { return newMeta; }; -export const apiRoute = < - Metadata extends APIRouteMetadata, - ZodSchema extends Zod.AnyZodObject, ->( - routeFunction: APIRouteExports["default"], -) => { - return routeFunction; -}; - export const idValidator = createRegExp( anyOf(digit, charIn("ABCDEF")).times(8), exactly("-"), diff --git a/utils/meilisearch.ts b/utils/meilisearch.ts index c4ff79b1..09b6e919 100644 --- a/utils/meilisearch.ts +++ b/utils/meilisearch.ts @@ -3,8 +3,6 @@ import { config } from "config-manager"; import { count } from "drizzle-orm"; import { LogLevel, type LogManager, type MultiLogManager } from "log-manager"; import { Meilisearch } from "meilisearch"; -import type { Status } from "~database/entities/Status"; -import type { UserType } from "~database/entities/User"; import { db } from "~drizzle/db"; import { Notes, Users } from "~drizzle/schema"; import type { User } from "~packages/database-interface/user"; @@ -54,18 +52,6 @@ export enum MeiliIndexType { Statuses = "statuses", } -export const addStausToMeilisearch = async (status: Status) => { - if (!config.meilisearch.enabled) return; - - await meilisearch.index(MeiliIndexType.Statuses).addDocuments([ - { - id: status.id, - content: status.content, - createdAt: status.createdAt, - }, - ]); -}; - export const addUserToMeilisearch = async (user: User) => { if (!config.meilisearch.enabled) return; diff --git a/utils/merge.ts b/utils/merge.ts deleted file mode 100644 index 87d77345..00000000 --- a/utils/merge.ts +++ /dev/null @@ -1,17 +0,0 @@ -export const deepMerge = ( - target: Record, - source: Record, -) => { - const result = { ...target, ...source }; - for (const key of Object.keys(result)) { - result[key] = - typeof target[key] === "object" && typeof source[key] === "object" - ? // @ts-expect-error deepMerge is recursive - deepMerge(target[key], source[key]) - : structuredClone(result[key]); - } - return result; -}; - -export const deepMergeArray = (array: Record[]) => - array.reduce((ci, ni) => deepMerge(ci, ni), {}); diff --git a/utils/module.ts b/utils/module.ts deleted file mode 100644 index a9aefd6a..00000000 --- a/utils/module.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { fileURLToPath } from "node:url"; - -/** - * Determines whether a module is the entry point for the running node process. - * This works for both CommonJS and ES6 environments. - * - * ### CommonJS - * ```js - * if (moduleIsEntry(module)) { - * console.log('WOO HOO!!!'); - * } - * ``` - * - * ### ES6 - * ```js - * if (moduleIsEntry(import.meta.url)) { - * console.log('WOO HOO!!!'); - * } - * ``` - */ -export const moduleIsEntry = (moduleOrImportMetaUrl: NodeModule | string) => { - if (typeof moduleOrImportMetaUrl === "string") { - return process.argv[1] === fileURLToPath(moduleOrImportMetaUrl); - } - - if (typeof require !== "undefined" && "exports" in moduleOrImportMetaUrl) { - return require.main === moduleOrImportMetaUrl; - } - - return false; -}; diff --git a/utils/oauth.ts b/utils/oauth.ts index a7dcc0c2..bf46088c 100644 --- a/utils/oauth.ts +++ b/utils/oauth.ts @@ -59,5 +59,3 @@ export const checkIfOauthIsValid = ( return false; }; - -export const oauthCodeVerifiers: Record = {}; diff --git a/utils/temp.ts b/utils/temp.ts deleted file mode 100644 index 865a1b59..00000000 --- a/utils/temp.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { exists, mkdir, readFile, writeFile } from "node:fs/promises"; -import { join } from "node:path"; - -export const writeToTempDirectory = async (filename: string, data: string) => { - const tempDir = join("/tmp/", "lysand"); - if (!(await exists(tempDir))) await mkdir(tempDir); - - const tempFile = join(tempDir, filename); - await writeFile(tempFile, data); - - return tempFile; -}; - -export const readFromTempDirectory = async (filename: string) => { - const tempDir = join("/tmp/", "lysand"); - if (!(await exists(tempDir))) await mkdir(tempDir); - - const tempFile = join(tempDir, filename); - return readFile(tempFile, "utf-8"); -};