From a1e02d0d78daa4a39cea10d9329282a3439c1cc6 Mon Sep 17 00:00:00 2001 From: Jesse Wierzbinski Date: Wed, 12 Jun 2024 16:26:43 -1000 Subject: [PATCH] refactor: :rotating_light: Turn every linter rule on and fix issues (there were a LOT :3) --- biome.json | 48 +++- build.ts | 8 - cli/base.ts | 2 +- cli/commands/emoji/add.ts | 2 +- cli/commands/emoji/import.ts | 2 +- cli/commands/start.ts | 4 +- cli/commands/user/create.ts | 2 +- .../{Application.ts => application.ts} | 4 +- .../entities/{Attachment.ts => attachment.ts} | 2 +- database/entities/{Emoji.ts => emoji.ts} | 13 +- .../entities/{Federation.ts => federation.ts} | 6 +- .../entities/{Instance.ts => instance.ts} | 6 +- database/entities/{Like.ts => like.ts} | 0 .../{Notification.ts => notification.ts} | 14 +- .../{Relationship.ts => relationship.ts} | 4 +- database/entities/{Status.ts => status.ts} | 32 +-- database/entities/{Token.ts => token.ts} | 2 +- database/entities/{User.ts => user.ts} | 64 +++-- drizzle/db.ts | 15 +- drizzle/schema.ts | 176 +++++++------- index.ts | 43 ++-- middlewares/bait.ts | 14 +- middlewares/ip-bans.ts | 10 +- middlewares/logger.ts | 4 +- packages/config-manager/config.type.ts | 6 +- packages/config-manager/index.ts | 17 -- packages/database-interface/attachment.ts | 82 ++++--- packages/database-interface/note.ts | 115 +++++---- packages/database-interface/oauth.ts | 16 +- packages/database-interface/role.ts | 12 +- packages/database-interface/timeline.ts | 169 +++++++++++-- packages/database-interface/user.ts | 224 +++++++++--------- packages/glitch-server/main.ts | 24 +- packages/log-manager/index.ts | 149 +++++++++--- .../log-manager/tests/log-manager.test.ts | 17 +- packages/media-manager/index.ts | 32 ++- .../tests/media-backends.test.ts | 8 +- server/api/api/_fe/config/index.ts | 2 +- server/api/api/auth/login/index.test.ts | 2 +- server/api/api/auth/login/index.ts | 21 +- server/api/api/auth/mastodon-login/index.ts | 16 +- server/api/api/auth/mastodon-logout/index.ts | 2 +- server/api/api/auth/redirect/index.ts | 3 +- server/api/api/v1/accounts/:id/block.test.ts | 6 +- server/api/api/v1/accounts/:id/block.ts | 18 +- server/api/api/v1/accounts/:id/follow.test.ts | 6 +- server/api/api/v1/accounts/:id/follow.ts | 18 +- .../api/api/v1/accounts/:id/followers.test.ts | 6 +- server/api/api/v1/accounts/:id/followers.ts | 10 +- .../api/api/v1/accounts/:id/following.test.ts | 6 +- server/api/api/v1/accounts/:id/following.ts | 10 +- server/api/api/v1/accounts/:id/index.test.ts | 6 +- server/api/api/v1/accounts/:id/index.ts | 8 +- server/api/api/v1/accounts/:id/mute.test.ts | 6 +- server/api/api/v1/accounts/:id/mute.ts | 18 +- server/api/api/v1/accounts/:id/note.ts | 18 +- server/api/api/v1/accounts/:id/pin.ts | 18 +- .../v1/accounts/:id/remove_from_followers.ts | 18 +- .../api/api/v1/accounts/:id/statuses.test.ts | 12 +- server/api/api/v1/accounts/:id/statuses.ts | 8 +- server/api/api/v1/accounts/:id/unblock.ts | 18 +- server/api/api/v1/accounts/:id/unfollow.ts | 18 +- server/api/api/v1/accounts/:id/unmute.test.ts | 6 +- server/api/api/v1/accounts/:id/unmute.ts | 18 +- server/api/api/v1/accounts/:id/unpin.ts | 18 +- .../v1/accounts/familiar_followers/index.ts | 8 +- server/api/api/v1/accounts/index.ts | 30 ++- .../api/api/v1/accounts/lookup/index.test.ts | 4 +- server/api/api/v1/accounts/lookup/index.ts | 10 +- .../api/v1/accounts/relationships/index.ts | 16 +- .../api/api/v1/accounts/search/index.test.ts | 4 +- server/api/api/v1/accounts/search/index.ts | 10 +- .../v1/accounts/update_credentials/index.ts | 20 +- .../v1/accounts/verify_credentials/index.ts | 8 +- server/api/api/v1/apps/index.ts | 2 +- .../api/v1/apps/verify_credentials/index.ts | 16 +- server/api/api/v1/blocks/index.ts | 8 +- server/api/api/v1/custom_emojis/index.ts | 6 +- server/api/api/v1/emojis/:id/index.ts | 35 +-- server/api/api/v1/emojis/index.ts | 15 +- server/api/api/v1/favourites/index.ts | 8 +- .../follow_requests/:account_id/authorize.ts | 21 +- .../v1/follow_requests/:account_id/reject.ts | 21 +- server/api/api/v1/follow_requests/index.ts | 8 +- server/api/api/v1/frontend/config/index.ts | 2 +- server/api/api/v1/instance/index.ts | 6 +- server/api/api/v1/instance/rules.ts | 2 +- server/api/api/v1/markers/index.ts | 24 +- server/api/api/v1/media/:id/index.ts | 12 +- server/api/api/v1/media/index.ts | 8 +- server/api/api/v1/mutes/index.ts | 8 +- .../api/v1/notifications/:id/dismiss.test.ts | 4 +- .../api/api/v1/notifications/:id/dismiss.ts | 6 +- .../api/v1/notifications/:id/index.test.ts | 6 +- server/api/api/v1/notifications/:id/index.ts | 11 +- .../api/v1/notifications/clear/index.test.ts | 4 +- .../api/api/v1/notifications/clear/index.ts | 6 +- .../destroy_multiple/index.test.ts | 4 +- .../notifications/destroy_multiple/index.ts | 6 +- server/api/api/v1/notifications/index.test.ts | 6 +- server/api/api/v1/notifications/index.ts | 16 +- server/api/api/v1/profile/avatar.ts | 8 +- server/api/api/v1/profile/header.ts | 8 +- server/api/api/v1/roles/:id/index.test.ts | 4 +- server/api/api/v1/roles/:id/index.ts | 6 +- server/api/api/v1/roles/index.ts | 2 +- server/api/api/v1/sso/:id/index.ts | 2 +- server/api/api/v1/sso/index.ts | 2 +- server/api/api/v1/statuses/:id/context.ts | 10 +- .../api/api/v1/statuses/:id/favourite.test.ts | 6 +- server/api/api/v1/statuses/:id/favourite.ts | 20 +- .../api/v1/statuses/:id/favourited_by.test.ts | 4 +- .../api/api/v1/statuses/:id/favourited_by.ts | 11 +- server/api/api/v1/statuses/:id/index.ts | 22 +- server/api/api/v1/statuses/:id/pin.ts | 18 +- server/api/api/v1/statuses/:id/reblog.ts | 14 +- .../api/v1/statuses/:id/reblogged_by.test.ts | 4 +- .../api/api/v1/statuses/:id/reblogged_by.ts | 14 +- server/api/api/v1/statuses/:id/source.ts | 16 +- .../api/v1/statuses/:id/unfavourite.test.ts | 6 +- server/api/api/v1/statuses/:id/unfavourite.ts | 20 +- server/api/api/v1/statuses/:id/unpin.ts | 22 +- server/api/api/v1/statuses/:id/unreblog.ts | 20 +- server/api/api/v1/statuses/index.test.ts | 26 +- server/api/api/v1/statuses/index.ts | 12 +- server/api/api/v1/timelines/home.test.ts | 14 +- server/api/api/v1/timelines/home.ts | 14 +- server/api/api/v1/timelines/public.test.ts | 18 +- server/api/api/v1/timelines/public.ts | 8 +- server/api/api/v2/filters/:id/index.ts | 14 +- server/api/api/v2/filters/index.ts | 9 +- server/api/api/v2/instance/index.ts | 4 +- server/api/api/v2/media/index.ts | 10 +- server/api/api/v2/search/index.ts | 18 +- server/api/media/:id/index.ts | 3 +- server/api/media/proxy/:id.ts | 3 +- server/api/oauth/authorize/index.ts | 57 +++-- .../api/oauth/sso/:issuer/callback/index.ts | 18 +- server/api/oauth/sso/index.ts | 3 +- server/api/objects/:id/index.ts | 4 +- server/api/users/:uuid/inbox/index.ts | 19 +- server/api/well-known/host-meta/index.ts | 2 +- server/api/well-known/lysand.ts | 2 +- server/api/well-known/nodeinfo/2.0/index.ts | 2 +- server/api/well-known/nodeinfo/index.ts | 2 +- .../well-known/openid-configuration/index.ts | 14 +- tests/api.test.ts | 4 +- tests/api/accounts.test.ts | 60 ++--- tests/api/statuses.test.ts | 70 +++--- tests/oauth-scopes.test.ts | 2 +- tests/oauth.test.ts | 36 +-- tests/utils.ts | 10 +- types/api.ts | 8 +- types/mastodon/account.ts | 4 +- types/mastodon/announcement.ts | 10 +- types/mastodon/context.ts | 4 +- types/mastodon/conversation.ts | 2 +- types/mastodon/filter.ts | 2 +- types/mastodon/follow_request.ts | 4 +- types/mastodon/instance.ts | 4 +- types/mastodon/poll.ts | 2 +- types/mastodon/reaction.ts | 4 +- types/mastodon/report.ts | 4 +- types/mastodon/results.ts | 6 +- types/mastodon/scheduled_status.ts | 2 +- types/mastodon/source.ts | 2 +- types/mastodon/status.ts | 8 +- types/mastodon/status_params.ts | 2 +- types/mastodon/tag.ts | 2 +- utils/api.ts | 176 ++++++++++---- utils/content_types.ts | 15 +- utils/loggers.ts | 4 +- utils/markdown.ts | 12 +- utils/meilisearch.ts | 45 ++-- utils/oauth.ts | 2 +- utils/sanitization.ts | 2 +- utils/timelines.ts | 6 +- 177 files changed, 1826 insertions(+), 1248 deletions(-) rename database/entities/{Application.ts => application.ts} (89%) rename database/entities/{Attachment.ts => attachment.ts} (87%) rename database/entities/{Emoji.ts => emoji.ts} (92%) rename database/entities/{Federation.ts => federation.ts} (95%) rename database/entities/{Instance.ts => instance.ts} (93%) rename database/entities/{Like.ts => like.ts} (100%) rename database/entities/{Notification.ts => notification.ts} (85%) rename database/entities/{Relationship.ts => relationship.ts} (95%) rename database/entities/{Status.ts => status.ts} (95%) rename database/entities/{Token.ts => token.ts} (90%) rename database/entities/{User.ts => user.ts} (93%) diff --git a/biome.json b/biome.json index 36d9da97..71e530ec 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/1.6.4/schema.json", + "$schema": "https://biomejs.dev/schemas/1.8.1/schema.json", "organizeImports": { "enabled": true, "ignore": ["node_modules", "dist", "glitch", "glitch-dev"] @@ -7,7 +7,48 @@ "linter": { "enabled": true, "rules": { - "recommended": true + "all": true, + "correctness": { + "noNodejsModules": "off" + }, + "style": { + "noDefaultExport": "off", + "noParameterProperties": "off", + "noNamespaceImport": "off", + "useFilenamingConvention": "off", + "useNamingConvention": { + "level": "warn", + "options": { + "requireAscii": false, + "strictCase": false, + "conventions": [ + { + "selector": { + "kind": "typeProperty" + }, + "formats": [ + "camelCase", + "CONSTANT_CASE", + "PascalCase", + "snake_case" + ] + }, + { + "selector": { + "kind": "objectLiteralProperty", + "scope": "any" + }, + "formats": [ + "camelCase", + "CONSTANT_CASE", + "PascalCase", + "snake_case" + ] + } + ] + } + } + } }, "ignore": ["node_modules", "dist", "glitch", "glitch-dev"] }, @@ -16,5 +57,8 @@ "indentStyle": "space", "indentWidth": 4, "ignore": ["node_modules", "dist", "glitch", "glitch-dev"] + }, + "javascript": { + "globals": ["Bun", "HTMLRewriter"] } } diff --git a/build.ts b/build.ts index 8bcbdb92..41772e57 100644 --- a/build.ts +++ b/build.ts @@ -1,5 +1,4 @@ import { $ } from "bun"; -import chalk from "chalk"; import ora from "ora"; import { routes } from "~/routes"; @@ -21,7 +20,6 @@ await Bun.build({ external: ["unzipit"], }).then((output) => { if (!output.success) { - console.log(output.logs); process.exit(1); } }); @@ -56,9 +54,3 @@ await $`cp package.json dist/package.json`; await $`cp cli/theme.json dist/cli/theme.json`; buildSpinner.stop(); - -console.log( - `${chalk.green("✓")} Built project. You can now run it with ${chalk.green( - "bun run dist/index.js", - )}`, -); diff --git a/cli/base.ts b/cli/base.ts index 3658a6bf..85164f1a 100644 --- a/cli/base.ts +++ b/cli/base.ts @@ -2,7 +2,7 @@ import { consoleLogger } from "@/loggers"; import { Command } from "@oclif/core"; import { setupDatabase } from "~/drizzle/db"; -export abstract class BaseCommand extends Command { +export abstract class BaseCommand<_T extends typeof Command> extends Command { protected async init(): Promise { await super.init(); diff --git a/cli/commands/emoji/add.ts b/cli/commands/emoji/add.ts index c7acbc9b..9757178c 100644 --- a/cli/commands/emoji/add.ts +++ b/cli/commands/emoji/add.ts @@ -2,7 +2,7 @@ import { Args } from "@oclif/core"; import chalk from "chalk"; import ora from "ora"; import { BaseCommand } from "~/cli/base"; -import { getUrl } from "~/database/entities/Attachment"; +import { getUrl } from "~/database/entities/attachment"; import { db } from "~/drizzle/db"; import { Emojis } from "~/drizzle/schema"; import { config } from "~/packages/config-manager"; diff --git a/cli/commands/emoji/import.ts b/cli/commands/emoji/import.ts index c89998ca..9eae7cf0 100644 --- a/cli/commands/emoji/import.ts +++ b/cli/commands/emoji/import.ts @@ -5,7 +5,7 @@ import { lookup } from "mime-types"; import ora from "ora"; import { unzip } from "unzipit"; import { BaseCommand } from "~/cli/base"; -import { getUrl } from "~/database/entities/Attachment"; +import { getUrl } from "~/database/entities/attachment"; import { db } from "~/drizzle/db"; import { Emojis } from "~/drizzle/schema"; import { config } from "~/packages/config-manager"; diff --git a/cli/commands/start.ts b/cli/commands/start.ts index b62426c3..250f80f3 100644 --- a/cli/commands/start.ts +++ b/cli/commands/start.ts @@ -33,14 +33,14 @@ export default class Start extends BaseCommand { public async run(): Promise { const { flags } = await this.parse(Start); - const numCPUs = flags["all-threads"] ? os.cpus().length : flags.threads; + const numCpUs = flags["all-threads"] ? os.cpus().length : flags.threads; // Check if index is a JS or TS file (depending on the environment) const index = (await Bun.file("index.ts").exists()) ? "index.ts" : "index.js"; - for (let i = 0; i < numCPUs; i++) { + for (let i = 0; i < numCpUs; i++) { const args = ["bun", index]; if (i !== 0 || flags.silent) { args.push("--silent"); diff --git a/cli/commands/user/create.ts b/cli/commands/user/create.ts index ba0f8303..bbe841d7 100644 --- a/cli/commands/user/create.ts +++ b/cli/commands/user/create.ts @@ -137,7 +137,7 @@ export default class UserCreate extends BaseCommand { ), ); - if (!flags.format && !flags["set-password"]) { + if (!(flags.format || flags["set-password"])) { const link = ""; this.log( diff --git a/database/entities/Application.ts b/database/entities/application.ts similarity index 89% rename from database/entities/Application.ts rename to database/entities/application.ts index cff3817c..599ac427 100644 --- a/database/entities/Application.ts +++ b/database/entities/application.ts @@ -1,7 +1,7 @@ import type { InferSelectModel } from "drizzle-orm"; import { db } from "~/drizzle/db"; import type { Applications } from "~/drizzle/schema"; -import type { Application as APIApplication } from "~/types/mastodon/application"; +import type { Application as apiApplication } from "~/types/mastodon/application"; export type Application = InferSelectModel; @@ -27,7 +27,7 @@ export const getFromToken = async ( * Converts this application to an API application. * @returns The API application representation of this application. */ -export const applicationToAPI = (app: Application): APIApplication => { +export const applicationToApi = (app: Application): apiApplication => { return { name: app.name, website: app.website, diff --git a/database/entities/Attachment.ts b/database/entities/attachment.ts similarity index 87% rename from database/entities/Attachment.ts rename to database/entities/attachment.ts index 7bb82912..98b81c8b 100644 --- a/database/entities/Attachment.ts +++ b/database/entities/attachment.ts @@ -2,7 +2,7 @@ import { MediaBackendType } from "media-manager"; import type { Config } from "~/packages/config-manager"; export const getUrl = (name: string, config: Config) => { - if (config.media.backend === MediaBackendType.LOCAL) { + if (config.media.backend === MediaBackendType.Local) { return new URL(`/media/${name}`, config.http.base_url).toString(); } if (config.media.backend === MediaBackendType.S3) { diff --git a/database/entities/Emoji.ts b/database/entities/emoji.ts similarity index 92% rename from database/entities/Emoji.ts rename to database/entities/emoji.ts index 90cd6947..2bbdd034 100644 --- a/database/entities/Emoji.ts +++ b/database/entities/emoji.ts @@ -4,8 +4,8 @@ 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 { Emoji as apiEmoji } from "~/types/mastodon/emoji"; +import { addInstanceIfNotExists } from "./instance"; export type EmojiWithInstance = InferSelectModel & { instance: InferSelectModel | null; @@ -18,7 +18,9 @@ export type EmojiWithInstance = InferSelectModel & { */ export const parseEmojis = async (text: string) => { const matches = text.match(emojiValidatorWithColons); - if (!matches) return []; + if (!matches) { + return []; + } const emojis = await db.query.Emojis.findMany({ where: (emoji, { eq, or }) => or( @@ -56,11 +58,12 @@ export const fetchEmoji = async ( ) .limit(1); - if (existingEmoji[0]) + if (existingEmoji[0]) { return { ...existingEmoji[0].Emojis, instance: existingEmoji[0].Instances, }; + } const foundInstance = host ? await addInstanceIfNotExists(host) : null; @@ -90,7 +93,7 @@ export const fetchEmoji = async ( * Converts the emoji to an APIEmoji object. * @returns The APIEmoji object. */ -export const emojiToAPI = (emoji: EmojiWithInstance): APIEmoji => { +export const emojiToApi = (emoji: EmojiWithInstance): apiEmoji => { return { // @ts-expect-error ID is not in regular Mastodon API id: emoji.id, diff --git a/database/entities/Federation.ts b/database/entities/federation.ts similarity index 95% rename from database/entities/Federation.ts rename to database/entities/federation.ts index 9e9a718b..b84048da 100644 --- a/database/entities/Federation.ts +++ b/database/entities/federation.ts @@ -7,7 +7,7 @@ import { config } from "config-manager"; import type { User } from "~/packages/database-interface/user"; import { LogLevel, LogManager } from "~/packages/log-manager"; -export const localObjectURI = (id: string) => +export const localObjectUri = (id: string) => new URL(`/objects/${id}`, config.http.base_url).toString(); export const objectToInboxRequest = async ( @@ -52,14 +52,14 @@ export const objectToInboxRequest = async ( // Log public key new LogManager(Bun.stdout).log( - LogLevel.DEBUG, + LogLevel.Debug, "Inbox.Signature", `Sender public key: ${author.data.publicKey}`, ); // Log signed string new LogManager(Bun.stdout).log( - LogLevel.DEBUG, + LogLevel.Debug, "Inbox.Signature", `Signed string:\n${signedString}`, ); diff --git a/database/entities/Instance.ts b/database/entities/instance.ts similarity index 93% rename from database/entities/Instance.ts rename to database/entities/instance.ts index a3ee752a..cef9e05c 100644 --- a/database/entities/Instance.ts +++ b/database/entities/instance.ts @@ -19,9 +19,9 @@ export const addInstanceIfNotExists = async (url: string) => { where: (instance, { eq }) => eq(instance.baseUrl, host), }); - if (found) return found; - - console.log(`Fetching instance metadata for ${origin}`); + if (found) { + return found; + } // Fetch the instance configuration const metadata = (await fetch(new URL("/.well-known/lysand", origin)).then( diff --git a/database/entities/Like.ts b/database/entities/like.ts similarity index 100% rename from database/entities/Like.ts rename to database/entities/like.ts diff --git a/database/entities/Notification.ts b/database/entities/notification.ts similarity index 85% rename from database/entities/Notification.ts rename to database/entities/notification.ts index 693f7e63..adf5699c 100644 --- a/database/entities/Notification.ts +++ b/database/entities/notification.ts @@ -3,14 +3,14 @@ import { db } from "~/drizzle/db"; import type { Notifications } from "~/drizzle/schema"; import { Note } from "~/packages/database-interface/note"; import { User } from "~/packages/database-interface/user"; -import type { Notification as APINotification } from "~/types/mastodon/notification"; -import type { StatusWithRelations } from "./Status"; +import type { Notification as apiNotification } from "~/types/mastodon/notification"; +import type { StatusWithRelations } from "./status"; import { type UserWithRelations, transformOutputToUserWithRelations, userExtrasTemplate, userRelations, -} from "./User"; +} from "./user"; export type Notification = InferSelectModel; @@ -48,17 +48,17 @@ export const findManyNotifications = async ( ); }; -export const notificationToAPI = async ( +export const notificationToApi = async ( notification: NotificationWithRelations, -): Promise => { +): Promise => { const account = new User(notification.account); return { - account: account.toAPI(), + account: account.toApi(), created_at: new Date(notification.createdAt).toISOString(), id: notification.id, type: notification.type, status: notification.status - ? await new Note(notification.status).toAPI(account) + ? await new Note(notification.status).toApi(account) : undefined, }; }; diff --git a/database/entities/Relationship.ts b/database/entities/relationship.ts similarity index 95% rename from database/entities/Relationship.ts rename to database/entities/relationship.ts index e592ec10..83061526 100644 --- a/database/entities/Relationship.ts +++ b/database/entities/relationship.ts @@ -2,7 +2,7 @@ import type { InferSelectModel } from "drizzle-orm"; import { db } from "~/drizzle/db"; import { Relationships } from "~/drizzle/schema"; import type { User } from "~/packages/database-interface/user"; -import type { Relationship as APIRelationship } from "~/types/mastodon/relationship"; +import type { Relationship as apiRelationship } from "~/types/mastodon/relationship"; export type Relationship = InferSelectModel & { requestedBy: boolean; @@ -76,7 +76,7 @@ export const checkForBidirectionalRelationships = async ( * Converts the relationship to an API-friendly format. * @returns The API-friendly relationship. */ -export const relationshipToAPI = (rel: Relationship): APIRelationship => { +export const relationshipToApi = (rel: Relationship): apiRelationship => { return { blocked_by: rel.blockedBy, blocking: rel.blocking, diff --git a/database/entities/Status.ts b/database/entities/status.ts similarity index 95% rename from database/entities/Status.ts rename to database/entities/status.ts index 4232715f..2f75bc63 100644 --- a/database/entities/Status.ts +++ b/database/entities/status.ts @@ -36,9 +36,9 @@ import { import type { Note } from "~/packages/database-interface/note"; import { User } from "~/packages/database-interface/user"; import { LogLevel } from "~/packages/log-manager"; -import type { Application } from "./Application"; -import type { EmojiWithInstance } from "./Emoji"; -import { objectToInboxRequest } from "./Federation"; +import type { Application } from "./application"; +import type { EmojiWithInstance } from "./emoji"; +import { objectToInboxRequest } from "./federation"; import { type UserWithInstance, type UserWithRelations, @@ -46,7 +46,7 @@ import { transformOutputToUserWithRelations, userExtrasTemplate, userRelations, -} from "./User"; +} from "./user"; export type Status = InferSelectModel; @@ -258,7 +258,9 @@ export const findManyNotes = async ( */ export const parseTextMentions = async (text: string): Promise => { const mentionedPeople = [...text.matchAll(mentionValidator)] ?? []; - if (mentionedPeople.length === 0) return []; + if (mentionedPeople.length === 0) { + return []; + } const baseUrlHost = new URL(config.http.base_url).host; @@ -287,11 +289,13 @@ export const parseTextMentions = async (text: string): Promise => { const notFoundRemoteUsers = mentionedPeople.filter( (person) => - !isLocal(person?.[2]) && - !foundUsers.find( - (user) => - user.username === person?.[1] && - user.baseUrl === person?.[2], + !( + isLocal(person?.[2]) || + foundUsers.find( + (user) => + user.username === person?.[1] && + user.baseUrl === person?.[2], + ) ), ); @@ -320,7 +324,7 @@ export const parseTextMentions = async (text: string): Promise => { return finalList; }; -export const replaceTextMentions = async (text: string, mentions: User[]) => { +export const replaceTextMentions = (text: string, mentions: User[]) => { let finalText = text; for (const mention of mentions) { const user = mention.data; @@ -412,7 +416,7 @@ export const markdownParse = async (content: string) => { return (await getMarkdownRenderer()).render(content); }; -export const getMarkdownRenderer = async () => { +export const getMarkdownRenderer = () => { const renderer = MarkdownIt({ html: true, linkify: true, @@ -448,12 +452,12 @@ export const federateNote = async (note: Note) => { if (!response.ok) { dualLogger.log( - LogLevel.DEBUG, + LogLevel.Debug, "Federation.Status", await response.text(), ); dualLogger.log( - LogLevel.ERROR, + LogLevel.Error, "Federation.Status", `Failed to federate status ${note.data.id} to ${user.getUri()}`, ); diff --git a/database/entities/Token.ts b/database/entities/token.ts similarity index 90% rename from database/entities/Token.ts rename to database/entities/token.ts index 8dd60f8e..feba0bda 100644 --- a/database/entities/Token.ts +++ b/database/entities/token.ts @@ -5,7 +5,7 @@ import type { Tokens } from "~/drizzle/schema"; * The type of token. */ export enum TokenType { - BEARER = "Bearer", + Bearer = "Bearer", } export type Token = InferSelectModel; diff --git a/database/entities/User.ts b/database/entities/user.ts similarity index 93% rename from database/entities/User.ts rename to database/entities/user.ts index f84874eb..3c8b67dc 100644 --- a/database/entities/User.ts +++ b/database/entities/user.ts @@ -14,15 +14,15 @@ import { } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; import { LogLevel } from "~/packages/log-manager"; -import type { Application } from "./Application"; -import type { EmojiWithInstance } from "./Emoji"; -import { objectToInboxRequest } from "./Federation"; +import type { Application } from "./application"; +import type { EmojiWithInstance } from "./emoji"; +import { objectToInboxRequest } from "./federation"; import { type Relationship, checkForBidirectionalRelationships, createNewRelationship, -} from "./Relationship"; -import type { Token } from "./Token"; +} from "./relationship"; +import type { Token } from "./token"; export type UserType = InferSelectModel; @@ -175,13 +175,13 @@ export const followRequestUser = async ( if (!response.ok) { dualLogger.log( - LogLevel.DEBUG, + LogLevel.Debug, "Federation.FollowRequest", await response.text(), ); dualLogger.log( - LogLevel.ERROR, + LogLevel.Error, "Federation.FollowRequest", `Failed to federate follow request from ${ follower.id @@ -230,13 +230,13 @@ export const sendFollowAccept = async (follower: User, followee: User) => { if (!response.ok) { dualLogger.log( - LogLevel.DEBUG, + LogLevel.Debug, "Federation.FollowAccept", await response.text(), ); dualLogger.log( - LogLevel.ERROR, + LogLevel.Error, "Federation.FollowAccept", `Failed to federate follow accept from ${ followee.id @@ -258,13 +258,13 @@ export const sendFollowReject = async (follower: User, followee: User) => { if (!response.ok) { dualLogger.log( - LogLevel.DEBUG, + LogLevel.Debug, "Federation.FollowReject", await response.text(), ); dualLogger.log( - LogLevel.ERROR, + LogLevel.Error, "Federation.FollowReject", `Failed to federate follow reject from ${ followee.id @@ -352,7 +352,9 @@ export const findFirstUser = async ( }, }); - if (!output) return null; + if (!output) { + return null; + } return transformOutputToUserWithRelations(output); }; @@ -373,7 +375,9 @@ export const resolveWebFinger = async ( .where(and(eq(Users.username, identifier), eq(Instances.baseUrl, host))) .limit(1); - if (foundUser[0]) return await User.fromId(foundUser[0].Users.id); + if (foundUser[0]) { + return await User.fromId(foundUser[0].Users.id); + } const hostWithProtocol = host.startsWith("http") ? host : `https://${host}`; @@ -405,7 +409,7 @@ export const resolveWebFinger = async ( }[]; }; - if (!data.subject || !data.links) { + if (!(data.subject && data.links)) { throw new Error( "Invalid WebFinger data (missing subject or links from response)", ); @@ -428,13 +432,17 @@ export const resolveWebFinger = async ( * @returns The user associated with the given access token. */ export const retrieveUserFromToken = async ( - access_token: string, + accessToken: string, ): Promise => { - if (!access_token) return null; + if (!accessToken) { + return null; + } - const token = await retrieveToken(access_token); + const token = await retrieveToken(accessToken); - if (!token || !token.userId) return null; + if (!token?.userId) { + return null; + } const user = await User.fromId(token.userId); @@ -442,12 +450,14 @@ export const retrieveUserFromToken = async ( }; export const retrieveUserAndApplicationFromToken = async ( - access_token: string, + accessToken: string, ): Promise<{ user: User | null; application: Application | null; }> => { - if (!access_token) return { user: null, application: null }; + if (!accessToken) { + return { user: null, application: null }; + } const output = ( await db @@ -457,11 +467,13 @@ export const retrieveUserAndApplicationFromToken = async ( }) .from(Tokens) .leftJoin(Applications, eq(Tokens.applicationId, Applications.id)) - .where(eq(Tokens.accessToken, access_token)) + .where(eq(Tokens.accessToken, accessToken)) .limit(1) )[0]; - if (!output?.token.userId) return { user: null, application: null }; + if (!output?.token.userId) { + return { user: null, application: null }; + } const user = await User.fromId(output.token.userId); @@ -469,13 +481,15 @@ export const retrieveUserAndApplicationFromToken = async ( }; export const retrieveToken = async ( - access_token: string, + accessToken: string, ): Promise => { - if (!access_token) return null; + if (!accessToken) { + return null; + } return ( (await db.query.Tokens.findFirst({ - where: (tokens, { eq }) => eq(tokens.accessToken, access_token), + where: (tokens, { eq }) => eq(tokens.accessToken, accessToken), })) ?? null ); }; diff --git a/drizzle/db.ts b/drizzle/db.ts index 66626768..95b683aa 100644 --- a/drizzle/db.ts +++ b/drizzle/db.ts @@ -23,13 +23,14 @@ export const setupDatabase = async ( if ( (e as Error).message === "Client has already been connected. You cannot reuse a client." - ) + ) { return; + } - await logger.logError(LogLevel.CRITICAL, "Database", e as Error); + await logger.logError(LogLevel.Critical, "Database", e as Error); await logger.log( - LogLevel.CRITICAL, + LogLevel.Critical, "Database", "Failed to connect to database. Please check your configuration.", ); @@ -38,23 +39,23 @@ export const setupDatabase = async ( // Migrate the database info && - (await logger.log(LogLevel.INFO, "Database", "Migrating database...")); + (await logger.log(LogLevel.Info, "Database", "Migrating database...")); try { await migrate(db, { migrationsFolder: "./drizzle/migrations", }); } catch (e) { - await logger.logError(LogLevel.CRITICAL, "Database", e as Error); + await logger.logError(LogLevel.Critical, "Database", e as Error); await logger.log( - LogLevel.CRITICAL, + LogLevel.Critical, "Database", "Failed to migrate database. Please check your configuration.", ); process.exit(1); } - info && (await logger.log(LogLevel.INFO, "Database", "Database migrated")); + info && (await logger.log(LogLevel.Info, "Database", "Database migrated")); }; export const db = drizzle(client, { schema }); diff --git a/drizzle/schema.ts b/drizzle/schema.ts index 72666452..0709fb42 100644 --- a/drizzle/schema.ts +++ b/drizzle/schema.ts @@ -14,7 +14,7 @@ import { uniqueIndex, uuid, } from "drizzle-orm/pg-core"; -import type { Source as APISource } from "~/types/mastodon/source"; +import type { Source as apiSource } from "~/types/mastodon/source"; export const CaptchaChallenges = pgTable("CaptchaChallenges", { id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(), @@ -386,7 +386,7 @@ export const Users = pgTable( inbox: string; outbox: string; }> | null>(), - source: jsonb("source").notNull().$type(), + source: jsonb("source").notNull().$type(), avatar: text("avatar").notNull(), header: text("header").notNull(), createdAt: timestamp("created_at", { precision: 3, mode: "string" }) @@ -508,100 +508,100 @@ export const ModTags = pgTable("ModTags", { * - Owner: Only manage their own resources */ export enum RolePermissions { - MANAGE_NOTES = "notes", - MANAGE_OWN_NOTES = "owner:note", - VIEW_NOTES = "read:note", - VIEW_NOTE_LIKES = "read:note_likes", - VIEW_NOTE_BOOSTS = "read:note_boosts", - MANAGE_ACCOUNTS = "accounts", - MANAGE_OWN_ACCOUNT = "owner:account", - VIEW_ACCOUNT_FOLLOWS = "read:account_follows", - MANAGE_LIKES = "likes", - MANAGE_OWN_LIKES = "owner:like", - MANAGE_BOOSTS = "boosts", - MANAGE_OWN_BOOSTS = "owner:boost", - VIEW_ACCOUNTS = "read:account", - MANAGE_EMOJIS = "emojis", - VIEW_EMOJIS = "read:emoji", - MANAGE_OWN_EMOJIS = "owner:emoji", - MANAGE_MEDIA = "media", - MANAGE_OWN_MEDIA = "owner:media", - MANAGE_BLOCKS = "blocks", - MANAGE_OWN_BLOCKS = "owner:block", - MANAGE_FILTERS = "filters", - MANAGE_OWN_FILTERS = "owner:filter", - MANAGE_MUTES = "mutes", - MANAGE_OWN_MUTES = "owner:mute", - MANAGE_REPORTS = "reports", - MANAGE_OWN_REPORTS = "owner:report", - MANAGE_SETTINGS = "settings", - MANAGE_OWN_SETTINGS = "owner:settings", - MANAGE_ROLES = "roles", - MANAGE_NOTIFICATIONS = "notifications", - MANAGE_OWN_NOTIFICATIONS = "owner:notification", - MANAGE_FOLLOWS = "follows", - MANAGE_OWN_FOLLOWS = "owner:follow", - MANAGE_OWN_APPS = "owner:app", - SEARCH = "search", - VIEW_PUBLIC_TIMELINES = "public_timelines", - VIEW_PRIVATE_TIMELINES = "private_timelines", - IGNORE_RATE_LIMITS = "ignore_rate_limits", - IMPERSONATE = "impersonate", - MANAGE_INSTANCE = "instance", - MANAGE_INSTANCE_FEDERATION = "instance:federation", - MANAGE_INSTANCE_SETTINGS = "instance:settings", + ManageNotes = "notes", + ManageOwnNotes = "owner:note", + ViewNotes = "read:note", + ViewNoteLikes = "read:note_likes", + ViewNoteBoosts = "read:note_boosts", + ManageAccounts = "accounts", + ManageOwnAccount = "owner:account", + ViewAccountFollows = "read:account_follows", + ManageLikes = "likes", + ManageOwnLikes = "owner:like", + ManageBoosts = "boosts", + ManageOwnBoosts = "owner:boost", + ViewAccounts = "read:account", + ManageEmojis = "emojis", + ViewEmojis = "read:emoji", + ManageOwnEmojis = "owner:emoji", + ManageMedia = "media", + ManageOwnMedia = "owner:media", + ManageBlocks = "blocks", + ManageOwnBlocks = "owner:block", + ManageFilters = "filters", + ManageOwnFilters = "owner:filter", + ManageMutes = "mutes", + ManageOwnMutes = "owner:mute", + ManageReports = "reports", + ManageOwnReports = "owner:report", + ManageSettings = "settings", + ManageOwnSettings = "owner:settings", + ManageRoles = "roles", + ManageNotifications = "notifications", + ManageOwnNotifications = "owner:notification", + ManageFollows = "follows", + ManageOwnFollows = "owner:follow", + ManageOwnApps = "owner:app", + Search = "search", + ViewPublicTimelines = "public_timelines", + ViewPrimateTimelines = "private_timelines", + IgnoreRateLimits = "ignore_rate_limits", + Impersonate = "impersonate", + ManageInstance = "instance", + ManageInstanceFederation = "instance:federation", + ManageInstanceSettings = "instance:settings", /** Users who do not have this permission will not be able to login! */ - OAUTH = "oauth", + OAuth = "oauth", } export const DEFAULT_ROLES = [ - RolePermissions.MANAGE_OWN_NOTES, - RolePermissions.VIEW_NOTES, - RolePermissions.VIEW_NOTE_LIKES, - RolePermissions.VIEW_NOTE_BOOSTS, - RolePermissions.MANAGE_OWN_ACCOUNT, - RolePermissions.VIEW_ACCOUNT_FOLLOWS, - RolePermissions.MANAGE_OWN_LIKES, - RolePermissions.MANAGE_OWN_BOOSTS, - RolePermissions.VIEW_ACCOUNTS, - RolePermissions.MANAGE_OWN_EMOJIS, - RolePermissions.VIEW_EMOJIS, - RolePermissions.MANAGE_OWN_MEDIA, - RolePermissions.MANAGE_OWN_BLOCKS, - RolePermissions.MANAGE_OWN_FILTERS, - RolePermissions.MANAGE_OWN_MUTES, - RolePermissions.MANAGE_OWN_REPORTS, - RolePermissions.MANAGE_OWN_SETTINGS, - RolePermissions.MANAGE_OWN_NOTIFICATIONS, - RolePermissions.MANAGE_OWN_FOLLOWS, - RolePermissions.MANAGE_OWN_APPS, - RolePermissions.SEARCH, - RolePermissions.VIEW_PUBLIC_TIMELINES, - RolePermissions.VIEW_PRIVATE_TIMELINES, - RolePermissions.OAUTH, + RolePermissions.ManageOwnNotes, + RolePermissions.ViewNotes, + RolePermissions.ViewNoteLikes, + RolePermissions.ViewNoteBoosts, + RolePermissions.ManageOwnAccount, + RolePermissions.ViewAccountFollows, + RolePermissions.ManageOwnLikes, + RolePermissions.ManageOwnBoosts, + RolePermissions.ViewAccounts, + RolePermissions.ManageOwnEmojis, + RolePermissions.ViewEmojis, + RolePermissions.ManageOwnMedia, + RolePermissions.ManageOwnBlocks, + RolePermissions.ManageOwnFilters, + RolePermissions.ManageOwnMutes, + RolePermissions.ManageOwnReports, + RolePermissions.ManageOwnSettings, + RolePermissions.ManageOwnNotifications, + RolePermissions.ManageOwnFollows, + RolePermissions.ManageOwnApps, + RolePermissions.Search, + RolePermissions.ViewPublicTimelines, + RolePermissions.ViewPrimateTimelines, + RolePermissions.OAuth, ]; export const ADMIN_ROLES = [ ...DEFAULT_ROLES, - RolePermissions.MANAGE_NOTES, - RolePermissions.MANAGE_ACCOUNTS, - RolePermissions.MANAGE_LIKES, - RolePermissions.MANAGE_BOOSTS, - RolePermissions.MANAGE_EMOJIS, - RolePermissions.MANAGE_MEDIA, - RolePermissions.MANAGE_BLOCKS, - RolePermissions.MANAGE_FILTERS, - RolePermissions.MANAGE_MUTES, - RolePermissions.MANAGE_REPORTS, - RolePermissions.MANAGE_SETTINGS, - RolePermissions.MANAGE_ROLES, - RolePermissions.MANAGE_NOTIFICATIONS, - RolePermissions.MANAGE_FOLLOWS, - RolePermissions.IMPERSONATE, - RolePermissions.IGNORE_RATE_LIMITS, - RolePermissions.MANAGE_INSTANCE, - RolePermissions.MANAGE_INSTANCE_FEDERATION, - RolePermissions.MANAGE_INSTANCE_SETTINGS, + RolePermissions.ManageNotes, + RolePermissions.ManageAccounts, + RolePermissions.ManageLikes, + RolePermissions.ManageBoosts, + RolePermissions.ManageEmojis, + RolePermissions.ManageMedia, + RolePermissions.ManageBlocks, + RolePermissions.ManageFilters, + RolePermissions.ManageMutes, + RolePermissions.ManageReports, + RolePermissions.ManageSettings, + RolePermissions.ManageRoles, + RolePermissions.ManageNotifications, + RolePermissions.ManageFollows, + RolePermissions.Impersonate, + RolePermissions.IgnoreRateLimits, + RolePermissions.ManageInstance, + RolePermissions.ManageInstanceFederation, + RolePermissions.ManageInstanceSettings, ]; export const Roles = pgTable("Roles", { diff --git a/index.ts b/index.ts index 0467da40..f07a2750 100644 --- a/index.ts +++ b/index.ts @@ -15,7 +15,7 @@ import { Note } from "~/packages/database-interface/note"; import { handleGlitchRequest } from "~/packages/glitch-server/main"; import { routes } from "~/routes"; import { createServer } from "~/server"; -import type { APIRouteExports } from "~/types/api"; +import type { ApiRouteExports } from "~/types/api"; const timeAtStart = performance.now(); @@ -30,7 +30,7 @@ if (isEntry) { dualServerLogger = dualLogger; } -await dualServerLogger.log(LogLevel.INFO, "Lysand", "Starting Lysand..."); +await dualServerLogger.log(LogLevel.Info, "Lysand", "Starting Lysand..."); await setupDatabase(dualServerLogger); @@ -45,12 +45,12 @@ if (isEntry) { // Check if JWT private key is set in config if (!config.oidc.jwt_key) { await dualServerLogger.log( - LogLevel.CRITICAL, + LogLevel.Critical, "Server", "The JWT private key is not set in the config", ); await dualServerLogger.log( - LogLevel.CRITICAL, + LogLevel.Critical, "Server", "Below is a generated key for you to copy in the config at oidc.jwt_key", ); @@ -69,7 +69,7 @@ if (isEntry) { ).toString("base64"); await dualServerLogger.log( - LogLevel.CRITICAL, + LogLevel.Critical, "Server", chalk.gray(`${privateKey};${publicKey}`), ); @@ -100,7 +100,7 @@ if (isEntry) { if (privateKey instanceof Error || publicKey instanceof Error) { await dualServerLogger.log( - LogLevel.CRITICAL, + LogLevel.Critical, "Server", "The JWT key could not be imported! You may generate a new one by removing the old one from the config and restarting the server (this will invalidate all current JWTs).", ); @@ -123,16 +123,16 @@ app.use(boundaryCheck); // Inject own filesystem router for (const [, path] of Object.entries(routes)) { // use app.get(path, handler) to add routes - const route: APIRouteExports = await import(path); + const route: ApiRouteExports = await import(path); - if (!route.meta || !route.default) { + if (!(route.meta && route.default)) { throw new Error(`Route ${path} does not have the correct exports.`); } route.default(app); } -app.options("*", async () => { +app.options("*", () => { return response(null); }); @@ -145,17 +145,14 @@ app.all("*", async (context) => { } } - const base_url_with_http = config.http.base_url.replace( - "https://", - "http://", - ); + const baseUrlWithHttp = config.http.base_url.replace("https://", "http://"); const replacedUrl = context.req.url .replace(config.http.base_url, config.frontend.url) - .replace(base_url_with_http, config.frontend.url); + .replace(baseUrlWithHttp, config.frontend.url); await dualLogger.log( - LogLevel.DEBUG, + LogLevel.Debug, "Server.Proxy", `Proxying ${replacedUrl}`, ); @@ -168,9 +165,9 @@ app.all("*", async (context) => { }, redirect: "manual", }).catch(async (e) => { - await dualLogger.logError(LogLevel.ERROR, "Server.Proxy", e as Error); + await dualLogger.logError(LogLevel.Error, "Server.Proxy", e as Error); await dualLogger.log( - LogLevel.ERROR, + LogLevel.Error, "Server.Proxy", `The Frontend is not running or the route is not found: ${replacedUrl}`, ); @@ -192,13 +189,13 @@ app.all("*", async (context) => { createServer(config, app); await dualServerLogger.log( - LogLevel.INFO, + LogLevel.Info, "Server", `Lysand started at ${config.http.bind}:${config.http.bind_port} in ${(performance.now() - timeAtStart).toFixed(0)}ms`, ); await dualServerLogger.log( - LogLevel.INFO, + LogLevel.Info, "Database", `Database is online, now serving ${postCount} posts`, ); @@ -206,7 +203,7 @@ await dualServerLogger.log( if (config.frontend.enabled) { if (!URL.canParse(config.frontend.url)) { await dualServerLogger.log( - LogLevel.ERROR, + LogLevel.Error, "Server", `Frontend URL is not a valid URL: ${config.frontend.url}`, ); @@ -220,19 +217,19 @@ if (config.frontend.enabled) { if (!response) { await dualServerLogger.log( - LogLevel.ERROR, + LogLevel.Error, "Server", `Frontend is unreachable at ${config.frontend.url}`, ); await dualServerLogger.log( - LogLevel.ERROR, + LogLevel.Error, "Server", "Please ensure the frontend is online and reachable", ); } } else { await dualServerLogger.log( - LogLevel.WARNING, + LogLevel.Warning, "Server", "Frontend is disabled, skipping check", ); diff --git a/middlewares/bait.ts b/middlewares/bait.ts index 6b281fa7..11efc27b 100644 --- a/middlewares/bait.ts +++ b/middlewares/bait.ts @@ -7,14 +7,14 @@ import { config } from "~/packages/config-manager"; import { LogLevel } from "~/packages/log-manager"; export const bait = createMiddleware(async (context, next) => { - const request_ip = context.env?.ip as SocketAddress | undefined | null; + const requestIp = context.env?.ip as SocketAddress | undefined | null; if (config.http.bait.enabled) { // Check for bait IPs - if (request_ip?.address) { + if (requestIp?.address) { for (const ip of config.http.bait.bait_ips) { try { - if (matches(ip, request_ip.address)) { + if (matches(ip, requestIp.address)) { const file = Bun.file( config.http.bait.send_file || "./beemovie.txt", ); @@ -23,19 +23,19 @@ export const bait = createMiddleware(async (context, next) => { return response(file); } await logger.log( - LogLevel.ERROR, + LogLevel.Error, "Server.Bait", `Bait file not found: ${config.http.bait.send_file}`, ); } } catch (e) { logger.log( - LogLevel.ERROR, + LogLevel.Error, "Server.IPCheck", `Error while parsing bait IP "${ip}" `, ); logger.logError( - LogLevel.ERROR, + LogLevel.Error, "Server.IPCheck", e as Error, ); @@ -61,7 +61,7 @@ export const bait = createMiddleware(async (context, next) => { return response(file); } await logger.log( - LogLevel.ERROR, + LogLevel.Error, "Server.Bait", `Bait file not found: ${config.http.bait.send_file}`, ); diff --git a/middlewares/ip-bans.ts b/middlewares/ip-bans.ts index 1b724daa..ff875154 100644 --- a/middlewares/ip-bans.ts +++ b/middlewares/ip-bans.ts @@ -9,25 +9,25 @@ import { LogLevel } from "~/packages/log-manager"; export const ipBans = createMiddleware(async (context, next) => { // Check for banned IPs - const request_ip = context.env?.ip as SocketAddress | undefined | null; + const requestIp = context.env?.ip as SocketAddress | undefined | null; - if (!request_ip?.address) { + if (!requestIp?.address) { await next(); return; } for (const ip of config.http.banned_ips) { try { - if (matches(ip, request_ip?.address)) { + if (matches(ip, requestIp?.address)) { return errorResponse("Forbidden", 403); } } catch (e) { logger.log( - LogLevel.ERROR, + LogLevel.Error, "Server.IPCheck", `Error while parsing banned IP "${ip}" `, ); - logger.logError(LogLevel.ERROR, "Server.IPCheck", e as Error); + logger.logError(LogLevel.Error, "Server.IPCheck", e as Error); return errorResponse( `A server error occured: ${(e as Error).message}`, diff --git a/middlewares/logger.ts b/middlewares/logger.ts index 9e24de2d..c1503adb 100644 --- a/middlewares/logger.ts +++ b/middlewares/logger.ts @@ -4,12 +4,12 @@ import { createMiddleware } from "hono/factory"; import { config } from "~/packages/config-manager"; export const logger = createMiddleware(async (context, next) => { - const request_ip = context.env?.ip as SocketAddress | undefined | null; + const requestIp = context.env?.ip as SocketAddress | undefined | null; if (config.logging.log_requests) { await dualLogger.logRequest( context.req.raw, - config.logging.log_ip ? request_ip?.address : undefined, + config.logging.log_ip ? requestIp?.address : undefined, config.logging.log_requests_verbose, ); } diff --git a/packages/config-manager/config.type.ts b/packages/config-manager/config.type.ts index 293c94c3..bb19efde 100644 --- a/packages/config-manager/config.type.ts +++ b/packages/config-manager/config.type.ts @@ -3,7 +3,7 @@ import { z } from "zod"; import { ADMIN_ROLES, DEFAULT_ROLES, RolePermissions } from "~/drizzle/schema"; export enum MediaBackendType { - LOCAL = "local", + Local = "local", S3 = "s3", } @@ -220,7 +220,7 @@ export const configValidator = z.object({ .object({ backend: z .nativeEnum(MediaBackendType) - .default(MediaBackendType.LOCAL), + .default(MediaBackendType.Local), deduplicate_media: z.boolean().default(true), local_uploads_folder: z.string().min(1).default("uploads"), conversion: z @@ -234,7 +234,7 @@ export const configValidator = z.object({ }), }) .default({ - backend: MediaBackendType.LOCAL, + backend: MediaBackendType.Local, deduplicate_media: true, local_uploads_folder: "uploads", conversion: { diff --git a/packages/config-manager/index.ts b/packages/config-manager/index.ts index ceabd53a..a661d3d9 100644 --- a/packages/config-manager/index.ts +++ b/packages/config-manager/index.ts @@ -6,8 +6,6 @@ */ import { loadConfig, watchConfig } from "c12"; -import chalk from "chalk"; -import { fromError } from "zod-validation-error"; import { type Config, configValidator } from "./config.type"; const { config } = await watchConfig({ @@ -23,21 +21,6 @@ const { config } = await watchConfig({ const parsed = await configValidator.safeParseAsync(config); if (!parsed.success) { - console.log( - `${chalk.bgRed.white( - " CRITICAL ", - )} There was an error parsing the config file at ${chalk.bold( - "./config/config.toml", - )}. Please fix the file and try again.`, - ); - console.log( - `${chalk.bgRed.white( - " CRITICAL ", - )} Follow the installation intructions and get a sample config file from the repository if needed.`, - ); - console.log( - `${chalk.bgRed.white(" CRITICAL ")} ${fromError(parsed.error).message}`, - ); process.exit(1); } diff --git a/packages/database-interface/attachment.ts b/packages/database-interface/attachment.ts index 25fc8872..cb00c458 100644 --- a/packages/database-interface/attachment.ts +++ b/packages/database-interface/attachment.ts @@ -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"; @@ -28,7 +28,9 @@ export class Attachment extends BaseInterface { } public static async fromId(id: string | null): Promise { - if (!id) return null; + if (!id) { + return null; + } return await Attachment.fromSql(eq(Attachments.id, id)); } @@ -46,7 +48,9 @@ export class Attachment extends BaseInterface { orderBy, }); - if (!found) return null; + if (!found) { + return null; + } return new Attachment(found); } @@ -86,7 +90,7 @@ export class Attachment extends BaseInterface { return updated.data; } - async save(): Promise { + save(): Promise { return this.update(this.data); } @@ -120,52 +124,60 @@ export class Attachment extends BaseInterface { return this.data.id; } - public toAPI(): APIAttachment | APIAsyncAttachment { - let type = "unknown"; - + public getMastodonType(): APIAttachment["type"] { if (this.data.mimeType.startsWith("image/")) { - type = "image"; - } else if (this.data.mimeType.startsWith("video/")) { - type = "video"; - } else if (this.data.mimeType.startsWith("audio/")) { - type = "audio"; + return "image"; + } + if (this.data.mimeType.startsWith("video/")) { + return "video"; + } + if (this.data.mimeType.startsWith("audio/")) { + return "audio"; } + return "unknown"; + } + + public toApiMeta(): APIAttachment["meta"] { return { - id: this.data.id, - type: type as "image" | "video" | "audio" | "unknown", - url: proxyUrl(this.data.url) ?? "", - remote_url: proxyUrl(this.data.remoteUrl), - preview_url: proxyUrl(this.data.thumbnailUrl || this.data.url), - text_url: null, - meta: { + width: this.data.width || undefined, + height: this.data.height || undefined, + fps: this.data.fps || undefined, + size: + this.data.width && this.data.height + ? `${this.data.width}x${this.data.height}` + : undefined, + duration: this.data.duration || undefined, + length: undefined, + aspect: + this.data.width && this.data.height + ? this.data.width / this.data.height + : undefined, + original: { width: this.data.width || undefined, height: this.data.height || undefined, - fps: this.data.fps || undefined, size: this.data.width && this.data.height ? `${this.data.width}x${this.data.height}` : undefined, - duration: this.data.duration || undefined, - length: undefined, aspect: this.data.width && this.data.height ? this.data.width / this.data.height : undefined, - original: { - width: this.data.width || undefined, - height: this.data.height || undefined, - size: - this.data.width && this.data.height - ? `${this.data.width}x${this.data.height}` - : undefined, - aspect: - this.data.width && this.data.height - ? this.data.width / this.data.height - : undefined, - }, - // Idk whether size or length is the right value }, + // Idk whether size or length is the right value + }; + } + + public toApi(): APIAttachment | apiAsyncAttachment { + return { + id: this.data.id, + type: this.getMastodonType(), + url: proxyUrl(this.data.url) ?? "", + remote_url: proxyUrl(this.data.remoteUrl), + preview_url: proxyUrl(this.data.thumbnailUrl || this.data.url), + text_url: null, + meta: this.toApiMeta(), description: this.data.description, blurhash: this.data.blurhash, }; diff --git a/packages/database-interface/note.ts b/packages/database-interface/note.ts index 6091add5..6216648e 100644 --- a/packages/database-interface/note.ts +++ b/packages/database-interface/note.ts @@ -19,21 +19,21 @@ import { LogLevel } from "log-manager"; import { createRegExp, exactly, global } from "magic-regexp"; import { type Application, - applicationToAPI, -} from "~/database/entities/Application"; + applicationToApi, +} from "~/database/entities/application"; import { type EmojiWithInstance, - emojiToAPI, + emojiToApi, emojiToLysand, fetchEmoji, parseEmojis, -} from "~/database/entities/Emoji"; -import { localObjectURI } from "~/database/entities/Federation"; +} from "~/database/entities/emoji"; +import { localObjectUri } from "~/database/entities/federation"; import { type StatusWithRelations, contentToHtml, findManyNotes, -} from "~/database/entities/Status"; +} from "~/database/entities/status"; import { db } from "~/drizzle/db"; import { Attachments, @@ -44,8 +44,8 @@ import { Users, } from "~/drizzle/schema"; import { config } from "~/packages/config-manager"; -import type { Attachment as APIAttachment } from "~/types/mastodon/attachment"; -import type { Status as APIStatus } from "~/types/mastodon/status"; +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 { User } from "./user"; @@ -54,7 +54,7 @@ import { User } from "./user"; * Gives helpers to fetch notes from database in a nice format */ export class Note extends BaseInterface { - async save(): Promise { + save(): Promise { return this.update(this.data); } @@ -87,7 +87,9 @@ export class Note extends BaseInterface { id: string | null, userRequestingNoteId?: string, ): Promise { - if (!id) return null; + if (!id) { + return null; + } return await Note.fromSql( eq(Notes.id, id), @@ -123,7 +125,9 @@ export class Note extends BaseInterface { userId, ); - if (!found[0]) return null; + if (!found[0]) { + return null; + } return new Note(found[0]); } @@ -225,7 +229,7 @@ export class Note extends BaseInterface { ); } - async isRemote() { + isRemote() { return this.author.isRemote(); } @@ -248,14 +252,14 @@ export class Note extends BaseInterface { static async fromData( author: User, content: typeof EntityValidator.$ContentFormat, - visibility: APIStatus["visibility"], - is_sensitive: boolean, - spoiler_text: string, + visibility: apiStatus["visibility"], + isSensitive: boolean, + spoilerText: string, emojis: EmojiWithInstance[], uri?: string, mentions?: User[], /** List of IDs of database Attachment objects */ - media_attachments?: string[], + mediaAttachments?: string[], replyId?: string, quoteId?: string, application?: Application, @@ -284,8 +288,8 @@ export class Note extends BaseInterface { "", contentType: "text/html", visibility, - sensitive: is_sensitive, - spoilerText: await sanitizedHtmlStrip(spoiler_text), + sensitive: isSensitive, + spoilerText: await sanitizedHtmlStrip(spoilerText), uri: uri || null, replyId: replyId ?? null, quotingId: quoteId ?? null, @@ -315,13 +319,13 @@ export class Note extends BaseInterface { } // Set attachment parents - if (media_attachments && media_attachments.length > 0) { + if (mediaAttachments && mediaAttachments.length > 0) { await db .update(Attachments) .set({ noteId: newNote.id, }) - .where(inArray(Attachments.id, media_attachments)); + .where(inArray(Attachments.id, mediaAttachments)); } // Send notifications for mentioned local users @@ -341,13 +345,13 @@ export class Note extends BaseInterface { async updateFromData( content?: typeof EntityValidator.$ContentFormat, - visibility?: APIStatus["visibility"], - is_sensitive?: boolean, - spoiler_text?: string, + visibility?: apiStatus["visibility"], + isSensitive?: boolean, + spoilerText?: string, emojis: EmojiWithInstance[] = [], mentions: User[] = [], /** List of IDs of database Attachment objects */ - media_attachments: string[] = [], + mediaAttachments: string[] = [], replyId?: string, quoteId?: string, application?: Application, @@ -378,8 +382,8 @@ export class Note extends BaseInterface { : undefined, contentType: "text/html", visibility, - sensitive: is_sensitive, - spoilerText: spoiler_text, + sensitive: isSensitive, + spoilerText: spoilerText, replyId, quotingId: quoteId, applicationId: application?.id, @@ -416,7 +420,7 @@ export class Note extends BaseInterface { } // Set attachment parents - if (media_attachments) { + if (mediaAttachments) { await db .update(Attachments) .set({ @@ -424,13 +428,14 @@ export class Note extends BaseInterface { }) .where(eq(Attachments.noteId, this.data.id)); - if (media_attachments.length > 0) + if (mediaAttachments.length > 0) { await db .update(Attachments) .set({ noteId: this.data.id, }) - .where(inArray(Attachments.id, media_attachments)); + .where(inArray(Attachments.id, mediaAttachments)); + } } return await Note.fromId(newNote.id, newNote.authorId); @@ -443,13 +448,15 @@ export class Note extends BaseInterface { // Check if note not already in database const foundNote = uri && (await Note.fromSql(eq(Notes.uri, uri))); - if (foundNote) return foundNote; + if (foundNote) { + return foundNote; + } // Check if URI is of a local note if (uri?.startsWith(config.http.base_url)) { const uuid = uri.match(idValidator); - if (!uuid || !uuid[0]) { + if (!uuid?.[0]) { throw new Error( `URI ${uri} is of a local note, but it could not be parsed`, ); @@ -465,7 +472,7 @@ export class Note extends BaseInterface { uri?: string, providedNote?: typeof EntityValidator.$Note, ): Promise { - if (!uri && !providedNote) { + if (!(uri || providedNote)) { throw new Error("No URI or note provided"); } @@ -515,7 +522,7 @@ export class Note extends BaseInterface { attachment, ).catch((e) => { dualLogger.logError( - LogLevel.ERROR, + LogLevel.Error, "Federation.StatusResolver", e, ); @@ -533,7 +540,7 @@ export class Note extends BaseInterface { ?.emojis ?? []) { const resolvedEmoji = await fetchEmoji(emoji).catch((e) => { dualLogger.logError( - LogLevel.ERROR, + LogLevel.Error, "Federation.StatusResolver", e, ); @@ -552,7 +559,7 @@ export class Note extends BaseInterface { content: "", }, }, - note.visibility as APIStatus["visibility"], + note.visibility as apiStatus["visibility"], note.is_sensitive ?? false, note.subject ?? "", emojis, @@ -582,7 +589,7 @@ export class Note extends BaseInterface { content: "", }, }, - note.visibility as APIStatus["visibility"], + note.visibility as apiStatus["visibility"], note.is_sensitive ?? false, note.subject ?? "", emojis, @@ -638,9 +645,15 @@ export class Note extends BaseInterface { * @returns Whether this status is viewable by the user. */ async isViewableByUser(user: User | null) { - if (this.author.id === user?.id) return true; - if (this.data.visibility === "public") return true; - if (this.data.visibility === "unlisted") return true; + if (this.author.id === user?.id) { + return true; + } + if (this.data.visibility === "public") { + return true; + } + if (this.data.visibility === "unlisted") { + return true; + } if (this.data.visibility === "private") { return user ? await db.query.Relationships.findFirst({ @@ -658,7 +671,7 @@ export class Note extends BaseInterface { ); } - async toAPI(userFetching?: User | null): Promise { + async toApi(userFetching?: User | null): Promise { const data = this.data; // Convert mentions of local users from @username@host to @username @@ -696,18 +709,18 @@ export class Note extends BaseInterface { id: data.id, in_reply_to_id: data.replyId || null, in_reply_to_account_id: data.reply?.authorId || null, - account: this.author.toAPI(userFetching?.id === data.authorId), + account: this.author.toApi(userFetching?.id === data.authorId), created_at: new Date(data.createdAt).toISOString(), application: data.application - ? applicationToAPI(data.application) + ? applicationToApi(data.application) : null, card: null, content: replacedContent, - emojis: data.emojis.map((emoji) => emojiToAPI(emoji)), + emojis: data.emojis.map((emoji) => emojiToApi(emoji)), favourited: data.liked, favourites_count: data.likeCount, media_attachments: (data.attachments ?? []).map( - (a) => new Attachment(a).toAPI() as APIAttachment, + (a) => new Attachment(a).toApi() as apiAttachment, ), mentions: data.mentions.map((mention) => ({ id: mention.id, @@ -725,7 +738,7 @@ export class Note extends BaseInterface { // TODO: Add polls poll: null, reblog: data.reblog - ? await new Note(data.reblog as StatusWithRelations).toAPI( + ? await new Note(data.reblog as StatusWithRelations).toApi( userFetching, ) : null, @@ -736,13 +749,13 @@ export class Note extends BaseInterface { spoiler_text: data.spoilerText, tags: [], uri: data.uri || this.getUri(), - visibility: data.visibility as APIStatus["visibility"], + visibility: data.visibility as apiStatus["visibility"], url: data.uri || this.getMastoUri(), bookmarked: false, // @ts-expect-error Glitch-SOC extension quote: data.quotingId ? (await Note.fromId(data.quotingId, userFetching?.id).then( - (n) => n?.toAPI(userFetching), + (n) => n?.toApi(userFetching), )) ?? null : null, quote_id: data.quotingId || undefined, @@ -750,12 +763,14 @@ export class Note extends BaseInterface { } getUri() { - return localObjectURI(this.data.id); + return localObjectUri(this.data.id); } static getUri(id?: string | null) { - if (!id) return null; - return localObjectURI(id); + if (!id) { + return null; + } + return localObjectUri(id); } getMastoUri() { diff --git a/packages/database-interface/oauth.ts b/packages/database-interface/oauth.ts index f9157630..0af1fd95 100644 --- a/packages/database-interface/oauth.ts +++ b/packages/database-interface/oauth.ts @@ -13,7 +13,7 @@ import { userInfoRequest, validateAuthResponse, } from "oauth4webapi"; -import type { Application } from "~/database/entities/Application"; +import type { Application } from "~/database/entities/application"; import { db } from "~/drizzle/db"; import { type Applications, OpenIdAccounts } from "~/drizzle/schema"; import { config } from "~/packages/config-manager"; @@ -21,13 +21,13 @@ import { config } from "~/packages/config-manager"; export class OAuthManager { public issuer: (typeof config.oidc.providers)[0]; - constructor(public issuer_id: string) { + constructor(public issuerId: string) { const found = config.oidc.providers.find( - (provider) => provider.id === this.issuer_id, + (provider) => provider.id === this.issuerId, ); if (!found) { - throw new Error(`Issuer ${this.issuer_id} not found`); + throw new Error(`Issuer ${this.issuerId} not found`); } this.issuer = found; @@ -48,7 +48,7 @@ export class OAuthManager { }).then((res) => processDiscoveryResponse(issuerUrl, res)); } - async getParameters( + getParameters( authServer: AuthorizationServer, issuer: (typeof config.oidc.providers)[0], currentUrl: URL, @@ -101,7 +101,7 @@ export class OAuthManager { async getUserInfo( authServer: AuthorizationServer, issuer: (typeof config.oidc.providers)[0], - access_token: string, + accessToken: string, sub: string, ) { return await userInfoRequest( @@ -110,7 +110,7 @@ export class OAuthManager { client_id: issuer.client_id, client_secret: issuer.client_secret, }, - access_token, + accessToken, ).then( async (res) => await processUserInfoResponse( @@ -125,7 +125,7 @@ export class OAuthManager { ); } - async processOAuth2Error( + processOAuth2Error( application: InferInsertModel | null, ) { return { diff --git a/packages/database-interface/role.ts b/packages/database-interface/role.ts index 340e02f7..cc3a1adc 100644 --- a/packages/database-interface/role.ts +++ b/packages/database-interface/role.ts @@ -27,7 +27,9 @@ export class Role extends BaseInterface { } public static async fromId(id: string | null): Promise { - if (!id) return null; + if (!id) { + return null; + } return await Role.fromSql(eq(Roles.id, id)); } @@ -45,7 +47,9 @@ export class Role extends BaseInterface { orderBy, }); - if (!found) return null; + if (!found) { + return null; + } return new Role(found); } @@ -123,7 +127,7 @@ export class Role extends BaseInterface { return updated.data; } - async save(): Promise { + save(): Promise { return this.update(this.data); } @@ -173,7 +177,7 @@ export class Role extends BaseInterface { return this.data.id; } - public toAPI() { + public toApi() { return { id: this.id, name: this.data.name, diff --git a/packages/database-interface/timeline.ts b/packages/database-interface/timeline.ts index a3d09145..7d7ed9ab 100644 --- a/packages/database-interface/timeline.ts +++ b/packages/database-interface/timeline.ts @@ -5,20 +5,20 @@ import { Note } from "./note"; import { User } from "./user"; enum TimelineType { - NOTE = "Note", - USER = "User", + Note = "Note", + User = "User", } -export class Timeline { +export class Timeline { constructor(private type: TimelineType) {} - static async getNoteTimeline( + static getNoteTimeline( sql: SQL | undefined, limit: number, url: string, userId?: string, ) { - return new Timeline(TimelineType.NOTE).fetchTimeline( + return new Timeline(TimelineType.Note).fetchTimeline( sql, limit, url, @@ -26,19 +26,158 @@ export class Timeline { ); } - static async getUserTimeline( + static getUserTimeline( sql: SQL | undefined, limit: number, url: string, ) { - return new Timeline(TimelineType.USER).fetchTimeline( + return new Timeline(TimelineType.User).fetchTimeline( sql, limit, url, ); } - private async fetchTimeline( + private async fetchObjects( + sql: SQL | undefined, + limit: number, + userId?: string, + ): Promise { + switch (this.type) { + case TimelineType.Note: + return (await Note.manyFromSql( + sql, + undefined, + limit, + undefined, + userId, + )) as Type[]; + case TimelineType.User: + return (await User.manyFromSql( + sql, + undefined, + limit, + )) as Type[]; + } + } + + private async fetchLinkHeader( + objects: Type[], + url: string, + limit: number, + ): Promise { + const linkHeader = []; + const urlWithoutQuery = new URL( + new URL(url).pathname, + config.http.base_url, + ).toString(); + + if (objects.length > 0) { + switch (this.type) { + case TimelineType.Note: + linkHeader.push( + ...(await this.fetchNoteLinkHeader( + objects as Note[], + urlWithoutQuery, + limit, + )), + ); + break; + case TimelineType.User: + linkHeader.push( + ...(await this.fetchUserLinkHeader( + objects as User[], + urlWithoutQuery, + limit, + )), + ); + break; + } + } + + return linkHeader.join(", "); + } + + private async fetchNoteLinkHeader( + notes: Note[], + urlWithoutQuery: string, + limit: number, + ): Promise { + const linkHeader = []; + + const objectBefore = await Note.fromSql(gt(Notes.id, notes[0].data.id)); + if (objectBefore) { + linkHeader.push( + `<${urlWithoutQuery}?limit=${limit ?? 20}&min_id=${notes[0].data.id}>; rel="prev"`, + ); + } + + if (notes.length >= (limit ?? 20)) { + const objectAfter = await Note.fromSql( + gt(Notes.id, notes[notes.length - 1].data.id), + ); + if (objectAfter) { + linkHeader.push( + `<${urlWithoutQuery}?limit=${limit ?? 20}&max_id=${notes[notes.length - 1].data.id}>; rel="next"`, + ); + } + } + + return linkHeader; + } + + private async fetchUserLinkHeader( + users: User[], + urlWithoutQuery: string, + limit: number, + ): Promise { + const linkHeader = []; + + const objectBefore = await User.fromSql(gt(Users.id, users[0].id)); + if (objectBefore) { + linkHeader.push( + `<${urlWithoutQuery}?limit=${limit ?? 20}&min_id=${users[0].id}>; rel="prev"`, + ); + } + + if (users.length >= (limit ?? 20)) { + const objectAfter = await User.fromSql( + gt(Users.id, users[users.length - 1].id), + ); + if (objectAfter) { + linkHeader.push( + `<${urlWithoutQuery}?limit=${limit ?? 20}&max_id=${users[users.length - 1].id}>; rel="next"`, + ); + } + } + + return linkHeader; + } + + private async fetchTimeline( + sql: SQL | undefined, + limit: number, + url: string, + userId?: string, + ): Promise<{ link: string; objects: Type[] }> { + const objects = await this.fetchObjects(sql, limit, userId); + const link = await this.fetchLinkHeader(objects, url, limit); + + switch (this.type) { + case TimelineType.Note: + return { + link, + objects: objects, + }; + case TimelineType.User: + return { + link, + objects: objects, + }; + } + } + + /* private async fetchTimeline( sql: SQL | undefined, limit: number, url: string, @@ -48,7 +187,7 @@ export class Timeline { const users: User[] = []; switch (this.type) { - case TimelineType.NOTE: + case TimelineType.Note: notes.push( ...(await Note.manyFromSql( sql, @@ -59,7 +198,7 @@ export class Timeline { )), ); break; - case TimelineType.USER: + case TimelineType.User: users.push(...(await User.manyFromSql(sql, undefined, limit))); break; } @@ -72,7 +211,7 @@ export class Timeline { if (notes.length > 0) { switch (this.type) { - case TimelineType.NOTE: { + case TimelineType.Note: { const objectBefore = await Note.fromSql( gt(Notes.id, notes[0].data.id), ); @@ -102,7 +241,7 @@ export class Timeline { } break; } - case TimelineType.USER: { + case TimelineType.User: { const objectBefore = await User.fromSql( gt(Users.id, users[0].id), ); @@ -136,16 +275,16 @@ export class Timeline { } switch (this.type) { - case TimelineType.NOTE: + case TimelineType.Note: return { link: linkHeader.join(", "), objects: notes as T[], }; - case TimelineType.USER: + case TimelineType.User: return { link: linkHeader.join(", "), objects: users as T[], }; } - } + } */ } diff --git a/packages/database-interface/user.ts b/packages/database-interface/user.ts index 5da5e248..10ca3d0f 100644 --- a/packages/database-interface/user.ts +++ b/packages/database-interface/user.ts @@ -5,6 +5,8 @@ import { addUserToMeilisearch } from "@/meilisearch"; import { proxyUrl } from "@/response"; import { EntityValidator } from "@lysand-org/federation"; import { + type InferInsertModel, + type InferSelectModel, type SQL, and, count, @@ -19,20 +21,21 @@ import { } from "drizzle-orm"; import { htmlToText } from "html-to-text"; import { - emojiToAPI, + emojiToApi, emojiToLysand, fetchEmoji, -} from "~/database/entities/Emoji"; -import { objectToInboxRequest } from "~/database/entities/Federation"; -import { addInstanceIfNotExists } from "~/database/entities/Instance"; +} from "~/database/entities/emoji"; +import { objectToInboxRequest } from "~/database/entities/federation"; +import { addInstanceIfNotExists } from "~/database/entities/instance"; import { type UserWithRelations, findFirstUser, findManyUsers, -} from "~/database/entities/User"; +} from "~/database/entities/user"; import { db } from "~/drizzle/db"; import { EmojiToUser, + type Instances, NoteToMentions, Notes, type RolePermissions, @@ -40,8 +43,8 @@ import { Users, } from "~/drizzle/schema"; 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 type { Account as apiAccount } from "~/types/mastodon/account"; +import type { Mention as apiMention } from "~/types/mastodon/mention"; import { BaseInterface } from "./base"; import type { Note } from "./note"; import { Role } from "./role"; @@ -61,7 +64,9 @@ export class User extends BaseInterface { } static async fromId(id: string | null): Promise { - if (!id) return null; + if (!id) { + return null; + } return await User.fromSql(eq(Users.id, id)); } @@ -79,7 +84,9 @@ export class User extends BaseInterface { orderBy, }); - if (!found) return null; + if (!found) { + return null; + } return new User(found); } @@ -137,7 +144,9 @@ export class User extends BaseInterface { // If admin, add admin permissions .concat(this.data.isAdmin ? config.permissions.admin : []) .reduce((acc, permission) => { - if (!acc.includes(permission)) acc.push(permission); + if (!acc.includes(permission)) { + acc.push(permission); + } return acc; }, [] as RolePermissions[]) ); @@ -220,11 +229,11 @@ export class User extends BaseInterface { )[0]; } - async save(): Promise { + save(): Promise { return this.update(this.data); } - async updateFromRemote() { + async updateFromRemote(): Promise { if (!this.isRemote()) { throw new Error( "Cannot refetch a local user (they are not remote)", @@ -233,16 +242,12 @@ export class User extends BaseInterface { const updated = await User.saveFromRemote(this.getUri()); - if (!updated) { - throw new Error("User not found after update"); - } - this.data = updated.data; return this; } - static async saveFromRemote(uri: string): Promise { + static async saveFromRemote(uri: string): Promise { if (!URL.canParse(uri)) { throw new Error(`Invalid URI to parse ${uri}`); } @@ -274,102 +279,25 @@ export class User extends BaseInterface { emojis.push(await fetchEmoji(emoji)); } - // Check if new user already exists - const foundUser = await User.fromSql(eq(Users.uri, data.uri)); - - // If it exists, simply update it - if (foundUser) { - await foundUser.update({ - updatedAt: new Date().toISOString(), - endpoints: { - dislikes: data.dislikes, - featured: data.featured, - likes: data.likes, - followers: data.followers, - following: data.following, - inbox: data.inbox, - outbox: data.outbox, - }, - avatar: data.avatar - ? Object.entries(data.avatar)[0][1].content - : "", - header: data.header - ? Object.entries(data.header)[0][1].content - : "", - displayName: data.display_name ?? "", - note: getBestContentType(data.bio).content, - publicKey: data.public_key.public_key, - }); - - // Add emojis - if (emojis.length > 0) { - await db - .delete(EmojiToUser) - .where(eq(EmojiToUser.userId, foundUser.id)); - - await db.insert(EmojiToUser).values( - emojis.map((emoji) => ({ - emojiId: emoji.id, - userId: foundUser.id, - })), - ); - } - - return foundUser; - } - - const newUser = ( - await db - .insert(Users) - .values({ - username: data.username, - uri: data.uri, - createdAt: new Date(data.created_at).toISOString(), - endpoints: { - dislikes: data.dislikes, - featured: data.featured, - likes: data.likes, - followers: data.followers, - following: data.following, - inbox: data.inbox, - outbox: data.outbox, - }, - fields: data.fields ?? [], - updatedAt: new Date(data.created_at).toISOString(), - instanceId: instance.id, - avatar: data.avatar - ? Object.entries(data.avatar)[0][1].content - : "", - header: data.header - ? Object.entries(data.header)[0][1].content - : "", - displayName: data.display_name ?? "", - note: getBestContentType(data.bio).content, - publicKey: data.public_key.public_key, - source: { - language: null, - note: "", - privacy: "public", - sensitive: false, - fields: [], - }, - }) - .returning() - )[0]; + const user = await User.fromLysand(data, instance); // Add emojis to user if (emojis.length > 0) { + await db.delete(EmojiToUser).where(eq(EmojiToUser.userId, user.id)); + await db.insert(EmojiToUser).values( emojis.map((emoji) => ({ emojiId: emoji.id, - userId: newUser.id, + userId: user.id, })), ); } - const finalUser = await User.fromId(newUser.id); + const finalUser = await User.fromId(user.id); - if (!finalUser) return null; + if (!finalUser) { + throw new Error("Failed to save user from remote"); + } // Add to Meilisearch await addUserToMeilisearch(finalUser); @@ -377,17 +305,86 @@ export class User extends BaseInterface { return finalUser; } + static async fromLysand( + user: typeof EntityValidator.$User, + instance: InferSelectModel, + ): Promise { + const data = { + username: user.username, + uri: user.uri, + createdAt: new Date(user.created_at).toISOString(), + endpoints: { + dislikes: user.dislikes, + featured: user.featured, + likes: user.likes, + followers: user.followers, + following: user.following, + inbox: user.inbox, + outbox: user.outbox, + }, + fields: user.fields ?? [], + updatedAt: new Date(user.created_at).toISOString(), + instanceId: instance.id, + avatar: user.avatar + ? Object.entries(user.avatar)[0][1].content + : "", + header: user.header + ? Object.entries(user.header)[0][1].content + : "", + displayName: user.display_name ?? "", + note: getBestContentType(user.bio).content, + publicKey: user.public_key.public_key, + source: { + language: null, + note: "", + privacy: "public", + sensitive: false, + fields: [], + }, + }; + + // Check if new user already exists + + const foundUser = await User.fromSql(eq(Users.uri, user.uri)); + + // If it exists, simply update it + if (foundUser) { + await foundUser.update(data); + + return foundUser; + } + + // Else, create a new user + return await User.insert(data); + } + + public static async insert( + data: InferInsertModel, + ): Promise { + const inserted = (await db.insert(Users).values(data).returning())[0]; + + const user = await User.fromId(inserted.id); + + if (!user) { + throw new Error("Failed to insert user"); + } + + return user; + } + static async resolve(uri: string): Promise { // Check if user not already in database const foundUser = await User.fromSql(eq(Users.uri, uri)); - if (foundUser) return foundUser; + if (foundUser) { + return foundUser; + } // Check if URI is of a local user if (uri.startsWith(config.http.base_url)) { const uuid = uri.match(idValidator); - if (!uuid || !uuid[0]) { + if (!uuid?.[0]) { throw new Error( `URI ${uri} is of a local user, but it could not be parsed`, ); @@ -405,11 +402,12 @@ export class User extends BaseInterface { * @returns The raw URL for the user's avatar */ getAvatarUrl(config: Config) { - if (!this.data.avatar) + if (!this.data.avatar) { return ( config.defaults.avatar || `https://api.dicebear.com/8.x/${config.defaults.placeholder_style}/svg?seed=${this.data.username}` ); + } return this.data.avatar; } @@ -480,7 +478,9 @@ export class User extends BaseInterface { const finalUser = await User.fromId(newUser.id); - if (!finalUser) return null; + if (!finalUser) { + return null; + } // Add to Meilisearch await addUserToMeilisearch(finalUser); @@ -494,7 +494,9 @@ export class User extends BaseInterface { * @returns The raw URL for the user's header */ getHeaderUrl(config: Config) { - if (!this.data.header) return config.defaults.header || ""; + if (!this.data.header) { + return config.defaults.header || ""; + } return this.data.header; } @@ -561,7 +563,7 @@ export class User extends BaseInterface { } } - toAPI(isOwnAccount = false): APIAccount { + toApi(isOwnAccount = false): apiAccount { const user = this.data; return { id: user.id, @@ -578,7 +580,7 @@ export class User extends BaseInterface { followers_count: user.followerCount, following_count: user.followingCount, statuses_count: user.statusCount, - emojis: user.emojis.map((emoji) => emojiToAPI(emoji)), + emojis: user.emojis.map((emoji) => emojiToApi(emoji)), fields: user.fields.map((field) => ({ name: htmlToText(getBestContentType(field.key).content), value: getBestContentType(field.value).content, @@ -625,7 +627,7 @@ export class User extends BaseInterface { ] : [], ) - .map((r) => r.toAPI()), + .map((r) => r.toApi()), group: false, }; } @@ -699,7 +701,7 @@ export class User extends BaseInterface { }; } - toMention(): APIMention { + toMention(): apiMention { return { url: this.getUri(), username: this.data.username, diff --git a/packages/glitch-server/main.ts b/packages/glitch-server/main.ts index 1c896b85..15f23982 100644 --- a/packages/glitch-server/main.ts +++ b/packages/glitch-server/main.ts @@ -2,12 +2,12 @@ import { join } from "node:path"; import { redirect } from "@/response"; import type { BunFile } from "bun"; import { config } from "config-manager"; -import { retrieveUserFromToken } from "~/database/entities/User"; +import { retrieveUserFromToken } from "~/database/entities/user"; import type { User } from "~/packages/database-interface/user"; import type { LogManager, MultiLogManager } from "~/packages/log-manager"; import { languages } from "./glitch-languages"; -const handleManifestRequest = async () => { +const handleManifestRequest = () => { const manifest = { id: "/home", name: config.instance.name, @@ -99,7 +99,7 @@ const handleManifestRequest = async () => { const handleSignInRequest = async ( req: Request, - path: string, + _path: string, url: URL, user: User | null, accessToken: string, @@ -156,7 +156,7 @@ const handleSignInRequest = async ( ); }; -const handleSignOutRequest = async (req: Request) => { +const handleSignOutRequest = (req: Request) => { if (req.method === "POST") { return redirect("/api/auth/mastodon-logout", 307); } @@ -176,7 +176,7 @@ const returnFile = async (file: BunFile, content?: string) => { }; const handleDefaultRequest = async ( - req: Request, + _req: Request, path: string, user: User | null, accessToken: string, @@ -198,10 +198,10 @@ const handleDefaultRequest = async ( return null; }; -const brandingTransforms = async ( +const brandingTransforms = ( fileContents: string, - accessToken: string, - user: User | null, + _accessToken: string, + _user: User | null, ) => { let newFileContents = fileContents; for (const server of config.frontend.glitch.server) { @@ -287,7 +287,7 @@ const htmlTransforms = async ( }, accounts: user ? { - [user.id]: user.toAPI(true), + [user.id]: user.toApi(true), } : {}, media_attachments: { @@ -327,7 +327,7 @@ const htmlTransforms = async ( export const handleGlitchRequest = async ( req: Request, - logger: LogManager | MultiLogManager, + _logger: LogManager | MultiLogManager, ): Promise => { const url = new URL(req.url); let path = url.pathname; @@ -336,7 +336,9 @@ export const handleGlitchRequest = async ( const user = await retrieveUserFromToken(accessToken ?? ""); // Strip leading /web from path - if (path.startsWith("/web")) path = path.slice(4); + if (path.startsWith("/web")) { + path = path.slice(4); + } if (path === "/manifest") { return handleManifestRequest(); diff --git a/packages/log-manager/index.ts b/packages/log-manager/index.ts index 0924f7bb..601e3954 100644 --- a/packages/log-manager/index.ts +++ b/packages/log-manager/index.ts @@ -5,19 +5,19 @@ import chalk from "chalk"; import { config } from "config-manager"; export enum LogLevel { - DEBUG = "debug", - INFO = "info", - WARNING = "warning", - ERROR = "error", - CRITICAL = "critical", + Debug = "debug", + Info = "info", + Warning = "warning", + Error = "error", + Critical = "critical", } const logOrder = [ - LogLevel.DEBUG, - LogLevel.INFO, - LogLevel.WARNING, - LogLevel.ERROR, - LogLevel.CRITICAL, + LogLevel.Debug, + LogLevel.Info, + LogLevel.Warning, + LogLevel.Error, + LogLevel.Critical, ]; /** @@ -37,15 +37,15 @@ export class LogManager { getLevelColor(level: LogLevel) { switch (level) { - case LogLevel.DEBUG: + case LogLevel.Debug: return chalk.blue; - case LogLevel.INFO: + case LogLevel.Info: return chalk.green; - case LogLevel.WARNING: + case LogLevel.Warning: return chalk.yellow; - case LogLevel.ERROR: + case LogLevel.Error: return chalk.red; - case LogLevel.CRITICAL: + case LogLevel.Critical: return chalk.bgRed; } } @@ -79,8 +79,9 @@ export class LogManager { if ( logOrder.indexOf(level) < logOrder.indexOf(config.logging.log_level as LogLevel) - ) + ) { return; + } if (this.enableColors) { await this.write( @@ -102,9 +103,8 @@ export class LogManager { } private async write(text: string) { - Bun.stdout.name; if (this.output === Bun.stdout) { - await console.log(`${text}`); + console.info(text); } else { if (!(await exists(this.output.name ?? ""))) { // Create file if it doesn't exist @@ -128,17 +128,112 @@ export class LogManager { * @param error Error to log */ async logError(level: LogLevel, entity: string, error: Error) { - error.stack && (await this.log(LogLevel.DEBUG, entity, error.stack)); + error.stack && (await this.log(LogLevel.Debug, entity, error.stack)); await this.log(level, entity, error.message); } + /** + * Logs the headers of a request + * @param req Request to log + */ + public logHeaders(req: Request): string { + let string = " [Headers]\n"; + for (const [key, value] of req.headers.entries()) { + string += ` ${key}: ${value}\n`; + } + return string; + } + + /** + * Logs the body of a request + * @param req Request to log + */ + async logBody(req: Request): Promise { + let string = " [Body]\n"; + const contentType = req.headers.get("Content-Type"); + + if (contentType?.includes("application/json")) { + string += await this.logJsonBody(req); + } else if ( + contentType && + (contentType.includes("application/x-www-form-urlencoded") || + contentType.includes("multipart/form-data")) + ) { + string += await this.logFormData(req); + } else { + const text = await req.text(); + string += ` ${text}\n`; + } + return string; + } + + /** + * Logs the JSON body of a request + * @param req Request to log + */ + async logJsonBody(req: Request): Promise { + let string = ""; + try { + const json = await req.clone().json(); + const stringified = JSON.stringify(json, null, 4) + .split("\n") + .map((line) => ` ${line}`) + .join("\n"); + + string += `${stringified}\n`; + } catch { + string += ` [Invalid JSON] (raw: ${await req.clone().text()})\n`; + } + return string; + } + + /** + * Logs the form data of a request + * @param req Request to log + */ + async logFormData(req: Request): Promise { + let string = ""; + const formData = await req.clone().formData(); + for (const [key, value] of formData.entries()) { + if (value.toString().length < 300) { + string += ` ${key}: ${value.toString()}\n`; + } else { + string += ` ${key}: <${value.toString().length} bytes>\n`; + } + } + return string; + } + /** * Logs a request to the output * @param req Request to log * @param ip IP of the request * @param logAllDetails Whether to log all details of the request */ - async logRequest(req: Request, ip?: string, logAllDetails = false) { + async logRequest( + req: Request, + ip?: string, + logAllDetails = false, + ): Promise { + let string = ip ? `${ip}: ` : ""; + + string += `${req.method} ${req.url}`; + + if (logAllDetails) { + string += "\n"; + string += await this.logHeaders(req); + string += await this.logBody(req); + } + await this.log(LogLevel.Info, "Request", string); + } + + /* + * Logs a request to the output + * @param req Request to log + * @param ip IP of the request + * @param logAllDetails Whether to log all details of the request + */ + /**async logRequest(req: Request, ip?: string, logAllDetails = false) { let string = ip ? `${ip}: ` : ""; string += `${req.method} ${req.url}`; @@ -153,9 +248,9 @@ export class LogManager { // Pretty print body string += " [Body]\n"; - const content_type = req.headers.get("Content-Type"); + const contentType = req.headers.get("Content-Type"); - if (content_type?.includes("application/json")) { + if (contentType?.includes("application/json")) { try { const json = await req.clone().json(); const stringified = JSON.stringify(json, null, 4) @@ -170,9 +265,9 @@ export class LogManager { .text()})\n`; } } else if ( - content_type && - (content_type.includes("application/x-www-form-urlencoded") || - content_type.includes("multipart/form-data")) + contentType && + (contentType.includes("application/x-www-form-urlencoded") || + contentType.includes("multipart/form-data")) ) { const formData = await req.clone().formData(); for (const [key, value] of formData.entries()) { @@ -189,8 +284,8 @@ export class LogManager { string += ` ${text}\n`; } } - await this.log(LogLevel.INFO, "Request", string); - } + await this.log(LogLevel.Info, "Request", string); + } */ } /** diff --git a/packages/log-manager/tests/log-manager.test.ts b/packages/log-manager/tests/log-manager.test.ts index 4a376b47..a4278378 100644 --- a/packages/log-manager/tests/log-manager.test.ts +++ b/packages/log-manager/tests/log-manager.test.ts @@ -36,7 +36,7 @@ describe("LogManager", () => { }); */ it("should log message with timestamp", async () => { - await logManager.log(LogLevel.INFO, "TestEntity", "Test message"); + await logManager.log(LogLevel.Info, "TestEntity", "Test message"); expect(mockAppend).toHaveBeenCalledWith( mockOutput.name, expect.stringContaining("[INFO] TestEntity: Test message"), @@ -45,7 +45,7 @@ describe("LogManager", () => { it("should log message without timestamp", async () => { await logManager.log( - LogLevel.INFO, + LogLevel.Info, "TestEntity", "Test message", false, @@ -56,9 +56,10 @@ describe("LogManager", () => { ); }); + // biome-ignore lint/suspicious/noSkippedTests: I need to fix this :sob: test.skip("should write to stdout", async () => { logManager = new LogManager(Bun.stdout); - await logManager.log(LogLevel.INFO, "TestEntity", "Test message"); + await logManager.log(LogLevel.Info, "TestEntity", "Test message"); const writeMock = jest.fn(); @@ -75,7 +76,7 @@ describe("LogManager", () => { it("should log error message", async () => { const error = new Error("Test error"); - await logManager.logError(LogLevel.ERROR, "TestEntity", error); + await logManager.logError(LogLevel.Error, "TestEntity", error); expect(mockAppend).toHaveBeenCalledWith( mockOutput.name, expect.stringContaining("[ERROR] TestEntity: Test error"), @@ -191,10 +192,10 @@ describe("MultiLogManager", () => { }); it("should log message to all logManagers", async () => { - await multiLogManager.log(LogLevel.INFO, "TestEntity", "Test message"); + await multiLogManager.log(LogLevel.Info, "TestEntity", "Test message"); expect(mockLog).toHaveBeenCalledTimes(2); expect(mockLog).toHaveBeenCalledWith( - LogLevel.INFO, + LogLevel.Info, "TestEntity", "Test message", true, @@ -203,10 +204,10 @@ describe("MultiLogManager", () => { it("should log error to all logManagers", async () => { const error = new Error("Test error"); - await multiLogManager.logError(LogLevel.ERROR, "TestEntity", error); + await multiLogManager.logError(LogLevel.Error, "TestEntity", error); expect(mockLogError).toHaveBeenCalledTimes(2); expect(mockLogError).toHaveBeenCalledWith( - LogLevel.ERROR, + LogLevel.Error, "TestEntity", error, ); diff --git a/packages/media-manager/index.ts b/packages/media-manager/index.ts index e84200f7..21ac47a0 100644 --- a/packages/media-manager/index.ts +++ b/packages/media-manager/index.ts @@ -4,7 +4,7 @@ import type { Config } from "config-manager"; import { MediaConverter } from "./media-converter"; export enum MediaBackendType { - LOCAL = "local", + Local = "local", S3 = "s3", } @@ -35,12 +35,12 @@ export class MediaBackend { public backend: MediaBackendType, ) {} - static async fromBackendType( + public static fromBackendType( backend: MediaBackendType, config: Config, - ): Promise { + ): MediaBackend { switch (backend) { - case MediaBackendType.LOCAL: + case MediaBackendType.Local: return new LocalMediaBackend(config); case MediaBackendType.S3: return new S3MediaBackend(config); @@ -64,15 +64,15 @@ export class MediaBackend { * @returns The file as a File object */ public getFileByHash( - file: string, - databaseHashFetcher: (sha256: string) => Promise, + _file: string, + _databaseHashFetcher: (sha256: string) => Promise, ): Promise { return Promise.reject( new Error("Do not call MediaBackend directly: use a subclass"), ); } - public deleteFileByUrl(url: string): Promise { + public deleteFileByUrl(_url: string): Promise { return Promise.reject( new Error("Do not call MediaBackend directly: use a subclass"), ); @@ -83,7 +83,7 @@ export class MediaBackend { * @param filename File name * @returns The file as a File object */ - public getFile(filename: string): Promise { + public getFile(_filename: string): Promise { return Promise.reject( new Error("Do not call MediaBackend directly: use a subclass"), ); @@ -94,7 +94,7 @@ export class MediaBackend { * @param file File to add * @returns Metadata about the uploaded file */ - public addFile(file: File): Promise { + public addFile(_file: File): Promise { return Promise.reject( new Error("Do not call MediaBackend directly: use a subclass"), ); @@ -103,7 +103,7 @@ export class MediaBackend { export class LocalMediaBackend extends MediaBackend { constructor(config: Config) { - super(config, MediaBackendType.LOCAL); + super(config, MediaBackendType.Local); } public async addFile(file: File) { @@ -164,7 +164,9 @@ export class LocalMediaBackend extends MediaBackend { ): Promise { const filename = await databaseHashFetcher(hash); - if (!filename) return null; + if (!filename) { + return null; + } return this.getFile(filename); } @@ -174,7 +176,9 @@ export class LocalMediaBackend extends MediaBackend { `${this.config.media.local_uploads_folder}/${filename}`, ); - if (!(await file.exists())) return null; + if (!(await file.exists())) { + return null; + } return new File([await file.arrayBuffer()], filename, { type: file.type, @@ -243,7 +247,9 @@ export class S3MediaBackend extends MediaBackend { ): Promise { const filename = await databaseHashFetcher(hash); - if (!filename) return null; + if (!filename) { + return null; + } return this.getFile(filename); } diff --git a/packages/media-manager/tests/media-backends.test.ts b/packages/media-manager/tests/media-backends.test.ts index 067b5c14..3eb3bbe9 100644 --- a/packages/media-manager/tests/media-backends.test.ts +++ b/packages/media-manager/tests/media-backends.test.ts @@ -36,7 +36,7 @@ describe("MediaBackend", () => { describe("fromBackendType", () => { it("should return a LocalMediaBackend instance for LOCAL backend type", async () => { const backend = await MediaBackend.fromBackendType( - MediaBackendType.LOCAL, + MediaBackendType.Local, mockConfig, ); expect(backend).toBeInstanceOf(LocalMediaBackend); @@ -62,8 +62,8 @@ describe("MediaBackend", () => { it("should throw an error for unknown backend type", () => { expect( // @ts-expect-error This is a test - MediaBackend.fromBackendType("unknown", mockConfig), - ).rejects.toThrow("Unknown backend type: unknown"); + () => MediaBackend.fromBackendType("unknown", mockConfig), + ).toThrow("Unknown backend type: unknown"); }); }); @@ -228,7 +228,7 @@ describe("LocalMediaBackend", () => { it("should initialize with correct type", () => { expect(localMediaBackend.getBackendType()).toEqual( - MediaBackendType.LOCAL, + MediaBackendType.Local, ); }); diff --git a/server/api/api/_fe/config/index.ts b/server/api/api/_fe/config/index.ts index 3b9fb59a..1f0038bf 100644 --- a/server/api/api/_fe/config/index.ts +++ b/server/api/api/_fe/config/index.ts @@ -16,7 +16,7 @@ export const meta = applyConfig({ }); export default (app: Hono) => - app.on(meta.allowedMethods, meta.route, async (context) => { + app.on(meta.allowedMethods, meta.route, (_context) => { return jsonResponse({ http: { bind: config.http.bind, diff --git a/server/api/api/auth/login/index.test.ts b/server/api/api/auth/login/index.test.ts index 230333c4..643003db 100644 --- a/server/api/api/auth/login/index.test.ts +++ b/server/api/api/auth/login/index.test.ts @@ -108,7 +108,7 @@ describe(meta.route, () => { expect(response.headers.get("Set-Cookie")).toMatch(/jwt=[^;]+;/); }); - describe("should reject invalid credentials", async () => { + describe("should reject invalid credentials", () => { // Redirects to /oauth/authorize on invalid test("invalid email", async () => { const formData = new FormData(); diff --git a/server/api/api/auth/login/index.ts b/server/api/api/auth/login/index.ts index fb30e21e..41573072 100644 --- a/server/api/api/auth/login/index.ts +++ b/server/api/api/auth/login/index.ts @@ -65,8 +65,9 @@ const returnError = (query: object, error: string, description: string) => { // Add all data that is not undefined except email and password for (const [key, value] of Object.entries(query)) { - if (key !== "email" && key !== "password" && value !== undefined) + if (key !== "email" && key !== "password" && value !== undefined) { searchParams.append(key, value); + } } searchParams.append("error", error); @@ -107,14 +108,20 @@ export default (app: Hono) => ); if ( - !user || - !(await Bun.password.verify(password, user.data.password || "")) - ) + !( + user && + (await Bun.password.verify( + password, + user.data.password || "", + )) + ) + ) { return returnError( context.req.query(), "invalid_grant", "Invalid identifier or password", ); + } if (user.data.passwordResetToken) { return response(null, 302, { @@ -163,8 +170,9 @@ export default (app: Hono) => application: application.name, }); - if (application.website) + if (application.website) { searchParams.append("website", application.website); + } // Add all data that is not undefined except email and password for (const [key, value] of Object.entries(context.req.query())) { @@ -172,8 +180,9 @@ export default (app: Hono) => key !== "email" && key !== "password" && value !== undefined - ) + ) { searchParams.append(key, String(value)); + } } // Redirect to OAuth authorize with JWT diff --git a/server/api/api/auth/mastodon-login/index.ts b/server/api/api/auth/mastodon-login/index.ts index 70cbcfa1..3bc4c11e 100644 --- a/server/api/api/auth/mastodon-login/index.ts +++ b/server/api/api/auth/mastodon-login/index.ts @@ -5,7 +5,7 @@ import { zValidator } from "@hono/zod-validator"; import { eq } from "drizzle-orm"; import type { Hono } from "hono"; import { z } from "zod"; -import { TokenType } from "~/database/entities/Token"; +import { TokenType } from "~/database/entities/token"; import { db } from "~/drizzle/db"; import { Tokens, Users } from "~/drizzle/schema"; import { config } from "~/packages/config-manager"; @@ -56,10 +56,16 @@ export default (app: Hono) => const user = await User.fromSql(eq(Users.email, email)); if ( - !user || - !(await Bun.password.verify(password, user.data.password || "")) - ) + !( + user && + (await Bun.password.verify( + password, + user.data.password || "", + )) + ) + ) { return redirectToLogin("Invalid email or password"); + } if (user.data.passwordResetToken) { return response(null, 302, { @@ -82,7 +88,7 @@ export default (app: Hono) => accessToken, code: code, scope: "read write follow push", - tokenType: TokenType.BEARER, + tokenType: TokenType.Bearer, applicationId: null, userId: user.id, }); diff --git a/server/api/api/auth/mastodon-logout/index.ts b/server/api/api/auth/mastodon-logout/index.ts index 1a97d59c..50b99c12 100644 --- a/server/api/api/auth/mastodon-logout/index.ts +++ b/server/api/api/auth/mastodon-logout/index.ts @@ -18,7 +18,7 @@ export const meta = applyConfig({ * Mastodon-FE logout route */ export default (app: Hono) => - app.on(meta.allowedMethods, meta.route, async () => { + app.on(meta.allowedMethods, meta.route, () => { return new Response(null, { headers: { Location: "/", diff --git a/server/api/api/auth/redirect/index.ts b/server/api/api/auth/redirect/index.ts index 170f414c..c2fc4b7d 100644 --- a/server/api/api/auth/redirect/index.ts +++ b/server/api/api/auth/redirect/index.ts @@ -63,8 +63,9 @@ export default (app: Hono) => ) .limit(1); - if (!foundToken || foundToken.length <= 0) + if (!foundToken || foundToken.length <= 0) { return redirectToLogin("Invalid code"); + } // Redirect back to application return Response.redirect(`${redirect_uri}?code=${code}`, 302); diff --git a/server/api/api/v1/accounts/:id/block.test.ts b/server/api/api/v1/accounts/:id/block.test.ts index 542d977e..ef4dc669 100644 --- a/server/api/api/v1/accounts/:id/block.test.ts +++ b/server/api/api/v1/accounts/:id/block.test.ts @@ -1,7 +1,7 @@ import { afterAll, describe, expect, test } from "bun:test"; import { config } from "config-manager"; import { getTestUsers, sendTestRequest } from "~/tests/utils"; -import type { Relationship as APIRelationship } from "~/types/mastodon/relationship"; +import type { Relationship as apiRelationship } from "~/types/mastodon/relationship"; import { meta } from "./block"; const { users, tokens, deleteUsers } = await getTestUsers(2); @@ -65,7 +65,7 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - const relationship = (await response.json()) as APIRelationship; + const relationship = (await response.json()) as apiRelationship; expect(relationship.blocking).toBe(true); }); @@ -86,7 +86,7 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - const relationship = (await response.json()) as APIRelationship; + const relationship = (await response.json()) as apiRelationship; expect(relationship.blocking).toBe(true); }); }); diff --git a/server/api/api/v1/accounts/:id/block.ts b/server/api/api/v1/accounts/:id/block.ts index ec6faa84..6d26e9d1 100644 --- a/server/api/api/v1/accounts/:id/block.ts +++ b/server/api/api/v1/accounts/:id/block.ts @@ -4,8 +4,8 @@ import { zValidator } from "@hono/zod-validator"; import { eq } from "drizzle-orm"; import type { Hono } from "hono"; import { z } from "zod"; -import { relationshipToAPI } from "~/database/entities/Relationship"; -import { getRelationshipToOtherUser } from "~/database/entities/User"; +import { relationshipToApi } from "~/database/entities/relationship"; +import { getRelationshipToOtherUser } from "~/database/entities/user"; import { db } from "~/drizzle/db"; import { Relationships, RolePermissions } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; @@ -23,8 +23,8 @@ export const meta = applyConfig({ }, permissions: { required: [ - RolePermissions.MANAGE_OWN_BLOCKS, - RolePermissions.VIEW_ACCOUNTS, + RolePermissions.ManageOwnBlocks, + RolePermissions.ViewAccounts, ], }, }); @@ -45,11 +45,15 @@ export default (app: Hono) => const { id } = context.req.valid("param"); const { user } = context.req.valid("header"); - if (!user) return errorResponse("Unauthorized", 401); + if (!user) { + return errorResponse("Unauthorized", 401); + } const otherUser = await User.fromId(id); - if (!otherUser) return errorResponse("User not found", 404); + if (!otherUser) { + return errorResponse("User not found", 404); + } const foundRelationship = await getRelationshipToOtherUser( user, @@ -67,6 +71,6 @@ export default (app: Hono) => }) .where(eq(Relationships.id, foundRelationship.id)); - return jsonResponse(relationshipToAPI(foundRelationship)); + return jsonResponse(relationshipToApi(foundRelationship)); }, ); diff --git a/server/api/api/v1/accounts/:id/follow.test.ts b/server/api/api/v1/accounts/:id/follow.test.ts index aeddd1e8..e8cdaa53 100644 --- a/server/api/api/v1/accounts/:id/follow.test.ts +++ b/server/api/api/v1/accounts/:id/follow.test.ts @@ -1,7 +1,7 @@ import { afterAll, describe, expect, test } from "bun:test"; import { config } from "config-manager"; import { getTestUsers, sendTestRequest } from "~/tests/utils"; -import type { Relationship as APIRelationship } from "~/types/mastodon/relationship"; +import type { Relationship as apiRelationship } from "~/types/mastodon/relationship"; import { meta } from "./follow"; const { users, tokens, deleteUsers } = await getTestUsers(2); @@ -73,7 +73,7 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - const relationship = (await response.json()) as APIRelationship; + const relationship = (await response.json()) as apiRelationship; expect(relationship.following).toBe(true); }); @@ -96,7 +96,7 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - const relationship = (await response.json()) as APIRelationship; + const relationship = (await response.json()) as apiRelationship; expect(relationship.following).toBe(true); }); }); diff --git a/server/api/api/v1/accounts/:id/follow.ts b/server/api/api/v1/accounts/:id/follow.ts index 63a4b3eb..3b2d7ca5 100644 --- a/server/api/api/v1/accounts/:id/follow.ts +++ b/server/api/api/v1/accounts/:id/follow.ts @@ -4,11 +4,11 @@ import { zValidator } from "@hono/zod-validator"; import type { Hono } from "hono"; import ISO6391 from "iso-639-1"; import { z } from "zod"; -import { relationshipToAPI } from "~/database/entities/Relationship"; +import { relationshipToApi } from "~/database/entities/relationship"; import { followRequestUser, getRelationshipToOtherUser, -} from "~/database/entities/User"; +} from "~/database/entities/user"; import { RolePermissions } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; @@ -25,8 +25,8 @@ export const meta = applyConfig({ }, permissions: { required: [ - RolePermissions.MANAGE_OWN_FOLLOWS, - RolePermissions.VIEW_ACCOUNTS, + RolePermissions.ManageOwnFollows, + RolePermissions.ViewAccounts, ], }, }); @@ -59,11 +59,15 @@ export default (app: Hono) => const { user } = context.req.valid("header"); const { reblogs, notify, languages } = context.req.valid("json"); - if (!user) return errorResponse("Unauthorized", 401); + if (!user) { + return errorResponse("Unauthorized", 401); + } const otherUser = await User.fromId(id); - if (!otherUser) return errorResponse("User not found", 404); + if (!otherUser) { + return errorResponse("User not found", 404); + } let relationship = await getRelationshipToOtherUser( user, @@ -81,6 +85,6 @@ export default (app: Hono) => ); } - return jsonResponse(relationshipToAPI(relationship)); + return jsonResponse(relationshipToApi(relationship)); }, ); diff --git a/server/api/api/v1/accounts/:id/followers.test.ts b/server/api/api/v1/accounts/:id/followers.test.ts index d41c72a1..339c1b8a 100644 --- a/server/api/api/v1/accounts/:id/followers.test.ts +++ b/server/api/api/v1/accounts/:id/followers.test.ts @@ -1,7 +1,7 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import { config } from "config-manager"; import { getTestUsers, sendTestRequest } from "~/tests/utils"; -import type { Account as APIAccount } from "~/types/mastodon/account"; +import type { Account as apiAccount } from "~/types/mastodon/account"; import { meta } from "./followers"; const { users, tokens, deleteUsers } = await getTestUsers(5); @@ -51,7 +51,7 @@ describe(meta.route, () => { expect(response.status).toBe(200); - const data = (await response.json()) as APIAccount[]; + const data = (await response.json()) as apiAccount[]; expect(data).toBeInstanceOf(Array); expect(data.length).toBe(1); @@ -93,7 +93,7 @@ describe(meta.route, () => { expect(response2.status).toBe(200); - const data = (await response2.json()) as APIAccount[]; + const data = (await response2.json()) as apiAccount[]; expect(data).toBeInstanceOf(Array); expect(data.length).toBe(0); diff --git a/server/api/api/v1/accounts/:id/followers.ts b/server/api/api/v1/accounts/:id/followers.ts index 1c989675..43501b87 100644 --- a/server/api/api/v1/accounts/:id/followers.ts +++ b/server/api/api/v1/accounts/:id/followers.ts @@ -21,8 +21,8 @@ export const meta = applyConfig({ }, permissions: { required: [ - RolePermissions.VIEW_ACCOUNT_FOLLOWS, - RolePermissions.VIEW_ACCOUNTS, + RolePermissions.ViewAccountFollows, + RolePermissions.ViewAccounts, ], }, }); @@ -55,7 +55,9 @@ export default (app: Hono) => // TODO: Add follower/following privacy settings - if (!otherUser) return errorResponse("User not found", 404); + if (!otherUser) { + return errorResponse("User not found", 404); + } const { objects, link } = await Timeline.getUserTimeline( and( @@ -69,7 +71,7 @@ export default (app: Hono) => ); return jsonResponse( - await Promise.all(objects.map((object) => object.toAPI())), + await Promise.all(objects.map((object) => object.toApi())), 200, { Link: link, diff --git a/server/api/api/v1/accounts/:id/following.test.ts b/server/api/api/v1/accounts/:id/following.test.ts index f644170a..43b07ea0 100644 --- a/server/api/api/v1/accounts/:id/following.test.ts +++ b/server/api/api/v1/accounts/:id/following.test.ts @@ -1,7 +1,7 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import { config } from "config-manager"; import { getTestUsers, sendTestRequest } from "~/tests/utils"; -import type { Account as APIAccount } from "~/types/mastodon/account"; +import type { Account as apiAccount } from "~/types/mastodon/account"; import { meta } from "./following"; const { users, tokens, deleteUsers } = await getTestUsers(5); @@ -51,7 +51,7 @@ describe(meta.route, () => { expect(response.status).toBe(200); - const data = (await response.json()) as APIAccount[]; + const data = (await response.json()) as apiAccount[]; expect(data).toBeInstanceOf(Array); expect(data.length).toBe(1); @@ -93,7 +93,7 @@ describe(meta.route, () => { expect(response2.status).toBe(200); - const data = (await response2.json()) as APIAccount[]; + const data = (await response2.json()) as apiAccount[]; expect(data).toBeInstanceOf(Array); expect(data.length).toBe(0); diff --git a/server/api/api/v1/accounts/:id/following.ts b/server/api/api/v1/accounts/:id/following.ts index 46c36a97..4de7262c 100644 --- a/server/api/api/v1/accounts/:id/following.ts +++ b/server/api/api/v1/accounts/:id/following.ts @@ -21,8 +21,8 @@ export const meta = applyConfig({ }, permissions: { required: [ - RolePermissions.VIEW_ACCOUNT_FOLLOWS, - RolePermissions.VIEW_ACCOUNTS, + RolePermissions.ViewAccountFollows, + RolePermissions.ViewAccounts, ], }, }); @@ -52,7 +52,9 @@ export default (app: Hono) => const otherUser = await User.fromId(id); - if (!otherUser) return errorResponse("User not found", 404); + if (!otherUser) { + return errorResponse("User not found", 404); + } // TODO: Add follower/following privacy settings @@ -68,7 +70,7 @@ export default (app: Hono) => ); return jsonResponse( - await Promise.all(objects.map((object) => object.toAPI())), + await Promise.all(objects.map((object) => object.toApi())), 200, { Link: link, diff --git a/server/api/api/v1/accounts/:id/index.test.ts b/server/api/api/v1/accounts/:id/index.test.ts index be1ce1f8..c20a9ef7 100644 --- a/server/api/api/v1/accounts/:id/index.test.ts +++ b/server/api/api/v1/accounts/:id/index.test.ts @@ -1,7 +1,7 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import { config } from "config-manager"; import { getTestStatuses, getTestUsers, sendTestRequest } from "~/tests/utils"; -import type { Account as APIAccount } from "~/types/mastodon/account"; +import type { Account as apiAccount } from "~/types/mastodon/account"; import { meta } from "./index"; const { users, tokens, deleteUsers } = await getTestUsers(5); @@ -58,7 +58,7 @@ describe(meta.route, () => { expect(response.status).toBe(200); - const data = (await response.json()) as APIAccount; + const data = (await response.json()) as apiAccount; expect(data).toMatchObject({ id: users[0].id, username: users[0].data.username, @@ -93,6 +93,6 @@ describe(meta.route, () => { icon: null, }), ]), - } satisfies APIAccount); + } satisfies apiAccount); }); }); diff --git a/server/api/api/v1/accounts/:id/index.ts b/server/api/api/v1/accounts/:id/index.ts index 09a77e17..273b6463 100644 --- a/server/api/api/v1/accounts/:id/index.ts +++ b/server/api/api/v1/accounts/:id/index.ts @@ -18,7 +18,7 @@ export const meta = applyConfig({ oauthPermissions: [], }, permissions: { - required: [RolePermissions.VIEW_ACCOUNTS], + required: [RolePermissions.ViewAccounts], }, }); @@ -40,8 +40,10 @@ export default (app: Hono) => const foundUser = await User.fromId(id); - if (!foundUser) return errorResponse("User not found", 404); + if (!foundUser) { + return errorResponse("User not found", 404); + } - return jsonResponse(foundUser.toAPI(user?.id === foundUser.id)); + return jsonResponse(foundUser.toApi(user?.id === foundUser.id)); }, ); diff --git a/server/api/api/v1/accounts/:id/mute.test.ts b/server/api/api/v1/accounts/:id/mute.test.ts index 4f648c07..04cb4b9b 100644 --- a/server/api/api/v1/accounts/:id/mute.test.ts +++ b/server/api/api/v1/accounts/:id/mute.test.ts @@ -1,7 +1,7 @@ import { afterAll, describe, expect, test } from "bun:test"; import { config } from "config-manager"; import { getTestUsers, sendTestRequest } from "~/tests/utils"; -import type { Relationship as APIRelationship } from "~/types/mastodon/relationship"; +import type { Relationship as apiRelationship } from "~/types/mastodon/relationship"; import { meta } from "./mute"; const { users, tokens, deleteUsers } = await getTestUsers(2); @@ -73,7 +73,7 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - const relationship = (await response.json()) as APIRelationship; + const relationship = (await response.json()) as apiRelationship; expect(relationship.muting).toBe(true); }); @@ -96,7 +96,7 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - const relationship = (await response.json()) as APIRelationship; + const relationship = (await response.json()) as apiRelationship; expect(relationship.muting).toBe(true); }); }); diff --git a/server/api/api/v1/accounts/:id/mute.ts b/server/api/api/v1/accounts/:id/mute.ts index 0ce5e24b..bf67826c 100644 --- a/server/api/api/v1/accounts/:id/mute.ts +++ b/server/api/api/v1/accounts/:id/mute.ts @@ -4,8 +4,8 @@ import { zValidator } from "@hono/zod-validator"; import { eq } from "drizzle-orm"; import type { Hono } from "hono"; import { z } from "zod"; -import { relationshipToAPI } from "~/database/entities/Relationship"; -import { getRelationshipToOtherUser } from "~/database/entities/User"; +import { relationshipToApi } from "~/database/entities/relationship"; +import { getRelationshipToOtherUser } from "~/database/entities/user"; import { db } from "~/drizzle/db"; import { Relationships, RolePermissions } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; @@ -23,8 +23,8 @@ export const meta = applyConfig({ }, permissions: { required: [ - RolePermissions.MANAGE_OWN_MUTES, - RolePermissions.VIEW_ACCOUNTS, + RolePermissions.ManageOwnMutes, + RolePermissions.ViewAccounts, ], }, }); @@ -57,11 +57,15 @@ export default (app: Hono) => // TODO: Add duration support const { notifications } = context.req.valid("json"); - if (!user) return errorResponse("Unauthorized", 401); + if (!user) { + return errorResponse("Unauthorized", 401); + } const otherUser = await User.fromId(id); - if (!otherUser) return errorResponse("User not found", 404); + if (!otherUser) { + return errorResponse("User not found", 404); + } const foundRelationship = await getRelationshipToOtherUser( user, @@ -85,6 +89,6 @@ export default (app: Hono) => // TODO: Implement duration - return jsonResponse(relationshipToAPI(foundRelationship)); + return jsonResponse(relationshipToApi(foundRelationship)); }, ); diff --git a/server/api/api/v1/accounts/:id/note.ts b/server/api/api/v1/accounts/:id/note.ts index c85cf7e4..d25f3976 100644 --- a/server/api/api/v1/accounts/:id/note.ts +++ b/server/api/api/v1/accounts/:id/note.ts @@ -4,8 +4,8 @@ import { zValidator } from "@hono/zod-validator"; import { eq } from "drizzle-orm"; import type { Hono } from "hono"; import { z } from "zod"; -import { relationshipToAPI } from "~/database/entities/Relationship"; -import { getRelationshipToOtherUser } from "~/database/entities/User"; +import { relationshipToApi } from "~/database/entities/relationship"; +import { getRelationshipToOtherUser } from "~/database/entities/user"; import { db } from "~/drizzle/db"; import { Relationships, RolePermissions } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; @@ -23,8 +23,8 @@ export const meta = applyConfig({ }, permissions: { required: [ - RolePermissions.MANAGE_OWN_ACCOUNT, - RolePermissions.VIEW_ACCOUNTS, + RolePermissions.ManageOwnAccount, + RolePermissions.ViewAccounts, ], }, }); @@ -50,11 +50,15 @@ export default (app: Hono) => const { user } = context.req.valid("header"); const { comment } = context.req.valid("json"); - if (!user) return errorResponse("Unauthorized", 401); + if (!user) { + return errorResponse("Unauthorized", 401); + } const otherUser = await User.fromId(id); - if (!otherUser) return errorResponse("User not found", 404); + if (!otherUser) { + return errorResponse("User not found", 404); + } const foundRelationship = await getRelationshipToOtherUser( user, @@ -70,6 +74,6 @@ export default (app: Hono) => }) .where(eq(Relationships.id, foundRelationship.id)); - return jsonResponse(relationshipToAPI(foundRelationship)); + return jsonResponse(relationshipToApi(foundRelationship)); }, ); diff --git a/server/api/api/v1/accounts/:id/pin.ts b/server/api/api/v1/accounts/:id/pin.ts index b4b3654d..f39a17d5 100644 --- a/server/api/api/v1/accounts/:id/pin.ts +++ b/server/api/api/v1/accounts/:id/pin.ts @@ -4,8 +4,8 @@ import { zValidator } from "@hono/zod-validator"; import { eq } from "drizzle-orm"; import type { Hono } from "hono"; import { z } from "zod"; -import { relationshipToAPI } from "~/database/entities/Relationship"; -import { getRelationshipToOtherUser } from "~/database/entities/User"; +import { relationshipToApi } from "~/database/entities/relationship"; +import { getRelationshipToOtherUser } from "~/database/entities/user"; import { db } from "~/drizzle/db"; import { Relationships, RolePermissions } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; @@ -23,8 +23,8 @@ export const meta = applyConfig({ }, permissions: { required: [ - RolePermissions.MANAGE_OWN_ACCOUNT, - RolePermissions.VIEW_ACCOUNTS, + RolePermissions.ManageOwnAccount, + RolePermissions.ViewAccounts, ], }, }); @@ -45,11 +45,15 @@ export default (app: Hono) => const { id } = context.req.valid("param"); const { user } = context.req.valid("header"); - if (!user) return errorResponse("Unauthorized", 401); + if (!user) { + return errorResponse("Unauthorized", 401); + } const otherUser = await User.fromId(id); - if (!otherUser) return errorResponse("User not found", 404); + if (!otherUser) { + return errorResponse("User not found", 404); + } const foundRelationship = await getRelationshipToOtherUser( user, @@ -67,6 +71,6 @@ export default (app: Hono) => }) .where(eq(Relationships.id, foundRelationship.id)); - return jsonResponse(relationshipToAPI(foundRelationship)); + return jsonResponse(relationshipToApi(foundRelationship)); }, ); 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 6438590f..de691217 100644 --- a/server/api/api/v1/accounts/:id/remove_from_followers.ts +++ b/server/api/api/v1/accounts/:id/remove_from_followers.ts @@ -4,8 +4,8 @@ import { zValidator } from "@hono/zod-validator"; import { and, eq } from "drizzle-orm"; import type { Hono } from "hono"; import { z } from "zod"; -import { relationshipToAPI } from "~/database/entities/Relationship"; -import { getRelationshipToOtherUser } from "~/database/entities/User"; +import { relationshipToApi } from "~/database/entities/relationship"; +import { getRelationshipToOtherUser } from "~/database/entities/user"; import { db } from "~/drizzle/db"; import { Relationships, RolePermissions } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; @@ -23,8 +23,8 @@ export const meta = applyConfig({ }, permissions: { required: [ - RolePermissions.MANAGE_OWN_FOLLOWS, - RolePermissions.VIEW_ACCOUNTS, + RolePermissions.ManageOwnFollows, + RolePermissions.ViewAccounts, ], }, }); @@ -45,11 +45,15 @@ export default (app: Hono) => const { id } = context.req.valid("param"); const { user: self } = context.req.valid("header"); - if (!self) return errorResponse("Unauthorized", 401); + if (!self) { + return errorResponse("Unauthorized", 401); + } const otherUser = await User.fromId(id); - if (!otherUser) return errorResponse("User not found", 404); + if (!otherUser) { + return errorResponse("User not found", 404); + } const foundRelationship = await getRelationshipToOtherUser( self, @@ -81,6 +85,6 @@ export default (app: Hono) => } } - return jsonResponse(relationshipToAPI(foundRelationship)); + return jsonResponse(relationshipToApi(foundRelationship)); }, ); diff --git a/server/api/api/v1/accounts/:id/statuses.test.ts b/server/api/api/v1/accounts/:id/statuses.test.ts index 3185bd47..3ff8345f 100644 --- a/server/api/api/v1/accounts/:id/statuses.test.ts +++ b/server/api/api/v1/accounts/:id/statuses.test.ts @@ -1,7 +1,7 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import { config } from "config-manager"; import { getTestStatuses, getTestUsers, sendTestRequest } from "~/tests/utils"; -import type { Status as APIStatus } from "~/types/mastodon/status"; +import type { Status as apiStatus } from "~/types/mastodon/status"; import { meta } from "./statuses"; const { users, tokens, deleteUsers } = await getTestUsers(5); @@ -50,7 +50,7 @@ describe(meta.route, () => { expect(response.status).toBe(200); - const data = (await response.json()) as APIStatus[]; + const data = (await response.json()) as apiStatus[]; expect(data.length).toBe(20); // Should have reblogs @@ -77,7 +77,7 @@ describe(meta.route, () => { expect(response.status).toBe(200); - const data = (await response.json()) as APIStatus[]; + const data = (await response.json()) as apiStatus[]; expect(data.length).toBe(20); // Should not have reblogs @@ -121,7 +121,7 @@ describe(meta.route, () => { expect(response.status).toBe(200); - const data = (await response.json()) as APIStatus[]; + const data = (await response.json()) as apiStatus[]; expect(data.length).toBe(20); // Should not have replies @@ -145,7 +145,7 @@ describe(meta.route, () => { expect(response.status).toBe(200); - const data = (await response.json()) as APIStatus[]; + const data = (await response.json()) as apiStatus[]; expect(data.length).toBe(0); @@ -183,7 +183,7 @@ describe(meta.route, () => { expect(response2.status).toBe(200); - const data2 = (await response2.json()) as APIStatus[]; + const data2 = (await response2.json()) as apiStatus[]; expect(data2.length).toBe(1); }); diff --git a/server/api/api/v1/accounts/:id/statuses.ts b/server/api/api/v1/accounts/:id/statuses.ts index 8ea37e77..14fc8692 100644 --- a/server/api/api/v1/accounts/:id/statuses.ts +++ b/server/api/api/v1/accounts/:id/statuses.ts @@ -20,7 +20,7 @@ export const meta = applyConfig({ oauthPermissions: ["read:statuses"], }, permissions: { - required: [RolePermissions.VIEW_NOTES, RolePermissions.VIEW_ACCOUNTS], + required: [RolePermissions.ViewNotes, RolePermissions.ViewAccounts], }, }); @@ -69,7 +69,9 @@ export default (app: Hono) => const otherUser = await User.fromId(id); - if (!otherUser) return errorResponse("User not found", 404); + if (!otherUser) { + return errorResponse("User not found", 404); + } const { max_id, @@ -103,7 +105,7 @@ export default (app: Hono) => ); return jsonResponse( - await Promise.all(objects.map((note) => note.toAPI(otherUser))), + await Promise.all(objects.map((note) => note.toApi(otherUser))), 200, { Link: link, diff --git a/server/api/api/v1/accounts/:id/unblock.ts b/server/api/api/v1/accounts/:id/unblock.ts index 2992a370..f0ef32e3 100644 --- a/server/api/api/v1/accounts/:id/unblock.ts +++ b/server/api/api/v1/accounts/:id/unblock.ts @@ -4,8 +4,8 @@ import { zValidator } from "@hono/zod-validator"; import { eq } from "drizzle-orm"; import type { Hono } from "hono"; import { z } from "zod"; -import { relationshipToAPI } from "~/database/entities/Relationship"; -import { getRelationshipToOtherUser } from "~/database/entities/User"; +import { relationshipToApi } from "~/database/entities/relationship"; +import { getRelationshipToOtherUser } from "~/database/entities/user"; import { db } from "~/drizzle/db"; import { Relationships, RolePermissions } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; @@ -23,8 +23,8 @@ export const meta = applyConfig({ }, permissions: { required: [ - RolePermissions.MANAGE_OWN_BLOCKS, - RolePermissions.VIEW_ACCOUNTS, + RolePermissions.ManageOwnBlocks, + RolePermissions.ViewAccounts, ], }, }); @@ -45,11 +45,15 @@ export default (app: Hono) => const { id } = context.req.valid("param"); const { user } = context.req.valid("header"); - if (!user) return errorResponse("Unauthorized", 401); + if (!user) { + return errorResponse("Unauthorized", 401); + } const otherUser = await User.fromId(id); - if (!otherUser) return errorResponse("User not found", 404); + if (!otherUser) { + return errorResponse("User not found", 404); + } const foundRelationship = await getRelationshipToOtherUser( user, @@ -67,6 +71,6 @@ export default (app: Hono) => .where(eq(Relationships.id, foundRelationship.id)); } - return jsonResponse(relationshipToAPI(foundRelationship)); + return jsonResponse(relationshipToApi(foundRelationship)); }, ); diff --git a/server/api/api/v1/accounts/:id/unfollow.ts b/server/api/api/v1/accounts/:id/unfollow.ts index b95626f8..1dc9a5d9 100644 --- a/server/api/api/v1/accounts/:id/unfollow.ts +++ b/server/api/api/v1/accounts/:id/unfollow.ts @@ -4,8 +4,8 @@ import { zValidator } from "@hono/zod-validator"; import { eq } from "drizzle-orm"; import type { Hono } from "hono"; import { z } from "zod"; -import { relationshipToAPI } from "~/database/entities/Relationship"; -import { getRelationshipToOtherUser } from "~/database/entities/User"; +import { relationshipToApi } from "~/database/entities/relationship"; +import { getRelationshipToOtherUser } from "~/database/entities/user"; import { db } from "~/drizzle/db"; import { Relationships, RolePermissions } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; @@ -23,8 +23,8 @@ export const meta = applyConfig({ }, permissions: { required: [ - RolePermissions.MANAGE_OWN_FOLLOWS, - RolePermissions.VIEW_ACCOUNTS, + RolePermissions.ManageOwnFollows, + RolePermissions.ViewAccounts, ], }, }); @@ -45,11 +45,15 @@ export default (app: Hono) => const { id } = context.req.valid("param"); const { user: self } = context.req.valid("header"); - if (!self) return errorResponse("Unauthorized", 401); + if (!self) { + return errorResponse("Unauthorized", 401); + } const otherUser = await User.fromId(id); - if (!otherUser) return errorResponse("User not found", 404); + if (!otherUser) { + return errorResponse("User not found", 404); + } const foundRelationship = await getRelationshipToOtherUser( self, @@ -68,6 +72,6 @@ export default (app: Hono) => .where(eq(Relationships.id, foundRelationship.id)); } - return jsonResponse(relationshipToAPI(foundRelationship)); + return jsonResponse(relationshipToApi(foundRelationship)); }, ); diff --git a/server/api/api/v1/accounts/:id/unmute.test.ts b/server/api/api/v1/accounts/:id/unmute.test.ts index c1c18903..6663b968 100644 --- a/server/api/api/v1/accounts/:id/unmute.test.ts +++ b/server/api/api/v1/accounts/:id/unmute.test.ts @@ -1,7 +1,7 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import { config } from "config-manager"; import { getTestUsers, sendTestRequest } from "~/tests/utils"; -import type { Relationship as APIRelationship } from "~/types/mastodon/relationship"; +import type { Relationship as apiRelationship } from "~/types/mastodon/relationship"; import { meta } from "./unmute"; const { users, tokens, deleteUsers } = await getTestUsers(2); @@ -82,7 +82,7 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - const relationship = (await response.json()) as APIRelationship; + const relationship = (await response.json()) as apiRelationship; expect(relationship.muting).toBe(false); }); @@ -103,7 +103,7 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - const relationship = (await response.json()) as APIRelationship; + const relationship = (await response.json()) as apiRelationship; expect(relationship.muting).toBe(false); }); }); diff --git a/server/api/api/v1/accounts/:id/unmute.ts b/server/api/api/v1/accounts/:id/unmute.ts index 31778738..d64e9731 100644 --- a/server/api/api/v1/accounts/:id/unmute.ts +++ b/server/api/api/v1/accounts/:id/unmute.ts @@ -4,8 +4,8 @@ import { zValidator } from "@hono/zod-validator"; import { eq } from "drizzle-orm"; import type { Hono } from "hono"; import { z } from "zod"; -import { relationshipToAPI } from "~/database/entities/Relationship"; -import { getRelationshipToOtherUser } from "~/database/entities/User"; +import { relationshipToApi } from "~/database/entities/relationship"; +import { getRelationshipToOtherUser } from "~/database/entities/user"; import { db } from "~/drizzle/db"; import { Relationships, RolePermissions } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; @@ -23,8 +23,8 @@ export const meta = applyConfig({ }, permissions: { required: [ - RolePermissions.MANAGE_OWN_MUTES, - RolePermissions.VIEW_ACCOUNTS, + RolePermissions.ManageOwnMutes, + RolePermissions.ViewAccounts, ], }, }); @@ -45,11 +45,15 @@ export default (app: Hono) => const { id } = context.req.valid("param"); const { user: self } = context.req.valid("header"); - if (!self) return errorResponse("Unauthorized", 401); + if (!self) { + return errorResponse("Unauthorized", 401); + } const user = await User.fromId(id); - if (!user) return errorResponse("User not found", 404); + if (!user) { + return errorResponse("User not found", 404); + } const foundRelationship = await getRelationshipToOtherUser( self, @@ -69,6 +73,6 @@ export default (app: Hono) => .where(eq(Relationships.id, foundRelationship.id)); } - return jsonResponse(relationshipToAPI(foundRelationship)); + return jsonResponse(relationshipToApi(foundRelationship)); }, ); diff --git a/server/api/api/v1/accounts/:id/unpin.ts b/server/api/api/v1/accounts/:id/unpin.ts index aef223b0..f9c43417 100644 --- a/server/api/api/v1/accounts/:id/unpin.ts +++ b/server/api/api/v1/accounts/:id/unpin.ts @@ -4,8 +4,8 @@ import { zValidator } from "@hono/zod-validator"; import { eq } from "drizzle-orm"; import type { Hono } from "hono"; import { z } from "zod"; -import { relationshipToAPI } from "~/database/entities/Relationship"; -import { getRelationshipToOtherUser } from "~/database/entities/User"; +import { relationshipToApi } from "~/database/entities/relationship"; +import { getRelationshipToOtherUser } from "~/database/entities/user"; import { db } from "~/drizzle/db"; import { Relationships, RolePermissions } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; @@ -23,8 +23,8 @@ export const meta = applyConfig({ }, permissions: { required: [ - RolePermissions.MANAGE_OWN_ACCOUNT, - RolePermissions.VIEW_ACCOUNTS, + RolePermissions.ManageOwnAccount, + RolePermissions.ViewAccounts, ], }, }); @@ -45,11 +45,15 @@ export default (app: Hono) => const { id } = context.req.valid("param"); const { user: self } = context.req.valid("header"); - if (!self) return errorResponse("Unauthorized", 401); + if (!self) { + return errorResponse("Unauthorized", 401); + } const otherUser = await User.fromId(id); - if (!otherUser) return errorResponse("User not found", 404); + if (!otherUser) { + return errorResponse("User not found", 404); + } const foundRelationship = await getRelationshipToOtherUser( self, @@ -67,6 +71,6 @@ export default (app: Hono) => .where(eq(Relationships.id, foundRelationship.id)); } - return jsonResponse(relationshipToAPI(foundRelationship)); + return jsonResponse(relationshipToApi(foundRelationship)); }, ); diff --git a/server/api/api/v1/accounts/familiar_followers/index.ts b/server/api/api/v1/accounts/familiar_followers/index.ts index 80e9b841..cabbd499 100644 --- a/server/api/api/v1/accounts/familiar_followers/index.ts +++ b/server/api/api/v1/accounts/familiar_followers/index.ts @@ -20,7 +20,7 @@ export const meta = applyConfig({ oauthPermissions: ["read:follows"], }, permissions: { - required: [RolePermissions.MANAGE_OWN_FOLLOWS], + required: [RolePermissions.ManageOwnFollows], }, }); @@ -41,7 +41,9 @@ export default (app: Hono) => const { user: self } = context.req.valid("header"); const { id: ids } = context.req.valid("query"); - if (!self) return errorResponse("Unauthorized", 401); + if (!self) { + return errorResponse("Unauthorized", 401); + } const idFollowerRelationships = await db.query.Relationships.findMany({ @@ -91,6 +93,6 @@ export default (app: Hono) => ), ); - return jsonResponse(finalUsers.map((o) => o.toAPI())); + return jsonResponse(finalUsers.map((o) => o.toApi())); }, ); diff --git a/server/api/api/v1/accounts/index.ts b/server/api/api/v1/accounts/index.ts index 28bc6e36..1930e02c 100644 --- a/server/api/api/v1/accounts/index.ts +++ b/server/api/api/v1/accounts/index.ts @@ -105,12 +105,13 @@ export default (app: Hono) => } // Check if username is valid - if (!username?.match(/^[a-z0-9_]+$/)) + if (!username?.match(/^[a-z0-9_]+$/)) { errors.details.username.push({ error: "ERR_INVALID", description: "must only contain lowercase letters, numbers, and underscores", }); + } // Check if username doesnt match filters if ( @@ -125,25 +126,28 @@ export default (app: Hono) => } // Check if username is too long - if ((username?.length ?? 0) > config.validation.max_username_size) + if ((username?.length ?? 0) > config.validation.max_username_size) { errors.details.username.push({ error: "ERR_TOO_LONG", description: `is too long (maximum is ${config.validation.max_username_size} characters)`, }); + } // Check if username is too short - if ((username?.length ?? 0) < 3) + if ((username?.length ?? 0) < 3) { errors.details.username.push({ error: "ERR_TOO_SHORT", description: "is too short (minimum is 3 characters)", }); + } // Check if username is reserved - if (config.validation.username_blacklist.includes(username ?? "")) + if (config.validation.username_blacklist.includes(username ?? "")) { errors.details.username.push({ error: "ERR_RESERVED", description: "is reserved", }); + } // Check if username is taken if (await User.fromSql(eq(Users.username, username))) { @@ -158,11 +162,12 @@ export default (app: Hono) => !email?.match( /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, ) - ) + ) { errors.details.email.push({ error: "ERR_INVALID", description: "must be a valid email address", }); + } // Check if email is blocked if ( @@ -171,37 +176,42 @@ export default (app: Hono) => tempmailDomains.domains.includes( (email ?? "").split("@")[1], )) - ) + ) { errors.details.email.push({ error: "ERR_BLOCKED", description: "is from a blocked email provider", }); + } // Check if email is taken - if (await User.fromSql(eq(Users.email, email))) + if (await User.fromSql(eq(Users.email, email))) { errors.details.email.push({ error: "ERR_TAKEN", description: "is already taken", }); + } // Check if agreement is accepted - if (!agreement) + if (!agreement) { errors.details.agreement.push({ error: "ERR_ACCEPTED", description: "must be accepted", }); + } - if (!locale) + if (!locale) { errors.details.locale.push({ error: "ERR_BLANK", description: `can't be blank`, }); + } - if (!ISO6391.validate(locale ?? "")) + if (!ISO6391.validate(locale ?? "")) { errors.details.locale.push({ error: "ERR_INVALID", description: "must be a valid ISO 639-1 code", }); + } // If any errors are present, return them if ( diff --git a/server/api/api/v1/accounts/lookup/index.test.ts b/server/api/api/v1/accounts/lookup/index.test.ts index a6f1499e..51255a0e 100644 --- a/server/api/api/v1/accounts/lookup/index.test.ts +++ b/server/api/api/v1/accounts/lookup/index.test.ts @@ -1,7 +1,7 @@ import { afterAll, describe, expect, test } from "bun:test"; import { config } from "config-manager"; import { getTestUsers, sendTestRequest } from "~/tests/utils"; -import type { Account as APIAccount } from "~/types/mastodon/account"; +import type { Account as apiAccount } from "~/types/mastodon/account"; import { meta } from "./index"; const { users, tokens, deleteUsers } = await getTestUsers(5); @@ -29,7 +29,7 @@ describe(meta.route, () => { expect(response.status).toBe(200); - const data = (await response.json()) as APIAccount[]; + const data = (await response.json()) as apiAccount[]; expect(data).toEqual( expect.objectContaining({ id: users[0].id, diff --git a/server/api/api/v1/accounts/lookup/index.ts b/server/api/api/v1/accounts/lookup/index.ts index 1d2c188d..7246326c 100644 --- a/server/api/api/v1/accounts/lookup/index.ts +++ b/server/api/api/v1/accounts/lookup/index.ts @@ -16,7 +16,7 @@ import { oneOrMore, } from "magic-regexp"; import { z } from "zod"; -import { resolveWebFinger } from "~/database/entities/User"; +import { resolveWebFinger } from "~/database/entities/user"; import { RolePermissions, Users } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; import { LogLevel } from "~/packages/log-manager"; @@ -33,7 +33,7 @@ export const meta = applyConfig({ oauthPermissions: [], }, permissions: { - required: [RolePermissions.SEARCH], + required: [RolePermissions.Search], }, }); @@ -84,7 +84,7 @@ export default (app: Hono) => domain, ).catch((e) => { dualLogger.logError( - LogLevel.ERROR, + LogLevel.Error, "WebFinger.Resolve", e as Error, ); @@ -92,7 +92,7 @@ export default (app: Hono) => }); if (foundAccount) { - return jsonResponse(foundAccount.toAPI()); + return jsonResponse(foundAccount.toApi()); } return errorResponse("Account not found", 404); @@ -106,7 +106,7 @@ export default (app: Hono) => const account = await User.fromSql(eq(Users.username, username)); if (account) { - return jsonResponse(account.toAPI()); + return jsonResponse(account.toApi()); } return errorResponse( diff --git a/server/api/api/v1/accounts/relationships/index.ts b/server/api/api/v1/accounts/relationships/index.ts index 7ccf080a..254d6a02 100644 --- a/server/api/api/v1/accounts/relationships/index.ts +++ b/server/api/api/v1/accounts/relationships/index.ts @@ -5,8 +5,8 @@ import type { Hono } from "hono"; import { z } from "zod"; import { createNewRelationship, - relationshipToAPI, -} from "~/database/entities/Relationship"; + relationshipToApi, +} from "~/database/entities/relationship"; import { db } from "~/drizzle/db"; import { RolePermissions } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; @@ -23,7 +23,7 @@ export const meta = applyConfig({ oauthPermissions: ["read:follows"], }, permissions: { - required: [RolePermissions.MANAGE_OWN_FOLLOWS], + required: [RolePermissions.ManageOwnFollows], }, }); @@ -46,7 +46,9 @@ export default (app: Hono) => const ids = Array.isArray(id) ? id : [id]; - if (!self) return errorResponse("Unauthorized", 401); + if (!self) { + return errorResponse("Unauthorized", 401); + } const relationships = await db.query.Relationships.findMany({ where: (relationship, { inArray, and, eq }) => @@ -62,7 +64,9 @@ export default (app: Hono) => for (const id of missingIds) { const user = await User.fromId(id); - if (!user) continue; + if (!user) { + continue; + } const relationship = await createNewRelationship(self, user); relationships.push(relationship); @@ -72,6 +76,6 @@ export default (app: Hono) => (a, b) => ids.indexOf(a.subjectId) - ids.indexOf(b.subjectId), ); - return jsonResponse(relationships.map((r) => relationshipToAPI(r))); + return jsonResponse(relationships.map((r) => relationshipToApi(r))); }, ); diff --git a/server/api/api/v1/accounts/search/index.test.ts b/server/api/api/v1/accounts/search/index.test.ts index 542c7b01..50f0534a 100644 --- a/server/api/api/v1/accounts/search/index.test.ts +++ b/server/api/api/v1/accounts/search/index.test.ts @@ -1,7 +1,7 @@ import { afterAll, describe, expect, test } from "bun:test"; import { config } from "config-manager"; import { getTestUsers, sendTestRequest } from "~/tests/utils"; -import type { Account as APIAccount } from "~/types/mastodon/account"; +import type { Account as apiAccount } from "~/types/mastodon/account"; import { meta } from "./index"; const { users, tokens, deleteUsers } = await getTestUsers(5); @@ -29,7 +29,7 @@ describe(meta.route, () => { expect(response.status).toBe(200); - const data = (await response.json()) as APIAccount[]; + const data = (await response.json()) as apiAccount[]; expect(data).toEqual( expect.arrayContaining([ expect.objectContaining({ diff --git a/server/api/api/v1/accounts/search/index.ts b/server/api/api/v1/accounts/search/index.ts index cb1d8299..dc3b4413 100644 --- a/server/api/api/v1/accounts/search/index.ts +++ b/server/api/api/v1/accounts/search/index.ts @@ -16,7 +16,7 @@ import { } from "magic-regexp"; import stringComparison from "string-comparison"; import { z } from "zod"; -import { resolveWebFinger } from "~/database/entities/User"; +import { resolveWebFinger } from "~/database/entities/user"; import { RolePermissions, Users } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; @@ -32,7 +32,7 @@ export const meta = applyConfig({ oauthPermissions: ["read:accounts"], }, permissions: { - required: [RolePermissions.SEARCH, RolePermissions.VIEW_ACCOUNTS], + required: [RolePermissions.Search, RolePermissions.ViewAccounts], }, }); @@ -81,7 +81,9 @@ export default (app: Hono) => context.req.valid("query"); const { user: self } = context.req.valid("header"); - if (!self && following) return errorResponse("Unauthorized", 401); + if (!self && following) { + return errorResponse("Unauthorized", 401); + } const [username, host] = q.replace(/^@/, "").split("@"); @@ -120,6 +122,6 @@ export default (app: Hono) => const result = indexOfCorrectSort.map((index) => accounts[index]); - return jsonResponse(result.map((acct) => acct.toAPI())); + return jsonResponse(result.map((acct) => acct.toApi())); }, ); diff --git a/server/api/api/v1/accounts/update_credentials/index.ts b/server/api/api/v1/accounts/update_credentials/index.ts index 6a8e74b8..d6d69170 100644 --- a/server/api/api/v1/accounts/update_credentials/index.ts +++ b/server/api/api/v1/accounts/update_credentials/index.ts @@ -10,9 +10,9 @@ import { MediaBackendType } from "media-manager"; 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 { contentToHtml } from "~/database/entities/Status"; +import { getUrl } from "~/database/entities/attachment"; +import { type EmojiWithInstance, parseEmojis } from "~/database/entities/emoji"; +import { contentToHtml } from "~/database/entities/status"; import { db } from "~/drizzle/db"; import { EmojiToUser, RolePermissions } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; @@ -29,7 +29,7 @@ export const meta = applyConfig({ oauthPermissions: ["write:accounts"], }, permissions: { - required: [RolePermissions.MANAGE_OWN_ACCOUNT], + required: [RolePermissions.ManageOwnAccount], }, }); @@ -116,7 +116,9 @@ export default (app: Hono) => fields_attributes, } = context.req.valid("form"); - if (!user) return errorResponse("Unauthorized", 401); + if (!user) { + return errorResponse("Unauthorized", 401); + } const self = user.data; @@ -127,7 +129,7 @@ export default (app: Hono) => let mediaManager: MediaBackend; switch (config.media.backend as MediaBackendType) { - case MediaBackendType.LOCAL: + case MediaBackendType.Local: mediaManager = new LocalMediaBackend(config); break; case MediaBackendType.S3: @@ -321,8 +323,10 @@ export default (app: Hono) => }); const output = await User.fromId(self.id); - if (!output) return errorResponse("Couldn't edit user", 500); + if (!output) { + return errorResponse("Couldn't edit user", 500); + } - return jsonResponse(output.toAPI()); + return jsonResponse(output.toApi()); }, ); diff --git a/server/api/api/v1/accounts/verify_credentials/index.ts b/server/api/api/v1/accounts/verify_credentials/index.ts index a64eb459..1624547c 100644 --- a/server/api/api/v1/accounts/verify_credentials/index.ts +++ b/server/api/api/v1/accounts/verify_credentials/index.ts @@ -20,12 +20,14 @@ export default (app: Hono) => meta.allowedMethods, meta.route, auth(meta.auth, meta.permissions), - async (context) => { + (context) => { // TODO: Add checks for disabled/unverified accounts const { user } = context.req.valid("header"); - if (!user) return errorResponse("Unauthorized", 401); + if (!user) { + return errorResponse("Unauthorized", 401); + } - return jsonResponse(user.toAPI(true)); + return jsonResponse(user.toApi(true)); }, ); diff --git a/server/api/api/v1/apps/index.ts b/server/api/api/v1/apps/index.ts index 3798b4ab..92e7615e 100644 --- a/server/api/api/v1/apps/index.ts +++ b/server/api/api/v1/apps/index.ts @@ -18,7 +18,7 @@ export const meta = applyConfig({ required: false, }, permissions: { - required: [RolePermissions.MANAGE_OWN_APPS], + required: [RolePermissions.ManageOwnApps], }, }); diff --git a/server/api/api/v1/apps/verify_credentials/index.ts b/server/api/api/v1/apps/verify_credentials/index.ts index 800395ae..bff0a4e3 100644 --- a/server/api/api/v1/apps/verify_credentials/index.ts +++ b/server/api/api/v1/apps/verify_credentials/index.ts @@ -1,7 +1,7 @@ import { applyConfig, auth } from "@/api"; import { errorResponse, jsonResponse } from "@/response"; import type { Hono } from "hono"; -import { getFromToken } from "~/database/entities/Application"; +import { getFromToken } from "~/database/entities/application"; import { RolePermissions } from "~/drizzle/schema"; export const meta = applyConfig({ @@ -15,7 +15,7 @@ export const meta = applyConfig({ required: true, }, permissions: { - required: [RolePermissions.MANAGE_OWN_APPS], + required: [RolePermissions.ManageOwnApps], }, }); @@ -27,12 +27,18 @@ export default (app: Hono) => async (context) => { const { user, token } = context.req.valid("header"); - if (!token) return errorResponse("Unauthorized", 401); - if (!user) return errorResponse("Unauthorized", 401); + if (!token) { + return errorResponse("Unauthorized", 401); + } + if (!user) { + return errorResponse("Unauthorized", 401); + } const application = await getFromToken(token); - if (!application) return errorResponse("Unauthorized", 401); + if (!application) { + return errorResponse("Unauthorized", 401); + } return jsonResponse({ name: application.name, diff --git a/server/api/api/v1/blocks/index.ts b/server/api/api/v1/blocks/index.ts index 67cc6473..884b856f 100644 --- a/server/api/api/v1/blocks/index.ts +++ b/server/api/api/v1/blocks/index.ts @@ -19,7 +19,7 @@ export const meta = applyConfig({ oauthPermissions: ["read:blocks"], }, permissions: { - required: [RolePermissions.MANAGE_OWN_BLOCKS], + required: [RolePermissions.ManageOwnBlocks], }, }); @@ -44,7 +44,9 @@ export default (app: Hono) => const { user } = context.req.valid("header"); - if (!user) return errorResponse("Unauthorized", 401); + if (!user) { + return errorResponse("Unauthorized", 401); + } const { objects: blocks, link } = await Timeline.getUserTimeline( and( @@ -58,7 +60,7 @@ export default (app: Hono) => ); return jsonResponse( - blocks.map((u) => u.toAPI()), + blocks.map((u) => u.toApi()), 200, { Link: link, diff --git a/server/api/api/v1/custom_emojis/index.ts b/server/api/api/v1/custom_emojis/index.ts index f7c801c1..7b2646ff 100644 --- a/server/api/api/v1/custom_emojis/index.ts +++ b/server/api/api/v1/custom_emojis/index.ts @@ -1,7 +1,7 @@ import { applyConfig, auth } from "@/api"; import { jsonResponse } from "@/response"; import type { Hono } from "hono"; -import { emojiToAPI } from "~/database/entities/Emoji"; +import { emojiToApi } from "~/database/entities/emoji"; import { db } from "~/drizzle/db"; import { RolePermissions } from "~/drizzle/schema"; @@ -16,7 +16,7 @@ export const meta = applyConfig({ required: false, }, permissions: { - required: [RolePermissions.VIEW_EMOJIS], + required: [RolePermissions.ViewEmojis], }, }); @@ -43,7 +43,7 @@ export default (app: Hono) => }); return jsonResponse( - await Promise.all(emojis.map((emoji) => emojiToAPI(emoji))), + await Promise.all(emojis.map((emoji) => emojiToApi(emoji))), ); }, ); diff --git a/server/api/api/v1/emojis/:id/index.ts b/server/api/api/v1/emojis/:id/index.ts index 35b4e081..2ee61b37 100644 --- a/server/api/api/v1/emojis/:id/index.ts +++ b/server/api/api/v1/emojis/:id/index.ts @@ -11,8 +11,8 @@ import { zValidator } from "@hono/zod-validator"; 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 { 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"; @@ -29,10 +29,7 @@ export const meta = applyConfig({ required: true, }, permissions: { - required: [ - RolePermissions.MANAGE_OWN_EMOJIS, - RolePermissions.VIEW_EMOJIS, - ], + required: [RolePermissions.ManageOwnEmojis, RolePermissions.ViewEmojis], }, }); @@ -93,16 +90,18 @@ export default (app: Hono) => }, }); - if (!emoji) return errorResponse("Emoji not found", 404); + if (!emoji) { + return errorResponse("Emoji not found", 404); + } // Check if user is admin if ( - !user.hasPermission(RolePermissions.MANAGE_EMOJIS) && + !user.hasPermission(RolePermissions.ManageEmojis) && emoji.ownerId !== user.data.id ) { return jsonResponse( { - error: `You cannot modify this emoji, as it is either global, not owned by you, or you do not have the '${RolePermissions.MANAGE_EMOJIS}' permission to manage global emojis`, + error: `You cannot modify this emoji, as it is either global, not owned by you, or you do not have the '${RolePermissions.ManageEmojis}' permission to manage global emojis`, }, 403, ); @@ -133,10 +132,12 @@ export default (app: Hono) => } if ( - !form.shortcode && - !form.element && - !form.alt && - !form.category && + !( + form.shortcode || + form.element || + form.alt || + form.category + ) && form.global === undefined ) { return errorResponse( @@ -146,11 +147,11 @@ export default (app: Hono) => } if ( - !user.hasPermission(RolePermissions.MANAGE_EMOJIS) && + !user.hasPermission(RolePermissions.ManageEmojis) && form.global ) { return errorResponse( - `Only users with the '${RolePermissions.MANAGE_EMOJIS}' permission can make an emoji global or not`, + `Only users with the '${RolePermissions.ManageEmojis}' permission can make an emoji global or not`, 401, ); } @@ -207,7 +208,7 @@ export default (app: Hono) => )[0]; return jsonResponse( - emojiToAPI({ + emojiToApi({ ...newEmoji, instance: null, }), @@ -215,7 +216,7 @@ export default (app: Hono) => } case "GET": { - return jsonResponse(emojiToAPI(emoji)); + return jsonResponse(emojiToApi(emoji)); } } }, diff --git a/server/api/api/v1/emojis/index.ts b/server/api/api/v1/emojis/index.ts index 926b1b4d..48c8cd24 100644 --- a/server/api/api/v1/emojis/index.ts +++ b/server/api/api/v1/emojis/index.ts @@ -10,8 +10,8 @@ import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import type { Hono } from "hono"; import { z } from "zod"; -import { getUrl } from "~/database/entities/Attachment"; -import { emojiToAPI } from "~/database/entities/Emoji"; +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"; @@ -28,10 +28,7 @@ export const meta = applyConfig({ required: true, }, permissions: { - required: [ - RolePermissions.MANAGE_OWN_EMOJIS, - RolePermissions.VIEW_EMOJIS, - ], + required: [RolePermissions.ManageOwnEmojis, RolePermissions.ViewEmojis], }, }); @@ -79,9 +76,9 @@ export default (app: Hono) => return errorResponse("Unauthorized", 401); } - if (!user.hasPermission(RolePermissions.MANAGE_EMOJIS) && global) { + if (!user.hasPermission(RolePermissions.ManageEmojis) && global) { return errorResponse( - `Only users with the '${RolePermissions.MANAGE_EMOJIS}' permission can upload global emojis`, + `Only users with the '${RolePermissions.ManageEmojis}' permission can upload global emojis`, 401, ); } @@ -148,7 +145,7 @@ export default (app: Hono) => )[0]; return jsonResponse( - emojiToAPI({ + emojiToApi({ ...emoji, instance: null, }), diff --git a/server/api/api/v1/favourites/index.ts b/server/api/api/v1/favourites/index.ts index 5e490380..50bffd59 100644 --- a/server/api/api/v1/favourites/index.ts +++ b/server/api/api/v1/favourites/index.ts @@ -18,7 +18,7 @@ export const meta = applyConfig({ required: true, }, permissions: { - required: [RolePermissions.MANAGE_OWN_LIKES], + required: [RolePermissions.ManageOwnLikes], }, }); @@ -43,7 +43,9 @@ export default (app: Hono) => const { user } = context.req.valid("header"); - if (!user) return errorResponse("Unauthorized", 401); + if (!user) { + return errorResponse("Unauthorized", 401); + } const { objects: favourites, link } = await Timeline.getNoteTimeline( @@ -60,7 +62,7 @@ export default (app: Hono) => return jsonResponse( await Promise.all( - favourites.map(async (note) => note.toAPI(user)), + favourites.map(async (note) => note.toApi(user)), ), 200, { diff --git a/server/api/api/v1/follow_requests/:account_id/authorize.ts b/server/api/api/v1/follow_requests/:account_id/authorize.ts index 059e1e95..1e002aba 100644 --- a/server/api/api/v1/follow_requests/:account_id/authorize.ts +++ b/server/api/api/v1/follow_requests/:account_id/authorize.ts @@ -6,12 +6,12 @@ import type { Hono } from "hono"; import { z } from "zod"; import { checkForBidirectionalRelationships, - relationshipToAPI, -} from "~/database/entities/Relationship"; + relationshipToApi, +} from "~/database/entities/relationship"; import { getRelationshipToOtherUser, sendFollowAccept, -} from "~/database/entities/User"; +} from "~/database/entities/user"; import { db } from "~/drizzle/db"; import { Relationships, RolePermissions } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; @@ -27,7 +27,7 @@ export const meta = applyConfig({ required: true, }, permissions: { - required: [RolePermissions.MANAGE_OWN_FOLLOWS], + required: [RolePermissions.ManageOwnFollows], }, }); @@ -46,13 +46,17 @@ export default (app: Hono) => async (context) => { const { user } = context.req.valid("header"); - if (!user) return errorResponse("Unauthorized", 401); + if (!user) { + return errorResponse("Unauthorized", 401); + } const { account_id } = context.req.valid("param"); const account = await User.fromId(account_id); - if (!account) return errorResponse("Account not found", 404); + if (!account) { + return errorResponse("Account not found", 404); + } // Check if there is a relationship on both sides await checkForBidirectionalRelationships(user, account); @@ -90,8 +94,9 @@ export default (app: Hono) => account, ); - if (!foundRelationship) + if (!foundRelationship) { return errorResponse("Relationship not found", 404); + } // Check if accepting remote follow if (account.isRemote()) { @@ -99,6 +104,6 @@ export default (app: Hono) => await sendFollowAccept(account, user); } - return jsonResponse(relationshipToAPI(foundRelationship)); + return jsonResponse(relationshipToApi(foundRelationship)); }, ); diff --git a/server/api/api/v1/follow_requests/:account_id/reject.ts b/server/api/api/v1/follow_requests/:account_id/reject.ts index 8dce3698..d48844b8 100644 --- a/server/api/api/v1/follow_requests/:account_id/reject.ts +++ b/server/api/api/v1/follow_requests/:account_id/reject.ts @@ -6,12 +6,12 @@ import type { Hono } from "hono"; import { z } from "zod"; import { checkForBidirectionalRelationships, - relationshipToAPI, -} from "~/database/entities/Relationship"; + relationshipToApi, +} from "~/database/entities/relationship"; import { getRelationshipToOtherUser, sendFollowReject, -} from "~/database/entities/User"; +} from "~/database/entities/user"; import { db } from "~/drizzle/db"; import { Relationships, RolePermissions } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; @@ -27,7 +27,7 @@ export const meta = applyConfig({ required: true, }, permissions: { - required: [RolePermissions.MANAGE_OWN_FOLLOWS], + required: [RolePermissions.ManageOwnFollows], }, }); @@ -46,13 +46,17 @@ export default (app: Hono) => async (context) => { const { user } = context.req.valid("header"); - if (!user) return errorResponse("Unauthorized", 401); + if (!user) { + return errorResponse("Unauthorized", 401); + } const { account_id } = context.req.valid("param"); const account = await User.fromId(account_id); - if (!account) return errorResponse("Account not found", 404); + if (!account) { + return errorResponse("Account not found", 404); + } // Check if there is a relationship on both sides await checkForBidirectionalRelationships(user, account); @@ -90,8 +94,9 @@ export default (app: Hono) => account, ); - if (!foundRelationship) + if (!foundRelationship) { return errorResponse("Relationship not found", 404); + } // Check if rejecting remote follow if (account.isRemote()) { @@ -99,6 +104,6 @@ export default (app: Hono) => await sendFollowReject(account, user); } - return jsonResponse(relationshipToAPI(foundRelationship)); + return jsonResponse(relationshipToApi(foundRelationship)); }, ); diff --git a/server/api/api/v1/follow_requests/index.ts b/server/api/api/v1/follow_requests/index.ts index d6efb44a..539c2ec3 100644 --- a/server/api/api/v1/follow_requests/index.ts +++ b/server/api/api/v1/follow_requests/index.ts @@ -18,7 +18,7 @@ export const meta = applyConfig({ required: true, }, permissions: { - required: [RolePermissions.MANAGE_OWN_FOLLOWS], + required: [RolePermissions.ManageOwnFollows], }, }); @@ -43,7 +43,9 @@ export default (app: Hono) => const { user } = context.req.valid("header"); - if (!user) return errorResponse("Unauthorized", 401); + if (!user) { + return errorResponse("Unauthorized", 401); + } const { objects: followRequests, link } = await Timeline.getUserTimeline( @@ -58,7 +60,7 @@ export default (app: Hono) => ); return jsonResponse( - followRequests.map((u) => u.toAPI()), + followRequests.map((u) => u.toApi()), 200, { Link: link, diff --git a/server/api/api/v1/frontend/config/index.ts b/server/api/api/v1/frontend/config/index.ts index 46297921..2eebc439 100644 --- a/server/api/api/v1/frontend/config/index.ts +++ b/server/api/api/v1/frontend/config/index.ts @@ -16,6 +16,6 @@ export const meta = applyConfig({ }); export default (app: Hono) => - app.on(meta.allowedMethods, meta.route, async () => { + app.on(meta.allowedMethods, meta.route, () => { return jsonResponse(config.frontend.settings); }); diff --git a/server/api/api/v1/instance/index.ts b/server/api/api/v1/instance/index.ts index c97b00ef..35b0e38d 100644 --- a/server/api/api/v1/instance/index.ts +++ b/server/api/api/v1/instance/index.ts @@ -8,7 +8,7 @@ import manifest from "~/package.json"; import { config } from "~/packages/config-manager"; import { Note } from "~/packages/database-interface/note"; import { User } from "~/packages/database-interface/user"; -import type { Instance as APIInstance } from "~/types/mastodon/instance"; +import type { Instance as apiInstance } from "~/types/mastodon/instance"; export const meta = applyConfig({ allowedMethods: ["GET"], @@ -96,8 +96,8 @@ export default (app: Hono) => id: p.id, })), }, - contact_account: contactAccount?.toAPI() || undefined, - } satisfies APIInstance & { + contact_account: contactAccount?.toApi() || undefined, + } satisfies apiInstance & { banner: string | null; lysand_version: string; sso: { diff --git a/server/api/api/v1/instance/rules.ts b/server/api/api/v1/instance/rules.ts index 9bb91f6c..cd14b1da 100644 --- a/server/api/api/v1/instance/rules.ts +++ b/server/api/api/v1/instance/rules.ts @@ -20,7 +20,7 @@ export default (app: Hono) => meta.allowedMethods, meta.route, auth(meta.auth, meta.permissions), - async (context) => { + async (_context) => { return jsonResponse( config.signups.rules.map((rule, index) => ({ id: String(index), diff --git a/server/api/api/v1/markers/index.ts b/server/api/api/v1/markers/index.ts index aff8a468..57aa49e8 100644 --- a/server/api/api/v1/markers/index.ts +++ b/server/api/api/v1/markers/index.ts @@ -6,7 +6,7 @@ import type { Hono } from "hono"; import { z } from "zod"; import { db } from "~/drizzle/db"; import { Markers, RolePermissions } from "~/drizzle/schema"; -import type { Marker as APIMarker } from "~/types/mastodon/marker"; +import type { Marker as apiMarker } from "~/types/mastodon/marker"; export const meta = applyConfig({ allowedMethods: ["GET", "POST"], @@ -20,7 +20,7 @@ export const meta = applyConfig({ oauthPermissions: ["read:blocks"], }, permissions: { - required: [RolePermissions.MANAGE_OWN_ACCOUNT], + required: [RolePermissions.ManageOwnAccount], }, }); @@ -58,7 +58,7 @@ export default (app: Hono) => return jsonResponse({}); } - const markers: APIMarker = { + const markers: apiMarker = { home: undefined, notifications: undefined, }; @@ -132,23 +132,23 @@ export default (app: Hono) => case "POST": { const { - "home[last_read_id]": home_id, - "notifications[last_read_id]": notifications_id, + "home[last_read_id]": homeId, + "notifications[last_read_id]": notificationsId, } = context.req.valid("query"); - const markers: APIMarker = { + const markers: apiMarker = { home: undefined, notifications: undefined, }; - if (home_id) { + if (homeId) { const insertedMarker = ( await db .insert(Markers) .values({ userId: user.id, timeline: "home", - noteId: home_id, + noteId: homeId, }) .returning() )[0]; @@ -166,7 +166,7 @@ export default (app: Hono) => ); markers.home = { - last_read_id: home_id, + last_read_id: homeId, version: totalCount[0].count, updated_at: new Date( insertedMarker.createdAt, @@ -174,14 +174,14 @@ export default (app: Hono) => }; } - if (notifications_id) { + if (notificationsId) { const insertedMarker = ( await db .insert(Markers) .values({ userId: user.id, timeline: "notifications", - notificationId: notifications_id, + notificationId: notificationsId, }) .returning() )[0]; @@ -199,7 +199,7 @@ export default (app: Hono) => ); markers.notifications = { - last_read_id: notifications_id, + last_read_id: notificationsId, version: totalCount[0].count, updated_at: new Date( insertedMarker.createdAt, diff --git a/server/api/api/v1/media/:id/index.ts b/server/api/api/v1/media/:id/index.ts index c83d0897..5584465b 100644 --- a/server/api/api/v1/media/:id/index.ts +++ b/server/api/api/v1/media/:id/index.ts @@ -7,7 +7,7 @@ import type { MediaBackend } from "media-manager"; import { MediaBackendType } from "media-manager"; import { LocalMediaBackend, S3MediaBackend } from "media-manager"; import { z } from "zod"; -import { getUrl } from "~/database/entities/Attachment"; +import { getUrl } from "~/database/entities/attachment"; import { RolePermissions } from "~/drizzle/schema"; import { Attachment } from "~/packages/database-interface/attachment"; @@ -23,7 +23,7 @@ export const meta = applyConfig({ oauthPermissions: ["write:media"], }, permissions: { - required: [RolePermissions.MANAGE_OWN_MEDIA], + required: [RolePermissions.ManageOwnMedia], }, }); @@ -64,7 +64,7 @@ export default (app: Hono) => switch (context.req.method) { case "GET": { if (attachment.data.url) { - return jsonResponse(attachment.toAPI()); + return jsonResponse(attachment.toApi()); } return response(null, 206); } @@ -77,7 +77,7 @@ export default (app: Hono) => let mediaManager: MediaBackend; switch (config.media.backend as MediaBackendType) { - case MediaBackendType.LOCAL: + case MediaBackendType.Local: mediaManager = new LocalMediaBackend(config); break; case MediaBackendType.S3: @@ -105,10 +105,10 @@ export default (app: Hono) => thumbnailUrl, }); - return jsonResponse(attachment.toAPI()); + return jsonResponse(attachment.toApi()); } - return jsonResponse(attachment.toAPI()); + return jsonResponse(attachment.toApi()); } } diff --git a/server/api/api/v1/media/index.ts b/server/api/api/v1/media/index.ts index 30c4fd81..74238fdc 100644 --- a/server/api/api/v1/media/index.ts +++ b/server/api/api/v1/media/index.ts @@ -9,7 +9,7 @@ import type { MediaBackend } from "media-manager"; import { LocalMediaBackend, S3MediaBackend } from "media-manager"; import sharp from "sharp"; import { z } from "zod"; -import { getUrl } from "~/database/entities/Attachment"; +import { getUrl } from "~/database/entities/attachment"; import { RolePermissions } from "~/drizzle/schema"; import { Attachment } from "~/packages/database-interface/attachment"; @@ -25,7 +25,7 @@ export const meta = applyConfig({ oauthPermissions: ["write:media"], }, permissions: { - required: [RolePermissions.MANAGE_OWN_MEDIA], + required: [RolePermissions.ManageOwnMedia], }, }); @@ -104,7 +104,7 @@ export default (app: Hono) => let mediaManager: MediaBackend; switch (config.media.backend as MediaBackendType) { - case MediaBackendType.LOCAL: + case MediaBackendType.Local: mediaManager = new LocalMediaBackend(config); break; case MediaBackendType.S3: @@ -141,6 +141,6 @@ export default (app: Hono) => // TODO: Add job to process videos and other media - return jsonResponse(newAttachment.toAPI()); + return jsonResponse(newAttachment.toApi()); }, ); diff --git a/server/api/api/v1/mutes/index.ts b/server/api/api/v1/mutes/index.ts index ba6d3586..7cfc49d5 100644 --- a/server/api/api/v1/mutes/index.ts +++ b/server/api/api/v1/mutes/index.ts @@ -19,7 +19,7 @@ export const meta = applyConfig({ oauthPermissions: ["read:mutes"], }, permissions: { - required: [RolePermissions.MANAGE_OWN_MUTES], + required: [RolePermissions.ManageOwnMutes], }, }); @@ -43,7 +43,9 @@ export default (app: Hono) => context.req.valid("query"); const { user } = context.req.valid("header"); - if (!user) return errorResponse("Unauthorized", 401); + if (!user) { + return errorResponse("Unauthorized", 401); + } const { objects: mutes, link } = await Timeline.getUserTimeline( and( @@ -57,7 +59,7 @@ export default (app: Hono) => ); return jsonResponse( - mutes.map((u) => u.toAPI()), + mutes.map((u) => u.toApi()), 200, { Link: link, diff --git a/server/api/api/v1/notifications/:id/dismiss.test.ts b/server/api/api/v1/notifications/:id/dismiss.test.ts index b5b8daae..c502146b 100644 --- a/server/api/api/v1/notifications/:id/dismiss.test.ts +++ b/server/api/api/v1/notifications/:id/dismiss.test.ts @@ -1,11 +1,11 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import { config } from "config-manager"; import { getTestUsers, sendTestRequest } from "~/tests/utils"; -import type { Notification as APINotification } from "~/types/mastodon/notification"; +import type { Notification as apiNotification } from "~/types/mastodon/notification"; import { meta } from "./dismiss"; const { users, tokens, deleteUsers } = await getTestUsers(2); -let notifications: APINotification[] = []; +let notifications: apiNotification[] = []; // Create some test notifications: follow, favourite, reblog, mention beforeAll(async () => { diff --git a/server/api/api/v1/notifications/:id/dismiss.ts b/server/api/api/v1/notifications/:id/dismiss.ts index 78b74984..7e402f1e 100644 --- a/server/api/api/v1/notifications/:id/dismiss.ts +++ b/server/api/api/v1/notifications/:id/dismiss.ts @@ -19,7 +19,7 @@ export const meta = applyConfig({ oauthPermissions: ["write:notifications"], }, permissions: { - required: [RolePermissions.MANAGE_OWN_NOTIFICATIONS], + required: [RolePermissions.ManageOwnNotifications], }, }); @@ -39,7 +39,9 @@ export default (app: Hono) => const { id } = context.req.valid("param"); const { user } = context.req.valid("header"); - if (!user) return errorResponse("Unauthorized", 401); + if (!user) { + return errorResponse("Unauthorized", 401); + } await db .update(Notifications) diff --git a/server/api/api/v1/notifications/:id/index.test.ts b/server/api/api/v1/notifications/:id/index.test.ts index 2f43ba6a..6ac40cfa 100644 --- a/server/api/api/v1/notifications/:id/index.test.ts +++ b/server/api/api/v1/notifications/:id/index.test.ts @@ -1,11 +1,11 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import { config } from "config-manager"; import { getTestUsers, sendTestRequest } from "~/tests/utils"; -import type { Notification as APINotification } from "~/types/mastodon/notification"; +import type { Notification as apiNotification } from "~/types/mastodon/notification"; import { meta } from "./index"; const { users, tokens, deleteUsers } = await getTestUsers(2); -let notifications: APINotification[] = []; +let notifications: apiNotification[] = []; // Create some test notifications: follow, favourite, reblog, mention beforeAll(async () => { @@ -114,7 +114,7 @@ describe(meta.route, () => { expect(response.status).toBe(200); - const notification = (await response.json()) as APINotification; + const notification = (await response.json()) as apiNotification; expect(notification).toBeDefined(); expect(notification.id).toBe(notifications[0].id); diff --git a/server/api/api/v1/notifications/:id/index.ts b/server/api/api/v1/notifications/:id/index.ts index c6315a9e..0ff7ec0c 100644 --- a/server/api/api/v1/notifications/:id/index.ts +++ b/server/api/api/v1/notifications/:id/index.ts @@ -3,7 +3,7 @@ import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import type { Hono } from "hono"; import { z } from "zod"; -import { findManyNotifications } from "~/database/entities/Notification"; +import { findManyNotifications } from "~/database/entities/notification"; import { RolePermissions } from "~/drizzle/schema"; export const meta = applyConfig({ @@ -18,7 +18,7 @@ export const meta = applyConfig({ oauthPermissions: ["read:notifications"], }, permissions: { - required: [RolePermissions.MANAGE_OWN_NOTIFICATIONS], + required: [RolePermissions.ManageOwnNotifications], }, }); @@ -38,7 +38,9 @@ export default (app: Hono) => const { id } = context.req.valid("param"); const { user } = context.req.valid("header"); - if (!user) return errorResponse("Unauthorized", 401); + if (!user) { + return errorResponse("Unauthorized", 401); + } const notification = ( await findManyNotifications( @@ -51,8 +53,9 @@ export default (app: Hono) => ) )[0]; - if (!notification) + if (!notification) { return errorResponse("Notification not found", 404); + } return jsonResponse(notification); }, diff --git a/server/api/api/v1/notifications/clear/index.test.ts b/server/api/api/v1/notifications/clear/index.test.ts index 70d0c020..bb4d5de1 100644 --- a/server/api/api/v1/notifications/clear/index.test.ts +++ b/server/api/api/v1/notifications/clear/index.test.ts @@ -1,11 +1,11 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import { config } from "config-manager"; import { getTestUsers, sendTestRequest } from "~/tests/utils"; -import type { Notification as APINotification } from "~/types/mastodon/notification"; +import type { Notification as apiNotification } from "~/types/mastodon/notification"; import { meta } from "./index"; const { users, tokens, deleteUsers } = await getTestUsers(2); -let notifications: APINotification[] = []; +let notifications: apiNotification[] = []; // Create some test notifications: follow, favourite, reblog, mention beforeAll(async () => { diff --git a/server/api/api/v1/notifications/clear/index.ts b/server/api/api/v1/notifications/clear/index.ts index b4f18aca..c4564e58 100644 --- a/server/api/api/v1/notifications/clear/index.ts +++ b/server/api/api/v1/notifications/clear/index.ts @@ -17,7 +17,7 @@ export const meta = applyConfig({ oauthPermissions: ["write:notifications"], }, permissions: { - required: [RolePermissions.MANAGE_OWN_NOTIFICATIONS], + required: [RolePermissions.ManageOwnNotifications], }, }); @@ -28,7 +28,9 @@ export default (app: Hono) => auth(meta.auth, meta.permissions), async (context) => { const { user } = context.req.valid("header"); - if (!user) return errorResponse("Unauthorized", 401); + if (!user) { + return errorResponse("Unauthorized", 401); + } await db .update(Notifications) diff --git a/server/api/api/v1/notifications/destroy_multiple/index.test.ts b/server/api/api/v1/notifications/destroy_multiple/index.test.ts index b5a9ab7c..763501d2 100644 --- a/server/api/api/v1/notifications/destroy_multiple/index.test.ts +++ b/server/api/api/v1/notifications/destroy_multiple/index.test.ts @@ -1,12 +1,12 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import { config } from "config-manager"; import { getTestStatuses, getTestUsers, sendTestRequest } from "~/tests/utils"; -import type { Notification as APINotification } from "~/types/mastodon/notification"; +import type { Notification as apiNotification } from "~/types/mastodon/notification"; import { meta } from "./index"; const { users, tokens, deleteUsers } = await getTestUsers(2); const statuses = await getTestStatuses(40, users[0]); -let notifications: APINotification[] = []; +let notifications: apiNotification[] = []; // Create some test notifications beforeAll(async () => { diff --git a/server/api/api/v1/notifications/destroy_multiple/index.ts b/server/api/api/v1/notifications/destroy_multiple/index.ts index 42eef872..23511cc5 100644 --- a/server/api/api/v1/notifications/destroy_multiple/index.ts +++ b/server/api/api/v1/notifications/destroy_multiple/index.ts @@ -19,7 +19,7 @@ export const meta = applyConfig({ oauthPermissions: ["write:notifications"], }, permissions: { - required: [RolePermissions.MANAGE_OWN_NOTIFICATIONS], + required: [RolePermissions.ManageOwnNotifications], }, }); @@ -38,7 +38,9 @@ export default (app: Hono) => async (context) => { const { user } = context.req.valid("header"); - if (!user) return errorResponse("Unauthorized", 401); + if (!user) { + return errorResponse("Unauthorized", 401); + } const { "ids[]": ids } = context.req.valid("query"); diff --git a/server/api/api/v1/notifications/index.test.ts b/server/api/api/v1/notifications/index.test.ts index aee0eb80..1112a4e9 100644 --- a/server/api/api/v1/notifications/index.test.ts +++ b/server/api/api/v1/notifications/index.test.ts @@ -1,7 +1,7 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import { config } from "config-manager"; import { getTestStatuses, getTestUsers, sendTestRequest } from "~/tests/utils"; -import type { Notification as APINotification } from "~/types/mastodon/notification"; +import type { Notification as apiNotification } from "~/types/mastodon/notification"; import { meta } from "./index"; const getFormData = (object: Record) => @@ -113,7 +113,7 @@ describe(meta.route, () => { expect(response.status).toBe(200); expect(response.headers.get("content-type")).toBe("application/json"); - const objects = (await response.json()) as APINotification[]; + const objects = (await response.json()) as apiNotification[]; expect(objects.length).toBe(4); for (const [index, notification] of objects.entries()) { @@ -165,7 +165,7 @@ describe(meta.route, () => { expect(response.status).toBe(200); expect(response.headers.get("content-type")).toBe("application/json"); - const objects = (await response.json()) as APINotification[]; + const objects = (await response.json()) as apiNotification[]; expect(objects.length).toBe(2); // There should be no element with a status with id of timeline[0].id diff --git a/server/api/api/v1/notifications/index.ts b/server/api/api/v1/notifications/index.ts index b3454e10..f8c4a99e 100644 --- a/server/api/api/v1/notifications/index.ts +++ b/server/api/api/v1/notifications/index.ts @@ -7,9 +7,9 @@ import type { Hono } from "hono"; import { z } from "zod"; import { findManyNotifications, - notificationToAPI, -} from "~/database/entities/Notification"; -import type { NotificationWithRelations } from "~/database/entities/Notification"; + notificationToApi, +} from "~/database/entities/notification"; +import type { NotificationWithRelations } from "~/database/entities/notification"; import { RolePermissions } from "~/drizzle/schema"; export const meta = applyConfig({ @@ -25,8 +25,8 @@ export const meta = applyConfig({ }, permissions: { required: [ - RolePermissions.MANAGE_OWN_NOTIFICATIONS, - RolePermissions.VIEW_PRIVATE_TIMELINES, + RolePermissions.ManageOwnNotifications, + RolePermissions.ViewPrimateTimelines, ], }, }); @@ -99,7 +99,9 @@ export default (app: Hono) => auth(meta.auth, meta.permissions), async (context) => { const { user } = context.req.valid("header"); - if (!user) return errorResponse("Unauthorized", 401); + if (!user) { + return errorResponse("Unauthorized", 401); + } const { account_id, @@ -185,7 +187,7 @@ export default (app: Hono) => ); return jsonResponse( - await Promise.all(objects.map((n) => notificationToAPI(n))), + await Promise.all(objects.map((n) => notificationToApi(n))), 200, { Link: link, diff --git a/server/api/api/v1/profile/avatar.ts b/server/api/api/v1/profile/avatar.ts index 43c1e3db..0c9c3472 100644 --- a/server/api/api/v1/profile/avatar.ts +++ b/server/api/api/v1/profile/avatar.ts @@ -14,7 +14,7 @@ export const meta = applyConfig({ required: true, }, permissions: { - required: [RolePermissions.MANAGE_OWN_ACCOUNT], + required: [RolePermissions.ManageOwnAccount], }, }); @@ -26,12 +26,14 @@ export default (app: Hono) => async (context) => { const { user: self } = context.req.valid("header"); - if (!self) return errorResponse("Unauthorized", 401); + if (!self) { + return errorResponse("Unauthorized", 401); + } await self.update({ avatar: "", }); - return jsonResponse(self.toAPI(true)); + return jsonResponse(self.toApi(true)); }, ); diff --git a/server/api/api/v1/profile/header.ts b/server/api/api/v1/profile/header.ts index 60c2dec0..871b4bb5 100644 --- a/server/api/api/v1/profile/header.ts +++ b/server/api/api/v1/profile/header.ts @@ -14,7 +14,7 @@ export const meta = applyConfig({ required: true, }, permissions: { - required: [RolePermissions.MANAGE_OWN_ACCOUNT], + required: [RolePermissions.ManageOwnAccount], }, }); @@ -26,12 +26,14 @@ export default (app: Hono) => async (context) => { const { user: self } = context.req.valid("header"); - if (!self) return errorResponse("Unauthorized", 401); + if (!self) { + return errorResponse("Unauthorized", 401); + } await self.update({ header: "", }); - return jsonResponse(self.toAPI(true)); + return jsonResponse(self.toApi(true)); }, ); diff --git a/server/api/api/v1/roles/:id/index.test.ts b/server/api/api/v1/roles/:id/index.test.ts index 7f589391..099dd95e 100644 --- a/server/api/api/v1/roles/:id/index.test.ts +++ b/server/api/api/v1/roles/:id/index.test.ts @@ -150,7 +150,7 @@ describe(meta.route, () => { test("should assign new role", async () => { await role.update({ - permissions: [RolePermissions.MANAGE_ROLES], + permissions: [RolePermissions.ManageRoles], }); const response = await sendTestRequest( @@ -244,7 +244,7 @@ describe(meta.route, () => { test("should return 403 if user tries to add role with higher priority", async () => { // Add MANAGE_ROLES permission to user await role.update({ - permissions: [RolePermissions.MANAGE_ROLES], + permissions: [RolePermissions.ManageRoles], }); const response = await sendTestRequest( diff --git a/server/api/api/v1/roles/:id/index.ts b/server/api/api/v1/roles/:id/index.ts index 4e79770d..7c3b6329 100644 --- a/server/api/api/v1/roles/:id/index.ts +++ b/server/api/api/v1/roles/:id/index.ts @@ -19,8 +19,8 @@ export const meta = applyConfig({ permissions: { required: [], methodOverrides: { - POST: [RolePermissions.MANAGE_ROLES], - DELETE: [RolePermissions.MANAGE_ROLES], + POST: [RolePermissions.ManageRoles], + DELETE: [RolePermissions.ManageRoles], }, }, }); @@ -57,7 +57,7 @@ export default (app: Hono) => switch (context.req.method) { case "GET": { - return jsonResponse(role.toAPI()); + return jsonResponse(role.toApi()); } case "POST": { diff --git a/server/api/api/v1/roles/index.ts b/server/api/api/v1/roles/index.ts index 8bd19327..a251617e 100644 --- a/server/api/api/v1/roles/index.ts +++ b/server/api/api/v1/roles/index.ts @@ -32,6 +32,6 @@ export default (app: Hono) => user.data.isAdmin, ); - return jsonResponse(userRoles.map((r) => r.toAPI())); + return jsonResponse(userRoles.map((r) => r.toApi())); }, ); diff --git a/server/api/api/v1/sso/:id/index.ts b/server/api/api/v1/sso/:id/index.ts index 6fa37486..e83523ef 100644 --- a/server/api/api/v1/sso/:id/index.ts +++ b/server/api/api/v1/sso/:id/index.ts @@ -19,7 +19,7 @@ export const meta = applyConfig({ }, route: "/api/v1/sso/:id", permissions: { - required: [RolePermissions.OAUTH], + required: [RolePermissions.OAuth], }, }); diff --git a/server/api/api/v1/sso/index.ts b/server/api/api/v1/sso/index.ts index de983527..da320e61 100644 --- a/server/api/api/v1/sso/index.ts +++ b/server/api/api/v1/sso/index.ts @@ -30,7 +30,7 @@ export const meta = applyConfig({ }, route: "/api/v1/sso", permissions: { - required: [RolePermissions.OAUTH], + required: [RolePermissions.OAuth], }, }); diff --git a/server/api/api/v1/statuses/:id/context.ts b/server/api/api/v1/statuses/:id/context.ts index 04214bd8..3e7570bd 100644 --- a/server/api/api/v1/statuses/:id/context.ts +++ b/server/api/api/v1/statuses/:id/context.ts @@ -17,7 +17,7 @@ export const meta = applyConfig({ required: false, }, permissions: { - required: [RolePermissions.VIEW_NOTES], + required: [RolePermissions.ViewNotes], }, }); @@ -40,7 +40,9 @@ export default (app: Hono) => const foundStatus = await Note.fromId(id, user?.id); - if (!foundStatus) return errorResponse("Record not found", 404); + if (!foundStatus) { + return errorResponse("Record not found", 404); + } const ancestors = await foundStatus.getAncestors(user ?? null); @@ -48,10 +50,10 @@ export default (app: Hono) => return jsonResponse({ ancestors: await Promise.all( - ancestors.map((status) => status.toAPI(user)), + ancestors.map((status) => status.toApi(user)), ), descendants: await Promise.all( - descendants.map((status) => status.toAPI(user)), + descendants.map((status) => status.toApi(user)), ), }); }, diff --git a/server/api/api/v1/statuses/:id/favourite.test.ts b/server/api/api/v1/statuses/:id/favourite.test.ts index 63b16a3a..2ff3d66d 100644 --- a/server/api/api/v1/statuses/:id/favourite.test.ts +++ b/server/api/api/v1/statuses/:id/favourite.test.ts @@ -1,7 +1,7 @@ import { afterAll, describe, expect, test } from "bun:test"; import { config } from "config-manager"; import { getTestStatuses, getTestUsers, sendTestRequest } from "~/tests/utils"; -import type { Status as APIStatus } from "~/types/mastodon/status"; +import type { Status as apiStatus } from "~/types/mastodon/status"; import { meta } from "./favourite"; const { users, tokens, deleteUsers } = await getTestUsers(5); @@ -47,7 +47,7 @@ describe(meta.route, () => { expect(response.status).toBe(200); - const json = (await response.json()) as APIStatus; + const json = (await response.json()) as apiStatus; expect(json.favourited).toBe(true); expect(json.favourites_count).toBe(1); @@ -70,7 +70,7 @@ describe(meta.route, () => { expect(response.status).toBe(200); - const json = (await response.json()) as APIStatus; + const json = (await response.json()) as apiStatus; expect(json.favourited).toBe(true); expect(json.favourites_count).toBe(1); diff --git a/server/api/api/v1/statuses/:id/favourite.ts b/server/api/api/v1/statuses/:id/favourite.ts index 86926e3b..ff2736e8 100644 --- a/server/api/api/v1/statuses/:id/favourite.ts +++ b/server/api/api/v1/statuses/:id/favourite.ts @@ -3,7 +3,7 @@ import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import type { Hono } from "hono"; import { z } from "zod"; -import { createLike } from "~/database/entities/Like"; +import { createLike } from "~/database/entities/like"; import { db } from "~/drizzle/db"; import { RolePermissions } from "~/drizzle/schema"; import { Note } from "~/packages/database-interface/note"; @@ -19,10 +19,7 @@ export const meta = applyConfig({ required: true, }, permissions: { - required: [ - RolePermissions.MANAGE_OWN_LIKES, - RolePermissions.VIEW_NOTES, - ], + required: [RolePermissions.ManageOwnLikes, RolePermissions.ViewNotes], }, }); @@ -43,12 +40,15 @@ export default (app: Hono) => const { user } = context.req.valid("header"); - if (!user) return errorResponse("Unauthorized", 401); + if (!user) { + return errorResponse("Unauthorized", 401); + } const note = await Note.fromId(id, user?.id); - if (!note?.isViewableByUser(user)) + if (!note?.isViewableByUser(user)) { return errorResponse("Record not found", 404); + } const existingLike = await db.query.Likes.findFirst({ where: (like, { and, eq }) => @@ -64,8 +64,10 @@ export default (app: Hono) => const newNote = await Note.fromId(id, user.id); - if (!newNote) return errorResponse("Record not found", 404); + if (!newNote) { + return errorResponse("Record not found", 404); + } - return jsonResponse(await newNote.toAPI(user)); + return jsonResponse(await newNote.toApi(user)); }, ); diff --git a/server/api/api/v1/statuses/:id/favourited_by.test.ts b/server/api/api/v1/statuses/:id/favourited_by.test.ts index 991fba72..fbb29518 100644 --- a/server/api/api/v1/statuses/:id/favourited_by.test.ts +++ b/server/api/api/v1/statuses/:id/favourited_by.test.ts @@ -1,7 +1,7 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import { config } from "config-manager"; import { getTestStatuses, getTestUsers, sendTestRequest } from "~/tests/utils"; -import type { Account as APIAccount } from "~/types/mastodon/account"; +import type { Account as apiAccount } from "~/types/mastodon/account"; import { meta } from "./favourited_by"; const { users, tokens, deleteUsers } = await getTestUsers(5); @@ -63,7 +63,7 @@ describe(meta.route, () => { expect(response.status).toBe(200); expect(response.headers.get("content-type")).toBe("application/json"); - const objects = (await response.json()) as APIAccount[]; + const objects = (await response.json()) as apiAccount[]; expect(objects.length).toBe(1); for (const [, status] of objects.entries()) { diff --git a/server/api/api/v1/statuses/:id/favourited_by.ts b/server/api/api/v1/statuses/:id/favourited_by.ts index 2dc915a8..2924ae4d 100644 --- a/server/api/api/v1/statuses/:id/favourited_by.ts +++ b/server/api/api/v1/statuses/:id/favourited_by.ts @@ -19,7 +19,7 @@ export const meta = applyConfig({ required: true, }, permissions: { - required: [RolePermissions.VIEW_NOTES, RolePermissions.VIEW_NOTE_LIKES], + required: [RolePermissions.ViewNotes, RolePermissions.ViewNoteLikes], }, }); @@ -49,12 +49,15 @@ export default (app: Hono) => const { user } = context.req.valid("header"); - if (!user) return errorResponse("Unauthorized", 401); + if (!user) { + return errorResponse("Unauthorized", 401); + } const status = await Note.fromId(id, user?.id); - if (!status?.isViewableByUser(user)) + if (!status?.isViewableByUser(user)) { return errorResponse("Record not found", 404); + } const { objects, link } = await Timeline.getUserTimeline( and( @@ -68,7 +71,7 @@ export default (app: Hono) => ); return jsonResponse( - objects.map((user) => user.toAPI()), + objects.map((user) => user.toApi()), 200, { Link: link, diff --git a/server/api/api/v1/statuses/:id/index.ts b/server/api/api/v1/statuses/:id/index.ts index 32e30ff1..8dae0f7d 100644 --- a/server/api/api/v1/statuses/:id/index.ts +++ b/server/api/api/v1/statuses/:id/index.ts @@ -11,7 +11,7 @@ import { config } from "config-manager"; import type { Hono } from "hono"; import ISO6391 from "iso-639-1"; import { z } from "zod"; -import { undoFederationRequest } from "~/database/entities/Federation"; +import { undoFederationRequest } from "~/database/entities/federation"; import { db } from "~/drizzle/db"; import { RolePermissions } from "~/drizzle/schema"; import { Note } from "~/packages/database-interface/note"; @@ -28,13 +28,10 @@ export const meta = applyConfig({ requiredOnMethods: ["DELETE", "PUT"], }, permissions: { - required: [RolePermissions.VIEW_NOTES], + required: [RolePermissions.ViewNotes], methodOverrides: { - DELETE: [ - RolePermissions.MANAGE_OWN_NOTES, - RolePermissions.VIEW_NOTES, - ], - PUT: [RolePermissions.MANAGE_OWN_NOTES, RolePermissions.VIEW_NOTES], + DELETE: [RolePermissions.ManageOwnNotes, RolePermissions.ViewNotes], + PUT: [RolePermissions.ManageOwnNotes, RolePermissions.ViewNotes], }, }, }); @@ -96,11 +93,12 @@ export default (app: Hono) => const foundStatus = await Note.fromId(id, user?.id); - if (!foundStatus?.isViewableByUser(user)) + if (!foundStatus?.isViewableByUser(user)) { return errorResponse("Record not found", 404); + } if (context.req.method === "GET") { - return jsonResponse(await foundStatus.toAPI(user)); + return jsonResponse(await foundStatus.toApi(user)); } if (context.req.method === "DELETE") { if (foundStatus.author.id !== user?.id) { @@ -115,7 +113,7 @@ export default (app: Hono) => undoFederationRequest(user, foundStatus.getUri()), ); - return jsonResponse(await foundStatus.toAPI(user), 200); + return jsonResponse(await foundStatus.toApi(user), 200); } // TODO: Polls @@ -128,7 +126,7 @@ export default (app: Hono) => sensitive, } = context.req.valid("form"); - if (!statusText && !(media_ids && media_ids.length > 0)) { + if (!(statusText || (media_ids && media_ids.length > 0))) { return errorResponse( "Status is required unless media is attached", 422, @@ -181,6 +179,6 @@ export default (app: Hono) => return errorResponse("Failed to update status", 500); } - return jsonResponse(await newNote.toAPI(user)); + return jsonResponse(await newNote.toApi(user)); }, ); diff --git a/server/api/api/v1/statuses/:id/pin.ts b/server/api/api/v1/statuses/:id/pin.ts index 5a292c5e..0ddc1b5d 100644 --- a/server/api/api/v1/statuses/:id/pin.ts +++ b/server/api/api/v1/statuses/:id/pin.ts @@ -18,10 +18,7 @@ export const meta = applyConfig({ required: true, }, permissions: { - required: [ - RolePermissions.MANAGE_OWN_NOTES, - RolePermissions.VIEW_NOTES, - ], + required: [RolePermissions.ManageOwnNotes, RolePermissions.ViewNotes], }, }); @@ -41,14 +38,19 @@ export default (app: Hono) => const { id } = context.req.valid("param"); const { user } = context.req.valid("header"); - if (!user) return errorResponse("Unauthorized", 401); + if (!user) { + return errorResponse("Unauthorized", 401); + } const foundStatus = await Note.fromId(id, user?.id); - if (!foundStatus) return errorResponse("Record not found", 404); + if (!foundStatus) { + return errorResponse("Record not found", 404); + } - if (foundStatus.author.id !== user.id) + if (foundStatus.author.id !== user.id) { return errorResponse("Unauthorized", 401); + } if ( await db.query.UserToPinnedNotes.findFirst({ @@ -64,6 +66,6 @@ export default (app: Hono) => await user.pin(foundStatus); - return jsonResponse(await foundStatus.toAPI(user)); + return jsonResponse(await foundStatus.toApi(user)); }, ); diff --git a/server/api/api/v1/statuses/:id/reblog.ts b/server/api/api/v1/statuses/:id/reblog.ts index 5251241e..44d7fb79 100644 --- a/server/api/api/v1/statuses/:id/reblog.ts +++ b/server/api/api/v1/statuses/:id/reblog.ts @@ -19,10 +19,7 @@ export const meta = applyConfig({ required: true, }, permissions: { - required: [ - RolePermissions.MANAGE_OWN_BOOSTS, - RolePermissions.VIEW_NOTES, - ], + required: [RolePermissions.ManageOwnBoosts, RolePermissions.ViewNotes], }, }); @@ -48,12 +45,15 @@ export default (app: Hono) => const { visibility } = context.req.valid("form"); const { user } = context.req.valid("header"); - if (!user) return errorResponse("Unauthorized", 401); + if (!user) { + return errorResponse("Unauthorized", 401); + } const foundStatus = await Note.fromId(id, user.id); - if (!foundStatus?.isViewableByUser(user)) + if (!foundStatus?.isViewableByUser(user)) { return errorResponse("Record not found", 404); + } const existingReblog = await Note.fromSql( and( @@ -94,6 +94,6 @@ export default (app: Hono) => }); } - return jsonResponse(await finalNewReblog.toAPI(user)); + return jsonResponse(await finalNewReblog.toApi(user)); }, ); diff --git a/server/api/api/v1/statuses/:id/reblogged_by.test.ts b/server/api/api/v1/statuses/:id/reblogged_by.test.ts index 0db898d9..9c1a8e70 100644 --- a/server/api/api/v1/statuses/:id/reblogged_by.test.ts +++ b/server/api/api/v1/statuses/:id/reblogged_by.test.ts @@ -1,7 +1,7 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import { config } from "config-manager"; import { getTestStatuses, getTestUsers, sendTestRequest } from "~/tests/utils"; -import type { Account as APIAccount } from "~/types/mastodon/account"; +import type { Account as apiAccount } from "~/types/mastodon/account"; import { meta } from "./reblogged_by"; const { users, tokens, deleteUsers } = await getTestUsers(5); @@ -63,7 +63,7 @@ describe(meta.route, () => { expect(response.status).toBe(200); expect(response.headers.get("content-type")).toBe("application/json"); - const objects = (await response.json()) as APIAccount[]; + const objects = (await response.json()) as apiAccount[]; expect(objects.length).toBe(1); for (const [, status] of objects.entries()) { diff --git a/server/api/api/v1/statuses/:id/reblogged_by.ts b/server/api/api/v1/statuses/:id/reblogged_by.ts index da667f2b..1031f0b8 100644 --- a/server/api/api/v1/statuses/:id/reblogged_by.ts +++ b/server/api/api/v1/statuses/:id/reblogged_by.ts @@ -19,10 +19,7 @@ export const meta = applyConfig({ required: true, }, permissions: { - required: [ - RolePermissions.VIEW_NOTES, - RolePermissions.VIEW_NOTE_BOOSTS, - ], + required: [RolePermissions.ViewNotes, RolePermissions.ViewNoteBoosts], }, }); @@ -51,12 +48,15 @@ export default (app: Hono) => context.req.valid("query"); const { user } = context.req.valid("header"); - if (!user) return errorResponse("Unauthorized", 401); + if (!user) { + return errorResponse("Unauthorized", 401); + } const status = await Note.fromId(id, user.id); - if (!status?.isViewableByUser(user)) + if (!status?.isViewableByUser(user)) { return errorResponse("Record not found", 404); + } const { objects, link } = await Timeline.getUserTimeline( and( @@ -70,7 +70,7 @@ export default (app: Hono) => ); return jsonResponse( - objects.map((user) => user.toAPI()), + objects.map((user) => user.toApi()), 200, { Link: link, diff --git a/server/api/api/v1/statuses/:id/source.ts b/server/api/api/v1/statuses/:id/source.ts index 4010f96c..33eee9db 100644 --- a/server/api/api/v1/statuses/:id/source.ts +++ b/server/api/api/v1/statuses/:id/source.ts @@ -5,7 +5,7 @@ import type { Hono } from "hono"; import { z } from "zod"; import { RolePermissions } from "~/drizzle/schema"; import { Note } from "~/packages/database-interface/note"; -import type { StatusSource as APIStatusSource } from "~/types/mastodon/status_source"; +import type { StatusSource as apiStatusSource } from "~/types/mastodon/status_source"; export const meta = applyConfig({ allowedMethods: ["GET"], @@ -18,10 +18,7 @@ export const meta = applyConfig({ required: true, }, permissions: { - required: [ - RolePermissions.MANAGE_OWN_NOTES, - RolePermissions.VIEW_NOTES, - ], + required: [RolePermissions.ManageOwnNotes, RolePermissions.ViewNotes], }, }); @@ -41,18 +38,21 @@ export default (app: Hono) => const { id } = context.req.valid("param"); const { user } = context.req.valid("header"); - if (!user) return errorResponse("Unauthorized", 401); + if (!user) { + return errorResponse("Unauthorized", 401); + } const status = await Note.fromId(id, user.id); - if (!status?.isViewableByUser(user)) + if (!status?.isViewableByUser(user)) { return errorResponse("Record not found", 404); + } return jsonResponse({ id: status.id, // TODO: Give real source for spoilerText spoiler_text: status.data.spoilerText, text: status.data.contentSource, - } as APIStatusSource); + } as apiStatusSource); }, ); diff --git a/server/api/api/v1/statuses/:id/unfavourite.test.ts b/server/api/api/v1/statuses/:id/unfavourite.test.ts index 4f708c3b..f71a1f60 100644 --- a/server/api/api/v1/statuses/:id/unfavourite.test.ts +++ b/server/api/api/v1/statuses/:id/unfavourite.test.ts @@ -1,7 +1,7 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import { config } from "config-manager"; import { getTestStatuses, getTestUsers, sendTestRequest } from "~/tests/utils"; -import type { Status as APIStatus } from "~/types/mastodon/status"; +import type { Status as apiStatus } from "~/types/mastodon/status"; import { meta } from "./unfavourite"; const { users, tokens, deleteUsers } = await getTestUsers(5); @@ -83,7 +83,7 @@ describe(meta.route, () => { expect(response.status).toBe(200); - const json = (await response.json()) as APIStatus; + const json = (await response.json()) as apiStatus; expect(json.favourited).toBe(false); expect(json.favourites_count).toBe(0); @@ -106,7 +106,7 @@ describe(meta.route, () => { expect(response.status).toBe(200); - const json = (await response.json()) as APIStatus; + const json = (await response.json()) as apiStatus; expect(json.favourited).toBe(false); expect(json.favourites_count).toBe(0); diff --git a/server/api/api/v1/statuses/:id/unfavourite.ts b/server/api/api/v1/statuses/:id/unfavourite.ts index 95a269ab..bb322142 100644 --- a/server/api/api/v1/statuses/:id/unfavourite.ts +++ b/server/api/api/v1/statuses/:id/unfavourite.ts @@ -3,7 +3,7 @@ import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import type { Hono } from "hono"; import { z } from "zod"; -import { deleteLike } from "~/database/entities/Like"; +import { deleteLike } from "~/database/entities/like"; import { RolePermissions } from "~/drizzle/schema"; import { Note } from "~/packages/database-interface/note"; @@ -18,10 +18,7 @@ export const meta = applyConfig({ required: true, }, permissions: { - required: [ - RolePermissions.MANAGE_OWN_NOTES, - RolePermissions.VIEW_NOTES, - ], + required: [RolePermissions.ManageOwnNotes, RolePermissions.ViewNotes], }, }); @@ -41,19 +38,24 @@ export default (app: Hono) => const { id } = context.req.valid("param"); const { user } = context.req.valid("header"); - if (!user) return errorResponse("Unauthorized", 401); + if (!user) { + return errorResponse("Unauthorized", 401); + } const note = await Note.fromId(id, user.id); - if (!note?.isViewableByUser(user)) + if (!note?.isViewableByUser(user)) { return errorResponse("Record not found", 404); + } await deleteLike(user, note); const newNote = await Note.fromId(id, user.id); - if (!newNote) return errorResponse("Record not found", 404); + if (!newNote) { + return errorResponse("Record not found", 404); + } - return jsonResponse(await newNote.toAPI(user)); + return jsonResponse(await newNote.toApi(user)); }, ); diff --git a/server/api/api/v1/statuses/:id/unpin.ts b/server/api/api/v1/statuses/:id/unpin.ts index 841414f5..ff628a96 100644 --- a/server/api/api/v1/statuses/:id/unpin.ts +++ b/server/api/api/v1/statuses/:id/unpin.ts @@ -17,10 +17,7 @@ export const meta = applyConfig({ required: true, }, permissions: { - required: [ - RolePermissions.MANAGE_OWN_NOTES, - RolePermissions.VIEW_NOTES, - ], + required: [RolePermissions.ManageOwnNotes, RolePermissions.ViewNotes], }, }); @@ -40,19 +37,26 @@ export default (app: Hono) => const { id } = context.req.valid("param"); const { user } = context.req.valid("header"); - if (!user) return errorResponse("Unauthorized", 401); + if (!user) { + return errorResponse("Unauthorized", 401); + } const status = await Note.fromId(id, user.id); - if (!status) return errorResponse("Record not found", 404); + if (!status) { + return errorResponse("Record not found", 404); + } - if (status.author.id !== user.id) + if (status.author.id !== user.id) { return errorResponse("Unauthorized", 401); + } await user.unpin(status); - if (!status) return errorResponse("Record not found", 404); + if (!status) { + return errorResponse("Record not found", 404); + } - return jsonResponse(await status.toAPI(user)); + return jsonResponse(await status.toApi(user)); }, ); diff --git a/server/api/api/v1/statuses/:id/unreblog.ts b/server/api/api/v1/statuses/:id/unreblog.ts index da64955d..8a2367af 100644 --- a/server/api/api/v1/statuses/:id/unreblog.ts +++ b/server/api/api/v1/statuses/:id/unreblog.ts @@ -4,7 +4,7 @@ import { zValidator } from "@hono/zod-validator"; import { and, eq } from "drizzle-orm"; import type { Hono } from "hono"; import { z } from "zod"; -import { undoFederationRequest } from "~/database/entities/Federation"; +import { undoFederationRequest } from "~/database/entities/federation"; import { Notes, RolePermissions } from "~/drizzle/schema"; import { Note } from "~/packages/database-interface/note"; @@ -19,10 +19,7 @@ export const meta = applyConfig({ required: true, }, permissions: { - required: [ - RolePermissions.MANAGE_OWN_NOTES, - RolePermissions.VIEW_NOTES, - ], + required: [RolePermissions.ManageOwnNotes, RolePermissions.ViewNotes], }, }); @@ -42,13 +39,16 @@ export default (app: Hono) => const { id } = context.req.valid("param"); const { user } = context.req.valid("header"); - if (!user) return errorResponse("Unauthorized", 401); + if (!user) { + return errorResponse("Unauthorized", 401); + } const foundStatus = await Note.fromId(id, user.id); // Check if user is authorized to view this status (if it's private) - if (!foundStatus?.isViewableByUser(user)) + if (!foundStatus?.isViewableByUser(user)) { return errorResponse("Record not found", 404); + } const existingReblog = await Note.fromSql( and( @@ -71,8 +71,10 @@ export default (app: Hono) => const newNote = await Note.fromId(id, user.id); - if (!newNote) return errorResponse("Record not found", 404); + if (!newNote) { + return errorResponse("Record not found", 404); + } - return jsonResponse(await newNote.toAPI(user)); + return jsonResponse(await newNote.toApi(user)); }, ); diff --git a/server/api/api/v1/statuses/index.test.ts b/server/api/api/v1/statuses/index.test.ts index fd0ee9b7..a34091e9 100644 --- a/server/api/api/v1/statuses/index.test.ts +++ b/server/api/api/v1/statuses/index.test.ts @@ -4,7 +4,7 @@ import { eq } from "drizzle-orm"; import { db } from "~/drizzle/db"; import { Emojis } from "~/drizzle/schema"; import { getTestUsers, sendTestRequest } from "~/tests/utils"; -import type { Status as APIStatus } from "~/types/mastodon/status"; +import type { Status as apiStatus } from "~/types/mastodon/status"; import { meta } from "./index"; const { users, tokens, deleteUsers } = await getTestUsers(5); @@ -173,7 +173,7 @@ describe(meta.route, () => { expect(response.status).toBe(200); expect(response.headers.get("content-type")).toBe("application/json"); - const object = (await response.json()) as APIStatus; + const object = (await response.json()) as apiStatus; expect(object.content).toBe("

