mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 08:28:19 +01:00
Replace config manager with unjs/c12
This commit is contained in:
parent
6b2e4044b6
commit
6a54c5b805
|
|
@ -3,9 +3,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import { ConfigManager } from "config-manager";
|
import { config } from "config-manager";
|
||||||
|
|
||||||
const config = await new ConfigManager({}).getConfig();
|
|
||||||
|
|
||||||
const token = process.env.TOKEN;
|
const token = process.env.TOKEN;
|
||||||
const requestCount = Number(process.argv[2]) || 100;
|
const requestCount = Number(process.argv[2]) || 100;
|
||||||
|
|
|
||||||
4
cli.ts
4
cli.ts
|
|
@ -7,7 +7,7 @@ import extract from "extract-zip";
|
||||||
import { client } from "~database/datasource";
|
import { client } from "~database/datasource";
|
||||||
import { CliBuilder, CliCommand } from "cli-parser";
|
import { CliBuilder, CliCommand } from "cli-parser";
|
||||||
import { CliParameterType } from "~packages/cli-parser/cli-builder.type";
|
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 { Parser } from "@json2csv/plainjs";
|
||||||
import type { Prisma } from "@prisma/client";
|
import type { Prisma } from "@prisma/client";
|
||||||
import { MediaBackend } from "media-manager";
|
import { MediaBackend } from "media-manager";
|
||||||
|
|
@ -17,8 +17,6 @@ import { tmpdir } from "os";
|
||||||
|
|
||||||
const args = process.argv;
|
const args = process.argv;
|
||||||
|
|
||||||
const config = await new ConfigManager({}).getConfig();
|
|
||||||
|
|
||||||
const filterObjects = <T extends object>(output: T[], fields: string[]) => {
|
const filterObjects = <T extends object>(output: T[], fields: string[]) => {
|
||||||
if (fields.length === 0) return output;
|
if (fields.length === 0) return output;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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]
|
[database]
|
||||||
|
# Main PostgreSQL database connection
|
||||||
host = "localhost"
|
host = "localhost"
|
||||||
port = 5432
|
port = 5432
|
||||||
username = "lysand"
|
username = "lysand"
|
||||||
password = "password123"
|
password = "lysand"
|
||||||
database = "lysand"
|
database = "lysand"
|
||||||
|
|
||||||
[redis.queue]
|
[redis.queue]
|
||||||
|
# Redis instance for storing the federation queue
|
||||||
|
# Required for federation
|
||||||
host = "localhost"
|
host = "localhost"
|
||||||
port = 6379
|
port = 6379
|
||||||
password = ""
|
password = ""
|
||||||
database = 0
|
database = 0
|
||||||
|
|
||||||
[redis.cache]
|
[redis.cache]
|
||||||
|
# Redis instance to be used as a timeline cache
|
||||||
|
# Optional, can be the same as the queue instance
|
||||||
host = "localhost"
|
host = "localhost"
|
||||||
port = 6379
|
port = 6379
|
||||||
password = ""
|
password = ""
|
||||||
|
|
@ -19,14 +30,15 @@ database = 1
|
||||||
enabled = false
|
enabled = false
|
||||||
|
|
||||||
[meilisearch]
|
[meilisearch]
|
||||||
|
# If Meilisearch is not configured, search will not be enabled
|
||||||
host = "localhost"
|
host = "localhost"
|
||||||
port = 40007
|
port = 7700
|
||||||
api_key = ""
|
api_key = "______________________________"
|
||||||
enabled = true
|
enabled = false
|
||||||
|
|
||||||
[signups]
|
[signups]
|
||||||
# URL of your Terms of Service
|
# 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
|
# Whether to enable registrations or not
|
||||||
registration = true
|
registration = true
|
||||||
rules = [
|
rules = [
|
||||||
|
|
@ -41,40 +53,64 @@ rules = [
|
||||||
# The provider MUST support OpenID Connect with .well-known discovery
|
# The provider MUST support OpenID Connect with .well-known discovery
|
||||||
# Most notably, GitHub does not support this
|
# Most notably, GitHub does not support this
|
||||||
[[oidc.providers]]
|
[[oidc.providers]]
|
||||||
|
# Test with custom Authentik instance
|
||||||
name = "CPlusPatch ID"
|
name = "CPlusPatch ID"
|
||||||
id = "cpluspatch-id"
|
id = "cpluspatch-id"
|
||||||
url = "https://id.cpluspatch.com/application/o/lysand-testing/"
|
url = "https://id.cpluspatch.com/application/o/lysand-testing/"
|
||||||
client_id = "XXXXXXXXXXXXXXXX"
|
client_id = "______________________________"
|
||||||
client_secret = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
client_secret = "__________________________________"
|
||||||
icon = "https://cpluspatch.com/images/icons/logo.svg"
|
icon = "https://cpluspatch.com/images/icons/logo.svg"
|
||||||
|
|
||||||
[http]
|
[http]
|
||||||
|
# The full URL Lysand will be reachable by (paths are not supported)
|
||||||
base_url = "https://lysand.social"
|
base_url = "https://lysand.social"
|
||||||
bind = "http://localhost"
|
# Address to bind to
|
||||||
|
bind = "0.0.0.0"
|
||||||
bind_port = "8080"
|
bind_port = "8080"
|
||||||
|
|
||||||
# Bans IPv4 or IPv6 IPs (wildcards, networks and ranges are supported)
|
# Bans IPv4 or IPv6 IPs (wildcards, networks and ranges are supported)
|
||||||
banned_ips = []
|
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]
|
||||||
# SMTP server to use for sending emails
|
# SMTP server to use for sending emails
|
||||||
server = "smtp.example.com"
|
server = "smtp.example.com"
|
||||||
port = 465
|
port = 465
|
||||||
username = "test@example.com"
|
username = "test@example.com"
|
||||||
password = "password123"
|
password = "____________"
|
||||||
tls = true
|
tls = true
|
||||||
|
# Disable all email functions (this will allow people to sign up without verifying
|
||||||
|
# their email)
|
||||||
|
enabled = false
|
||||||
|
|
||||||
[media]
|
[media]
|
||||||
# Can be "s3" or "local", where "local" uploads the file to the local filesystem
|
# 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
|
# 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
|
# from one backend to the other manually (the CLI will have an option to do this later)
|
||||||
backend = "s3"
|
# TODO: Add CLI command to move files
|
||||||
|
backend = "local"
|
||||||
# Whether to check the hash of media when uploading to avoid duplication
|
# Whether to check the hash of media when uploading to avoid duplication
|
||||||
deduplicate_media = true
|
deduplicate_media = true
|
||||||
# If media backend is "local", this is the folder where the files will be stored
|
# If media backend is "local", this is the folder where the files will be stored
|
||||||
|
# Can be any path
|
||||||
local_uploads_folder = "uploads"
|
local_uploads_folder = "uploads"
|
||||||
|
|
||||||
[media.conversion]
|
[media.conversion]
|
||||||
|
# Whether to automatically convert images to another format on upload
|
||||||
convert_images = false
|
convert_images = false
|
||||||
# Can be: "jxl", "webp", "avif", "png", "jpg", "heif"
|
# Can be: "jxl", "webp", "avif", "png", "jpg", "heif"
|
||||||
# JXL support will likely not work
|
# JXL support will likely not work
|
||||||
|
|
@ -82,26 +118,26 @@ convert_to = "webp"
|
||||||
|
|
||||||
[s3]
|
[s3]
|
||||||
# Can be left blank if you don't use the S3 media backend
|
# Can be left blank if you don't use the S3 media backend
|
||||||
endpoint = "https://s3-us-west-2.amazonaws.com"
|
endpoint = "myhostname.banana.com"
|
||||||
access_key = ""
|
access_key = "_____________"
|
||||||
secret_access_key = ""
|
secret_access_key = "_________________"
|
||||||
region = "us-west-2"
|
region = ""
|
||||||
bucket_name = "lysand"
|
bucket_name = "lysand"
|
||||||
public_url = "https://cdn.example.com"
|
public_url = "https://cdn.test.com"
|
||||||
|
|
||||||
[email]
|
[email]
|
||||||
# Sends an email to moderators when a report is received
|
# Sends an email to moderators when a report is received
|
||||||
# NOT IMPLEMENTED
|
|
||||||
send_on_report = false
|
send_on_report = false
|
||||||
# Sends an email to moderators when a user is suspended
|
# Sends an email to moderators when a user is suspended
|
||||||
# NOT IMPLEMENTED
|
|
||||||
send_on_suspend = false
|
send_on_suspend = false
|
||||||
# Sends an email to moderators when a user is unsuspended
|
# Sends an email to moderators when a user is unsuspended
|
||||||
# NOT IMPLEMENTED
|
|
||||||
send_on_unsuspend = false
|
send_on_unsuspend = false
|
||||||
|
# Verify user emails when signing up (except via OIDC)
|
||||||
|
verify_email = false
|
||||||
|
|
||||||
[validation]
|
[validation]
|
||||||
# Self explanatory
|
# Checks user data
|
||||||
|
# Does not retroactively apply to previously entered data
|
||||||
max_displayname_size = 50
|
max_displayname_size = 50
|
||||||
max_bio_size = 160
|
max_bio_size = 160
|
||||||
max_note_size = 5000
|
max_note_size = 5000
|
||||||
|
|
@ -115,7 +151,7 @@ max_poll_option_size = 500
|
||||||
min_poll_duration = 60
|
min_poll_duration = 60
|
||||||
max_poll_duration = 1893456000
|
max_poll_duration = 1893456000
|
||||||
max_username_size = 30
|
max_username_size = 30
|
||||||
# An array of strings, defaults are from Akkoma
|
# Forbidden usernames, defaults are from Akkoma
|
||||||
username_blacklist = [
|
username_blacklist = [
|
||||||
".well-known",
|
".well-known",
|
||||||
"~",
|
"~",
|
||||||
|
|
@ -146,7 +182,7 @@ username_blacklist = [
|
||||||
]
|
]
|
||||||
# Whether to blacklist known temporary email providers
|
# Whether to blacklist known temporary email providers
|
||||||
blacklist_tempmail = false
|
blacklist_tempmail = false
|
||||||
# Additional email providers to blacklist
|
# Additional email providers to blacklist (list of domains)
|
||||||
email_blacklist = []
|
email_blacklist = []
|
||||||
# Valid URL schemes, otherwise the URL is parsed as text
|
# Valid URL schemes, otherwise the URL is parsed as text
|
||||||
url_scheme_whitelist = [
|
url_scheme_whitelist = [
|
||||||
|
|
@ -167,8 +203,10 @@ url_scheme_whitelist = [
|
||||||
"mumble",
|
"mumble",
|
||||||
"ssb",
|
"ssb",
|
||||||
"gemini",
|
"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
|
enforce_mime_types = false
|
||||||
allowed_mime_types = [
|
allowed_mime_types = [
|
||||||
"image/jpeg",
|
"image/jpeg",
|
||||||
|
|
@ -203,46 +241,38 @@ allowed_mime_types = [
|
||||||
|
|
||||||
[defaults]
|
[defaults]
|
||||||
# Default visibility for new notes
|
# 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"
|
visibility = "public"
|
||||||
# Default language for new notes
|
# Default language for new notes (ISO code)
|
||||||
language = "en"
|
language = "en"
|
||||||
# Default avatar, must be a valid URL or ""
|
# Default avatar, must be a valid URL or "" for none
|
||||||
avatar = ""
|
avatar = ""
|
||||||
# Default header, must be a valid URL or ""
|
# Default header, must be a valid URL or "" for none
|
||||||
header = ""
|
header = ""
|
||||||
|
|
||||||
[activitypub]
|
[federation]
|
||||||
# Use ActivityPub Tombstones instead of deleting objects
|
# This is a list of domain names, such as "mastodon.social" or "pleroma.site"
|
||||||
use_tombstones = true
|
# These changes will not retroactively apply to existing data before they were changed
|
||||||
# Fetch all members of collections (followers, following, etc) when receiving them
|
# For that, please use the CLI
|
||||||
# WARNING: This can be a lot of data, and is not recommended
|
|
||||||
fetch_all_collection_members = false # NOT IMPLEMENTED
|
|
||||||
|
|
||||||
# The following values must be instance domain names without "https" or glob patterns
|
# These instances will not be federated with
|
||||||
# Rejects all activities from these instances (fediblocking)
|
blocked = []
|
||||||
reject_activities = []
|
# These instances' data will only be shown to followers, not in public timelines
|
||||||
# Force posts from this instance to be followers only
|
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
|
|
||||||
|
|
||||||
# Whether to verify HTTP signatures for every request (warning: can slow down your server
|
[federation.discard]
|
||||||
# significantly depending on processing power)
|
# These objects will be discarded when received from these instances
|
||||||
authorized_fetch = false
|
reports = []
|
||||||
|
deletes = []
|
||||||
|
updates = []
|
||||||
|
media = []
|
||||||
|
follows = []
|
||||||
|
# If instance reactions are blocked, likes will also be discarded
|
||||||
|
likes = []
|
||||||
|
reactions = []
|
||||||
|
banners = []
|
||||||
|
avatars = []
|
||||||
|
|
||||||
[instance]
|
[instance]
|
||||||
name = "Lysand"
|
name = "Lysand"
|
||||||
|
|
@ -254,22 +284,23 @@ banner = ""
|
||||||
|
|
||||||
|
|
||||||
[filters]
|
[filters]
|
||||||
# Drop notes with these regex filters (only applies to new activities)
|
# Regex filters for federated and local data
|
||||||
note_filters = [
|
# 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\\.)?youtube\\.com/watch\\?v=[a-zA-Z0-9_-]+",
|
||||||
# "(https?://)?(www\\.)?youtu\\.be/[a-zA-Z0-9_-]+",
|
# "(https?://)?(www\\.)?youtu\\.be/[a-zA-Z0-9_-]+",
|
||||||
]
|
]
|
||||||
# Drop users with these regex filters (only applies to new activities)
|
emoji = []
|
||||||
username_filters = []
|
# These will drop users matching the filters
|
||||||
# Drop users with these regex filters (only applies to new activities)
|
username = []
|
||||||
displayname_filters = []
|
displayname = []
|
||||||
# Drop users with these regex filters (only applies to new activities)
|
bio = []
|
||||||
bio_filters = []
|
|
||||||
emoji_filters = [] # NOT IMPLEMENTED
|
|
||||||
|
|
||||||
[logging]
|
[logging]
|
||||||
# Log all requests (warning: this is a lot of data)
|
# 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 request and their contents (warning: this is a lot of data)
|
||||||
log_requests_verbose = false
|
log_requests_verbose = false
|
||||||
# For GDPR compliance, you can disable logging of IPs
|
# For GDPR compliance, you can disable logging of IPs
|
||||||
|
|
@ -278,12 +309,19 @@ log_ip = false
|
||||||
# Log all filtered objects
|
# Log all filtered objects
|
||||||
log_filters = true
|
log_filters = true
|
||||||
|
|
||||||
|
[logging.storage]
|
||||||
|
# Path to logfile for requests
|
||||||
|
requests = "logs/requests.log"
|
||||||
|
|
||||||
[ratelimits]
|
[ratelimits]
|
||||||
|
# These settings apply to every route at once
|
||||||
# Amount to multiply every route's duration by
|
# Amount to multiply every route's duration by
|
||||||
duration_coeff = 1.0
|
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
|
max_coeff = 1.0
|
||||||
|
|
||||||
[custom_ratelimits]
|
[custom_ratelimits]
|
||||||
# Add in any API route in this style here
|
# 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 }
|
"/api/v1/timelines/public" = { duration = 60, max = 200 }
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
// import { Queue } from "bullmq";
|
// import { Queue } from "bullmq";
|
||||||
import { PrismaClient } from "@prisma/client";
|
import { PrismaClient } from "@prisma/client";
|
||||||
import { ConfigManager } from "config-manager";
|
import { config } from "config-manager";
|
||||||
|
|
||||||
const config = await new ConfigManager({}).getConfig();
|
|
||||||
|
|
||||||
const client = new PrismaClient({
|
const client = new PrismaClient({
|
||||||
datasourceUrl: `postgresql://${config.database.username}:${config.database.password}@${config.database.host}:${config.database.port}/${config.database.database}`,
|
datasourceUrl: `postgresql://${config.database.username}:${config.database.password}@${config.database.host}:${config.database.port}/${config.database.database}`,
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,7 @@ import type { Like } from "@prisma/client";
|
||||||
import { client } from "~database/datasource";
|
import { client } from "~database/datasource";
|
||||||
import type { UserWithRelations } from "./User";
|
import type { UserWithRelations } from "./User";
|
||||||
import type { StatusWithRelations } from "./Status";
|
import type { StatusWithRelations } from "./Status";
|
||||||
import { ConfigManager } from "config-manager";
|
import { config } from "config-manager";
|
||||||
|
|
||||||
const config = await new ConfigManager({}).getConfig();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a Like entity in the database.
|
* Represents a Like entity in the database.
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
// import { Worker } from "bullmq";
|
// import { Worker } from "bullmq";
|
||||||
import { statusToLysand, type StatusWithRelations } from "./Status";
|
import { statusToLysand, type StatusWithRelations } from "./Status";
|
||||||
import type { User } from "@prisma/client";
|
import type { User } from "@prisma/client";
|
||||||
import { ConfigManager } from "config-manager";
|
import { config } from "config-manager";
|
||||||
|
|
||||||
const config = await new ConfigManager({}).getConfig();
|
|
||||||
|
|
||||||
/* export const federationWorker = new Worker(
|
/* export const federationWorker = new Worker(
|
||||||
"federation",
|
"federation",
|
||||||
|
|
|
||||||
|
|
@ -23,11 +23,9 @@ import { parse } from "marked";
|
||||||
import linkifyStr from "linkify-string";
|
import linkifyStr from "linkify-string";
|
||||||
import linkifyHtml from "linkify-html";
|
import linkifyHtml from "linkify-html";
|
||||||
import { addStausToMeilisearch } from "@meilisearch";
|
import { addStausToMeilisearch } from "@meilisearch";
|
||||||
import { ConfigManager } from "config-manager";
|
import { config } from "config-manager";
|
||||||
import { statusAndUserRelations, userRelations } from "./relations";
|
import { statusAndUserRelations, userRelations } from "./relations";
|
||||||
|
|
||||||
const config = await new ConfigManager({}).getConfig();
|
|
||||||
|
|
||||||
const statusRelations = Prisma.validator<Prisma.StatusDefaultArgs>()({
|
const statusRelations = Prisma.validator<Prisma.StatusDefaultArgs>()({
|
||||||
include: statusAndUserRelations,
|
include: statusAndUserRelations,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import type { APIAccount } from "~types/entities/account";
|
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 { htmlToText } from "html-to-text";
|
||||||
import type { User } from "@prisma/client";
|
import type { User } from "@prisma/client";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
|
|
@ -8,13 +8,10 @@ import { addEmojiIfNotExists, emojiToAPI, emojiToLysand } from "./Emoji";
|
||||||
import { addInstanceIfNotExists } from "./Instance";
|
import { addInstanceIfNotExists } from "./Instance";
|
||||||
import type { APISource } from "~types/entities/source";
|
import type { APISource } from "~types/entities/source";
|
||||||
import { addUserToMeilisearch } from "@meilisearch";
|
import { addUserToMeilisearch } from "@meilisearch";
|
||||||
import { ConfigManager, type ConfigType } from "config-manager";
|
import { config, type Config } from "config-manager";
|
||||||
import { userRelations } from "./relations";
|
import { userRelations } from "./relations";
|
||||||
import { MediaBackendType } from "~packages/media-manager";
|
import { MediaBackendType } from "~packages/media-manager";
|
||||||
|
|
||||||
const configManager = new ConfigManager({});
|
|
||||||
const config = await configManager.getConfig();
|
|
||||||
|
|
||||||
export interface AuthData {
|
export interface AuthData {
|
||||||
user: UserWithRelations | null;
|
user: UserWithRelations | null;
|
||||||
token: string;
|
token: string;
|
||||||
|
|
@ -36,7 +33,7 @@ export type UserWithRelations = Prisma.UserGetPayload<typeof userRelations2>;
|
||||||
* @param config The config to use
|
* @param config The config to use
|
||||||
* @returns The raw URL for the user's avatar
|
* @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 (!user.avatar) return config.defaults.avatar;
|
||||||
if (config.media.backend === MediaBackendType.LOCAL) {
|
if (config.media.backend === MediaBackendType.LOCAL) {
|
||||||
return `${config.http.base_url}/media/${user.avatar}`;
|
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
|
* @param config The config to use
|
||||||
* @returns The raw URL for the user's header
|
* @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 (!user.header) return config.defaults.header;
|
||||||
if (config.media.backend === MediaBackendType.LOCAL) {
|
if (config.media.backend === MediaBackendType.LOCAL) {
|
||||||
return `${config.http.base_url}/media/${user.header}`;
|
return `${config.http.base_url}/media/${user.header}`;
|
||||||
|
|
@ -192,8 +189,6 @@ export const createNewLocalUser = async (data: {
|
||||||
header?: string;
|
header?: string;
|
||||||
admin?: boolean;
|
admin?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const config = await configManager.getConfig();
|
|
||||||
|
|
||||||
const keys = await generateUserKeys();
|
const keys = await generateUserKeys();
|
||||||
|
|
||||||
const user = await client.user.create({
|
const user = await client.user.create({
|
||||||
|
|
|
||||||
9
index.ts
9
index.ts
|
|
@ -1,7 +1,7 @@
|
||||||
import type { PrismaClientInitializationError } from "@prisma/client/runtime/library";
|
import type { PrismaClientInitializationError } from "@prisma/client/runtime/library";
|
||||||
import { initializeRedisCache } from "@redis";
|
import { initializeRedisCache } from "@redis";
|
||||||
import { connectMeili } from "@meilisearch";
|
import { connectMeili } from "@meilisearch";
|
||||||
import { ConfigManager } from "config-manager";
|
import { config } from "config-manager";
|
||||||
import { client } from "~database/datasource";
|
import { client } from "~database/datasource";
|
||||||
import { LogLevel, LogManager, MultiLogManager } from "log-manager";
|
import { LogLevel, LogManager, MultiLogManager } from "log-manager";
|
||||||
import { moduleIsEntry } from "@module";
|
import { moduleIsEntry } from "@module";
|
||||||
|
|
@ -10,9 +10,6 @@ import { exists, mkdir } from "fs/promises";
|
||||||
|
|
||||||
const timeAtStart = performance.now();
|
const timeAtStart = performance.now();
|
||||||
|
|
||||||
const configManager = new ConfigManager({});
|
|
||||||
const config = await configManager.getConfig();
|
|
||||||
|
|
||||||
const requests_log = Bun.file(process.cwd() + "/logs/requests.log");
|
const requests_log = Bun.file(process.cwd() + "/logs/requests.log");
|
||||||
const isEntry = moduleIsEntry(import.meta.url);
|
const isEntry = moduleIsEntry(import.meta.url);
|
||||||
// If imported as a module, redirect logs to /dev/null to not pollute console (e.g. in tests)
|
// 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]);
|
const dualLogger = new MultiLogManager([logger, consoleLogger]);
|
||||||
|
|
||||||
if (!(await exists(process.cwd() + "/logs/"))) {
|
if (!(await exists(config.logging.storage.requests))) {
|
||||||
await consoleLogger.log(
|
await consoleLogger.log(
|
||||||
LogLevel.WARNING,
|
LogLevel.WARNING,
|
||||||
"Lysand",
|
"Lysand",
|
||||||
|
|
@ -59,7 +56,7 @@ try {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const server = createServer(config, configManager, dualLogger, isProd);
|
const server = createServer(config, dualLogger, isProd);
|
||||||
|
|
||||||
await dualLogger.log(
|
await dualLogger.log(
|
||||||
LogLevel.INFO,
|
LogLevel.INFO,
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,8 @@
|
||||||
"@typescript-eslint/eslint-plugin": "latest",
|
"@typescript-eslint/eslint-plugin": "latest",
|
||||||
"@typescript-eslint/parser": "latest",
|
"@typescript-eslint/parser": "latest",
|
||||||
"@unocss/cli": "latest",
|
"@unocss/cli": "latest",
|
||||||
|
"@vitejs/plugin-vue": "latest",
|
||||||
|
"@vueuse/head": "^2.0.0",
|
||||||
"activitypub-types": "^1.0.3",
|
"activitypub-types": "^1.0.3",
|
||||||
"bun-types": "latest",
|
"bun-types": "latest",
|
||||||
"eslint": "^8.54.0",
|
"eslint": "^8.54.0",
|
||||||
|
|
@ -71,8 +73,7 @@
|
||||||
"prettier": "^3.1.0",
|
"prettier": "^3.1.0",
|
||||||
"typescript": "latest",
|
"typescript": "latest",
|
||||||
"unocss": "latest",
|
"unocss": "latest",
|
||||||
"@vitejs/plugin-vue": "latest",
|
"untyped": "^1.4.2",
|
||||||
"@vueuse/head": "^2.0.0",
|
|
||||||
"vite": "latest",
|
"vite": "latest",
|
||||||
"vite-ssr": "^0.17.1",
|
"vite-ssr": "^0.17.1",
|
||||||
"vue": "^3.3.9",
|
"vue": "^3.3.9",
|
||||||
|
|
@ -86,11 +87,12 @@
|
||||||
"@aws-sdk/client-s3": "^3.461.0",
|
"@aws-sdk/client-s3": "^3.461.0",
|
||||||
"@iarna/toml": "^2.2.5",
|
"@iarna/toml": "^2.2.5",
|
||||||
"@json2csv/plainjs": "^7.0.6",
|
"@json2csv/plainjs": "^7.0.6",
|
||||||
"cli-parser": "workspace:*",
|
|
||||||
"@prisma/client": "^5.6.0",
|
"@prisma/client": "^5.6.0",
|
||||||
"blurhash": "^2.0.5",
|
"blurhash": "^2.0.5",
|
||||||
"bullmq": "latest",
|
"bullmq": "latest",
|
||||||
|
"c12": "^1.10.0",
|
||||||
"chalk": "^5.3.0",
|
"chalk": "^5.3.0",
|
||||||
|
"cli-parser": "workspace:*",
|
||||||
"cli-table": "^0.3.11",
|
"cli-table": "^0.3.11",
|
||||||
"config-manager": "workspace:*",
|
"config-manager": "workspace:*",
|
||||||
"eventemitter3": "^5.0.1",
|
"eventemitter3": "^5.0.1",
|
||||||
|
|
|
||||||
|
|
@ -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: {},
|
|
||||||
};
|
|
||||||
579
packages/config-manager/config.type.ts
Normal file
579
packages/config-manager/config.type.ts
Normal file
|
|
@ -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<string, any>[];
|
||||||
|
};
|
||||||
|
|
||||||
|
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: {},
|
||||||
|
};
|
||||||
|
|
@ -5,122 +5,22 @@
|
||||||
* Fuses both and provides a way to retrieve individual values
|
* Fuses both and provides a way to retrieve individual values
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { parse, stringify, type JsonMap } from "@iarna/toml";
|
import { watchConfig } from "c12";
|
||||||
import type { ConfigType } from "./config-type.type";
|
import { defaultConfig, type Config } from "./config.type";
|
||||||
import { configDefaults } from "./config-type.type";
|
|
||||||
import merge from "merge-deep-ts";
|
|
||||||
|
|
||||||
export class ConfigManager {
|
const { config } = await watchConfig<Config>({
|
||||||
constructor(
|
configFile: "./config/config.toml",
|
||||||
public config: {
|
defaultConfig: defaultConfig,
|
||||||
configPathOverride?: string;
|
overrides:
|
||||||
internalConfigPathOverride?: string;
|
(
|
||||||
}
|
await watchConfig<Config>({
|
||||||
) {}
|
configFile: "./config/config.internal.toml",
|
||||||
|
defaultConfig: {} as Config,
|
||||||
|
})
|
||||||
|
).config ?? undefined,
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
const exportedConfig = config ?? defaultConfig;
|
||||||
* @summary Reads the config files and returns the merge as a JSON object
|
|
||||||
* @returns {Promise<T = ConfigType>} The merged config file as a JSON object
|
|
||||||
*/
|
|
||||||
async getConfig<T = ConfigType>() {
|
|
||||||
const config = await this.readConfig<T>();
|
|
||||||
const internalConfig = await this.readInternalConfig<T>();
|
|
||||||
|
|
||||||
return this.mergeConfigs<T>(
|
export { exportedConfig as config };
|
||||||
configDefaults as T,
|
export type { Config };
|
||||||
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<T = ConfigType>} The internal config file as a JSON object
|
|
||||||
*/
|
|
||||||
private async readInternalConfig<T = ConfigType>() {
|
|
||||||
const config = Bun.file(this.getInternalConfigPath());
|
|
||||||
|
|
||||||
if (!(await config.exists())) {
|
|
||||||
await Bun.write(config, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.parseConfig<T>(await config.text());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Reads the config file and returns it as a JSON object
|
|
||||||
* @returns {Promise<T = ConfigType>} The config file as a JSON object
|
|
||||||
*/
|
|
||||||
private async readConfig<T = ConfigType>() {
|
|
||||||
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<T>(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<T = ConfigType>(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<T = ConfigType>(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<T = ConfigType>(...configs: T[]) {
|
|
||||||
return merge(configs) as T;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type { ConfigType };
|
|
||||||
export const defaultConfig = configDefaults;
|
|
||||||
|
|
|
||||||
|
|
@ -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<typeof mockConfig>();
|
|
||||||
|
|
||||||
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<typeof mockConfig>();
|
|
||||||
|
|
||||||
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<Record<string, string>>(
|
|
||||||
config1,
|
|
||||||
config2
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(mergedConfig).toEqual({
|
|
||||||
key1: "value1",
|
|
||||||
key2: "newValue2",
|
|
||||||
key3: "value3",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { ConfigManager } from "config-manager";
|
import { config } from "config-manager";
|
||||||
|
|
||||||
// Proxies all `bunx prisma` commands with an environment variable
|
// Proxies all `bunx prisma` commands with an environment variable
|
||||||
const config = await new ConfigManager({}).getConfig();
|
|
||||||
|
|
||||||
process.stdout.write(
|
process.stdout.write(
|
||||||
`postgresql://${config.database.username}:${config.database.password}@${config.database.host}:${config.database.port}/${config.database.database}\n`
|
`postgresql://${config.database.username}:${config.database.password}@${config.database.host}:${config.database.port}/${config.database.database}\n`
|
||||||
|
|
|
||||||
10
server.ts
10
server.ts
|
|
@ -1,15 +1,14 @@
|
||||||
import { errorResponse, jsonResponse } from "@response";
|
import { errorResponse, jsonResponse } from "@response";
|
||||||
import { matches } from "ip-matching";
|
import { matches } from "ip-matching";
|
||||||
import { getFromRequest } from "~database/entities/User";
|
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 type { LogManager, MultiLogManager } from "log-manager";
|
||||||
import { LogLevel } from "log-manager";
|
import { LogLevel } from "log-manager";
|
||||||
import { RequestParser } from "request-parser";
|
import { RequestParser } from "request-parser";
|
||||||
import { matchRoute } from "~routes";
|
import { matchRoute } from "~routes";
|
||||||
|
|
||||||
export const createServer = (
|
export const createServer = (
|
||||||
config: ConfigType,
|
config: Config,
|
||||||
configManager: ConfigManager,
|
|
||||||
logger: LogManager | MultiLogManager,
|
logger: LogManager | MultiLogManager,
|
||||||
isProd: boolean
|
isProd: boolean
|
||||||
) =>
|
) =>
|
||||||
|
|
@ -182,8 +181,11 @@ export const createServer = (
|
||||||
|
|
||||||
return await file.default(req.clone(), matchedRoute, {
|
return await file.default(req.clone(), matchedRoute, {
|
||||||
auth,
|
auth,
|
||||||
configManager,
|
|
||||||
parsedRequest,
|
parsedRequest,
|
||||||
|
// To avoid having to rewrite each route
|
||||||
|
configManager: {
|
||||||
|
getConfig: () => Promise.resolve(config),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else if (matchedRoute?.name === "/[...404]" || !matchedRoute) {
|
} else if (matchedRoute?.name === "/[...404]" || !matchedRoute) {
|
||||||
if (new URL(req.url).pathname.startsWith("/api")) {
|
if (new URL(req.url).pathname.startsWith("/api")) {
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ export default apiRoute<{
|
||||||
|
|
||||||
// Check if display name doesnt match filters
|
// Check if display name doesnt match filters
|
||||||
if (
|
if (
|
||||||
config.filters.displayname_filters.some(filter =>
|
config.filters.displayname.some(filter =>
|
||||||
sanitizedDisplayName.match(filter)
|
sanitizedDisplayName.match(filter)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
|
@ -126,11 +126,7 @@ export default apiRoute<{
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if bio doesnt match filters
|
// Check if bio doesnt match filters
|
||||||
if (
|
if (config.filters.bio.some(filter => sanitizedNote.match(filter))) {
|
||||||
config.filters.bio_filters.some(filter =>
|
|
||||||
sanitizedNote.match(filter)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return errorResponse("Bio contains blocked words", 422);
|
return errorResponse("Bio contains blocked words", 422);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ export default apiRoute<{
|
||||||
content_type,
|
content_type,
|
||||||
"poll[expires_in]": expires_in,
|
"poll[expires_in]": expires_in,
|
||||||
"poll[options]": options,
|
"poll[options]": options,
|
||||||
media_ids: media_ids,
|
media_ids,
|
||||||
spoiler_text,
|
spoiler_text,
|
||||||
sensitive,
|
sensitive,
|
||||||
} = extraData.parsedRequest;
|
} = extraData.parsedRequest;
|
||||||
|
|
@ -181,7 +181,7 @@ export default apiRoute<{
|
||||||
|
|
||||||
// Check if status body doesnt match filters
|
// Check if status body doesnt match filters
|
||||||
if (
|
if (
|
||||||
config.filters.note_filters.some(filter =>
|
config.filters.note_content.some(filter =>
|
||||||
statusText?.match(filter)
|
statusText?.match(filter)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -194,7 +194,7 @@ export default apiRoute<{
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if status body doesnt match filters
|
// 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);
|
return errorResponse("Status contains blocked words", 422);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import type { MatchedRoute } from "bun";
|
import type { MatchedRoute } from "bun";
|
||||||
import type { ConfigManager } from "config-manager";
|
import type { Config } from "config-manager";
|
||||||
import type { AuthData } from "~database/entities/User";
|
import type { AuthData } from "~database/entities/User";
|
||||||
|
|
||||||
export type RouteHandler<T> = (
|
export type RouteHandler<T> = (
|
||||||
|
|
@ -8,6 +8,8 @@ export type RouteHandler<T> = (
|
||||||
extraData: {
|
extraData: {
|
||||||
auth: AuthData;
|
auth: AuthData;
|
||||||
parsedRequest: Partial<T>;
|
parsedRequest: Partial<T>;
|
||||||
configManager: ConfigManager;
|
configManager: {
|
||||||
|
getConfig: () => Promise<Config>;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
) => Response | Promise<Response>;
|
) => Response | Promise<Response>;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import type { Token } from "@prisma/client";
|
import type { Token } from "@prisma/client";
|
||||||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
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 { client } from "~database/datasource";
|
||||||
import { TokenType } from "~database/entities/Token";
|
import { TokenType } from "~database/entities/Token";
|
||||||
import {
|
import {
|
||||||
|
|
@ -11,7 +11,6 @@ import type { APIEmoji } from "~types/entities/emoji";
|
||||||
import type { APIInstance } from "~types/entities/instance";
|
import type { APIInstance } from "~types/entities/instance";
|
||||||
import { sendTestRequest, wrapRelativeUrl } from "./utils";
|
import { sendTestRequest, wrapRelativeUrl } from "./utils";
|
||||||
|
|
||||||
const config = await new ConfigManager({}).getConfig();
|
|
||||||
const base_url = config.http.base_url;
|
const base_url = config.http.base_url;
|
||||||
|
|
||||||
let token: Token;
|
let token: Token;
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,9 @@ import {
|
||||||
import type { APIAccount } from "~types/entities/account";
|
import type { APIAccount } from "~types/entities/account";
|
||||||
import type { APIRelationship } from "~types/entities/relationship";
|
import type { APIRelationship } from "~types/entities/relationship";
|
||||||
import type { APIStatus } from "~types/entities/status";
|
import type { APIStatus } from "~types/entities/status";
|
||||||
import { ConfigManager } from "config-manager";
|
import { config } from "config-manager";
|
||||||
import { sendTestRequest, wrapRelativeUrl } from "~tests/utils";
|
import { sendTestRequest, wrapRelativeUrl } from "~tests/utils";
|
||||||
|
|
||||||
const config = await new ConfigManager({}).getConfig();
|
|
||||||
const base_url = config.http.base_url;
|
const base_url = config.http.base_url;
|
||||||
|
|
||||||
let token: Token;
|
let token: Token;
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,9 @@ import type { APIAccount } from "~types/entities/account";
|
||||||
import type { APIAsyncAttachment } from "~types/entities/async_attachment";
|
import type { APIAsyncAttachment } from "~types/entities/async_attachment";
|
||||||
import type { APIContext } from "~types/entities/context";
|
import type { APIContext } from "~types/entities/context";
|
||||||
import type { APIStatus } from "~types/entities/status";
|
import type { APIStatus } from "~types/entities/status";
|
||||||
import { ConfigManager } from "config-manager";
|
import { config } from "config-manager";
|
||||||
import { sendTestRequest, wrapRelativeUrl } from "~tests/utils";
|
import { sendTestRequest, wrapRelativeUrl } from "~tests/utils";
|
||||||
|
|
||||||
const config = await new ConfigManager({}).getConfig();
|
|
||||||
const base_url = config.http.base_url;
|
const base_url = config.http.base_url;
|
||||||
|
|
||||||
let token: Token;
|
let token: Token;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import { client } from "~database/datasource";
|
||||||
import { createNewLocalUser } from "~database/entities/User";
|
import { createNewLocalUser } from "~database/entities/User";
|
||||||
import { sendTestRequest, wrapRelativeUrl } from "./utils";
|
import { sendTestRequest, wrapRelativeUrl } from "./utils";
|
||||||
|
|
||||||
// const config = await new ConfigManager({}).getConfig();
|
|
||||||
const base_url = "http://lysand.localhost:8080"; //config.http.base_url;
|
const base_url = "http://lysand.localhost:8080"; //config.http.base_url;
|
||||||
|
|
||||||
let client_id: string;
|
let client_id: string;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
import { ConfigManager } from "config-manager";
|
import { config } from "config-manager";
|
||||||
import type { RouteHandler } from "~server/api/routes.type";
|
import type { RouteHandler } from "~server/api/routes.type";
|
||||||
import type { APIRouteMeta } from "~types/api";
|
import type { APIRouteMeta } from "~types/api";
|
||||||
|
|
||||||
const config = await new ConfigManager({}).getConfig();
|
|
||||||
|
|
||||||
export const applyConfig = (routeMeta: APIRouteMeta) => {
|
export const applyConfig = (routeMeta: APIRouteMeta) => {
|
||||||
const newMeta = routeMeta;
|
const newMeta = routeMeta;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
import { ConfigManager } from "config-manager";
|
import { config } from "config-manager";
|
||||||
|
|
||||||
const config = await new ConfigManager({}).getConfig();
|
|
||||||
|
|
||||||
export const oauthRedirectUri = (issuer: string) =>
|
export const oauthRedirectUri = (issuer: string) =>
|
||||||
`${config.http.base_url}/oauth/callback/${issuer}`;
|
`${config.http.base_url}/oauth/callback/${issuer}`;
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,9 @@ import chalk from "chalk";
|
||||||
import { client } from "~database/datasource";
|
import { client } from "~database/datasource";
|
||||||
import { Meilisearch } from "meilisearch";
|
import { Meilisearch } from "meilisearch";
|
||||||
import type { Status, User } from "@prisma/client";
|
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";
|
import { LogLevel, type LogManager, type MultiLogManager } from "log-manager";
|
||||||
|
|
||||||
const config = await new ConfigManager({}).getConfig();
|
|
||||||
|
|
||||||
export const meilisearch = new Meilisearch({
|
export const meilisearch = new Meilisearch({
|
||||||
host: `${config.meilisearch.host}:${config.meilisearch.port}`,
|
host: `${config.meilisearch.host}:${config.meilisearch.port}`,
|
||||||
apiKey: config.meilisearch.api_key,
|
apiKey: config.meilisearch.api_key,
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,15 @@
|
||||||
import type { Prisma } from "@prisma/client";
|
import type { Prisma } from "@prisma/client";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import { ConfigManager } from "config-manager";
|
import { config } from "config-manager";
|
||||||
import Redis from "ioredis";
|
import Redis from "ioredis";
|
||||||
import { createPrismaRedisCache } from "prisma-redis-middleware";
|
import { createPrismaRedisCache } from "prisma-redis-middleware";
|
||||||
|
|
||||||
const config = await new ConfigManager({}).getConfig();
|
|
||||||
|
|
||||||
const cacheRedis = config.redis.cache.enabled
|
const cacheRedis = config.redis.cache.enabled
|
||||||
? new Redis({
|
? new Redis({
|
||||||
host: config.redis.cache.host,
|
host: config.redis.cache.host,
|
||||||
port: Number(config.redis.cache.port),
|
port: Number(config.redis.cache.port),
|
||||||
password: config.redis.cache.password,
|
password: config.redis.cache.password,
|
||||||
db: Number(config.redis.cache.database ?? 0),
|
db: Number(config.redis.cache.database),
|
||||||
})
|
})
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
import { ConfigManager } from "config-manager";
|
import { config } from "config-manager";
|
||||||
import { sanitize } from "isomorphic-dompurify";
|
import { sanitize } from "isomorphic-dompurify";
|
||||||
|
|
||||||
export const sanitizeHtml = async (html: string) => {
|
export const sanitizeHtml = async (html: string) => {
|
||||||
const config = await new ConfigManager({}).getConfig();
|
|
||||||
|
|
||||||
const sanitizedHtml = sanitize(html, {
|
const sanitizedHtml = sanitize(html, {
|
||||||
ALLOWED_TAGS: [
|
ALLOWED_TAGS: [
|
||||||
"a",
|
"a",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue