diff --git a/benchmarks/timelines.ts b/benchmarks/timelines.ts index 2804f691..410d5958 100644 --- a/benchmarks/timelines.ts +++ b/benchmarks/timelines.ts @@ -2,7 +2,7 @@ * Usage: TOKEN=your_token_here bun benchmark:timeline */ -import { getConfig } from "@config"; +import { getConfig } from "~classes/configmanager"; import chalk from "chalk"; const config = getConfig(); diff --git a/classes/configmanager.ts b/classes/configmanager.ts new file mode 100644 index 00000000..d5969b33 --- /dev/null +++ b/classes/configmanager.ts @@ -0,0 +1,443 @@ +/** + * @file configmanager.ts + * @summary ConfigManager system to retrieve and modify system configuration + * @description Can read from a hand-written file, config.toml, or from a machine-saved file, config.internal.toml + * Fuses both and provides a way to retrieve individual values + */ + +import { parse, stringify } from "@iarna/toml"; +import { deepMerge, deepMergeArray } from "@merge"; +import chalk from "chalk"; + +const scanConfig = async () => { + const config = Bun.file(process.cwd() + "/config/config.toml"); + + if (!(await config.exists())) { + console.error( + `${chalk.red(`✗`)} ${chalk.bold( + "Error while reading config: " + )} Config file not found` + ); + process.exit(1); + } + + return parse(await config.text()) as ConfigType; +}; + +// Creates the internal config with nothing in it if it doesnt exist +const scanInternalConfig = async () => { + const config = Bun.file(process.cwd() + "/config/config.internal.toml"); + + if (!(await config.exists())) { + await Bun.write(config, ""); + } + + return parse(await config.text()) as ConfigType; +}; + +let config = await scanConfig(); +const internalConfig = await scanInternalConfig(); + +export interface ConfigType { + database: { + host: string; + port: number; + username: string; + password: string; + database: string; + }; + + redis: { + queue: { + host: string; + port: number; + password: string; + database: number | null; + }; + cache: { + host: string; + port: number; + password: string; + database: number | null; + enabled: boolean; + }; + }; + + meilisearch: { + host: string; + port: number; + api_key: string; + enabled: boolean; + }; + + signups: { + tos_url: string; + rules: string[]; + registration: boolean; + }; + + oidc: { + providers: { + name: string; + id: string; + url: string; + client_id: string; + client_secret: string; + icon: string; + }[]; + }; + + http: { + base_url: string; + bind: string; + bind_port: string; + banned_ips: string[]; + }; + + instance: { + name: string; + description: string; + banner: string; + logo: string; + }; + + smtp: { + server: string; + port: number; + username: string; + password: string; + tls: boolean; + }; + + validation: { + max_displayname_size: number; + max_bio_size: number; + max_username_size: number; + max_note_size: number; + max_avatar_size: number; + max_header_size: number; + max_media_size: number; + max_media_attachments: number; + max_media_description_size: number; + max_poll_options: number; + max_poll_option_size: number; + min_poll_duration: number; + max_poll_duration: number; + + username_blacklist: string[]; + blacklist_tempmail: boolean; + email_blacklist: string[]; + url_scheme_whitelist: string[]; + + enforce_mime_types: boolean; + allowed_mime_types: string[]; + }; + + media: { + backend: string; + deduplicate_media: boolean; + conversion: { + convert_images: boolean; + convert_to: string; + }; + }; + + s3: { + endpoint: string; + access_key: string; + secret_access_key: string; + region: string; + bucket_name: string; + public_url: string; + }; + + defaults: { + visibility: string; + language: string; + avatar: string; + header: string; + }; + + email: { + send_on_report: boolean; + send_on_suspend: boolean; + send_on_unsuspend: boolean; + }; + + activitypub: { + use_tombstones: boolean; + reject_activities: string[]; + force_followers_only: string[]; + discard_reports: string[]; + discard_deletes: string[]; + discard_banners: string[]; + discard_avatars: string[]; + discard_updates: string[]; + discard_follows: string[]; + force_sensitive: string[]; + remove_media: string[]; + fetch_all_collection_members: boolean; + authorized_fetch: boolean; + }; + + filters: { + note_filters: string[]; + username_filters: string[]; + displayname_filters: string[]; + bio_filters: string[]; + emoji_filters: string[]; + }; + + logging: { + log_requests: boolean; + log_requests_verbose: boolean; + log_filters: boolean; + }; + + ratelimits: { + duration_coeff: number; + max_coeff: number; + }; + + custom_ratelimits: Record< + string, + { + duration: number; + max: number; + } + >; + [key: string]: unknown; +} + +export const configDefaults: ConfigType = { + http: { + bind: "http://0.0.0.0", + bind_port: "8000", + base_url: "http://lysand.localhost:8000", + banned_ips: [], + }, + database: { + host: "localhost", + port: 5432, + username: "postgres", + password: "postgres", + database: "lysand", + }, + redis: { + queue: { + host: "localhost", + port: 6379, + password: "", + database: 0, + }, + cache: { + host: "localhost", + port: 6379, + password: "", + database: 1, + enabled: false, + }, + }, + meilisearch: { + host: "localhost", + port: 1491, + api_key: "", + enabled: false, + }, + signups: { + tos_url: "", + rules: [], + registration: false, + }, + oidc: { + providers: [], + }, + instance: { + banner: "", + description: "", + logo: "", + name: "", + }, + smtp: { + password: "", + port: 465, + server: "", + tls: true, + username: "", + }, + media: { + backend: "local", + deduplicate_media: true, + conversion: { + convert_images: false, + convert_to: "webp", + }, + }, + email: { + send_on_report: false, + send_on_suspend: false, + send_on_unsuspend: false, + }, + s3: { + access_key: "", + bucket_name: "", + endpoint: "", + public_url: "", + region: "", + secret_access_key: "", + }, + validation: { + max_displayname_size: 50, + max_bio_size: 6000, + max_note_size: 5000, + max_avatar_size: 5_000_000, + max_header_size: 5_000_000, + max_media_size: 40_000_000, + max_media_attachments: 10, + max_media_description_size: 1000, + max_poll_options: 20, + max_poll_option_size: 500, + min_poll_duration: 60, + max_poll_duration: 1893456000, + max_username_size: 30, + + username_blacklist: [ + ".well-known", + "~", + "about", + "activities", + "api", + "auth", + "dev", + "inbox", + "internal", + "main", + "media", + "nodeinfo", + "notice", + "oauth", + "objects", + "proxy", + "push", + "registration", + "relay", + "settings", + "status", + "tag", + "users", + "web", + "search", + "mfa", + ], + + blacklist_tempmail: false, + + email_blacklist: [], + + url_scheme_whitelist: [ + "http", + "https", + "ftp", + "dat", + "dweb", + "gopher", + "hyper", + "ipfs", + "ipns", + "irc", + "xmpp", + "ircs", + "magnet", + "mailto", + "mumble", + "ssb", + ], + + enforce_mime_types: false, + allowed_mime_types: [], + }, + defaults: { + visibility: "public", + language: "en", + avatar: "", + header: "", + }, + activitypub: { + use_tombstones: true, + reject_activities: [], + force_followers_only: [], + discard_reports: [], + discard_deletes: [], + discard_banners: [], + discard_avatars: [], + force_sensitive: [], + discard_updates: [], + discard_follows: [], + remove_media: [], + fetch_all_collection_members: false, + authorized_fetch: false, + }, + filters: { + note_filters: [], + username_filters: [], + displayname_filters: [], + bio_filters: [], + emoji_filters: [], + }, + logging: { + log_requests: false, + log_requests_verbose: false, + log_filters: true, + }, + ratelimits: { + duration_coeff: 1, + max_coeff: 1, + }, + custom_ratelimits: {}, +}; + +export const getConfig = () => { + // Deeply merge configDefaults, config and internalConfig + return deepMergeArray([ + configDefaults, + config, + internalConfig, + ]) as any as ConfigType; +}; + +/** + * Sets the internal config + * @param newConfig Any part of ConfigType + */ +export const setConfig = async (newConfig: Partial) => { + const newInternalConfig = deepMerge(internalConfig, newConfig); + + // Prepend a warning comment and write the new TOML to the file + await Bun.write( + Bun.file(process.cwd() + "/config/config.internal.toml"), + `# This file is automatically generated. Do not modify it manually.\n${stringify( + newInternalConfig + )}` + ); +}; + +export const getHost = () => { + const url = new URL(getConfig().http.base_url); + + return url.host; +}; + +// Refresh config every 5 seconds +setInterval(() => { + scanConfig() + .then(newConfig => { + if (newConfig !== config) { + config = newConfig; + } + }) + .catch(e => { + console.error(e); + }); +}, 5000); + +export { config }; diff --git a/classes/media.ts b/classes/media.ts index 9d40ebe0..f8d273b8 100644 --- a/classes/media.ts +++ b/classes/media.ts @@ -4,7 +4,7 @@ import { PutObjectCommand, S3Client, } from "@aws-sdk/client-s3"; -import type { ConfigType } from "@config"; +import type { ConfigType } from "~classes/configmanager"; import sharp from "sharp"; import { exists, mkdir } from "fs/promises"; class MediaBackend { diff --git a/cli.ts b/cli.ts index dbe29f0f..7a30ef48 100644 --- a/cli.ts +++ b/cli.ts @@ -4,7 +4,7 @@ import { client } from "~database/datasource"; import { createNewLocalUser } from "~database/entities/User"; import Table from "cli-table"; import { rebuildSearchIndexes, MeiliIndexType } from "@meilisearch"; -import { getConfig } from "@config"; +import { getConfig } from "~classes/configmanager"; import { uploadFile } from "~classes/media"; import { getUrl } from "~database/entities/Attachment"; import { mkdir, exists } from "fs/promises"; diff --git a/database/entities/Attachment.ts b/database/entities/Attachment.ts index e85bdeac..fefa8cd7 100644 --- a/database/entities/Attachment.ts +++ b/database/entities/Attachment.ts @@ -1,4 +1,4 @@ -import type { ConfigType } from "@config"; +import type { ConfigType } from "~classes/configmanager"; import type { Attachment } from "@prisma/client"; import type { APIAsyncAttachment } from "~types/entities/async_attachment"; import type { APIAttachment } from "~types/entities/attachment"; diff --git a/database/entities/Like.ts b/database/entities/Like.ts index f4dbf482..c1839b90 100644 --- a/database/entities/Like.ts +++ b/database/entities/Like.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ import type { Like as LysandLike } from "~types/lysand/Object"; -import { getConfig } from "@config"; +import { getConfig } from "~classes/configmanager"; import type { Like } from "@prisma/client"; import { client } from "~database/datasource"; import type { UserWithRelations } from "./User"; diff --git a/database/entities/Queue.ts b/database/entities/Queue.ts index 5e3e1d01..90ebd37b 100644 --- a/database/entities/Queue.ts +++ b/database/entities/Queue.ts @@ -1,4 +1,4 @@ -import { getConfig } from "@config"; +import { getConfig } from "~classes/configmanager"; import { Worker } from "bullmq"; import { client, federationQueue } from "~database/datasource"; import { diff --git a/database/entities/Status.ts b/database/entities/Status.ts index 16849dc3..ee1d5496 100644 --- a/database/entities/Status.ts +++ b/database/entities/Status.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ -import { getConfig } from "@config"; +import { getConfig } from "~classes/configmanager"; import type { UserWithRelations } from "./User"; import { fetchRemoteUser, diff --git a/database/entities/User.ts b/database/entities/User.ts index f257756e..8abb4126 100644 --- a/database/entities/User.ts +++ b/database/entities/User.ts @@ -1,5 +1,5 @@ -import type { ConfigType } from "@config"; -import { getConfig } from "@config"; +import type { ConfigType } from "~classes/configmanager"; +import { getConfig } from "~classes/configmanager"; import type { APIAccount } from "~types/entities/account"; import type { User as LysandUser } from "~types/lysand/Object"; import { htmlToText } from "html-to-text"; diff --git a/index.ts b/index.ts index db74de9f..7b84a7ad 100644 --- a/index.ts +++ b/index.ts @@ -1,4 +1,4 @@ -import { getConfig } from "@config"; +import { getConfig } from "~classes/configmanager"; import { jsonResponse } from "@response"; import chalk from "chalk"; import { appendFile } from "fs/promises"; diff --git a/prisma.ts b/prisma.ts index a0ee9c59..a4fd0076 100644 --- a/prisma.ts +++ b/prisma.ts @@ -1,9 +1,12 @@ // Proxies all `bunx prisma` commands with an environment variable -import { getConfig } from "@config"; +import { getConfig } from "~classes/configmanager"; const config = getConfig(); process.stdout.write( - `postgresql://${config.database.username}:${config.database.password}@${config.database.host}:${config.database.port}/${config.database.database}` + `postgresql://${config.database.username}:${config.database.password}@${config.database.host}:${config.database.port}/${config.database.database}\n` ); + +// Ends +process.exit(0); diff --git a/prisma/migrations/20240304012450_add_moderation_data/migration.sql b/prisma/migrations/20240304012450_add_moderation_data/migration.sql new file mode 100644 index 00000000..8b7a8fc1 --- /dev/null +++ b/prisma/migrations/20240304012450_add_moderation_data/migration.sql @@ -0,0 +1,40 @@ +-- AlterTable +ALTER TABLE "Instance" ADD COLUMN "disableAutomoderation" BOOLEAN NOT NULL DEFAULT false; + +-- AlterTable +ALTER TABLE "User" ADD COLUMN "disableAutomoderation" BOOLEAN NOT NULL DEFAULT false; + +-- CreateTable +CREATE TABLE "ModerationData" ( + "id" UUID NOT NULL DEFAULT uuid_generate_v7(), + "statusId" UUID NOT NULL, + "creatorId" UUID NOT NULL, + "note" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "ModerationData_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Flag" ( + "id" UUID NOT NULL DEFAULT uuid_generate_v7(), + "statusId" UUID NOT NULL, + "userId" UUID NOT NULL, + "flagType" TEXT NOT NULL DEFAULT 'other', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Flag_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "ModerationData" ADD CONSTRAINT "ModerationData_statusId_fkey" FOREIGN KEY ("statusId") REFERENCES "Status"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ModerationData" ADD CONSTRAINT "ModerationData_creatorId_fkey" FOREIGN KEY ("creatorId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Flag" ADD CONSTRAINT "Flag_statusId_fkey" FOREIGN KEY ("statusId") REFERENCES "Status"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Flag" ADD CONSTRAINT "Flag_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 77f97f04..593c9a8b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -37,14 +37,15 @@ model Emoji { } model Instance { - id String @id @default(dbgenerated("uuid_generate_v7()")) @db.Uuid - base_url String - name String - version String - logo Json - emojis Emoji[] // One to many relation with Emoji - statuses Status[] // One to many relation with Status - users User[] // One to many relation with User + id String @id @default(dbgenerated("uuid_generate_v7()")) @db.Uuid + base_url String + name String + version String + logo Json + emojis Emoji[] // One to many relation with Emoji + statuses Status[] // One to many relation with Status + users User[] // One to many relation with User + disableAutomoderation Boolean @default(false) } model Like { @@ -93,38 +94,62 @@ model Relationship { } model Status { - id String @id @default(dbgenerated("uuid_generate_v7()")) @db.Uuid - uri String @unique - author User @relation("UserStatuses", fields: [authorId], references: [id], onDelete: Cascade) - authorId String @db.Uuid - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - reblog Status? @relation("StatusToStatus", fields: [reblogId], references: [id], onDelete: Cascade) - reblogId String? @db.Uuid + id String @id @default(dbgenerated("uuid_generate_v7()")) @db.Uuid + uri String @unique + author User @relation("UserStatuses", fields: [authorId], references: [id], onDelete: Cascade) + authorId String @db.Uuid + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + reblog Status? @relation("StatusToStatus", fields: [reblogId], references: [id], onDelete: Cascade) + reblogId String? @db.Uuid isReblog Boolean - content String @default("") - contentType String @default("text/plain") - contentSource String @default("") + content String @default("") + contentType String @default("text/plain") + contentSource String @default("") visibility String - inReplyToPost Status? @relation("StatusToStatusReply", fields: [inReplyToPostId], references: [id], onDelete: SetNull) - inReplyToPostId String? @db.Uuid - quotingPost Status? @relation("StatusToStatusQuote", fields: [quotingPostId], references: [id], onDelete: SetNull) - quotingPostId String? @db.Uuid - instance Instance? @relation(fields: [instanceId], references: [id], onDelete: Cascade) - instanceId String? @db.Uuid + inReplyToPost Status? @relation("StatusToStatusReply", fields: [inReplyToPostId], references: [id], onDelete: SetNull) + inReplyToPostId String? @db.Uuid + quotingPost Status? @relation("StatusToStatusQuote", fields: [quotingPostId], references: [id], onDelete: SetNull) + quotingPostId String? @db.Uuid + instance Instance? @relation(fields: [instanceId], references: [id], onDelete: Cascade) + instanceId String? @db.Uuid sensitive Boolean - spoilerText String @default("") - application Application? @relation(fields: [applicationId], references: [id], onDelete: SetNull) - applicationId String? @db.Uuid - emojis Emoji[] @relation + spoilerText String @default("") + application Application? @relation(fields: [applicationId], references: [id], onDelete: SetNull) + applicationId String? @db.Uuid + emojis Emoji[] @relation mentions User[] - likes Like[] @relation("LikedToStatus") - reblogs Status[] @relation("StatusToStatus") - replies Status[] @relation("StatusToStatusReply") - quotes Status[] @relation("StatusToStatusQuote") - pinnedBy User[] @relation("UserPinnedNotes") + likes Like[] @relation("LikedToStatus") + reblogs Status[] @relation("StatusToStatus") + replies Status[] @relation("StatusToStatusReply") + quotes Status[] @relation("StatusToStatusQuote") + pinnedBy User[] @relation("UserPinnedNotes") attachments Attachment[] relatedNotifications Notification[] + flags Flag[] + moderationData ModerationData[] +} + +model ModerationData { + id String @id @default(dbgenerated("uuid_generate_v7()")) @db.Uuid + status Status @relation(fields: [statusId], references: [id], onDelete: Cascade) + statusId String @db.Uuid + creator User @relation(fields: [creatorId], references: [id], onDelete: Cascade) + creatorId String @db.Uuid + note String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +// Used for moderation purposes +model Flag { + id String @id @default(dbgenerated("uuid_generate_v7()")) @db.Uuid + status Status @relation(fields: [statusId], references: [id], onDelete: Cascade) + statusId String @db.Uuid + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + userId String @db.Uuid + flagType String @default("other") + createdAt DateTime @default(now()) } model Token { @@ -179,39 +204,42 @@ model Notification { } model User { - id String @id @default(dbgenerated("uuid_generate_v7()")) @db.Uuid - uri String @unique - username String @unique - displayName String - password String? // Nullable - email String? @unique // Nullable - note String @default("") - isAdmin Boolean @default(false) - endpoints Json? // Nullable - source Json - avatar String - header String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - isBot Boolean @default(false) - isLocked Boolean @default(false) - isDiscoverable Boolean @default(false) - sanctions String[] @default([]) - publicKey String - privateKey String? // Nullable - relationships Relationship[] @relation("OwnerToRelationship") // One to many relation with Relationship - relationshipSubjects Relationship[] @relation("SubjectToRelationship") // One to many relation with Relationship - instance Instance? @relation(fields: [instanceId], references: [id], onDelete: Cascade) // Many to one relation with Instance - instanceId String? @db.Uuid - pinnedNotes Status[] @relation("UserPinnedNotes") // Many to many relation with Status - emojis Emoji[] // Many to many relation with Emoji - statuses Status[] @relation("UserStatuses") // One to many relation with Status - tokens Token[] // One to many relation with Token - likes Like[] @relation("UserLiked") // One to many relation with Like - statusesMentioned Status[] // Many to many relation with Status - notifications Notification[] // One to many relation with Notification - notified Notification[] @relation("NotificationToNotified") // One to many relation with Notification - linkedOpenIdAccounts OpenIdAccount[] // One to many relation with OpenIdAccount + id String @id @default(dbgenerated("uuid_generate_v7()")) @db.Uuid + uri String @unique + username String @unique + displayName String + password String? // Nullable + email String? @unique // Nullable + note String @default("") + isAdmin Boolean @default(false) + endpoints Json? // Nullable + source Json + avatar String + header String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + isBot Boolean @default(false) + isLocked Boolean @default(false) + isDiscoverable Boolean @default(false) + sanctions String[] @default([]) + publicKey String + privateKey String? // Nullable + relationships Relationship[] @relation("OwnerToRelationship") // One to many relation with Relationship + relationshipSubjects Relationship[] @relation("SubjectToRelationship") // One to many relation with Relationship + instance Instance? @relation(fields: [instanceId], references: [id], onDelete: Cascade) // Many to one relation with Instance + instanceId String? @db.Uuid + pinnedNotes Status[] @relation("UserPinnedNotes") // Many to many relation with Status + emojis Emoji[] // Many to many relation with Emoji + statuses Status[] @relation("UserStatuses") // One to many relation with Status + tokens Token[] // One to many relation with Token + likes Like[] @relation("UserLiked") // One to many relation with Like + statusesMentioned Status[] // Many to many relation with Status + notifications Notification[] // One to many relation with Notification + notified Notification[] @relation("NotificationToNotified") // One to many relation with Notification + linkedOpenIdAccounts OpenIdAccount[] // One to many relation with OpenIdAccount + flags Flag[] @relation + moderationData ModerationData[] + disableAutomoderation Boolean @default(false) } model OpenIdAccount { diff --git a/server/api/.well-known/host-meta/index.ts b/server/api/.well-known/host-meta/index.ts index 2d746185..ca416ba3 100644 --- a/server/api/.well-known/host-meta/index.ts +++ b/server/api/.well-known/host-meta/index.ts @@ -1,5 +1,5 @@ import { MatchedRoute } from "bun"; -import { getConfig, getHost } from "@config"; +import { getConfig, getHost } from "~classes/configmanager"; import { xmlResponse } from "@response"; import { applyConfig } from "@api"; diff --git a/server/api/.well-known/lysand.ts b/server/api/.well-known/lysand.ts index dd685ce0..6a80d16e 100644 --- a/server/api/.well-known/lysand.ts +++ b/server/api/.well-known/lysand.ts @@ -1,6 +1,6 @@ import { jsonResponse } from "@response"; import { MatchedRoute } from "bun"; -import { getConfig } from "@config"; +import { getConfig } from "~classes/configmanager"; import { applyConfig } from "@api"; export const meta = applyConfig({ diff --git a/server/api/.well-known/nodeinfo/index.ts b/server/api/.well-known/nodeinfo/index.ts index b8ae2fea..81bd149b 100644 --- a/server/api/.well-known/nodeinfo/index.ts +++ b/server/api/.well-known/nodeinfo/index.ts @@ -1,5 +1,5 @@ import { MatchedRoute } from "bun"; -import { getConfig, getHost } from "@config"; +import { getConfig, getHost } from "~classes/configmanager"; import { applyConfig } from "@api"; export const meta = applyConfig({ diff --git a/server/api/.well-known/webfinger/index.ts b/server/api/.well-known/webfinger/index.ts index 15e28d1b..9e9e7e1e 100644 --- a/server/api/.well-known/webfinger/index.ts +++ b/server/api/.well-known/webfinger/index.ts @@ -1,6 +1,6 @@ import { errorResponse, jsonResponse } from "@response"; import { MatchedRoute } from "bun"; -import { getConfig, getHost } from "@config"; +import { getConfig, getHost } from "~classes/configmanager"; import { applyConfig } from "@api"; import { client } from "~database/datasource"; diff --git a/server/api/api/v1/accounts/index.ts b/server/api/api/v1/accounts/index.ts index a0d20fec..ecd3211c 100644 --- a/server/api/api/v1/accounts/index.ts +++ b/server/api/api/v1/accounts/index.ts @@ -1,4 +1,4 @@ -import { getConfig } from "@config"; +import { getConfig } from "~classes/configmanager"; import { parseRequest } from "@request"; import { jsonResponse } from "@response"; import { tempmailDomains } from "@tempmail"; diff --git a/server/api/api/v1/accounts/update_credentials/index.ts b/server/api/api/v1/accounts/update_credentials/index.ts index b3675846..028759e3 100644 --- a/server/api/api/v1/accounts/update_credentials/index.ts +++ b/server/api/api/v1/accounts/update_credentials/index.ts @@ -1,4 +1,4 @@ -import { getConfig } from "@config"; +import { getConfig } from "~classes/configmanager"; import { parseRequest } from "@request"; import { errorResponse, jsonResponse } from "@response"; import { diff --git a/server/api/api/v1/instance/index.ts b/server/api/api/v1/instance/index.ts index 2be5fc51..259dbe0b 100644 --- a/server/api/api/v1/instance/index.ts +++ b/server/api/api/v1/instance/index.ts @@ -1,5 +1,5 @@ import { applyConfig } from "@api"; -import { getConfig } from "@config"; +import { getConfig } from "~classes/configmanager"; import { jsonResponse } from "@response"; import { client } from "~database/datasource"; import { userRelations, userToAPI } from "~database/entities/User"; diff --git a/server/api/api/v1/media/[id]/index.ts b/server/api/api/v1/media/[id]/index.ts index cc4c8e3c..f1e16511 100644 --- a/server/api/api/v1/media/[id]/index.ts +++ b/server/api/api/v1/media/[id]/index.ts @@ -4,7 +4,7 @@ import { client } from "~database/datasource"; import { getFromRequest } from "~database/entities/User"; import type { APIRouteMeta } from "~types/api"; import { uploadFile } from "~classes/media"; -import { getConfig } from "@config"; +import { getConfig } from "~classes/configmanager"; import { attachmentToAPI, getUrl } from "~database/entities/Attachment"; import type { MatchedRoute } from "bun"; import { parseRequest } from "@request"; diff --git a/server/api/api/v1/media/index.ts b/server/api/api/v1/media/index.ts index da3019ef..568d3bf1 100644 --- a/server/api/api/v1/media/index.ts +++ b/server/api/api/v1/media/index.ts @@ -6,7 +6,7 @@ import { getFromRequest } from "~database/entities/User"; import type { APIRouteMeta } from "~types/api"; import sharp from "sharp"; import { uploadFile } from "~classes/media"; -import { getConfig } from "@config"; +import { getConfig } from "~classes/configmanager"; import { attachmentToAPI, getUrl } from "~database/entities/Attachment"; export const meta: APIRouteMeta = applyConfig({ diff --git a/server/api/api/v1/moderation/accounts/search/index.ts b/server/api/api/v1/moderation/accounts/search/index.ts new file mode 100644 index 00000000..e69de29b diff --git a/server/api/api/v1/statuses/[id]/index.ts b/server/api/api/v1/statuses/[id]/index.ts index 60722cbb..22e335ed 100644 --- a/server/api/api/v1/statuses/[id]/index.ts +++ b/server/api/api/v1/statuses/[id]/index.ts @@ -1,5 +1,5 @@ import { applyConfig } from "@api"; -import { getConfig } from "@config"; +import { getConfig } from "~classes/configmanager"; import { parseRequest } from "@request"; import { errorResponse, jsonResponse } from "@response"; import { sanitizeHtml } from "@sanitization"; diff --git a/server/api/api/v1/statuses/[id]/reblog.ts b/server/api/api/v1/statuses/[id]/reblog.ts index dc67ee8d..fcb511c9 100644 --- a/server/api/api/v1/statuses/[id]/reblog.ts +++ b/server/api/api/v1/statuses/[id]/reblog.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ import { applyConfig } from "@api"; -import { getConfig } from "@config"; +import { getConfig } from "~classes/configmanager"; import { parseRequest } from "@request"; import { errorResponse, jsonResponse } from "@response"; import type { MatchedRoute } from "bun"; diff --git a/server/api/api/v1/statuses/index.ts b/server/api/api/v1/statuses/index.ts index 86275366..93b395ca 100644 --- a/server/api/api/v1/statuses/index.ts +++ b/server/api/api/v1/statuses/index.ts @@ -2,7 +2,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unused-vars */ import { applyConfig } from "@api"; -import { getConfig } from "@config"; +import { getConfig } from "~classes/configmanager"; import { parseRequest } from "@request"; import { errorResponse, jsonResponse } from "@response"; import { sanitizeHtml } from "@sanitization"; diff --git a/server/api/api/v2/media/index.ts b/server/api/api/v2/media/index.ts index 51bc2997..32585cdf 100644 --- a/server/api/api/v2/media/index.ts +++ b/server/api/api/v2/media/index.ts @@ -6,7 +6,7 @@ import { getFromRequest } from "~database/entities/User"; import type { APIRouteMeta } from "~types/api"; import sharp from "sharp"; import { uploadFile } from "~classes/media"; -import { getConfig } from "@config"; +import { getConfig } from "~classes/configmanager"; import { attachmentToAPI, getUrl } from "~database/entities/Attachment"; export const meta: APIRouteMeta = applyConfig({ diff --git a/server/api/api/v2/search/index.ts b/server/api/api/v2/search/index.ts index 1aef1f7c..c40f7a92 100644 --- a/server/api/api/v2/search/index.ts +++ b/server/api/api/v2/search/index.ts @@ -1,5 +1,5 @@ import { applyConfig } from "@api"; -import { getConfig } from "@config"; +import { getConfig } from "~classes/configmanager"; import { MeiliIndexType, meilisearch } from "@meilisearch"; import { parseRequest } from "@request"; import { errorResponse, jsonResponse } from "@response"; diff --git a/server/api/oauth/authorize-external/index.ts b/server/api/oauth/authorize-external/index.ts index 34807566..db24bbcb 100644 --- a/server/api/oauth/authorize-external/index.ts +++ b/server/api/oauth/authorize-external/index.ts @@ -1,5 +1,5 @@ import { applyConfig } from "@api"; -import { getConfig } from "@config"; +import { getConfig } from "~classes/configmanager"; import { oauthRedirectUri } from "@constants"; import type { MatchedRoute } from "bun"; import { diff --git a/server/api/oauth/callback/[issuer]/index.ts b/server/api/oauth/callback/[issuer]/index.ts index 88280906..dd5290a2 100644 --- a/server/api/oauth/callback/[issuer]/index.ts +++ b/server/api/oauth/callback/[issuer]/index.ts @@ -1,5 +1,5 @@ import { applyConfig } from "@api"; -import { getConfig } from "@config"; +import { getConfig } from "~classes/configmanager"; import { oauthRedirectUri } from "@constants"; import type { MatchedRoute } from "bun"; import { randomBytes } from "crypto"; diff --git a/server/api/oauth/providers/index.ts b/server/api/oauth/providers/index.ts index 86061716..c3cc1ac8 100644 --- a/server/api/oauth/providers/index.ts +++ b/server/api/oauth/providers/index.ts @@ -1,5 +1,5 @@ import { applyConfig } from "@api"; -import { getConfig } from "@config"; +import { getConfig } from "~classes/configmanager"; import { jsonResponse } from "@response"; export const meta = applyConfig({ diff --git a/server/api/users/[uuid]/inbox/index.ts b/server/api/users/[uuid]/inbox/index.ts index f749908d..bc325870 100644 --- a/server/api/users/[uuid]/inbox/index.ts +++ b/server/api/users/[uuid]/inbox/index.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unused-vars */ import { applyConfig } from "@api"; -import { getConfig } from "@config"; +import { getConfig } from "~classes/configmanager"; import { getBestContentType } from "@content_types"; import { errorResponse, jsonResponse } from "@response"; import type { MatchedRoute } from "bun"; diff --git a/server/api/users/[uuid]/index.ts b/server/api/users/[uuid]/index.ts index d56dc905..3c392ee6 100644 --- a/server/api/users/[uuid]/index.ts +++ b/server/api/users/[uuid]/index.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unused-vars */ import { applyConfig } from "@api"; -import { getConfig } from "@config"; +import { getConfig } from "~classes/configmanager"; import { errorResponse, jsonResponse } from "@response"; import type { MatchedRoute } from "bun"; import { client } from "~database/datasource"; diff --git a/server/api/users/[uuid]/outbox/index.ts b/server/api/users/[uuid]/outbox/index.ts index b6b47750..d130668a 100644 --- a/server/api/users/[uuid]/outbox/index.ts +++ b/server/api/users/[uuid]/outbox/index.ts @@ -1,6 +1,6 @@ import { jsonResponse } from "@response"; import type { MatchedRoute } from "bun"; -import { getConfig, getHost } from "@config"; +import { getConfig, getHost } from "~classes/configmanager"; import { applyConfig } from "@api"; import { statusAndUserRelations, diff --git a/tests/api.test.ts b/tests/api.test.ts index 1570393d..18dcb432 100644 --- a/tests/api.test.ts +++ b/tests/api.test.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { getConfig } from "@config"; +import { getConfig } from "~classes/configmanager"; import type { Token } from "@prisma/client"; import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import { client } from "~database/datasource"; diff --git a/tests/api/accounts.test.ts b/tests/api/accounts.test.ts index 0c21f46c..cfbb43d1 100644 --- a/tests/api/accounts.test.ts +++ b/tests/api/accounts.test.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { getConfig } from "@config"; +import { getConfig } from "~classes/configmanager"; import type { Token } from "@prisma/client"; import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import { client } from "~database/datasource"; diff --git a/tests/api/statuses.test.ts b/tests/api/statuses.test.ts index 3d9c1ea2..82ca886c 100644 --- a/tests/api/statuses.test.ts +++ b/tests/api/statuses.test.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { getConfig } from "@config"; +import { getConfig } from "~classes/configmanager"; import type { Token } from "@prisma/client"; import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import { client } from "~database/datasource"; diff --git a/tests/entities/Media.test.ts b/tests/entities/Media.test.ts index df078d39..df24228d 100644 --- a/tests/entities/Media.test.ts +++ b/tests/entities/Media.test.ts @@ -1,4 +1,4 @@ -import { type ConfigType, getConfig } from "@config"; +import { type ConfigType, getConfig } from "~classes/configmanager"; import { afterAll, beforeAll, describe, expect, it } from "bun:test"; import { LocalBackend, S3Backend } from "~classes/media"; import { unlink } from "fs/promises"; diff --git a/tests/oauth.test.ts b/tests/oauth.test.ts index 6147d07b..a4f018d4 100644 --- a/tests/oauth.test.ts +++ b/tests/oauth.test.ts @@ -1,4 +1,4 @@ -import { getConfig } from "@config"; +import { getConfig } from "~classes/configmanager"; import type { Application, Token } from "@prisma/client"; import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import { client } from "~database/datasource"; diff --git a/utils/api.ts b/utils/api.ts index 0e088cdd..01a479f7 100644 --- a/utils/api.ts +++ b/utils/api.ts @@ -1,4 +1,4 @@ -import { getConfig } from "@config"; +import { getConfig } from "~classes/configmanager"; import type { APIRouteMeta } from "~types/api"; export const applyConfig = (routeMeta: APIRouteMeta) => { diff --git a/utils/constants.ts b/utils/constants.ts index 91f729d7..3e9c7686 100644 --- a/utils/constants.ts +++ b/utils/constants.ts @@ -1,4 +1,4 @@ -import { getConfig } from "@config"; +import { getConfig } from "~classes/configmanager"; const config = getConfig(); diff --git a/utils/meilisearch.ts b/utils/meilisearch.ts index af4f4141..aa80b73a 100644 --- a/utils/meilisearch.ts +++ b/utils/meilisearch.ts @@ -1,4 +1,4 @@ -import { getConfig } from "@config"; +import { getConfig } from "~classes/configmanager"; import chalk from "chalk"; import { client } from "~database/datasource"; import { Meilisearch } from "meilisearch"; diff --git a/utils/merge.ts b/utils/merge.ts new file mode 100644 index 00000000..fe480b6d --- /dev/null +++ b/utils/merge.ts @@ -0,0 +1,16 @@ +export const deepMerge = ( + target: Record, + source: Record +) => { + const result = { ...target, ...source }; + for (const key of Object.keys(result)) { + result[key] = + typeof target[key] == "object" && typeof source[key] == "object" + ? deepMerge(target[key], source[key]) + : structuredClone(result[key]); + } + return result; +}; + +export const deepMergeArray = (array: Record[]) => + array.reduce((ci, ni) => deepMerge(ci, ni), {}); diff --git a/utils/redis.ts b/utils/redis.ts index a5715bc1..a38faa8b 100644 --- a/utils/redis.ts +++ b/utils/redis.ts @@ -1,4 +1,4 @@ -import { getConfig } from "@config"; +import { getConfig } from "~classes/configmanager"; import type { Prisma } from "@prisma/client"; import chalk from "chalk"; import Redis from "ioredis"; diff --git a/utils/sanitization.ts b/utils/sanitization.ts index 7512a063..e26e1074 100644 --- a/utils/sanitization.ts +++ b/utils/sanitization.ts @@ -1,4 +1,4 @@ -import { getConfig } from "@config"; +import { getConfig } from "~classes/configmanager"; import { sanitize } from "isomorphic-dompurify"; export const sanitizeHtml = async (html: string) => {