Hello, world!

"); }); @@ -198,7 +198,7 @@ describe(meta.route, () => { expect(response.status).toBe(200); expect(response.headers.get("content-type")).toBe("application/json"); - const object = (await response.json()) as APIStatus; + const object = (await response.json()) as apiStatus; expect(object.content).toBe("

Hello, world!

"); expect(object.visibility).toBe("unlisted"); @@ -218,7 +218,7 @@ describe(meta.route, () => { }), ); - const object = (await response.json()) as APIStatus; + const object = (await response.json()) as apiStatus; const response2 = await sendTestRequest( new Request(new URL(meta.route, config.http.base_url), { @@ -237,7 +237,7 @@ describe(meta.route, () => { expect(response2.status).toBe(200); expect(response2.headers.get("content-type")).toBe("application/json"); - const object2 = (await response2.json()) as APIStatus; + const object2 = (await response2.json()) as apiStatus; expect(object2.content).toBe("

Hello, world again!

"); expect(object2.in_reply_to_id).toBe(object.id); @@ -257,7 +257,7 @@ describe(meta.route, () => { }), ); - const object = (await response.json()) as APIStatus; + const object = (await response.json()) as apiStatus; const response2 = await sendTestRequest( new Request(new URL(meta.route, config.http.base_url), { @@ -276,7 +276,7 @@ describe(meta.route, () => { expect(response2.status).toBe(200); expect(response2.headers.get("content-type")).toBe("application/json"); - const object2 = (await response2.json()) as APIStatus; + const object2 = (await response2.json()) as apiStatus; expect(object2.content).toBe("

