diff --git a/benchmarks/timelines.ts b/benchmarks/timelines.ts index b5348b69..c12fe3e6 100644 --- a/benchmarks/timelines.ts +++ b/benchmarks/timelines.ts @@ -3,9 +3,7 @@ */ import chalk from "chalk"; -import { ConfigManager } from "config-manager"; - -const config = await new ConfigManager({}).getConfig(); +import { config } from "config-manager"; const token = process.env.TOKEN; const requestCount = Number(process.argv[2]) || 100; diff --git a/bun.lockb b/bun.lockb index 5aa839bf..fd9669d6 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/cli.ts b/cli.ts index 232cde7d..69e8afb9 100644 --- a/cli.ts +++ b/cli.ts @@ -7,7 +7,7 @@ import extract from "extract-zip"; import { client } from "~database/datasource"; import { CliBuilder, CliCommand } from "cli-parser"; import { CliParameterType } from "~packages/cli-parser/cli-builder.type"; -import { ConfigManager } from "~packages/config-manager"; +import { config } from "~packages/config-manager"; import { Parser } from "@json2csv/plainjs"; import type { Prisma } from "@prisma/client"; import { MediaBackend } from "media-manager"; @@ -17,8 +17,6 @@ import { tmpdir } from "os"; const args = process.argv; -const config = await new ConfigManager({}).getConfig(); - const filterObjects = (output: T[], fields: string[]) => { if (fields.length === 0) return output; diff --git a/config/config.example.toml b/config/config.example.toml index de256901..dd075c67 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -1,17 +1,28 @@ +# Lysand Config +# All of these values can be changed via the CLI (they will be saved in a file named config.internal.toml +# in the same directory as this one) +# Changing this file does not require a restart, but might take a few seconds to apply +# This file will be merged with the CLI configuration, taking precedence over it + [database] +# Main PostgreSQL database connection host = "localhost" port = 5432 username = "lysand" -password = "password123" +password = "lysand" database = "lysand" [redis.queue] +# Redis instance for storing the federation queue +# Required for federation host = "localhost" port = 6379 password = "" database = 0 [redis.cache] +# Redis instance to be used as a timeline cache +# Optional, can be the same as the queue instance host = "localhost" port = 6379 password = "" @@ -19,14 +30,15 @@ database = 1 enabled = false [meilisearch] +# If Meilisearch is not configured, search will not be enabled host = "localhost" -port = 40007 -api_key = "" -enabled = true +port = 7700 +api_key = "______________________________" +enabled = false [signups] # URL of your Terms of Service -tos_url = "https://example.com/tos" +tos_url = "https://my-site.com/tos" # Whether to enable registrations or not registration = true rules = [ @@ -41,40 +53,64 @@ rules = [ # The provider MUST support OpenID Connect with .well-known discovery # Most notably, GitHub does not support this [[oidc.providers]] +# Test with custom Authentik instance name = "CPlusPatch ID" id = "cpluspatch-id" url = "https://id.cpluspatch.com/application/o/lysand-testing/" -client_id = "XXXXXXXXXXXXXXXX" -client_secret = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" +client_id = "______________________________" +client_secret = "__________________________________" icon = "https://cpluspatch.com/images/icons/logo.svg" [http] +# The full URL Lysand will be reachable by (paths are not supported) base_url = "https://lysand.social" -bind = "http://localhost" +# Address to bind to +bind = "0.0.0.0" bind_port = "8080" # Bans IPv4 or IPv6 IPs (wildcards, networks and ranges are supported) banned_ips = [] +# Banned user agents, regex format +banned_user_agents = [ + # "curl\/7.68.0", + # "wget\/1.20.3", +] + +[http.bait] +# Enable the bait feature (sends fake data to those who are flagged) +enabled = false +# Path to file of bait data (if not provided, Lysand will send the entire Bee Movie script) +send_file = "" +# IPs to send bait data to (wildcards, networks and ranges are supported) +bait_ips = ["127.0.0.1", "::1"] +# User agents to send bait data to (regex format) +bait_user_agents = ["curl", "wget"] [smtp] # SMTP server to use for sending emails server = "smtp.example.com" port = 465 username = "test@example.com" -password = "password123" +password = "____________" tls = true +# Disable all email functions (this will allow people to sign up without verifying +# their email) +enabled = false [media] # Can be "s3" or "local", where "local" uploads the file to the local filesystem # If you need to change this value after setting up your instance, you must move all the files -# from one backend to the other manually -backend = "s3" +# from one backend to the other manually (the CLI will have an option to do this later) +# TODO: Add CLI command to move files +backend = "local" # Whether to check the hash of media when uploading to avoid duplication deduplicate_media = true # If media backend is "local", this is the folder where the files will be stored +# Can be any path local_uploads_folder = "uploads" [media.conversion] +# Whether to automatically convert images to another format on upload convert_images = false # Can be: "jxl", "webp", "avif", "png", "jpg", "heif" # JXL support will likely not work @@ -82,26 +118,26 @@ convert_to = "webp" [s3] # Can be left blank if you don't use the S3 media backend -endpoint = "https://s3-us-west-2.amazonaws.com" -access_key = "" -secret_access_key = "" -region = "us-west-2" +endpoint = "myhostname.banana.com" +access_key = "_____________" +secret_access_key = "_________________" +region = "" bucket_name = "lysand" -public_url = "https://cdn.example.com" +public_url = "https://cdn.test.com" [email] # Sends an email to moderators when a report is received -# NOT IMPLEMENTED send_on_report = false # Sends an email to moderators when a user is suspended -# NOT IMPLEMENTED send_on_suspend = false # Sends an email to moderators when a user is unsuspended -# NOT IMPLEMENTED send_on_unsuspend = false +# Verify user emails when signing up (except via OIDC) +verify_email = false [validation] -# Self explanatory +# Checks user data +# Does not retroactively apply to previously entered data max_displayname_size = 50 max_bio_size = 160 max_note_size = 5000 @@ -115,7 +151,7 @@ max_poll_option_size = 500 min_poll_duration = 60 max_poll_duration = 1893456000 max_username_size = 30 -# An array of strings, defaults are from Akkoma +# Forbidden usernames, defaults are from Akkoma username_blacklist = [ ".well-known", "~", @@ -146,7 +182,7 @@ username_blacklist = [ ] # Whether to blacklist known temporary email providers blacklist_tempmail = false -# Additional email providers to blacklist +# Additional email providers to blacklist (list of domains) email_blacklist = [] # Valid URL schemes, otherwise the URL is parsed as text url_scheme_whitelist = [ @@ -167,8 +203,10 @@ url_scheme_whitelist = [ "mumble", "ssb", "gemini", -] # NOT IMPLEMENTED - +] +# Only allow those MIME types of data to be uploaded +# This can easily be spoofed, but if it is spoofed it will appear broken +# to normal clients until despoofed enforce_mime_types = false allowed_mime_types = [ "image/jpeg", @@ -203,46 +241,38 @@ allowed_mime_types = [ [defaults] # Default visibility for new notes +# Can be public, unlisted, private or direct +# Private only sends to followers, unlisted doesn't show up in timelines visibility = "public" -# Default language for new notes +# Default language for new notes (ISO code) language = "en" -# Default avatar, must be a valid URL or "" +# Default avatar, must be a valid URL or "" for none avatar = "" -# Default header, must be a valid URL or "" +# Default header, must be a valid URL or "" for none header = "" -[activitypub] -# Use ActivityPub Tombstones instead of deleting objects -use_tombstones = true -# Fetch all members of collections (followers, following, etc) when receiving them -# WARNING: This can be a lot of data, and is not recommended -fetch_all_collection_members = false # NOT IMPLEMENTED +[federation] +# This is a list of domain names, such as "mastodon.social" or "pleroma.site" +# These changes will not retroactively apply to existing data before they were changed +# For that, please use the CLI -# The following values must be instance domain names without "https" or glob patterns -# Rejects all activities from these instances (fediblocking) -reject_activities = [] -# Force posts from this instance to be followers only -force_followers_only = [] # NOT IMPLEMENTED -# Discard all reports from these instances -discard_reports = [] # NOT IMPLEMENTED -# Discard all deletes from these instances -discard_deletes = [] -# Discard all updates (edits) from these instances -discard_updates = [] -# Discard all banners from these instances -discard_banners = [] # NOT IMPLEMENTED -# Discard all avatars from these instances -discard_avatars = [] # NOT IMPLEMENTED -# Discard all follow requests from these instances -discard_follows = [] -# Force set these instances' media as sensitive -force_sensitive = [] # NOT IMPLEMENTED -# Remove theses instances' media -remove_media = [] # NOT IMPLEMENTED +# These instances will not be federated with +blocked = [] +# These instances' data will only be shown to followers, not in public timelines +followers_only = [] -# Whether to verify HTTP signatures for every request (warning: can slow down your server -# significantly depending on processing power) -authorized_fetch = false +[federation.discard] +# These objects will be discarded when received from these instances +reports = [] +deletes = [] +updates = [] +media = [] +follows = [] +# If instance reactions are blocked, likes will also be discarded +likes = [] +reactions = [] +banners = [] +avatars = [] [instance] name = "Lysand" @@ -254,22 +284,23 @@ banner = "" [filters] -# Drop notes with these regex filters (only applies to new activities) -note_filters = [ +# Regex filters for federated and local data +# Does not apply retroactively (try the CLI for that) + +# Note contents +note_content = [ # "(https?://)?(www\\.)?youtube\\.com/watch\\?v=[a-zA-Z0-9_-]+", # "(https?://)?(www\\.)?youtu\\.be/[a-zA-Z0-9_-]+", ] -# Drop users with these regex filters (only applies to new activities) -username_filters = [] -# Drop users with these regex filters (only applies to new activities) -displayname_filters = [] -# Drop users with these regex filters (only applies to new activities) -bio_filters = [] -emoji_filters = [] # NOT IMPLEMENTED +emoji = [] +# These will drop users matching the filters +username = [] +displayname = [] +bio = [] [logging] # Log all requests (warning: this is a lot of data) -log_requests = true +log_requests = false # Log request and their contents (warning: this is a lot of data) log_requests_verbose = false # For GDPR compliance, you can disable logging of IPs @@ -278,12 +309,19 @@ log_ip = false # Log all filtered objects log_filters = true +[logging.storage] +# Path to logfile for requests +requests = "logs/requests.log" + [ratelimits] +# These settings apply to every route at once # Amount to multiply every route's duration by duration_coeff = 1.0 -# Amount to multiply every route's max by +# Amount to multiply every route's max requests per [duration] by max_coeff = 1.0 [custom_ratelimits] # Add in any API route in this style here +# Applies before the global ratelimit changes +"/api/v1/accounts/:id/block" = { duration = 30, max = 60 } "/api/v1/timelines/public" = { duration = 60, max = 200 } diff --git a/database/datasource.ts b/database/datasource.ts index fc8078ae..05a32cad 100644 --- a/database/datasource.ts +++ b/database/datasource.ts @@ -1,8 +1,6 @@ // import { Queue } from "bullmq"; import { PrismaClient } from "@prisma/client"; -import { ConfigManager } from "config-manager"; - -const config = await new ConfigManager({}).getConfig(); +import { config } from "config-manager"; const client = new PrismaClient({ datasourceUrl: `postgresql://${config.database.username}:${config.database.password}@${config.database.host}:${config.database.port}/${config.database.database}`, diff --git a/database/entities/Like.ts b/database/entities/Like.ts index 037fd547..ea6f726e 100644 --- a/database/entities/Like.ts +++ b/database/entities/Like.ts @@ -4,9 +4,7 @@ import type { Like } from "@prisma/client"; import { client } from "~database/datasource"; import type { UserWithRelations } from "./User"; import type { StatusWithRelations } from "./Status"; -import { ConfigManager } from "config-manager"; - -const config = await new ConfigManager({}).getConfig(); +import { config } from "config-manager"; /** * Represents a Like entity in the database. diff --git a/database/entities/Queue.ts b/database/entities/Queue.ts index c5775142..570d79de 100644 --- a/database/entities/Queue.ts +++ b/database/entities/Queue.ts @@ -1,9 +1,7 @@ // import { Worker } from "bullmq"; import { statusToLysand, type StatusWithRelations } from "./Status"; import type { User } from "@prisma/client"; -import { ConfigManager } from "config-manager"; - -const config = await new ConfigManager({}).getConfig(); +import { config } from "config-manager"; /* export const federationWorker = new Worker( "federation", diff --git a/database/entities/Status.ts b/database/entities/Status.ts index a0fec241..6eb4c6f8 100644 --- a/database/entities/Status.ts +++ b/database/entities/Status.ts @@ -23,11 +23,9 @@ import { parse } from "marked"; import linkifyStr from "linkify-string"; import linkifyHtml from "linkify-html"; import { addStausToMeilisearch } from "@meilisearch"; -import { ConfigManager } from "config-manager"; +import { config } from "config-manager"; import { statusAndUserRelations, userRelations } from "./relations"; -const config = await new ConfigManager({}).getConfig(); - const statusRelations = Prisma.validator()({ include: statusAndUserRelations, }); diff --git a/database/entities/User.ts b/database/entities/User.ts index 1b06b1cf..141ab851 100644 --- a/database/entities/User.ts +++ b/database/entities/User.ts @@ -1,5 +1,5 @@ import type { APIAccount } from "~types/entities/account"; -import type { LysandUser as LysandUser } from "~types/lysand/Object"; +import type { LysandUser } from "~types/lysand/Object"; import { htmlToText } from "html-to-text"; import type { User } from "@prisma/client"; import { Prisma } from "@prisma/client"; @@ -8,13 +8,10 @@ import { addEmojiIfNotExists, emojiToAPI, emojiToLysand } from "./Emoji"; import { addInstanceIfNotExists } from "./Instance"; import type { APISource } from "~types/entities/source"; import { addUserToMeilisearch } from "@meilisearch"; -import { ConfigManager, type ConfigType } from "config-manager"; +import { config, type Config } from "config-manager"; import { userRelations } from "./relations"; import { MediaBackendType } from "~packages/media-manager"; -const configManager = new ConfigManager({}); -const config = await configManager.getConfig(); - export interface AuthData { user: UserWithRelations | null; token: string; @@ -36,7 +33,7 @@ export type UserWithRelations = Prisma.UserGetPayload; * @param config The config to use * @returns The raw URL for the user's avatar */ -export const getAvatarUrl = (user: User, config: ConfigType) => { +export const getAvatarUrl = (user: User, config: Config) => { if (!user.avatar) return config.defaults.avatar; if (config.media.backend === MediaBackendType.LOCAL) { return `${config.http.base_url}/media/${user.avatar}`; @@ -52,7 +49,7 @@ export const getAvatarUrl = (user: User, config: ConfigType) => { * @param config The config to use * @returns The raw URL for the user's header */ -export const getHeaderUrl = (user: User, config: ConfigType) => { +export const getHeaderUrl = (user: User, config: Config) => { if (!user.header) return config.defaults.header; if (config.media.backend === MediaBackendType.LOCAL) { return `${config.http.base_url}/media/${user.header}`; @@ -192,8 +189,6 @@ export const createNewLocalUser = async (data: { header?: string; admin?: boolean; }) => { - const config = await configManager.getConfig(); - const keys = await generateUserKeys(); const user = await client.user.create({ diff --git a/index.ts b/index.ts index 6006e96e..f27cc66c 100644 --- a/index.ts +++ b/index.ts @@ -1,7 +1,7 @@ import type { PrismaClientInitializationError } from "@prisma/client/runtime/library"; import { initializeRedisCache } from "@redis"; import { connectMeili } from "@meilisearch"; -import { ConfigManager } from "config-manager"; +import { config } from "config-manager"; import { client } from "~database/datasource"; import { LogLevel, LogManager, MultiLogManager } from "log-manager"; import { moduleIsEntry } from "@module"; @@ -10,9 +10,6 @@ import { exists, mkdir } from "fs/promises"; const timeAtStart = performance.now(); -const configManager = new ConfigManager({}); -const config = await configManager.getConfig(); - const requests_log = Bun.file(process.cwd() + "/logs/requests.log"); const isEntry = moduleIsEntry(import.meta.url); // If imported as a module, redirect logs to /dev/null to not pollute console (e.g. in tests) @@ -22,7 +19,7 @@ const consoleLogger = new LogManager( ); const dualLogger = new MultiLogManager([logger, consoleLogger]); -if (!(await exists(process.cwd() + "/logs/"))) { +if (!(await exists(config.logging.storage.requests))) { await consoleLogger.log( LogLevel.WARNING, "Lysand", @@ -59,7 +56,7 @@ try { process.exit(1); } -const server = createServer(config, configManager, dualLogger, isProd); +const server = createServer(config, dualLogger, isProd); await dualLogger.log( LogLevel.INFO, diff --git a/package.json b/package.json index 120ebae9..48f42842 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,8 @@ "@typescript-eslint/eslint-plugin": "latest", "@typescript-eslint/parser": "latest", "@unocss/cli": "latest", + "@vitejs/plugin-vue": "latest", + "@vueuse/head": "^2.0.0", "activitypub-types": "^1.0.3", "bun-types": "latest", "eslint": "^8.54.0", @@ -71,8 +73,7 @@ "prettier": "^3.1.0", "typescript": "latest", "unocss": "latest", - "@vitejs/plugin-vue": "latest", - "@vueuse/head": "^2.0.0", + "untyped": "^1.4.2", "vite": "latest", "vite-ssr": "^0.17.1", "vue": "^3.3.9", @@ -86,11 +87,12 @@ "@aws-sdk/client-s3": "^3.461.0", "@iarna/toml": "^2.2.5", "@json2csv/plainjs": "^7.0.6", - "cli-parser": "workspace:*", "@prisma/client": "^5.6.0", "blurhash": "^2.0.5", "bullmq": "latest", + "c12": "^1.10.0", "chalk": "^5.3.0", + "cli-parser": "workspace:*", "cli-table": "^0.3.11", "config-manager": "workspace:*", "eventemitter3": "^5.0.1", diff --git a/packages/config-manager/config-type.type.ts b/packages/config-manager/config-type.type.ts deleted file mode 100644 index b9a13306..00000000 --- a/packages/config-manager/config-type.type.ts +++ /dev/null @@ -1,377 +0,0 @@ -import { MediaBackendType } from "media-manager"; - -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[]; - banned_user_agents: string[]; - bait: { - enabled: boolean; - send_file?: string; - bait_ips: string[]; - bait_user_agents: 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: MediaBackendType; - deduplicate_media: boolean; - conversion: { - convert_images: boolean; - convert_to: string; - }; - local_uploads_folder: 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_ip: 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: [], - banned_user_agents: [], - bait: { - enabled: false, - send_file: "", - bait_ips: [], - bait_user_agents: [], - }, - }, - 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: MediaBackendType.LOCAL, - deduplicate_media: true, - conversion: { - convert_images: false, - convert_to: "webp", - }, - local_uploads_folder: "uploads", - }, - 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_ip: false, - log_filters: true, - }, - ratelimits: { - duration_coeff: 1, - max_coeff: 1, - }, - custom_ratelimits: {}, -}; diff --git a/packages/config-manager/config.type.ts b/packages/config-manager/config.type.ts new file mode 100644 index 00000000..8a0fc162 --- /dev/null +++ b/packages/config-manager/config.type.ts @@ -0,0 +1,579 @@ +import { MediaBackendType } from "~packages/media-manager"; + +export interface Config { + database: { + /** @default "localhost" */ + host: string; + + /** @default 5432 */ + port: number; + + /** @default "lysand" */ + username: string; + + /** @default "lysand" */ + password: string; + + /** @default "lysand" */ + database: string; + }; + + redis: { + queue: { + /** @default "localhost" */ + host: string; + + /** @default 6379 */ + port: number; + + /** @default "" */ + password: string; + + /** @default 0 */ + database: number; + }; + + cache: { + /** @default "localhost" */ + host: string; + + /** @default 6379 */ + port: number; + + /** @default "" */ + password: string; + + /** @default 1 */ + database: number; + + /** @default false */ + enabled: boolean; + }; + }; + + meilisearch: { + /** @default "localhost" */ + host: string; + + /** @default 7700 */ + port: number; + + /** @default "______________________________" */ + api_key: string; + + /** @default false */ + enabled: boolean; + }; + + signups: { + /** @default "https://my-site.com/tos" */ + tos_url: string; + + /** @default true */ + registration: boolean; + + /** @default ["Do not harass others","Be nice to people","Don't spam","Don't post illegal content"] */ + rules: string[]; + }; + + oidc: { + /** @default [] */ + providers: Record[]; + }; + + http: { + /** @default "https://lysand.social" */ + base_url: string; + + /** @default "0.0.0.0" */ + bind: string; + + /** @default "8080" */ + bind_port: string; + + banned_ips: any[]; + + banned_user_agents: any[]; + + bait: { + /** @default false */ + enabled: boolean; + + /** @default "" */ + send_file: string; + + /** @default ["127.0.0.1","::1"] */ + bait_ips: string[]; + + /** @default ["curl","wget"] */ + bait_user_agents: string[]; + }; + }; + + smtp: { + /** @default "smtp.example.com" */ + server: string; + + /** @default 465 */ + port: number; + + /** @default "test@example.com" */ + username: string; + + /** @default "____________" */ + password: string; + + /** @default true */ + tls: boolean; + + /** @default false */ + enabled: boolean; + }; + + media: { + /** @default "local" */ + backend: MediaBackendType; + + /** @default true */ + deduplicate_media: boolean; + + /** @default "uploads" */ + local_uploads_folder: string; + + conversion: { + /** @default false */ + convert_images: boolean; + + /** @default "webp" */ + convert_to: string; + }; + }; + + s3: { + /** @default "myhostname.banana.com" */ + endpoint: string; + + /** @default "_____________" */ + access_key: string; + + /** @default "_________________" */ + secret_access_key: string; + + /** @default "" */ + region: string; + + /** @default "lysand" */ + bucket_name: string; + + /** @default "https://cdn.test.com" */ + public_url: string; + }; + + email: { + /** @default false */ + send_on_report: boolean; + + /** @default false */ + send_on_suspend: boolean; + + /** @default false */ + send_on_unsuspend: boolean; + + /** @default false */ + verify_email: boolean; + }; + + validation: { + /** @default 50 */ + max_displayname_size: number; + + /** @default 160 */ + max_bio_size: number; + + /** @default 5000 */ + max_note_size: number; + + /** @default 5000000 */ + max_avatar_size: number; + + /** @default 5000000 */ + max_header_size: number; + + /** @default 40000000 */ + max_media_size: number; + + /** @default 10 */ + max_media_attachments: number; + + /** @default 1000 */ + max_media_description_size: number; + + /** @default 20 */ + max_poll_options: number; + + /** @default 500 */ + max_poll_option_size: number; + + /** @default 60 */ + min_poll_duration: number; + + /** @default 1893456000 */ + max_poll_duration: number; + + /** @default 30 */ + max_username_size: number; + + /** @default [".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"] */ + username_blacklist: string[]; + + /** @default false */ + blacklist_tempmail: boolean; + + email_blacklist: any[]; + + /** @default ["http","https","ftp","dat","dweb","gopher","hyper","ipfs","ipns","irc","xmpp","ircs","magnet","mailto","mumble","ssb","gemini"] */ + url_scheme_whitelist: string[]; + + /** @default false */ + enforce_mime_types: boolean; + + /** @default ["image/jpeg","image/png","image/gif","image/heic","image/heif","image/webp","image/avif","video/webm","video/mp4","video/quicktime","video/ogg","audio/wave","audio/wav","audio/x-wav","audio/x-pn-wave","audio/vnd.wave","audio/ogg","audio/vorbis","audio/mpeg","audio/mp3","audio/webm","audio/flac","audio/aac","audio/m4a","audio/x-m4a","audio/mp4","audio/3gpp","video/x-ms-asf"] */ + allowed_mime_types: string[]; + }; + + defaults: { + /** @default "public" */ + visibility: string; + + /** @default "en" */ + language: string; + + /** @default "" */ + avatar: string; + + /** @default "" */ + header: string; + }; + + federation: { + blocked: any[]; + + followers_only: any[]; + + discard: { + reports: any[]; + + deletes: any[]; + + updates: any[]; + + media: any[]; + + follows: any[]; + + likes: any[]; + + reactions: any[]; + + banners: any[]; + + avatars: any[]; + }; + }; + + instance: { + /** @default "Lysand" */ + name: string; + + /** @default "A test instance of Lysand" */ + description: string; + + /** @default "" */ + logo: string; + + /** @default "" */ + banner: string; + }; + + filters: { + note_content: any[]; + + emoji: any[]; + + username: any[]; + + displayname: any[]; + + bio: any[]; + }; + + logging: { + /** @default false */ + log_requests: boolean; + + /** @default false */ + log_requests_verbose: boolean; + + /** @default false */ + log_ip: boolean; + + /** @default true */ + log_filters: boolean; + + storage: { + /** @default "logs/requests.log" */ + requests: string; + }; + }; + + ratelimits: { + /** @default 1 */ + duration_coeff: number; + + /** @default 1 */ + max_coeff: number; + }; + + /** @default {} */ + custom_ratelimits: Record< + string, + { + /** @default 30 */ + duration: number; + + /** @default 60 */ + max: number; + } + >; +} + +export const defaultConfig: Config = { + database: { + host: "localhost", + port: 5432, + username: "lysand", + password: "lysand", + 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: 7700, + api_key: "______________________________", + enabled: false, + }, + signups: { + tos_url: "https://my-site.com/tos", + registration: true, + rules: [ + "Do not harass others", + "Be nice to people", + "Don't spam", + "Don't post illegal content", + ], + }, + oidc: { + providers: [[]], + }, + http: { + base_url: "https://lysand.social", + bind: "0.0.0.0", + bind_port: "8080", + banned_ips: [], + banned_user_agents: [], + bait: { + enabled: false, + send_file: "", + bait_ips: ["127.0.0.1", "::1"], + bait_user_agents: ["curl", "wget"], + }, + }, + smtp: { + server: "smtp.example.com", + port: 465, + username: "test@example.com", + password: "____________", + tls: true, + enabled: false, + }, + media: { + backend: MediaBackendType.LOCAL, + deduplicate_media: true, + local_uploads_folder: "uploads", + conversion: { + convert_images: false, + convert_to: "webp", + }, + }, + s3: { + endpoint: "myhostname.banana.com", + access_key: "_____________", + secret_access_key: "_________________", + region: "", + bucket_name: "lysand", + public_url: "https://cdn.test.com", + }, + email: { + send_on_report: false, + send_on_suspend: false, + send_on_unsuspend: false, + verify_email: false, + }, + validation: { + max_displayname_size: 50, + max_bio_size: 160, + max_note_size: 5000, + max_avatar_size: 5000000, + max_header_size: 5000000, + max_media_size: 40000000, + 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", + "gemini", + ], + enforce_mime_types: false, + allowed_mime_types: [ + "image/jpeg", + "image/png", + "image/gif", + "image/heic", + "image/heif", + "image/webp", + "image/avif", + "video/webm", + "video/mp4", + "video/quicktime", + "video/ogg", + "audio/wave", + "audio/wav", + "audio/x-wav", + "audio/x-pn-wave", + "audio/vnd.wave", + "audio/ogg", + "audio/vorbis", + "audio/mpeg", + "audio/mp3", + "audio/webm", + "audio/flac", + "audio/aac", + "audio/m4a", + "audio/x-m4a", + "audio/mp4", + "audio/3gpp", + "video/x-ms-asf", + ], + }, + defaults: { + visibility: "public", + language: "en", + avatar: "", + header: "", + }, + federation: { + blocked: [], + followers_only: [], + discard: { + reports: [], + deletes: [], + updates: [], + media: [], + follows: [], + likes: [], + reactions: [], + banners: [], + avatars: [], + }, + }, + instance: { + name: "Lysand", + description: "A test instance of Lysand", + logo: "", + banner: "", + }, + filters: { + note_content: [], + emoji: [], + username: [], + displayname: [], + bio: [], + }, + logging: { + log_requests: false, + log_requests_verbose: false, + log_ip: false, + log_filters: true, + storage: { + requests: "logs/requests.log", + }, + }, + ratelimits: { + duration_coeff: 1, + max_coeff: 1, + }, + custom_ratelimits: {}, +}; diff --git a/packages/config-manager/index.ts b/packages/config-manager/index.ts index fb507489..27d2fb9b 100644 --- a/packages/config-manager/index.ts +++ b/packages/config-manager/index.ts @@ -5,122 +5,22 @@ * Fuses both and provides a way to retrieve individual values */ -import { parse, stringify, type JsonMap } from "@iarna/toml"; -import type { ConfigType } from "./config-type.type"; -import { configDefaults } from "./config-type.type"; -import merge from "merge-deep-ts"; +import { watchConfig } from "c12"; +import { defaultConfig, type Config } from "./config.type"; -export class ConfigManager { - constructor( - public config: { - configPathOverride?: string; - internalConfigPathOverride?: string; - } - ) {} +const { config } = await watchConfig({ + configFile: "./config/config.toml", + defaultConfig: defaultConfig, + overrides: + ( + await watchConfig({ + configFile: "./config/config.internal.toml", + defaultConfig: {} as Config, + }) + ).config ?? undefined, +}); - /** - * @summary Reads the config files and returns the merge as a JSON object - * @returns {Promise} The merged config file as a JSON object - */ - async getConfig() { - const config = await this.readConfig(); - const internalConfig = await this.readInternalConfig(); +const exportedConfig = config ?? defaultConfig; - return this.mergeConfigs( - configDefaults as T, - config, - internalConfig - ); - } - - getConfigPath() { - return ( - this.config.configPathOverride || - process.cwd() + "/config/config.toml" - ); - } - - getInternalConfigPath() { - return ( - this.config.internalConfigPathOverride || - process.cwd() + "/config/config.internal.toml" - ); - } - - /** - * @summary Reads the internal config file and returns it as a JSON object - * @returns {Promise} The internal config file as a JSON object - */ - private async readInternalConfig() { - const config = Bun.file(this.getInternalConfigPath()); - - if (!(await config.exists())) { - await Bun.write(config, ""); - } - - return this.parseConfig(await config.text()); - } - - /** - * @summary Reads the config file and returns it as a JSON object - * @returns {Promise} The config file as a JSON object - */ - private async readConfig() { - const config = Bun.file(this.getConfigPath()); - - if (!(await config.exists())) { - throw new Error( - `Error while reading config at path ${this.getConfigPath()}: Config file not found` - ); - } - - return this.parseConfig(await config.text()); - } - - /** - * @summary Parses a TOML string and returns it as a JSON object - * @param text The TOML string to parse - * @returns {T = ConfigType} The parsed TOML string as a JSON object - * @throws {Error} If the TOML string is invalid - * @private - */ - private parseConfig(text: string) { - try { - // To all [Symbol] keys from the object - return JSON.parse(JSON.stringify(parse(text))) as T; - } catch (e: any) { - throw new Error( - `Error while parsing config at path ${this.getConfigPath()}: ${e}` - ); - } - } - - /** - * Writes changed values to the internal config - * @param config The new config object - */ - async writeConfig(config: T) { - const path = this.getInternalConfigPath(); - const file = Bun.file(path); - - await Bun.write( - file, - `# THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT IT MANUALLY, EDIT THE STANDARD CONFIG.TOML INSTEAD.\n${stringify( - config as JsonMap - )}` - ); - } - - /** - * @summary Merges two config objects together, with - * the latter configs' values taking precedence - * @param configs - * @returns - */ - private mergeConfigs(...configs: T[]) { - return merge(configs) as T; - } -} - -export type { ConfigType }; -export const defaultConfig = configDefaults; +export { exportedConfig as config }; +export type { Config }; diff --git a/packages/config-manager/tests/config-manager.test.ts b/packages/config-manager/tests/config-manager.test.ts deleted file mode 100644 index 9ae1bb06..00000000 --- a/packages/config-manager/tests/config-manager.test.ts +++ /dev/null @@ -1,96 +0,0 @@ -// FILEPATH: /home/jessew/Dev/lysand/packages/config-manager/config-manager.test.ts -import { stringify } from "@iarna/toml"; -import { ConfigManager } from ".."; -import { describe, beforeEach, spyOn, it, expect } from "bun:test"; - -describe("ConfigManager", () => { - let configManager: ConfigManager; - - beforeEach(() => { - configManager = new ConfigManager({ - configPathOverride: "./config/config.toml", - internalConfigPathOverride: "./config/config.internal.toml", - }); - }); - - it("should get the correct config path", () => { - expect(configManager.getConfigPath()).toEqual("./config/config.toml"); - }); - - it("should get the correct internal config path", () => { - expect(configManager.getInternalConfigPath()).toEqual( - "./config/config.internal.toml" - ); - }); - - it("should read the config file correctly", async () => { - const mockConfig = { key: "value" }; - - // @ts-expect-error This is a mock - spyOn(Bun, "file").mockImplementationOnce(() => ({ - exists: () => - new Promise(resolve => { - resolve(true); - }), - text: () => - new Promise(resolve => { - resolve(stringify(mockConfig)); - }), - })); - - const config = await configManager.getConfig(); - - expect(config).toContainKeys(Object.keys(mockConfig)); - }); - - it("should read the internal config file correctly", async () => { - const mockConfig = { key: "value" }; - - // @ts-expect-error This is a mock - spyOn(Bun, "file").mockImplementationOnce(() => ({ - exists: () => - new Promise(resolve => { - resolve(true); - }), - text: () => - new Promise(resolve => { - resolve(stringify(mockConfig)); - }), - })); - - const config = - // @ts-expect-error Force call private function for testing - await configManager.readInternalConfig(); - - expect(config).toEqual(mockConfig); - }); - - it("should write to the internal config file correctly", async () => { - const mockConfig = { key: "value" }; - - spyOn(Bun, "write").mockImplementationOnce( - () => - new Promise(resolve => { - resolve(10); - }) - ); - - await configManager.writeConfig(mockConfig); - }); - - it("should merge configs correctly", () => { - const config1 = { key1: "value1", key2: "value2" }; - const config2 = { key2: "newValue2", key3: "value3" }; - // @ts-expect-error Force call private function for testing - const mergedConfig = configManager.mergeConfigs>( - config1, - config2 - ); - - expect(mergedConfig).toEqual({ - key1: "value1", - key2: "newValue2", - key3: "value3", - }); - }); -}); diff --git a/prisma.ts b/prisma.ts index 63566b3d..40312282 100644 --- a/prisma.ts +++ b/prisma.ts @@ -1,7 +1,6 @@ -import { ConfigManager } from "config-manager"; +import { config } from "config-manager"; // Proxies all `bunx prisma` commands with an environment variable -const config = await new ConfigManager({}).getConfig(); process.stdout.write( `postgresql://${config.database.username}:${config.database.password}@${config.database.host}:${config.database.port}/${config.database.database}\n` diff --git a/server.ts b/server.ts index 3a3f1c99..4ad77a76 100644 --- a/server.ts +++ b/server.ts @@ -1,15 +1,14 @@ import { errorResponse, jsonResponse } from "@response"; import { matches } from "ip-matching"; import { getFromRequest } from "~database/entities/User"; -import type { ConfigManager, ConfigType } from "config-manager"; +import { type Config } from "config-manager"; import type { LogManager, MultiLogManager } from "log-manager"; import { LogLevel } from "log-manager"; import { RequestParser } from "request-parser"; import { matchRoute } from "~routes"; export const createServer = ( - config: ConfigType, - configManager: ConfigManager, + config: Config, logger: LogManager | MultiLogManager, isProd: boolean ) => @@ -182,8 +181,11 @@ export const createServer = ( return await file.default(req.clone(), matchedRoute, { auth, - configManager, parsedRequest, + // To avoid having to rewrite each route + configManager: { + getConfig: () => Promise.resolve(config), + }, }); } else if (matchedRoute?.name === "/[...404]" || !matchedRoute) { if (new URL(req.url).pathname.startsWith("/api")) { diff --git a/server/api/api/v1/accounts/update_credentials/index.ts b/server/api/api/v1/accounts/update_credentials/index.ts index 5eaa20bf..18be18f9 100644 --- a/server/api/api/v1/accounts/update_credentials/index.ts +++ b/server/api/api/v1/accounts/update_credentials/index.ts @@ -103,7 +103,7 @@ export default apiRoute<{ // Check if display name doesnt match filters if ( - config.filters.displayname_filters.some(filter => + config.filters.displayname.some(filter => sanitizedDisplayName.match(filter) ) ) { @@ -126,11 +126,7 @@ export default apiRoute<{ } // Check if bio doesnt match filters - if ( - config.filters.bio_filters.some(filter => - sanitizedNote.match(filter) - ) - ) { + if (config.filters.bio.some(filter => sanitizedNote.match(filter))) { return errorResponse("Bio contains blocked words", 422); } diff --git a/server/api/api/v1/statuses/[id]/index.ts b/server/api/api/v1/statuses/[id]/index.ts index 31e10e83..39810f2c 100644 --- a/server/api/api/v1/statuses/[id]/index.ts +++ b/server/api/api/v1/statuses/[id]/index.ts @@ -89,7 +89,7 @@ export default apiRoute<{ content_type, "poll[expires_in]": expires_in, "poll[options]": options, - media_ids: media_ids, + media_ids, spoiler_text, sensitive, } = extraData.parsedRequest; @@ -181,7 +181,7 @@ export default apiRoute<{ // Check if status body doesnt match filters if ( - config.filters.note_filters.some(filter => + config.filters.note_content.some(filter => statusText?.match(filter) ) ) { diff --git a/server/api/api/v1/statuses/index.ts b/server/api/api/v1/statuses/index.ts index 418c0833..8e4e8674 100644 --- a/server/api/api/v1/statuses/index.ts +++ b/server/api/api/v1/statuses/index.ts @@ -194,7 +194,7 @@ export default apiRoute<{ } // Check if status body doesnt match filters - if (config.filters.note_filters.some(filter => status?.match(filter))) { + if (config.filters.note_content.some(filter => status?.match(filter))) { return errorResponse("Status contains blocked words", 422); } diff --git a/server/api/routes.type.ts b/server/api/routes.type.ts index d3cea716..b3ff1ef7 100644 --- a/server/api/routes.type.ts +++ b/server/api/routes.type.ts @@ -1,5 +1,5 @@ import type { MatchedRoute } from "bun"; -import type { ConfigManager } from "config-manager"; +import type { Config } from "config-manager"; import type { AuthData } from "~database/entities/User"; export type RouteHandler = ( @@ -8,6 +8,8 @@ export type RouteHandler = ( extraData: { auth: AuthData; parsedRequest: Partial; - configManager: ConfigManager; + configManager: { + getConfig: () => Promise; + }; } ) => Response | Promise; diff --git a/tests/api.test.ts b/tests/api.test.ts index 3416c9b3..54c1958a 100644 --- a/tests/api.test.ts +++ b/tests/api.test.ts @@ -1,6 +1,6 @@ import type { Token } from "@prisma/client"; import { afterAll, beforeAll, describe, expect, test } from "bun:test"; -import { ConfigManager } from "config-manager"; +import { config } from "config-manager"; import { client } from "~database/datasource"; import { TokenType } from "~database/entities/Token"; import { @@ -11,7 +11,6 @@ import type { APIEmoji } from "~types/entities/emoji"; import type { APIInstance } from "~types/entities/instance"; import { sendTestRequest, wrapRelativeUrl } from "./utils"; -const config = await new ConfigManager({}).getConfig(); const base_url = config.http.base_url; let token: Token; diff --git a/tests/api/accounts.test.ts b/tests/api/accounts.test.ts index 201d1066..a62ebc43 100644 --- a/tests/api/accounts.test.ts +++ b/tests/api/accounts.test.ts @@ -9,10 +9,9 @@ import { import type { APIAccount } from "~types/entities/account"; import type { APIRelationship } from "~types/entities/relationship"; import type { APIStatus } from "~types/entities/status"; -import { ConfigManager } from "config-manager"; +import { config } from "config-manager"; import { sendTestRequest, wrapRelativeUrl } from "~tests/utils"; -const config = await new ConfigManager({}).getConfig(); const base_url = config.http.base_url; let token: Token; diff --git a/tests/api/statuses.test.ts b/tests/api/statuses.test.ts index 94bd9287..4f65dcd3 100644 --- a/tests/api/statuses.test.ts +++ b/tests/api/statuses.test.ts @@ -10,10 +10,9 @@ import type { APIAccount } from "~types/entities/account"; import type { APIAsyncAttachment } from "~types/entities/async_attachment"; import type { APIContext } from "~types/entities/context"; import type { APIStatus } from "~types/entities/status"; -import { ConfigManager } from "config-manager"; +import { config } from "config-manager"; import { sendTestRequest, wrapRelativeUrl } from "~tests/utils"; -const config = await new ConfigManager({}).getConfig(); const base_url = config.http.base_url; let token: Token; diff --git a/tests/oauth.test.ts b/tests/oauth.test.ts index 9d56fa97..d8b1b41d 100644 --- a/tests/oauth.test.ts +++ b/tests/oauth.test.ts @@ -4,7 +4,6 @@ import { client } from "~database/datasource"; import { createNewLocalUser } from "~database/entities/User"; import { sendTestRequest, wrapRelativeUrl } from "./utils"; -// const config = await new ConfigManager({}).getConfig(); const base_url = "http://lysand.localhost:8080"; //config.http.base_url; let client_id: string; diff --git a/utils/api.ts b/utils/api.ts index 283cf136..d197aa74 100644 --- a/utils/api.ts +++ b/utils/api.ts @@ -1,9 +1,7 @@ -import { ConfigManager } from "config-manager"; +import { config } from "config-manager"; import type { RouteHandler } from "~server/api/routes.type"; import type { APIRouteMeta } from "~types/api"; -const config = await new ConfigManager({}).getConfig(); - export const applyConfig = (routeMeta: APIRouteMeta) => { const newMeta = routeMeta; diff --git a/utils/constants.ts b/utils/constants.ts index 4de00425..29b479bf 100644 --- a/utils/constants.ts +++ b/utils/constants.ts @@ -1,6 +1,4 @@ -import { ConfigManager } from "config-manager"; - -const config = await new ConfigManager({}).getConfig(); +import { config } from "config-manager"; export const oauthRedirectUri = (issuer: string) => `${config.http.base_url}/oauth/callback/${issuer}`; diff --git a/utils/meilisearch.ts b/utils/meilisearch.ts index e885ddbc..bfde2890 100644 --- a/utils/meilisearch.ts +++ b/utils/meilisearch.ts @@ -2,11 +2,9 @@ import chalk from "chalk"; import { client } from "~database/datasource"; import { Meilisearch } from "meilisearch"; import type { Status, User } from "@prisma/client"; -import { ConfigManager } from "config-manager"; +import { config } from "config-manager"; import { LogLevel, type LogManager, type MultiLogManager } from "log-manager"; -const config = await new ConfigManager({}).getConfig(); - export const meilisearch = new Meilisearch({ host: `${config.meilisearch.host}:${config.meilisearch.port}`, apiKey: config.meilisearch.api_key, diff --git a/utils/redis.ts b/utils/redis.ts index e6d61910..0f3714bb 100644 --- a/utils/redis.ts +++ b/utils/redis.ts @@ -1,17 +1,15 @@ import type { Prisma } from "@prisma/client"; import chalk from "chalk"; -import { ConfigManager } from "config-manager"; +import { config } from "config-manager"; import Redis from "ioredis"; import { createPrismaRedisCache } from "prisma-redis-middleware"; -const config = await new ConfigManager({}).getConfig(); - const cacheRedis = config.redis.cache.enabled ? new Redis({ host: config.redis.cache.host, port: Number(config.redis.cache.port), password: config.redis.cache.password, - db: Number(config.redis.cache.database ?? 0), + db: Number(config.redis.cache.database), }) : null; diff --git a/utils/sanitization.ts b/utils/sanitization.ts index e2426b95..89f85b08 100644 --- a/utils/sanitization.ts +++ b/utils/sanitization.ts @@ -1,9 +1,7 @@ -import { ConfigManager } from "config-manager"; +import { config } from "config-manager"; import { sanitize } from "isomorphic-dompurify"; export const sanitizeHtml = async (html: string) => { - const config = await new ConfigManager({}).getConfig(); - const sanitizedHtml = sanitize(html, { ALLOWED_TAGS: [ "a",