Hello, world again!

"); // @ts-expect-error Pleroma extension @@ -302,7 +302,7 @@ describe(meta.route, () => { expect(response.status).toBe(200); expect(response.headers.get("content-type")).toBe("application/json"); - const object = (await response.json()) as APIStatus; + const object = (await response.json()) as apiStatus; expect(object.emojis).toBeArrayOfSize(1); expect(object.emojis[0]).toMatchObject({ @@ -331,7 +331,7 @@ describe(meta.route, () => { "application/json", ); - const object = (await response.json()) as APIStatus; + const object = (await response.json()) as apiStatus; expect(object.mentions).toBeArrayOfSize(1); expect(object.mentions[0]).toMatchObject({ @@ -362,7 +362,7 @@ describe(meta.route, () => { "application/json", ); - const object = (await response.json()) as APIStatus; + const object = (await response.json()) as apiStatus; expect(object.mentions).toBeArrayOfSize(1); expect(object.mentions[0]).toMatchObject({ @@ -393,7 +393,7 @@ describe(meta.route, () => { "application/json", ); - const object = (await response.json()) as APIStatus; + const object = (await response.json()) as apiStatus; expect(object.content).toBe( "

Hi! <script>alert('Hello, world!');</script>

", @@ -421,7 +421,7 @@ describe(meta.route, () => { "application/json", ); - const object = (await response.json()) as APIStatus; + const object = (await response.json()) as apiStatus; expect(object.spoiler_text).toBe( "uwu <script>alert('Hello, world!');</script>", @@ -447,7 +447,7 @@ describe(meta.route, () => { "application/json", ); - const object = (await response.json()) as APIStatus; + const object = (await response.json()) as apiStatus; // Proxy url is base_url/media/proxy/ expect(object.content).toBe( `

async (context) => { const { user, application } = context.req.valid("header"); - if (!user) return errorResponse("Unauthorized", 401); + if (!user) { + return errorResponse("Unauthorized", 401); + } const { status, @@ -110,7 +112,7 @@ export default (app: Hono) => } = context.req.valid("form"); // Validate status - if (!status && !(media_ids && media_ids.length > 0)) { + if (!(status || (media_ids && media_ids.length > 0))) { return errorResponse( "Status is required unless media is attached", 422, @@ -202,6 +204,6 @@ export default (app: Hono) => await federateNote(newNote); } - return jsonResponse(await newNote.toAPI(user)); + return jsonResponse(await newNote.toApi(user)); }, ); diff --git a/server/api/api/v1/timelines/home.test.ts b/server/api/api/v1/timelines/home.test.ts index 4cf5ef70..ddad64e5 100644 --- a/server/api/api/v1/timelines/home.test.ts +++ b/server/api/api/v1/timelines/home.test.ts @@ -1,7 +1,7 @@ import { afterAll, describe, expect, test } from "bun:test"; import { config } from "config-manager"; import { getTestStatuses, getTestUsers, sendTestRequest } from "~/tests/utils"; -import type { Status as APIStatus } from "~/types/mastodon/status"; +import type { Status as apiStatus } from "~/types/mastodon/status"; import { meta } from "./home"; const { users, tokens, deleteUsers } = await getTestUsers(5); @@ -35,7 +35,7 @@ describe(meta.route, () => { expect(response.status).toBe(200); expect(response.headers.get("content-type")).toBe("application/json"); - const objects = (await response.json()) as APIStatus[]; + const objects = (await response.json()) as apiStatus[]; expect(objects.length).toBe(5); }); @@ -52,7 +52,7 @@ describe(meta.route, () => { expect(response.status).toBe(200); expect(response.headers.get("content-type")).toBe("application/json"); - const objects = (await response.json()) as APIStatus[]; + const objects = (await response.json()) as apiStatus[]; expect(objects.length).toBe(20); for (const [index, status] of objects.entries()) { @@ -64,7 +64,7 @@ describe(meta.route, () => { } }); - describe("should paginate properly", async () => { + describe("should paginate properly", () => { test("should send correct Link header", async () => { const response = await sendTestRequest( new Request( @@ -102,7 +102,7 @@ describe(meta.route, () => { "application/json", ); - const objects = (await response.json()) as APIStatus[]; + const objects = (await response.json()) as apiStatus[]; expect(objects.length).toBe(20); for (const [index, status] of objects.entries()) { @@ -154,7 +154,7 @@ describe(meta.route, () => { "application/json", ); - const objects = (await response.json()) as APIStatus[]; + const objects = (await response.json()) as apiStatus[]; expect(objects.length).toBe(20); for (const [index, status] of objects.entries()) { @@ -203,7 +203,7 @@ describe(meta.route, () => { "application/json", ); - const objects = (await response.json()) as APIStatus[]; + const objects = (await response.json()) as apiStatus[]; expect(objects.length).toBe(20); // There should be no element with id of timeline[0].id diff --git a/server/api/api/v1/timelines/home.ts b/server/api/api/v1/timelines/home.ts index 9c824f20..13d8ccba 100644 --- a/server/api/api/v1/timelines/home.ts +++ b/server/api/api/v1/timelines/home.ts @@ -19,10 +19,10 @@ export const meta = applyConfig({ }, permissions: { required: [ - RolePermissions.MANAGE_OWN_NOTES, - RolePermissions.VIEW_NOTES, - RolePermissions.VIEW_ACCOUNTS, - RolePermissions.VIEW_PRIVATE_TIMELINES, + RolePermissions.ManageOwnNotes, + RolePermissions.ViewNotes, + RolePermissions.ViewAccounts, + RolePermissions.ViewPrimateTimelines, ], }, }); @@ -48,7 +48,9 @@ export default (app: Hono) => const { user } = context.req.valid("header"); - if (!user) return errorResponse("Unauthorized", 401); + if (!user) { + return errorResponse("Unauthorized", 401); + } const { objects, link } = await Timeline.getNoteTimeline( and( @@ -71,7 +73,7 @@ export default (app: Hono) => return jsonResponse( await Promise.all( - objects.map(async (note) => note.toAPI(user)), + objects.map(async (note) => note.toApi(user)), ), 200, { diff --git a/server/api/api/v1/timelines/public.test.ts b/server/api/api/v1/timelines/public.test.ts index 83b56e79..821e8508 100644 --- a/server/api/api/v1/timelines/public.test.ts +++ b/server/api/api/v1/timelines/public.test.ts @@ -1,7 +1,7 @@ import { afterAll, describe, expect, test } from "bun:test"; import { config } from "config-manager"; import { getTestStatuses, getTestUsers, sendTestRequest } from "~/tests/utils"; -import type { Status as APIStatus } from "~/types/mastodon/status"; +import type { Status as apiStatus } from "~/types/mastodon/status"; import { meta } from "./public"; const { users, tokens, deleteUsers } = await getTestUsers(5); @@ -27,7 +27,7 @@ describe(meta.route, () => { expect(response.status).toBe(200); expect(response.headers.get("content-type")).toBe("application/json"); - const objects = (await response.json()) as APIStatus[]; + const objects = (await response.json()) as apiStatus[]; expect(objects.length).toBe(5); }); @@ -44,7 +44,7 @@ describe(meta.route, () => { expect(response.status).toBe(200); expect(response.headers.get("content-type")).toBe("application/json"); - const objects = (await response.json()) as APIStatus[]; + const objects = (await response.json()) as apiStatus[]; expect(objects.length).toBe(20); for (const [index, status] of objects.entries()) { @@ -74,7 +74,7 @@ describe(meta.route, () => { expect(response.status).toBe(200); expect(response.headers.get("content-type")).toBe("application/json"); - const objects = (await response.json()) as APIStatus[]; + const objects = (await response.json()) as apiStatus[]; expect(objects.length).toBe(20); for (const [index, status] of objects.entries()) { @@ -104,12 +104,12 @@ describe(meta.route, () => { expect(response.status).toBe(200); expect(response.headers.get("content-type")).toBe("application/json"); - const objects = (await response.json()) as APIStatus[]; + const objects = (await response.json()) as apiStatus[]; expect(objects).toBeArray(); }); - describe("should paginate properly", async () => { + describe("should paginate properly", () => { test("should send correct Link header", async () => { const response = await sendTestRequest( new Request( @@ -147,7 +147,7 @@ describe(meta.route, () => { "application/json", ); - const objects = (await response.json()) as APIStatus[]; + const objects = (await response.json()) as apiStatus[]; expect(objects.length).toBe(20); for (const [index, status] of objects.entries()) { @@ -199,7 +199,7 @@ describe(meta.route, () => { "application/json", ); - const objects = (await response.json()) as APIStatus[]; + const objects = (await response.json()) as apiStatus[]; expect(objects.length).toBe(20); for (const [index, status] of objects.entries()) { @@ -247,7 +247,7 @@ describe(meta.route, () => { expect(response.status).toBe(200); expect(response.headers.get("content-type")).toBe("application/json"); - const objects = (await response.json()) as APIStatus[]; + const objects = (await response.json()) as apiStatus[]; expect(objects.length).toBe(20); // There should be no element with id of timeline[0].id diff --git a/server/api/api/v1/timelines/public.ts b/server/api/api/v1/timelines/public.ts index b5b56e2e..03a3dcf4 100644 --- a/server/api/api/v1/timelines/public.ts +++ b/server/api/api/v1/timelines/public.ts @@ -19,9 +19,9 @@ export const meta = applyConfig({ }, permissions: { required: [ - RolePermissions.VIEW_NOTES, - RolePermissions.VIEW_ACCOUNTS, - RolePermissions.VIEW_PUBLIC_TIMELINES, + RolePermissions.ViewNotes, + RolePermissions.ViewAccounts, + RolePermissions.ViewPublicTimelines, ], }, }); @@ -91,7 +91,7 @@ export default (app: Hono) => return jsonResponse( await Promise.all( - objects.map(async (note) => note.toAPI(user)), + objects.map(async (note) => note.toApi(user)), ), 200, { diff --git a/server/api/api/v2/filters/:id/index.ts b/server/api/api/v2/filters/:id/index.ts index b73672bf..7ac8bcf6 100644 --- a/server/api/api/v2/filters/:id/index.ts +++ b/server/api/api/v2/filters/:id/index.ts @@ -18,7 +18,7 @@ export const meta = applyConfig({ required: true, }, permissions: { - required: [RolePermissions.MANAGE_OWN_FILTERS], + required: [RolePermissions.ManageOwnFilters], }, }); @@ -57,6 +57,7 @@ export const schemas = { ["true", "1", "on"].includes(v.toLowerCase()), ) .optional(), + // biome-ignore lint/style/useNamingConvention: _destroy is a Mastodon API imposed variable name _destroy: z .string() .transform((v) => @@ -81,7 +82,9 @@ export default (app: Hono) => const { user } = context.req.valid("header"); const { id } = context.req.valid("param"); - if (!user) return errorResponse("Unauthorized", 401); + if (!user) { + return errorResponse("Unauthorized", 401); + } const userFilter = await db.query.Filters.findFirst({ where: (filter, { eq, and }) => @@ -91,7 +94,9 @@ export default (app: Hono) => }, }); - if (!userFilter) return errorResponse("Filter not found", 404); + if (!userFilter) { + return errorResponse("Filter not found", 404); + } switch (context.req.method) { case "GET": { @@ -182,8 +187,9 @@ export default (app: Hono) => }, }); - if (!updatedFilter) + if (!updatedFilter) { return errorResponse("Failed to update filter", 500); + } return jsonResponse({ id: updatedFilter.id, diff --git a/server/api/api/v2/filters/index.ts b/server/api/api/v2/filters/index.ts index 0f932ae5..338a8d89 100644 --- a/server/api/api/v2/filters/index.ts +++ b/server/api/api/v2/filters/index.ts @@ -16,7 +16,7 @@ export const meta = applyConfig({ required: true, }, permissions: { - required: [RolePermissions.MANAGE_OWN_FILTERS], + required: [RolePermissions.ManageOwnFilters], }, }); @@ -69,7 +69,9 @@ export default (app: Hono) => async (context) => { const { user } = context.req.valid("header"); - if (!user) return errorResponse("Unauthorized", 401); + if (!user) { + return errorResponse("Unauthorized", 401); + } switch (context.req.method) { case "GET": { const userFilters = await db.query.Filters.findMany({ @@ -138,8 +140,9 @@ export default (app: Hono) => .returning() )[0]; - if (!newFilter) + if (!newFilter) { return errorResponse("Failed to create filter", 500); + } const insertedKeywords = keywords_attributes && keywords_attributes.length > 0 diff --git a/server/api/api/v2/instance/index.ts b/server/api/api/v2/instance/index.ts index 57d83fa7..9cc278d9 100644 --- a/server/api/api/v2/instance/index.ts +++ b/server/api/api/v2/instance/index.ts @@ -20,7 +20,7 @@ export const meta = applyConfig({ }); export default (app: Hono) => - app.on(meta.allowedMethods, meta.route, async (context) => { + app.on(meta.allowedMethods, meta.route, async (_context) => { // Get software version from package.json const version = manifest.version; @@ -93,7 +93,7 @@ export default (app: Hono) => }, contact: { email: contactAccount?.data.email || null, - account: contactAccount?.toAPI() || null, + account: contactAccount?.toApi() || null, }, rules: config.signups.rules.map((rule, index) => ({ id: String(index), diff --git a/server/api/api/v2/media/index.ts b/server/api/api/v2/media/index.ts index 19cbc164..744199fb 100644 --- a/server/api/api/v2/media/index.ts +++ b/server/api/api/v2/media/index.ts @@ -9,7 +9,7 @@ import { MediaBackendType } from "media-manager"; import { LocalMediaBackend, S3MediaBackend } from "media-manager"; import sharp from "sharp"; import { z } from "zod"; -import { getUrl } from "~/database/entities/Attachment"; +import { getUrl } from "~/database/entities/attachment"; import { RolePermissions } from "~/drizzle/schema"; import { Attachment } from "~/packages/database-interface/attachment"; @@ -25,7 +25,7 @@ export const meta = applyConfig({ oauthPermissions: ["write:media"], }, permissions: { - required: [RolePermissions.MANAGE_OWN_MEDIA], + required: [RolePermissions.ManageOwnMedia], }, }); @@ -104,7 +104,7 @@ export default (app: Hono) => let mediaManager: MediaBackend; switch (config.media.backend as MediaBackendType) { - case MediaBackendType.LOCAL: + case MediaBackendType.Local: mediaManager = new LocalMediaBackend(config); break; case MediaBackendType.S3: @@ -142,12 +142,12 @@ export default (app: Hono) => // TODO: Add job to process videos and other media if (isImage) { - return jsonResponse(newAttachment.toAPI()); + return jsonResponse(newAttachment.toApi()); } return jsonResponse( { - ...newAttachment.toAPI(), + ...newAttachment.toApi(), url: null, }, 202, diff --git a/server/api/api/v2/search/index.ts b/server/api/api/v2/search/index.ts index 192c8913..1e831979 100644 --- a/server/api/api/v2/search/index.ts +++ b/server/api/api/v2/search/index.ts @@ -6,7 +6,7 @@ import { zValidator } from "@hono/zod-validator"; import { and, eq, inArray, sql } from "drizzle-orm"; import type { Hono } from "hono"; import { z } from "zod"; -import { resolveWebFinger } from "~/database/entities/User"; +import { resolveWebFinger } from "~/database/entities/user"; import { db } from "~/drizzle/db"; import { Instances, Notes, RolePermissions, Users } from "~/drizzle/schema"; import { config } from "~/packages/config-manager"; @@ -27,9 +27,9 @@ export const meta = applyConfig({ }, permissions: { required: [ - RolePermissions.SEARCH, - RolePermissions.VIEW_ACCOUNTS, - RolePermissions.VIEW_NOTES, + RolePermissions.Search, + RolePermissions.ViewAccounts, + RolePermissions.ViewNotes, ], }, }); @@ -110,7 +110,7 @@ export default (app: Hono) => if (account) { return jsonResponse({ - accounts: [account.toAPI()], + accounts: [account.toApi()], statuses: [], hashtags: [], }); @@ -122,7 +122,7 @@ export default (app: Hono) => domain, ).catch((e) => { dualLogger.logError( - LogLevel.ERROR, + LogLevel.Error, "WebFinger.Resolve", e, ); @@ -131,7 +131,7 @@ export default (app: Hono) => if (newUser) { return jsonResponse({ - accounts: [newUser.toAPI()], + accounts: [newUser.toApi()], statuses: [], hashtags: [], }); @@ -200,9 +200,9 @@ export default (app: Hono) => ); return jsonResponse({ - accounts: accounts.map((account) => account.toAPI()), + accounts: accounts.map((account) => account.toApi()), statuses: await Promise.all( - statuses.map((status) => status.toAPI(self)), + statuses.map((status) => status.toApi(self)), ), hashtags: [], }); diff --git a/server/api/media/:id/index.ts b/server/api/media/:id/index.ts index ad59ffcf..ee5ff824 100644 --- a/server/api/media/:id/index.ts +++ b/server/api/media/:id/index.ts @@ -49,8 +49,9 @@ export default (app: Hono) => const buffer = await file.arrayBuffer(); - if (!(await file.exists())) + if (!(await file.exists())) { return errorResponse("File not found", 404); + } // Can't directly copy file into Response because this crashes Bun for now return response(buffer, 200, { diff --git a/server/api/media/proxy/:id.ts b/server/api/media/proxy/:id.ts index adf777f7..7fba6c40 100644 --- a/server/api/media/proxy/:id.ts +++ b/server/api/media/proxy/:id.ts @@ -33,11 +33,12 @@ export default (app: Hono) => const { id } = context.req.valid("param"); // Check if URL is valid - if (!URL.canParse(id)) + if (!URL.canParse(id)) { return errorResponse( "Invalid URL (it should be encoded as base64url", 400, ); + } const media = await fetch(id, { headers: { diff --git a/server/api/oauth/authorize/index.ts b/server/api/oauth/authorize/index.ts index a3106ccb..86f48a9a 100644 --- a/server/api/oauth/authorize/index.ts +++ b/server/api/oauth/authorize/index.ts @@ -5,7 +5,7 @@ import { zValidator } from "@hono/zod-validator"; import type { Hono } from "hono"; import { SignJWT, jwtVerify } from "jose"; import { z } from "zod"; -import { TokenType } from "~/database/entities/Token"; +import { TokenType } from "~/database/entities/token"; import { db } from "~/drizzle/db"; import { RolePermissions, Tokens } from "~/drizzle/schema"; import { config } from "~/packages/config-manager"; @@ -64,8 +64,9 @@ const returnError = (query: object, error: string, description: string) => { // Add all data that is not undefined except email and password for (const [key, value] of Object.entries(query)) { - if (key !== "email" && key !== "password" && value !== undefined) + if (key !== "email" && key !== "password" && value !== undefined) { searchParams.append(key, value); + } } searchParams.append("error", error); @@ -91,24 +92,26 @@ export default (app: Hono) => const cookie = context.req.header("Cookie"); - if (!cookie) + if (!cookie) { return returnError( body, "invalid_request", "No cookies were sent with the request", ); + } const jwt = cookie .split(";") .find((c) => c.trim().startsWith("jwt=")) ?.split("=")[1]; - if (!jwt) + if (!jwt) { return returnError( body, "invalid_request", "No jwt cookie was sent in the request", ); + } // Try and import the key const privateKey = await crypto.subtle.importKey( @@ -136,33 +139,38 @@ export default (app: Hono) => return null; }); - if (!result) + if (!result) { return returnError( body, "invalid_request", "Invalid JWT, could not verify", ); + } const payload = result.payload; - if (!payload.sub) + if (!payload.sub) { return returnError(body, "invalid_request", "Invalid sub"); - if (!payload.aud) + } + if (!payload.aud) { return returnError(body, "invalid_request", "Invalid aud"); - if (!payload.exp) + } + if (!payload.exp) { return returnError(body, "invalid_request", "Invalid exp"); + } // Check if the user is authenticated const user = await User.fromId(payload.sub); - if (!user) + if (!user) { return returnError(body, "invalid_request", "Invalid sub"); + } - if (!user.hasPermission(RolePermissions.OAUTH)) { + if (!user.hasPermission(RolePermissions.OAuth)) { return returnError( body, "invalid_request", - `User is missing the ${RolePermissions.OAUTH} permission`, + `User is missing the ${RolePermissions.OAuth} permission`, ); } @@ -172,19 +180,21 @@ export default (app: Hono) => const asksToken = responseTypes.includes("token"); const asksIdToken = responseTypes.includes("id_token"); - if (!asksCode && !asksToken && !asksIdToken) + if (!(asksCode || asksToken || asksIdToken)) { return returnError( body, "invalid_request", "Invalid response_type, must ask for code, token, or id_token", ); + } - if (asksCode && !redirect_uri) + if (asksCode && !redirect_uri) { return returnError( body, "invalid_request", "Redirect URI is required for code flow (can be urn:ietf:wg:oauth:2.0:oob)", ); + } /* if (asksCode && !code_challenge) return returnError( @@ -203,19 +213,21 @@ export default (app: Hono) => where: (app, { eq }) => eq(app.clientId, client_id), }); - if (!application) + if (!application) { return returnError( body, "invalid_client", "Invalid client_id or client_secret", ); + } - if (application.redirectUri !== redirect_uri) + if (application.redirectUri !== redirect_uri) { return returnError( body, "invalid_request", "Redirect URI does not match client_id", ); + } // Validate scopes, they can either be equal or a subset of the application's scopes const applicationScopes = application.scopes.split(" "); @@ -223,19 +235,20 @@ export default (app: Hono) => if ( scope && !scope.split(" ").every((s) => applicationScopes.includes(s)) - ) + ) { return returnError(body, "invalid_scope", "Invalid scope"); + } // Generate tokens const code = randomBytes(256).toString("base64url"); // Handle the requested scopes let idTokenPayload = {}; - const scopeIncludesOpenID = scope?.split(" ").includes("openid"); + const scopeIncludesOpenId = scope?.split(" ").includes("openid"); const scopeIncludesProfile = scope?.split(" ").includes("profile"); const scopeIncludesEmail = scope?.split(" ").includes("email"); if (scope) { - if (scopeIncludesOpenID) { + if (scopeIncludesOpenId) { // Include the standard OpenID claims idTokenPayload = { ...idTokenPayload, @@ -277,14 +290,14 @@ export default (app: Hono) => accessToken: randomBytes(64).toString("base64url"), code: code, scope: scope ?? application.scopes, - tokenType: TokenType.BEARER, + tokenType: TokenType.Bearer, applicationId: application.id, redirectUri: redirect_uri ?? application.redirectUri, expiresAt: new Date( Date.now() + 60 * 60 * 24 * 14, ).toISOString(), idToken: - scopeIncludesOpenID || + scopeIncludesOpenId || scopeIncludesEmail || scopeIncludesProfile ? idToken @@ -303,7 +316,9 @@ export default (app: Hono) => code: code, }); - if (state) searchParams.append("state", state); + if (state) { + searchParams.append("state", state); + } redirectUri.search = searchParams.toString(); diff --git a/server/api/oauth/sso/:issuer/callback/index.ts b/server/api/oauth/sso/:issuer/callback/index.ts index 2cdb3a4a..e2d045f2 100644 --- a/server/api/oauth/sso/:issuer/callback/index.ts +++ b/server/api/oauth/sso/:issuer/callback/index.ts @@ -5,7 +5,7 @@ import { zValidator } from "@hono/zod-validator"; import type { Hono } from "hono"; import { SignJWT } from "jose"; import { z } from "zod"; -import { TokenType } from "~/database/entities/Token"; +import { TokenType } from "~/database/entities/token"; import { db } from "~/drizzle/db"; import { RolePermissions, Tokens } from "~/drizzle/schema"; import { config } from "~/packages/config-manager"; @@ -44,8 +44,9 @@ const returnError = (query: object, error: string, description: string) => { // Add all data that is not undefined except email and password for (const [key, value] of Object.entries(query)) { - if (key !== "email" && key !== "password" && value !== undefined) + if (key !== "email" && key !== "password" && value !== undefined) { searchParams.append(key, value); + } } searchParams.append("error", error); @@ -106,7 +107,9 @@ export default (app: Hono) => ), ); - if (userInfo instanceof Response) return userInfo; + if (userInfo instanceof Response) { + return userInfo; + } const { sub } = userInfo.userInfo; const flow = userInfo.flow; @@ -154,7 +157,7 @@ export default (app: Hono) => ); } - if (!user.hasPermission(RolePermissions.OAUTH)) { + if (!user.hasPermission(RolePermissions.OAuth)) { return returnError( { redirect_uri: flow.application?.redirectUri, @@ -163,12 +166,13 @@ export default (app: Hono) => scope: flow.application?.scopes, }, "invalid_request", - `User does not have the '${RolePermissions.OAUTH}' permission`, + `User does not have the '${RolePermissions.OAuth}' permission`, ); } - if (!flow.application) + if (!flow.application) { return errorResponse("Application not found", 500); + } const code = randomBytes(32).toString("hex"); @@ -176,7 +180,7 @@ export default (app: Hono) => accessToken: randomBytes(64).toString("base64url"), code: code, scope: flow.application.scopes, - tokenType: TokenType.BEARER, + tokenType: TokenType.Bearer, userId: user.id, applicationId: flow.application.id, }); diff --git a/server/api/oauth/sso/index.ts b/server/api/oauth/sso/index.ts index eb7f462f..58928d70 100644 --- a/server/api/oauth/sso/index.ts +++ b/server/api/oauth/sso/index.ts @@ -41,8 +41,9 @@ const returnError = (query: object, error: string, description: string) => { // Add all data that is not undefined except email and password for (const [key, value] of Object.entries(query)) { - if (key !== "email" && key !== "password" && value !== undefined) + if (key !== "email" && key !== "password" && value !== undefined) { searchParams.append(key, value); + } } searchParams.append("error", error); diff --git a/server/api/objects/:id/index.ts b/server/api/objects/:id/index.ts index c1626559..cd8eeb51 100644 --- a/server/api/objects/:id/index.ts +++ b/server/api/objects/:id/index.ts @@ -5,7 +5,7 @@ import type { EntityValidator } from "@lysand-org/federation"; import { and, eq, inArray, sql } from "drizzle-orm"; import type { Hono } from "hono"; import { z } from "zod"; -import { type Like, likeToLysand } from "~/database/entities/Like"; +import { type Like, likeToLysand } from "~/database/entities/like"; import { db } from "~/drizzle/db"; import { Notes } from "~/drizzle/schema"; import { Note } from "~/packages/database-interface/note"; @@ -59,7 +59,7 @@ export default (app: Hono) => apiObject = foundObject ? likeToLysand(foundObject) : null; } - if (!foundObject || !apiObject) { + if (!(foundObject && apiObject)) { return errorResponse("Object not found", 404); } diff --git a/server/api/users/:uuid/inbox/index.ts b/server/api/users/:uuid/inbox/index.ts index 96be95a4..d7696dc5 100644 --- a/server/api/users/:uuid/inbox/index.ts +++ b/server/api/users/:uuid/inbox/index.ts @@ -16,7 +16,7 @@ import { type ValidationError, isValidationError } from "zod-validation-error"; import { getRelationshipToOtherUser, sendFollowAccept, -} from "~/database/entities/User"; +} from "~/database/entities/user"; import { db } from "~/drizzle/db"; import { Notes, Notifications, Relationships } from "~/drizzle/schema"; import { config } from "~/packages/config-manager"; @@ -93,7 +93,7 @@ export default (app: Hono) => } // @ts-expect-error IP attribute is not in types - const request_ip = context.env?.ip as + const requestIp = context.env?.ip as | SocketAddress | undefined | null; @@ -111,12 +111,13 @@ export default (app: Hono) => ); } - if (request_ip?.address) { - if (config.federation.bridge.allowed_ips.length > 0) + if (requestIp?.address) { + if (config.federation.bridge.allowed_ips.length > 0) { checkSignature = false; + } for (const ip of config.federation.bridge.allowed_ips) { - if (matches(ip, request_ip?.address)) { + if (matches(ip, requestIp?.address)) { checkSignature = false; break; } @@ -146,7 +147,7 @@ export default (app: Hono) => if (config.debug.federation) { // Log public key new LogManager(Bun.stdout).log( - LogLevel.DEBUG, + LogLevel.Debug, "Inbox.Signature", `Sender public key: ${sender.data.publicKey}`, ); @@ -179,7 +180,7 @@ export default (app: Hono) => ) .catch((e) => { new LogManager(Bun.stdout).logError( - LogLevel.ERROR, + LogLevel.Error, "Inbox.Signature", e as Error, ); @@ -208,7 +209,7 @@ export default (app: Hono) => note, ).catch((e) => { dualLogger.logError( - LogLevel.ERROR, + LogLevel.Error, "Inbox.NoteResolve", e as Error, ); @@ -435,7 +436,7 @@ export default (app: Hono) => if (isValidationError(e)) { return errorResponse((e as ValidationError).message, 400); } - dualLogger.logError(LogLevel.ERROR, "Inbox", e as Error); + dualLogger.logError(LogLevel.Error, "Inbox", e as Error); return jsonResponse( { error: "Failed to process request", diff --git a/server/api/well-known/host-meta/index.ts b/server/api/well-known/host-meta/index.ts index fdafabad..4b3c37c2 100644 --- a/server/api/well-known/host-meta/index.ts +++ b/server/api/well-known/host-meta/index.ts @@ -16,7 +16,7 @@ export const meta = applyConfig({ }); export default (app: Hono) => - app.on(meta.allowedMethods, meta.route, async () => { + app.on(meta.allowedMethods, meta.route, () => { return xmlResponse( ` - app.on(meta.allowedMethods, meta.route, async () => { + app.on(meta.allowedMethods, meta.route, () => { return jsonResponse({ type: "ServerMetadata", name: config.instance.name, diff --git a/server/api/well-known/nodeinfo/2.0/index.ts b/server/api/well-known/nodeinfo/2.0/index.ts index d2a60f53..69191b74 100644 --- a/server/api/well-known/nodeinfo/2.0/index.ts +++ b/server/api/well-known/nodeinfo/2.0/index.ts @@ -16,7 +16,7 @@ export const meta = applyConfig({ }); export default (app: Hono) => - app.on(meta.allowedMethods, meta.route, async () => { + app.on(meta.allowedMethods, meta.route, () => { return jsonResponse({ version: "2.0", software: { name: "lysand", version: manifest.version }, diff --git a/server/api/well-known/nodeinfo/index.ts b/server/api/well-known/nodeinfo/index.ts index 7f0dfbc1..d580f44c 100644 --- a/server/api/well-known/nodeinfo/index.ts +++ b/server/api/well-known/nodeinfo/index.ts @@ -16,7 +16,7 @@ export const meta = applyConfig({ }); export default (app: Hono) => - app.on(meta.allowedMethods, meta.route, async () => { + app.on(meta.allowedMethods, meta.route, () => { return redirect( new URL("/.well-known/nodeinfo/2.0", config.http.base_url), 301, diff --git a/server/api/well-known/openid-configuration/index.ts b/server/api/well-known/openid-configuration/index.ts index bf4bd156..56115e97 100644 --- a/server/api/well-known/openid-configuration/index.ts +++ b/server/api/well-known/openid-configuration/index.ts @@ -16,14 +16,14 @@ export const meta = applyConfig({ }); export default (app: Hono) => - app.on(meta.allowedMethods, meta.route, async () => { - const base_url = new URL(config.http.base_url); + app.on(meta.allowedMethods, meta.route, () => { + const baseUrl = new URL(config.http.base_url); return jsonResponse({ - issuer: base_url.origin.toString(), - authorization_endpoint: `${base_url.origin}/oauth/authorize`, - token_endpoint: `${base_url.origin}/oauth/token`, - userinfo_endpoint: `${base_url.origin}/api/v1/accounts/verify_credentials`, - jwks_uri: `${base_url.origin}/.well-known/jwks`, + issuer: baseUrl.origin.toString(), + authorization_endpoint: `${baseUrl.origin}/oauth/authorize`, + token_endpoint: `${baseUrl.origin}/oauth/token`, + userinfo_endpoint: `${baseUrl.origin}/api/v1/accounts/verify_credentials`, + jwks_uri: `${baseUrl.origin}/.well-known/jwks`, response_types_supported: ["code"], subject_types_supported: ["public"], id_token_signing_alg_values_supported: ["EdDSA"], diff --git a/tests/api.test.ts b/tests/api.test.ts index 1335b502..7e47416d 100644 --- a/tests/api.test.ts +++ b/tests/api.test.ts @@ -2,7 +2,7 @@ import { afterAll, describe, expect, test } from "bun:test"; import { config } from "config-manager"; import { getTestUsers, sendTestRequest, wrapRelativeUrl } from "./utils"; -const base_url = config.http.base_url; +const baseUrl = config.http.base_url; const { tokens, deleteUsers } = await getTestUsers(1); @@ -17,7 +17,7 @@ describe("API Tests", () => { const response = await sendTestRequest( new Request( - wrapRelativeUrl(`${base_url}/api/v1/statuses`, base_url), + wrapRelativeUrl(`${baseUrl}/api/v1/statuses`, baseUrl), { method: "POST", headers: { diff --git a/tests/api/accounts.test.ts b/tests/api/accounts.test.ts index e9a1cac0..39b33e3e 100644 --- a/tests/api/accounts.test.ts +++ b/tests/api/accounts.test.ts @@ -4,10 +4,10 @@ import { afterAll, describe, expect, test } from "bun:test"; import { config } from "config-manager"; import { getTestUsers, sendTestRequest, wrapRelativeUrl } from "~/tests/utils"; -import type { Account as APIAccount } from "~/types/mastodon/account"; -import type { Relationship as APIRelationship } from "~/types/mastodon/relationship"; +import type { Account as apiAccount } from "~/types/mastodon/account"; +import type { Relationship as apiRelationship } from "~/types/mastodon/relationship"; -const base_url = config.http.base_url; +const baseUrl = config.http.base_url; const { users, tokens, deleteUsers } = await getTestUsers(2); const user = users[0]; @@ -31,7 +31,7 @@ describe("API Tests", () => { new Request( wrapRelativeUrl( "/api/v1/accounts/update_credentials", - base_url, + baseUrl, ), { method: "PATCH", @@ -50,7 +50,7 @@ describe("API Tests", () => { "application/json", ); - const user = (await response.json()) as APIAccount; + const user = (await response.json()) as apiAccount; expect(user.display_name).toBe("New Display Name"); }); @@ -62,7 +62,7 @@ describe("API Tests", () => { new Request( wrapRelativeUrl( "/api/v1/accounts/verify_credentials", - base_url, + baseUrl, ), { headers: { @@ -77,7 +77,7 @@ describe("API Tests", () => { "application/json", ); - const account = (await response.json()) as APIAccount; + const account = (await response.json()) as apiAccount; expect(account.username).toBe(user.data.username); expect(account.bot).toBe(false); @@ -113,7 +113,7 @@ describe("API Tests", () => { new Request( wrapRelativeUrl( `/api/v1/accounts/${user2.id}/remove_from_followers`, - base_url, + baseUrl, ), { method: "POST", @@ -131,7 +131,7 @@ describe("API Tests", () => { "application/json", ); - const account = (await response.json()) as APIRelationship; + const account = (await response.json()) as apiRelationship; expect(account.id).toBe(user2.id); expect(account.followed_by).toBe(false); @@ -144,7 +144,7 @@ describe("API Tests", () => { new Request( wrapRelativeUrl( `/api/v1/accounts/${user2.id}/block`, - base_url, + baseUrl, ), { method: "POST", @@ -162,7 +162,7 @@ describe("API Tests", () => { "application/json", ); - const account = (await response.json()) as APIRelationship; + const account = (await response.json()) as apiRelationship; expect(account.id).toBe(user2.id); expect(account.blocking).toBe(true); @@ -172,7 +172,7 @@ describe("API Tests", () => { describe("GET /api/v1/blocks", () => { test("should return an array of APIAccount objects for the user's blocked accounts", async () => { const response = await sendTestRequest( - new Request(wrapRelativeUrl("/api/v1/blocks", base_url), { + new Request(wrapRelativeUrl("/api/v1/blocks", baseUrl), { method: "GET", headers: { Authorization: `Bearer ${token.accessToken}`, @@ -184,7 +184,7 @@ describe("API Tests", () => { expect(response.headers.get("content-type")).toBe( "application/json", ); - const body = (await response.json()) as APIAccount[]; + const body = (await response.json()) as apiAccount[]; expect(Array.isArray(body)).toBe(true); expect(body.length).toBe(1); @@ -198,7 +198,7 @@ describe("API Tests", () => { new Request( wrapRelativeUrl( `/api/v1/accounts/${user2.id}/unblock`, - base_url, + baseUrl, ), { method: "POST", @@ -216,7 +216,7 @@ describe("API Tests", () => { "application/json", ); - const account = (await response.json()) as APIRelationship; + const account = (await response.json()) as apiRelationship; expect(account.id).toBe(user2.id); expect(account.blocking).toBe(false); @@ -229,7 +229,7 @@ describe("API Tests", () => { new Request( wrapRelativeUrl( `/api/v1/accounts/${user2.id}/pin`, - base_url, + baseUrl, ), { method: "POST", @@ -247,7 +247,7 @@ describe("API Tests", () => { "application/json", ); - const account = (await response.json()) as APIRelationship; + const account = (await response.json()) as apiRelationship; expect(account.id).toBe(user2.id); expect(account.endorsed).toBe(true); @@ -260,7 +260,7 @@ describe("API Tests", () => { new Request( wrapRelativeUrl( `/api/v1/accounts/${user2.id}/unpin`, - base_url, + baseUrl, ), { method: "POST", @@ -278,7 +278,7 @@ describe("API Tests", () => { "application/json", ); - const account = (await response.json()) as APIRelationship; + const account = (await response.json()) as apiRelationship; expect(account.id).toBe(user2.id); expect(account.endorsed).toBe(false); @@ -291,7 +291,7 @@ describe("API Tests", () => { new Request( wrapRelativeUrl( `/api/v1/accounts/${user2.id}/note`, - base_url, + baseUrl, ), { method: "POST", @@ -309,7 +309,7 @@ describe("API Tests", () => { "application/json", ); - const account = (await response.json()) as APIAccount; + const account = (await response.json()) as apiAccount; expect(account.id).toBe(user2.id); expect(account.note).toBe("This is a new note"); @@ -322,7 +322,7 @@ describe("API Tests", () => { new Request( wrapRelativeUrl( `/api/v1/accounts/relationships?id[]=${user2.id}`, - base_url, + baseUrl, ), { method: "GET", @@ -338,7 +338,7 @@ describe("API Tests", () => { "application/json", ); - const relationships = (await response.json()) as APIRelationship[]; + const relationships = (await response.json()) as apiRelationship[]; expect(Array.isArray(relationships)).toBe(true); expect(relationships.length).toBeGreaterThan(0); @@ -358,7 +358,7 @@ describe("API Tests", () => { test("should delete the avatar of the authenticated user and return the updated account object", async () => { const response = await sendTestRequest( new Request( - wrapRelativeUrl("/api/v1/profile/avatar", base_url), + wrapRelativeUrl("/api/v1/profile/avatar", baseUrl), { method: "DELETE", headers: { @@ -373,7 +373,7 @@ describe("API Tests", () => { "application/json", ); - const account = (await response.json()) as APIAccount; + const account = (await response.json()) as apiAccount; expect(account.id).toBeDefined(); expect(account.avatar).toBeDefined(); @@ -384,7 +384,7 @@ describe("API Tests", () => { test("should delete the header of the authenticated user and return the updated account object", async () => { const response = await sendTestRequest( new Request( - wrapRelativeUrl("/api/v1/profile/header", base_url), + wrapRelativeUrl("/api/v1/profile/header", baseUrl), { method: "DELETE", headers: { @@ -399,7 +399,7 @@ describe("API Tests", () => { "application/json", ); - const account = (await response.json()) as APIAccount; + const account = (await response.json()) as apiAccount; expect(account.id).toBeDefined(); expect(account.header).toBe(""); @@ -412,7 +412,7 @@ describe("API Tests", () => { new Request( wrapRelativeUrl( `/api/v1/accounts/${user2.id}/follow`, - base_url, + baseUrl, ), { method: "POST", @@ -436,7 +436,7 @@ describe("API Tests", () => { new Request( wrapRelativeUrl( `/api/v1/accounts/familiar_followers?id[]=${user2.id}`, - base_url, + baseUrl, ), { method: "GET", @@ -454,7 +454,7 @@ describe("API Tests", () => { const familiarFollowers = (await response.json()) as { id: string; - accounts: APIAccount[]; + accounts: apiAccount[]; }[]; expect(Array.isArray(familiarFollowers)).toBe(true); diff --git a/tests/api/statuses.test.ts b/tests/api/statuses.test.ts index ce549f0b..dd553717 100644 --- a/tests/api/statuses.test.ts +++ b/tests/api/statuses.test.ts @@ -4,18 +4,18 @@ import { afterAll, describe, expect, test } from "bun:test"; import { config } from "config-manager"; import { getTestUsers, sendTestRequest, wrapRelativeUrl } from "~/tests/utils"; -import type { AsyncAttachment as APIAsyncAttachment } from "~/types/mastodon/async_attachment"; -import type { Context as APIContext } from "~/types/mastodon/context"; -import type { Status as APIStatus } from "~/types/mastodon/status"; +import type { AsyncAttachment as apiAsyncAttachment } from "~/types/mastodon/async_attachment"; +import type { Context as apiContext } from "~/types/mastodon/context"; +import type { Status as apiStatus } from "~/types/mastodon/status"; -const base_url = config.http.base_url; +const baseUrl = config.http.base_url; const { users, tokens, deleteUsers } = await getTestUsers(1); const user = users[0]; const token = tokens[0]; -let status: APIStatus | null = null; -let status2: APIStatus | null = null; -let media1: APIAsyncAttachment | null = null; +let status: apiStatus | null = null; +let status2: apiStatus | null = null; +let media1: apiAsyncAttachment | null = null; describe("API Tests", () => { afterAll(async () => { @@ -29,7 +29,7 @@ describe("API Tests", () => { const response = await sendTestRequest( new Request( - wrapRelativeUrl(`${base_url}/api/v2/media`, base_url), + wrapRelativeUrl(`${baseUrl}/api/v2/media`, baseUrl), { method: "POST", headers: { @@ -45,7 +45,7 @@ describe("API Tests", () => { "application/json", ); - media1 = (await response.json()) as APIAsyncAttachment; + media1 = (await response.json()) as apiAsyncAttachment; expect(media1.id).toBeDefined(); expect(media1.type).toBe("unknown"); @@ -57,7 +57,7 @@ describe("API Tests", () => { test("should create a new status and return an APIStatus object", async () => { const response = await sendTestRequest( new Request( - wrapRelativeUrl(`${base_url}/api/v1/statuses`, base_url), + wrapRelativeUrl(`${baseUrl}/api/v1/statuses`, baseUrl), { method: "POST", headers: { @@ -78,7 +78,7 @@ describe("API Tests", () => { "application/json", ); - status = (await response.json()) as APIStatus; + status = (await response.json()) as apiStatus; expect(status.content).toContain("Hello, world!"); expect(status.visibility).toBe("public"); expect(status.account.id).toBe(user.id); @@ -104,7 +104,7 @@ describe("API Tests", () => { test("should create a new status in reply to the previous one", async () => { const response = await sendTestRequest( new Request( - wrapRelativeUrl(`${base_url}/api/v1/statuses`, base_url), + wrapRelativeUrl(`${baseUrl}/api/v1/statuses`, baseUrl), { method: "POST", headers: { @@ -125,7 +125,7 @@ describe("API Tests", () => { "application/json", ); - status2 = (await response.json()) as APIStatus; + status2 = (await response.json()) as apiStatus; expect(status2.content).toContain("This is a reply!"); expect(status2.visibility).toBe("public"); expect(status2.account.id).toBe(user.id); @@ -154,8 +154,8 @@ describe("API Tests", () => { const response = await sendTestRequest( new Request( wrapRelativeUrl( - `${base_url}/api/v1/statuses/${status?.id}`, - base_url, + `${baseUrl}/api/v1/statuses/${status?.id}`, + baseUrl, ), { headers: { @@ -170,7 +170,7 @@ describe("API Tests", () => { "application/json", ); - const statusJson = (await response.json()) as APIStatus; + const statusJson = (await response.json()) as apiStatus; expect(statusJson.id).toBe(status?.id || ""); expect(statusJson.content).toBeDefined(); @@ -203,8 +203,8 @@ describe("API Tests", () => { const response = await sendTestRequest( new Request( wrapRelativeUrl( - `${base_url}/api/v1/statuses/${status?.id}/reblog`, - base_url, + `${baseUrl}/api/v1/statuses/${status?.id}/reblog`, + baseUrl, ), { method: "POST", @@ -220,7 +220,7 @@ describe("API Tests", () => { "application/json", ); - const rebloggedStatus = (await response.json()) as APIStatus; + const rebloggedStatus = (await response.json()) as apiStatus; expect(rebloggedStatus.id).toBeDefined(); expect(rebloggedStatus.reblog?.id).toEqual(status?.id ?? ""); @@ -233,8 +233,8 @@ describe("API Tests", () => { const response = await sendTestRequest( new Request( wrapRelativeUrl( - `${base_url}/api/v1/statuses/${status?.id}/unreblog`, - base_url, + `${baseUrl}/api/v1/statuses/${status?.id}/unreblog`, + baseUrl, ), { method: "POST", @@ -250,7 +250,7 @@ describe("API Tests", () => { "application/json", ); - const unrebloggedStatus = (await response.json()) as APIStatus; + const unrebloggedStatus = (await response.json()) as apiStatus; expect(unrebloggedStatus.id).toBeDefined(); expect(unrebloggedStatus.reblogged).toBe(false); @@ -262,8 +262,8 @@ describe("API Tests", () => { const response = await sendTestRequest( new Request( wrapRelativeUrl( - `${base_url}/api/v1/statuses/${status?.id}/context`, - base_url, + `${baseUrl}/api/v1/statuses/${status?.id}/context`, + baseUrl, ), { headers: { @@ -278,7 +278,7 @@ describe("API Tests", () => { "application/json", ); - const context = (await response.json()) as APIContext; + const context = (await response.json()) as apiContext; expect(context.ancestors.length).toBe(0); expect(context.descendants.length).toBe(1); @@ -293,8 +293,8 @@ describe("API Tests", () => { const response = await sendTestRequest( new Request( wrapRelativeUrl( - `${base_url}/api/v1/accounts/${user.id}/statuses`, - base_url, + `${baseUrl}/api/v1/accounts/${user.id}/statuses`, + baseUrl, ), { method: "GET", @@ -310,7 +310,7 @@ describe("API Tests", () => { "application/json", ); - const statuses = (await response.json()) as APIStatus[]; + const statuses = (await response.json()) as apiStatus[]; expect(statuses.length).toBe(2); @@ -328,8 +328,8 @@ describe("API Tests", () => { const response = await sendTestRequest( new Request( wrapRelativeUrl( - `${base_url}/api/v1/statuses/${status?.id}/favourite`, - base_url, + `${baseUrl}/api/v1/statuses/${status?.id}/favourite`, + baseUrl, ), { method: "POST", @@ -350,8 +350,8 @@ describe("API Tests", () => { const response = await sendTestRequest( new Request( wrapRelativeUrl( - `${base_url}/api/v1/statuses/${status?.id}/unfavourite`, - base_url, + `${baseUrl}/api/v1/statuses/${status?.id}/unfavourite`, + baseUrl, ), { method: "POST", @@ -367,7 +367,7 @@ describe("API Tests", () => { "application/json", ); - const updatedStatus = (await response.json()) as APIStatus; + const updatedStatus = (await response.json()) as apiStatus; expect(updatedStatus.favourited).toBe(false); expect(updatedStatus.favourites_count).toBe(0); @@ -379,8 +379,8 @@ describe("API Tests", () => { const response = await sendTestRequest( new Request( wrapRelativeUrl( - `${base_url}/api/v1/statuses/${status?.id}`, - base_url, + `${baseUrl}/api/v1/statuses/${status?.id}`, + baseUrl, ), { method: "DELETE", diff --git a/tests/oauth-scopes.test.ts b/tests/oauth-scopes.test.ts index 728fa540..2569607b 100644 --- a/tests/oauth-scopes.test.ts +++ b/tests/oauth-scopes.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "bun:test"; import { checkIfOauthIsValid } from "@/oauth"; -import type { Application } from "~/database/entities/Application"; +import type { Application } from "~/database/entities/application"; describe("checkIfOauthIsValid", () => { it("should return true when routeScopes and application.scopes are empty", () => { diff --git a/tests/oauth.test.ts b/tests/oauth.test.ts index a728303d..78b6a146 100644 --- a/tests/oauth.test.ts +++ b/tests/oauth.test.ts @@ -3,17 +3,17 @@ */ import { afterAll, describe, expect, test } from "bun:test"; import { config } from "~/packages/config-manager"; -import type { Application as APIApplication } from "~/types/mastodon/application"; -import type { Token as APIToken } from "~/types/mastodon/token"; +import type { Application as apiApplication } from "~/types/mastodon/application"; +import type { Token as apiToken } from "~/types/mastodon/token"; import { getTestUsers, sendTestRequest, wrapRelativeUrl } from "./utils"; -const base_url = config.http.base_url; +const baseUrl = config.http.base_url; -let client_id: string; -let client_secret: string; +let clientId: string; +let clientSecret: string; let code: string; let jwt: string; -let token: APIToken; +let token: apiToken; const { users, passwords, deleteUsers } = await getTestUsers(1); afterAll(async () => { @@ -59,8 +59,8 @@ describe("POST /api/v1/apps/", () => { vapid_link: null, }); - client_id = json.client_id; - client_secret = json.client_secret; + clientId = json.client_id; + clientSecret = json.client_secret; }); }); @@ -74,8 +74,8 @@ describe("POST /api/auth/login/", () => { const response = await sendTestRequest( new Request( new URL( - `/api/auth/login?client_id=${client_id}&redirect_uri=https://example.com&response_type=code&scope=read+write`, - base_url, + `/api/auth/login?client_id=${clientId}&redirect_uri=https://example.com&response_type=code&scope=read+write`, + baseUrl, ), { method: "POST", @@ -95,14 +95,14 @@ describe("POST /api/auth/login/", () => { describe("GET /oauth/authorize/", () => { test("should get a code", async () => { const response = await sendTestRequest( - new Request(new URL("/oauth/authorize", base_url), { + new Request(new URL("/oauth/authorize", baseUrl), { method: "POST", headers: { Cookie: `jwt=${jwt}`, }, body: new URLSearchParams({ - client_id, - client_secret, + client_id: clientId, + client_secret: clientSecret, redirect_uri: "https://example.com", response_type: "code", scope: "read write", @@ -127,7 +127,7 @@ describe("GET /oauth/authorize/", () => { describe("POST /oauth/token/", () => { test("should get an access token", async () => { const response = await sendTestRequest( - new Request(wrapRelativeUrl("/oauth/token", base_url), { + new Request(wrapRelativeUrl("/oauth/token", baseUrl), { method: "POST", headers: { Authorization: `Bearer ${jwt}`, @@ -137,8 +137,8 @@ describe("POST /oauth/token/", () => { grant_type: "authorization_code", code, redirect_uri: "https://example.com", - client_id, - client_secret, + client_id: clientId, + client_secret: clientSecret, scope: "read write", }), }), @@ -166,7 +166,7 @@ describe("GET /api/v1/apps/verify_credentials", () => { test("should return the authenticated application's credentials", async () => { const response = await sendTestRequest( new Request( - wrapRelativeUrl("/api/v1/apps/verify_credentials", base_url), + wrapRelativeUrl("/api/v1/apps/verify_credentials", baseUrl), { headers: { Authorization: `Bearer ${token.access_token}`, @@ -178,7 +178,7 @@ describe("GET /api/v1/apps/verify_credentials", () => { expect(response.status).toBe(200); expect(response.headers.get("content-type")).toBe("application/json"); - const credentials = (await response.json()) as Partial; + const credentials = (await response.json()) as Partial; expect(credentials.name).toBe("Test Application"); expect(credentials.website).toBe("https://example.com"); diff --git a/tests/utils.ts b/tests/utils.ts index 8dd53342..7ad390b5 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -1,7 +1,7 @@ import { randomBytes } from "node:crypto"; import { consoleLogger } from "@/loggers"; import { asc, inArray, like } from "drizzle-orm"; -import type { Status } from "~/database/entities/Status"; +import type { Status } from "~/database/entities/status"; import { db } from "~/drizzle/db"; import { setupDatabase } from "~/drizzle/db"; import { Notes, Tokens, Users } from "~/drizzle/schema"; @@ -16,13 +16,13 @@ await setupDatabase(consoleLogger); * @param req Request to send * @returns Response from the server */ -export async function sendTestRequest(req: Request) { +export function sendTestRequest(req: Request): Promise { // return fetch(req); - return app.fetch(req); + return Promise.resolve(app.fetch(req)); } -export function wrapRelativeUrl(url: string, base_url: string) { - return new URL(url, base_url); +export function wrapRelativeUrl(url: string, baseUrl: string) { + return new URL(url, baseUrl); } export const deleteOldTestUsers = async () => { diff --git a/types/api.ts b/types/api.ts index 6ae0a558..66f1db51 100644 --- a/types/api.ts +++ b/types/api.ts @@ -4,7 +4,7 @@ import type { z } from "zod"; import type { RolePermissions } from "~/drizzle/schema"; export type HttpVerb = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "OPTIONS"; -export interface APIRouteMetadata { +export interface ApiRouteMetadata { allowedMethods: HttpVerb[]; ratelimits: { max: number; @@ -19,13 +19,13 @@ export interface APIRouteMetadata { permissions?: { required: RolePermissions[]; methodOverrides?: { - [key in HttpVerb]?: RolePermissions[]; + [Key in HttpVerb]?: RolePermissions[]; }; }; } -export interface APIRouteExports { - meta: APIRouteMetadata; +export interface ApiRouteExports { + meta: ApiRouteMetadata; schemas?: { query?: z.AnyZodObject; body?: z.AnyZodObject; diff --git a/types/mastodon/account.ts b/types/mastodon/account.ts index 3e8366b6..0a9f43fe 100644 --- a/types/mastodon/account.ts +++ b/types/mastodon/account.ts @@ -25,9 +25,9 @@ export type Account = { avatar_static: string; header: string; header_static: string; - emojis: Array; + emojis: Emoji[]; moved: Account | null; - fields: Array; + fields: Field[]; bot: boolean | null; source?: Source; role?: Role; diff --git a/types/mastodon/announcement.ts b/types/mastodon/announcement.ts index 5a4642cf..0a3cb108 100644 --- a/types/mastodon/announcement.ts +++ b/types/mastodon/announcement.ts @@ -11,11 +11,11 @@ export type Announcement = { published_at: string; updated_at: string | null; read: boolean | null; - mentions: Array; - statuses: Array; - tags: Array; - emojis: Array; - reactions: Array; + mentions: AnnouncementAccount[]; + statuses: AnnouncementStatus[]; + tags: StatusTag[]; + emojis: Emoji[]; + reactions: AnnouncementReaction[]; }; export type AnnouncementAccount = { diff --git a/types/mastodon/context.ts b/types/mastodon/context.ts index 23f96436..c053f512 100644 --- a/types/mastodon/context.ts +++ b/types/mastodon/context.ts @@ -1,6 +1,6 @@ import type { Status } from "./status"; export type Context = { - ancestors: Array; - descendants: Array; + ancestors: Status[]; + descendants: Status[]; }; diff --git a/types/mastodon/conversation.ts b/types/mastodon/conversation.ts index 4a44c9b8..5edf6091 100644 --- a/types/mastodon/conversation.ts +++ b/types/mastodon/conversation.ts @@ -3,7 +3,7 @@ import type { Status } from "./status"; export type Conversation = { id: string; - accounts: Array; + accounts: Account[]; last_status: Status | null; unread: boolean; }; diff --git a/types/mastodon/filter.ts b/types/mastodon/filter.ts index a39928cc..da97b419 100644 --- a/types/mastodon/filter.ts +++ b/types/mastodon/filter.ts @@ -1,7 +1,7 @@ export type Filter = { id: string; phrase: string; - context: Array; + context: FilterContext[]; expires_at: string | null; irreversible: boolean; whole_word: boolean; diff --git a/types/mastodon/follow_request.ts b/types/mastodon/follow_request.ts index edcc4476..676b583a 100644 --- a/types/mastodon/follow_request.ts +++ b/types/mastodon/follow_request.ts @@ -20,6 +20,6 @@ export type FollowRequest = { followers_count: number; following_count: number; statuses_count: number; - emojis: Array; - fields: Array; + emojis: Emoji[]; + fields: Field[]; }; diff --git a/types/mastodon/instance.ts b/types/mastodon/instance.ts index b2768809..c5030964 100644 --- a/types/mastodon/instance.ts +++ b/types/mastodon/instance.ts @@ -11,7 +11,7 @@ export type Instance = { thumbnail: string | null; urls: URLs | null; stats: Stats; - languages: Array; + languages: string[]; registrations: boolean; approval_required: boolean; invites_enabled?: boolean; @@ -29,7 +29,7 @@ export type Instance = { }; }; contact_account?: Account; - rules?: Array; + rules?: InstanceRule[]; }; export type InstanceRule = { diff --git a/types/mastodon/poll.ts b/types/mastodon/poll.ts index 48fed02c..33fef2f8 100644 --- a/types/mastodon/poll.ts +++ b/types/mastodon/poll.ts @@ -4,7 +4,7 @@ export type Poll = { expired: boolean; multiple: boolean; votes_count: number; - options: Array; + options: PollOption[]; voted: boolean; }; diff --git a/types/mastodon/reaction.ts b/types/mastodon/reaction.ts index e7fffdf9..74832695 100644 --- a/types/mastodon/reaction.ts +++ b/types/mastodon/reaction.ts @@ -6,6 +6,6 @@ export type Reaction = { name: string; url?: string; static_url?: string; - accounts?: Array; - account_ids?: Array; + accounts?: Account[]; + account_ids?: string[]; }; diff --git a/types/mastodon/report.ts b/types/mastodon/report.ts index 385e5d8e..ee98aa33 100644 --- a/types/mastodon/report.ts +++ b/types/mastodon/report.ts @@ -4,8 +4,8 @@ export type Report = { id: string; action_taken: boolean; action_taken_at: string | null; - status_ids: Array | null; - rule_ids: Array | null; + status_ids: string[] | null; + rule_ids: string[] | null; // These parameters don't exist in Pleroma category: Category | null; comment: string | null; diff --git a/types/mastodon/results.ts b/types/mastodon/results.ts index 68ffd531..229f98ee 100644 --- a/types/mastodon/results.ts +++ b/types/mastodon/results.ts @@ -3,7 +3,7 @@ import type { Status } from "./status"; import type { Tag } from "./tag"; export type Results = { - accounts: Array; - statuses: Array; - hashtags: Array; + accounts: Account[]; + statuses: Status[]; + hashtags: Tag[]; }; diff --git a/types/mastodon/scheduled_status.ts b/types/mastodon/scheduled_status.ts index e8f8a2a9..69389ef8 100644 --- a/types/mastodon/scheduled_status.ts +++ b/types/mastodon/scheduled_status.ts @@ -5,5 +5,5 @@ export type ScheduledStatus = { id: string; scheduled_at: string; params: StatusParams; - media_attachments: Array | null; + media_attachments: Attachment[] | null; }; diff --git a/types/mastodon/source.ts b/types/mastodon/source.ts index 161be04f..cab190e5 100644 --- a/types/mastodon/source.ts +++ b/types/mastodon/source.ts @@ -5,5 +5,5 @@ export type Source = { sensitive: boolean | null; language: string | null; note: string; - fields: Array; + fields: Field[]; }; diff --git a/types/mastodon/status.ts b/types/mastodon/status.ts index c61c984d..30f9df6c 100644 --- a/types/mastodon/status.ts +++ b/types/mastodon/status.ts @@ -29,15 +29,15 @@ export type Status = { sensitive: boolean; spoiler_text: string; visibility: StatusVisibility; - media_attachments: Array; - mentions: Array; - tags: Array; + media_attachments: Attachment[]; + mentions: Mention[]; + tags: StatusTag[]; card: Card | null; poll: Poll | null; application: Application | null; language: string | null; pinned: boolean | null; - emoji_reactions: Array; + emoji_reactions: Reaction[]; quote: boolean; bookmarked: boolean; }; diff --git a/types/mastodon/status_params.ts b/types/mastodon/status_params.ts index 89babfbf..56c1874c 100644 --- a/types/mastodon/status_params.ts +++ b/types/mastodon/status_params.ts @@ -3,7 +3,7 @@ import type { StatusVisibility } from "./status"; export type StatusParams = { text: string; in_reply_to_id: string | null; - media_ids: Array | null; + media_ids: string[] | null; sensitive: boolean | null; spoiler_text: string | null; visibility: StatusVisibility | null; diff --git a/types/mastodon/tag.ts b/types/mastodon/tag.ts index da41dd32..69237425 100644 --- a/types/mastodon/tag.ts +++ b/types/mastodon/tag.ts @@ -3,6 +3,6 @@ import type { History } from "./history"; export type Tag = { name: string; url: string; - history: Array; + history: History[]; following?: boolean; }; diff --git a/utils/api.ts b/utils/api.ts index 6744234b..5a5ee83b 100644 --- a/utils/api.ts +++ b/utils/api.ts @@ -20,13 +20,13 @@ import { import { parse } from "qs"; import type { z } from "zod"; import { fromZodError } from "zod-validation-error"; -import type { Application } from "~/database/entities/Application"; -import { getFromHeader } from "~/database/entities/User"; +import type { Application } from "~/database/entities/application"; +import { type AuthData, getFromHeader } from "~/database/entities/user"; import type { User } from "~/packages/database-interface/user"; import { LogLevel, LogManager } from "~/packages/log-manager"; -import type { APIRouteMetadata, HttpVerb } from "~/types/api"; +import type { ApiRouteMetadata, HttpVerb } from "~/types/api"; -export const applyConfig = (routeMeta: APIRouteMetadata) => { +export const applyConfig = (routeMeta: ApiRouteMetadata) => { const newMeta = routeMeta; // Apply ratelimits from config @@ -94,16 +94,113 @@ export const handleZodError = ( result: | { success: true; data?: object } | { success: false; error: z.ZodError; data?: object }, - context: Context, + _context: Context, ) => { if (!result.success) { return errorResponse(fromZodError(result.error).message, 422); } }; +const getAuth = async (value: Record) => { + return value.authorization + ? await getFromHeader(value.authorization) + : null; +}; + +const checkPermissions = ( + auth: AuthData | null, + permissionData: ApiRouteMetadata["permissions"], + context: Context, +) => { + const userPerms = auth?.user + ? auth.user.getAllPermissions() + : config.permissions.anonymous; + const requiredPerms = + permissionData?.methodOverrides?.[context.req.method as HttpVerb] ?? + permissionData?.required ?? + []; + const error = errorResponse("Unauthorized", 401); + + 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(), + ); + } +}; + +const checkRouteNeedsAuth = ( + auth: AuthData | null, + authData: ApiRouteMetadata["auth"], + context: Context, +) => { + const error = errorResponse("Unauthorized", 401); + + 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, + }; +}; + export const auth = ( - authData: APIRouteMetadata["auth"], - permissionData?: APIRouteMetadata["permissions"], + authData: ApiRouteMetadata["auth"], + permissionData?: ApiRouteMetadata["permissions"], +) => + validator("header", async (value, context) => { + const auth = await getAuth(value); + + // Permissions check + if (permissionData) { + const permissionCheck = checkPermissions( + auth, + permissionData, + context, + ); + if (permissionCheck) { + return permissionCheck; + } + } + + return checkRouteNeedsAuth(auth, authData, context); + }); + +/* export const auth = ( + authData: ApiRouteMetadata["auth"], + permissionData?: ApiRouteMetadata["permissions"], ) => validator("header", async (value, context) => { const auth = value.authorization @@ -140,46 +237,41 @@ export const auth = ( } } - if (!auth?.user) { - 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(), - ); - } - - // Check role permissions - } else { + 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 @@ -200,12 +292,12 @@ export const qs = () => { for (const val of value) { urlparams.append(key, val); } - } else if (!(value instanceof File)) { - urlparams.append(key, String(value)); - } else { + } else if (value instanceof File) { if (!files.has(key)) { files.set(key, value); } + } else { + urlparams.append(key, String(value)); } } @@ -297,12 +389,12 @@ export const jsonOrForm = () => { for (const val of value) { urlparams.append(key, val); } - } else if (!(value instanceof File)) { - urlparams.append(key, String(value)); - } else { + } else if (value instanceof File) { if (!files.has(key)) { files.set(key, value); } + } else { + urlparams.append(key, String(value)); } } @@ -341,7 +433,7 @@ export const debugRequest = async ( ) => { const body = await req.clone().text(); await logger.log( - LogLevel.DEBUG, + LogLevel.Debug, "RequestDebugger", `\n${chalk.green(req.method)} ${chalk.blue(req.url)}\n${chalk.bold( "Hash", diff --git a/utils/content_types.ts b/utils/content_types.ts index 3899c42f..49d51b8c 100644 --- a/utils/content_types.ts +++ b/utils/content_types.ts @@ -4,7 +4,9 @@ import { lookup } from "mime-types"; export const getBestContentType = ( content?: typeof EntityValidator.$ContentFormat, ) => { - if (!content) return { content: "", format: "text/plain" }; + if (!content) { + return { content: "", format: "text/plain" }; + } const bestFormatsRanked = [ "text/x.misskeymarkdown", @@ -14,8 +16,9 @@ export const getBestContentType = ( ]; for (const format of bestFormatsRanked) { - if (content[format]) + if (content[format]) { return { content: content[format].content, format }; + } } return { content: "", format: "text/plain" }; @@ -24,7 +27,9 @@ export const getBestContentType = ( export const urlToContentFormat = ( url?: string, ): typeof EntityValidator.$ContentFormat | null => { - if (!url) return null; + if (!url) { + return null; + } if (url.startsWith("https://api.dicebear.com/")) { return { "image/svg+xml": { @@ -46,7 +51,9 @@ export const urlToContentFormat = ( export const mimeLookup = async (url: string) => { const naiveLookup = lookup(url.replace(new URL(url).search, "")); - if (naiveLookup) return naiveLookup; + if (naiveLookup) { + return naiveLookup; + } const fetchLookup = fetch(url, { method: "HEAD" }).then( (response) => response.headers.get("content-type") || "", diff --git a/utils/loggers.ts b/utils/loggers.ts index 3b6c97b8..3e798cac 100644 --- a/utils/loggers.ts +++ b/utils/loggers.ts @@ -4,11 +4,11 @@ import { config } from "~/packages/config-manager"; const noColors = process.env.NO_COLORS === "true"; const noFancyDates = process.env.NO_FANCY_DATES === "true"; -const requests_log = Bun.file(config.logging.storage.requests); +const requestsLog = Bun.file(config.logging.storage.requests); const isEntry = true; export const logger = new LogManager( - isEntry ? requests_log : Bun.file("/dev/null"), + isEntry ? requestsLog : Bun.file("/dev/null"), ); export const consoleLogger = new LogManager( diff --git a/utils/markdown.ts b/utils/markdown.ts index 2e753442..2047035e 100644 --- a/utils/markdown.ts +++ b/utils/markdown.ts @@ -1,4 +1,4 @@ -import { markdownParse } from "~/database/entities/Status"; +import { markdownParse } from "~/database/entities/status"; import { LogLevel } from "~/packages/log-manager"; import { dualLogger } from "./loggers"; @@ -9,19 +9,19 @@ export const renderMarkdownInPath = async ( let content = await markdownParse(defaultText ?? ""); let lastModified = new Date(1970, 0, 0); - const extended_description_file = Bun.file(path || ""); + const extendedDescriptionFile = Bun.file(path || ""); - if (path && (await extended_description_file.exists())) { + if (path && (await extendedDescriptionFile.exists())) { content = (await markdownParse( - (await extended_description_file.text().catch(async (e) => { - await dualLogger.logError(LogLevel.ERROR, "Routes", e); + (await extendedDescriptionFile.text().catch(async (e) => { + await dualLogger.logError(LogLevel.Error, "Routes", e); return ""; })) || defaultText || "", )) || ""; - lastModified = new Date(extended_description_file.lastModified); + lastModified = new Date(extendedDescriptionFile.lastModified); } return { diff --git a/utils/meilisearch.ts b/utils/meilisearch.ts index 0a704f45..8aacc6c1 100644 --- a/utils/meilisearch.ts +++ b/utils/meilisearch.ts @@ -1,4 +1,3 @@ -import chalk from "chalk"; import { config } from "config-manager"; import { count } from "drizzle-orm"; import { LogLevel, type LogManager, type MultiLogManager } from "log-manager"; @@ -13,7 +12,9 @@ export const meilisearch = new Meilisearch({ }); export const connectMeili = async (logger: MultiLogManager | LogManager) => { - if (!config.meilisearch.enabled) return; + if (!config.meilisearch.enabled) { + return; + } if (await meilisearch.isHealthy()) { await meilisearch @@ -33,13 +34,13 @@ export const connectMeili = async (logger: MultiLogManager | LogManager) => { .updateSearchableAttributes(["content"]); await logger.log( - LogLevel.INFO, + LogLevel.Info, "Meilisearch", "Connected to Meilisearch", ); } else { await logger.log( - LogLevel.CRITICAL, + LogLevel.Critical, "Meilisearch", "Error while connecting to Meilisearch", ); @@ -53,7 +54,9 @@ export enum MeiliIndexType { } export const addUserToMeilisearch = async (user: User) => { - if (!config.meilisearch.enabled) return; + if (!config.meilisearch.enabled) { + return; + } await meilisearch.index(MeiliIndexType.Accounts).addDocuments([ { @@ -116,25 +119,19 @@ export const rebuildSearchIndexes = async ( for (let i = 0; i < accountCount / batchSize; i++) { const accounts = await getNthDatabaseAccountBatch(i, batchSize); - const progress = Math.round((i / (accountCount / batchSize)) * 100); - - console.log(`${chalk.green("✓")} ${progress}%`); - + /* const _progress = Math.round( + (i / (accountCount / batchSize)) * 100, + ); + */ // Sync with Meilisearch await meilisearch .index(MeiliIndexType.Accounts) .addDocuments(accounts); } - const meiliAccountCount = ( + /* const _meiliAccountCount = ( await meilisearch.index(MeiliIndexType.Accounts).getStats() - ).numberOfDocuments; - - console.log( - `${chalk.green("✓")} ${chalk.bold( - `Done! ${meiliAccountCount} accounts indexed`, - )}`, - ); + ).numberOfDocuments; */ } if (indexes.includes(MeiliIndexType.Statuses)) { @@ -149,9 +146,7 @@ export const rebuildSearchIndexes = async ( for (let i = 0; i < statusCount / batchSize; i++) { const statuses = await getNthDatabaseStatusBatch(i, batchSize); - const progress = Math.round((i / (statusCount / batchSize)) * 100); - - console.log(`${chalk.green("✓")} ${progress}%`); + /* const _progress = Math.round((i / (statusCount / batchSize)) * 100); */ // Sync with Meilisearch await meilisearch @@ -159,14 +154,8 @@ export const rebuildSearchIndexes = async ( .addDocuments(statuses); } - const meiliStatusCount = ( + /* const _meiliStatusCount = ( await meilisearch.index(MeiliIndexType.Statuses).getStats() - ).numberOfDocuments; - - console.log( - `${chalk.green("✓")} ${chalk.bold( - `Done! ${meiliStatusCount} statuses indexed`, - )}`, - ); + ).numberOfDocuments; */ } }; diff --git a/utils/oauth.ts b/utils/oauth.ts index 28b50515..4da680a8 100644 --- a/utils/oauth.ts +++ b/utils/oauth.ts @@ -1,4 +1,4 @@ -import type { Application } from "~/database/entities/Application"; +import type { Application } from "~/database/entities/application"; /** * Check if an OAuth application is valid for a route diff --git a/utils/sanitization.ts b/utils/sanitization.ts index 02f583e5..35fb4f93 100644 --- a/utils/sanitization.ts +++ b/utils/sanitization.ts @@ -7,7 +7,7 @@ export const sanitizedHtmlStrip = (html: string) => { }); }; -export const sanitizeHtmlInline = async ( +export const sanitizeHtmlInline = ( html: string, extraConfig?: IFilterXSSOptions, ) => { diff --git a/utils/timelines.ts b/utils/timelines.ts index ed78fd9e..8c6830cb 100644 --- a/utils/timelines.ts +++ b/utils/timelines.ts @@ -2,9 +2,9 @@ import { config } from "config-manager"; import type { Notification, findManyNotifications, -} from "~/database/entities/Notification"; -import type { Status, findManyNotes } from "~/database/entities/Status"; -import type { UserType, findManyUsers } from "~/database/entities/User"; +} from "~/database/entities/notification"; +import type { Status, findManyNotes } from "~/database/entities/status"; +import type { UserType, findManyUsers } from "~/database/entities/user"; import type { db } from "~/drizzle/db"; export async function fetchTimeline(