diff --git a/.config/example.yml b/.config/example.yml index 9082dfb868..17149f6c3a 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -178,19 +178,13 @@ logLevel: [ # Media Proxy #mediaProxy: https://example.com/proxy -# Proxy remote files (default: false) +# Proxy remote files (default: true) #proxyRemoteFiles: true #allowedPrivateNetworks: [ # '127.0.0.1/32' #] -# TWA -#twa: -# nameSpace: android_app -# packageName: tld.domain.twa -# sha256CertFingerprints: ['AB:CD:EF'] - # Upload or download file size limits (bytes) #maxFileSize: 262144000 diff --git a/.gitlab/issue_templates/bug.md b/.gitlab/issue_templates/bug.md index 3bffa21cde..f96bdec01e 100644 --- a/.gitlab/issue_templates/bug.md +++ b/.gitlab/issue_templates/bug.md @@ -3,30 +3,47 @@ 🔒 Found a security vulnerability? [Please disclose it responsibly.](https://firefish.dev/firefish/firefish/-/blob/develop/SECURITY.md) 🤝 By submitting this issue, you agree to follow our [Contribution Guidelines.](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md) --> -**What happened?** _(Please give us a brief description of what happened.)_ +## What happened? -**What did you expect to happen?** _(Please give us a brief description of what you expected to happen.)_ -**Version** _(What version of firefish is your instance running? You can find this by clicking your instance's logo at the bottom left and then clicking instance information.)_ +## What did you expect to happen? -**Instance** _(What instance of firefish are you using?)_ -**What type of issue is this?** _(If this happens on your device and has to do with the user interface, it's client-side. If this happens on either with the API or the backend, or you got a server-side error in the client, it's server-side.)_ +## Version -**What browser are you using? (Client-side issues only)** -**What operating system are you using? (Client-side issues only)** +## What type of issue is this? -**How do you deploy Firefish on your server? (Server-side issues only)** +- [ ] server-side +- [ ] client-side +- [ ] not sure -**What operating system are you using? (Server-side issues only)** +
-**Relevant log output** _(Please copy and paste any relevant log output. You can find your log by inspecting the page, and going to the "console" tab. This will be automatically formatted into code, so no need for backticks.)_ +### Instance -**Contribution Guidelines** + +### What browser are you using? (client-side issues only) + + +### What operating system are you using? (client-side issues only) + + +### How do you deploy Firefish on your server? (server-side issues only) + + +### What operating system are you using? (Server-side issues only) + + +### Relevant log output + + +
+ +## Contribution Guidelines By submitting this issue, you agree to follow our [Contribution Guidelines](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md) - [ ] I agree to follow this project's Contribution Guidelines - [ ] I have searched the issue tracker for similar issues, and this is not a duplicate. -**Are you willing to fix this bug?** (optional) +## Are you willing to fix this bug? (optional) - [ ] Yes. I will fix this bug and open a merge request if the change is agreed upon. diff --git a/.gitlab/issue_templates/feature.md b/.gitlab/issue_templates/feature.md index b4af4884b7..4c9ee56226 100644 --- a/.gitlab/issue_templates/feature.md +++ b/.gitlab/issue_templates/feature.md @@ -3,18 +3,22 @@ 🔒 Found a security vulnerability? [Please disclose it responsibly.](https://firefish.dev/firefish/firefish/-/blob/develop/SECURITY.md) 🤝 By submitting this feature request, you agree to follow our [Contribution Guidelines.](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md) --> -**What feature would you like implemented?** _(Please give us a brief description of what you'd like.)_ +## What feature would you like implemented? -**Why should we add this feature?** _(Please give us a brief description of why your feature is important.)_ -**Version** _(What version of firefish is your instance running? You can find this by clicking your instance's logo at the bottom left and then clicking instance information.)_ +## Why should we add this feature? -**Instance** _(What instance of firefish are you using?)_ -**Contribution Guidelines** +## Version + + +## Instance + + +## Contribution Guidelines By submitting this issue, you agree to follow our [Contribution Guidelines](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md) - [ ] I agree to follow this project's Contribution Guidelines - [ ] I have searched the issue tracker for similar requests, and this is not a duplicate. -**Are you willing to implement this feature?** (optional) +## Are you willing to implement this feature? (optional) - [ ] Yes. I will implement this feature and open a merge request if the change is agreed upon. diff --git a/.gitlab/merge_request_templates/default.md b/.gitlab/merge_request_templates/default.md index 2a1c926223..d13a146da0 100644 --- a/.gitlab/merge_request_templates/default.md +++ b/.gitlab/merge_request_templates/default.md @@ -1,8 +1,9 @@ -**What does this PR do?** _(Please give us a brief description of what this PR does.)_ +## What does this PR do? -**Contribution Guidelines** + +## Contribution Guidelines By submitting this merge request, you agree to follow our [Contribution Guidelines](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md) - [ ] This change is reviewed in an issue / This is a minor bug fix - [ ] I agree to follow this project's Contribution Guidelines diff --git a/Cargo.lock b/Cargo.lock index c6ba96e683..6cb32a21d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -220,12 +220,14 @@ dependencies = [ "parse-display", "pretty_assertions", "rand", + "redis", "regex", "schemars", "sea-orm", "serde", "serde_json", "serde_yaml", + "strum 0.26.2", "thiserror", "tokio", "url", @@ -524,6 +526,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -1858,6 +1870,21 @@ dependencies = [ "getrandom", ] +[[package]] +name = "redis" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6472825949c09872e8f2c50bde59fcefc17748b6be5c90fd67cd8b4daca73bfd" +dependencies = [ + "combine", + "itoa", + "percent-encoding", + "ryu", + "sha1_smol", + "socket2", + "url", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -2070,6 +2097,12 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustversion" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" + [[package]] name = "ryu" version = "1.0.17" @@ -2150,7 +2183,7 @@ dependencies = [ "serde", "serde_json", "sqlx", - "strum", + "strum 0.25.0", "thiserror", "time", "tracing", @@ -2295,6 +2328,12 @@ dependencies = [ "digest", ] +[[package]] +name = "sha1_smol" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" + [[package]] name = "sha2" version = "0.10.8" @@ -2676,6 +2715,28 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +[[package]] +name = "strum" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.58", +] + [[package]] name = "subtle" version = "2.5.0" diff --git a/Cargo.toml b/Cargo.toml index 7ca43c960b..0bde324447 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,12 +26,14 @@ pretty_assertions = "1.4.0" proc-macro2 = "1.0.79" quote = "1.0.36" rand = "0.8.5" +redis = "0.25.3" regex = "1.10.4" schemars = "0.8.16" sea-orm = "0.12.15" serde = "1.0.197" serde_json = "1.0.115" serde_yaml = "0.9.34" +strum = "0.26.2" syn = "2.0.58" thiserror = "1.0.58" tokio = "1.37.0" diff --git a/biome.json b/biome.json index 9bf08ad553..21b711f457 100644 --- a/biome.json +++ b/biome.json @@ -1,7 +1,7 @@ { "$schema": "https://biomejs.dev/schemas/1.6.4/schema.json", "organizeImports": { - "enabled": true + "enabled": false }, "linter": { "enabled": true, @@ -21,7 +21,8 @@ "useImportType": "warn", "useShorthandFunctionType": "warn", "useTemplate": "warn", - "noNonNullAssertion": "off" + "noNonNullAssertion": "off", + "useNodejsImportProtocol": "off" } } } diff --git a/dev/docs/local-installation.md b/dev/docs/local-installation.md index 154f768037..15c7dad7d4 100644 --- a/dev/docs/local-installation.md +++ b/dev/docs/local-installation.md @@ -42,6 +42,8 @@ cargo --version ### PostgreSQL and PGroonga +Firefish requires PostgreSQL v12 or later. We recommend that you install v12.x for the same reason as Node.js. + PostgreSQL install instructions can be found at [this page](https://www.postgresql.org/download/). ```sh diff --git a/docker-compose.example.yml b/docker-compose.example.yml index fc6c0268a2..9cd6d1cdce 100644 --- a/docker-compose.example.yml +++ b/docker-compose.example.yml @@ -17,6 +17,7 @@ services: # - web environment: NODE_ENV: production + NODE_OPTIONS: --max-old-space-size=3072 volumes: - ./custom:/firefish/custom:ro - ./files:/firefish/files diff --git a/docs/api-change.md b/docs/api-change.md index f3ed584c32..dcd4329a27 100644 --- a/docs/api-change.md +++ b/docs/api-change.md @@ -2,6 +2,10 @@ Breaking changes are indicated by the :warning: icon. +## Unreleased + +- Added `antennaLimit` field to the response of `meta` and `admin/meta`, and the request of `admin/update-meta` (optional). + ## v20240413 - :warning: Removed `patrons` endpoint. diff --git a/docs/changelog.md b/docs/changelog.md index a818e09835..70f8b34fbe 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -5,6 +5,10 @@ Critical security updates are indicated by the :warning: icon. - Server administrators should check [notice-for-admins.md](./notice-for-admins.md) as well. - Third-party client/bot developers may want to check [api-change.md](./api-change.md) as well. +## [v20240421](https://firefish.dev/firefish/firefish/-/merge_requests/10756/commits) + +- Fix bugs + ## [v20240413](https://firefish.dev/firefish/firefish/-/merge_requests/10741/commits) - Add "Media" tab to user page diff --git a/docs/downgrade.sql b/docs/downgrade.sql index 0b6f25e08c..44222f818f 100644 --- a/docs/downgrade.sql +++ b/docs/downgrade.sql @@ -1,6 +1,8 @@ BEGIN; DELETE FROM "migrations" WHERE name IN ( + 'AddDriveFileUsage1713451569342', + 'ConvertCwVarcharToText1713225866247', 'FixChatFileConstraint1712855579316', 'DropTimeZone1712425488543', 'ExpandNoteEdit1711936358554', @@ -22,6 +24,15 @@ DELETE FROM "migrations" WHERE name IN ( 'RemoveNativeUtilsMigration1705877093218' ); +-- AddDriveFileUsage +ALTER TABLE "drive_file" DROP COLUMN "usageHint"; +DROP TYPE "drive_file_usage_hint_enum"; + +-- convert-cw-varchar-to-text +DROP INDEX "IDX_8e3bbbeb3df04d1a8105da4c8f"; +ALTER TABLE "note" ALTER COLUMN "cw" TYPE character varying(512); +CREATE INDEX "IDX_8e3bbbeb3df04d1a8105da4c8f" ON "note" USING "pgroonga" ("cw" pgroonga_varchar_full_text_search_ops_v2); + -- fix-chat-file-constraint ALTER TABLE "messaging_message" DROP CONSTRAINT "FK_535def119223ac05ad3fa9ef64b"; ALTER TABLE "messaging_message" ADD CONSTRAINT "FK_535def119223ac05ad3fa9ef64b" FOREIGN KEY ("fileId") REFERENCES "drive_file"("id") ON DELETE CASCADE ON UPDATE NO ACTION; diff --git a/docs/install.md b/docs/install.md index 061000fa32..0e850e2545 100644 --- a/docs/install.md +++ b/docs/install.md @@ -1,9 +1,36 @@ # Install Firefish -This document shows an example procedure for installing Firefish on Debian 12. Note that there is much room for customizing the server setup; this document merely demonstrates a simple installation. +Firefish depends on the following software. + +## Runtime dependencies + +- At least [NodeJS](https://nodejs.org/en/) v18.17.0 (v20/v21 recommended) +- At least [PostgreSQL](https://www.postgresql.org/) v12 (v16 recommended) with [PGroonga](https://pgroonga.github.io/) extension +- At least [Redis](https://redis.io/) v7 +- Web Proxy (one of the following) + - Caddy (recommended) + - Nginx (recommended) + - Apache +- [FFmpeg](https://ffmpeg.org/) for video transcoding (**optional**) +- Caching server (**optional**, one of the following) + - [DragonflyDB](https://www.dragonflydb.io/) + - [KeyDB](https://keydb.dev/) + - Another [Redis](https://redis.io/) server + +## Build dependencies + +- At least [Rust](https://www.rust-lang.org/) v1.74 +- C/C++ compiler & build tools + - `build-essential` on Debian/Ubuntu Linux + - `base-devel` on Arch Linux +- [Python 3](https://www.python.org/) + +This document shows an example procedure for installing these dependencies and Firefish on Debian 12. Note that there is much room for customizing the server setup; this document merely demonstrates a simple installation. If you want to use the pre-built container image, please refer to [`install-container.md`](./install-container.md). +If you do not prepare your environment as document, be sure to meet the minimum dependencies given at the bottom of the page. + Make sure that you can use the `sudo` command before proceeding. ## 1. Install dependencies @@ -154,7 +181,7 @@ sudo apt install ffmpeg 1. Build ```sh pnpm install --frozen-lockfile - NODE_ENV=production pnpm run build + NODE_ENV=production NODE_OPTIONS='--max-old-space-size=3072' pnpm run build ``` 1. Execute database migrations ```sh @@ -242,6 +269,7 @@ In this instruction, we use [Caddy](https://caddyserver.com/) to make the Firefi WorkingDirectory=/home/firefish/firefish Environment="NODE_ENV=production" Environment="npm_config_cache=/tmp" + Environment="NODE_OPTIONS=--max-old-space-size=3072" # uncomment the following line if you use jemalloc (note that the path varies on different environments) # Environment="LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2" StandardOutput=journal diff --git a/locales/en-US.yml b/locales/en-US.yml index ce6fd91ca1..a6ab32f2e6 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -394,6 +394,7 @@ enableRegistration: "Enable new user registration" invite: "Invite" driveCapacityPerLocalAccount: "Drive capacity per local user" driveCapacityPerRemoteAccount: "Drive capacity per remote user" +antennaLimit: "The maximum number of antennas that each user can create" inMb: "In megabytes" iconUrl: "Icon URL" bannerUrl: "Banner image URL" @@ -1226,6 +1227,8 @@ publishTimelinesDescription: "If enabled, the Local and Global timelines will be on {url} even when signed out." noAltTextWarning: "Some attached file(s) have no description. Did you forget to write?" showNoAltTextWarning: "Show a warning if you attempt to post files without a description" +showAddFileDescriptionAtFirstPost: "Automatically open a form to write a description when you + attempt to post files without a description" _emojiModPerm: unauthorized: "None" diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index 9dff23f110..6367dda9f4 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -2318,3 +2318,4 @@ markLocalFilesNsfwByDefaultDescription: Indépendamment de ce réglage, les util peuvent supprimer le drapeau « sensible » (NSFW) eux-mêmes. Les fichiers existants ne sont pas affectés. noteEditHistory: Historique des publications +media: Multimédia diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index 552efb268e..4dac78713a 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -340,6 +340,7 @@ invite: "邀请" driveCapacityPerLocalAccount: "每个本地用户的网盘容量" driveCapacityPerRemoteAccount: "每个远程用户的网盘容量" inMb: "以兆字节 (MegaByte) 为单位" +antennaLimit: "每个用户最多可以创建的天线数量" iconUrl: "图标 URL" bannerUrl: "横幅图 URL" backgroundImageUrl: "背景图 URL" @@ -2053,6 +2054,7 @@ searchRangeDescription: "如果您要过滤时间段,请按以下格式输入 messagingUnencryptedInfo: "Firefish 上的聊天没有经过端到端加密,请不要在聊天中分享您的敏感信息。" noAltTextWarning: 有些附件没有描述。您是否忘记写描述了? showNoAltTextWarning: 当您尝试发布没有描述的帖子附件时显示警告 +showAddFileDescriptionAtFirstPost: 当您首次尝试发布没有描述的帖子附件时自动弹出添加描述页面 autocorrectNoteLanguage: 当帖子语言不符合自动检测的结果的时候显示警告 incorrectLanguageWarning: "看上去您帖子使用的语言是{detected},但您选择的语言是{current}。\n要改为以{detected}发帖吗?" noteEditHistory: "帖子编辑历史" diff --git a/package.json b/package.json index dd0766bc7b..93594a5698 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firefish", - "version": "20240413", + "version": "20240421", "repository": { "type": "git", "url": "https://firefish.dev/firefish/firefish.git" @@ -26,7 +26,9 @@ "debug": "pnpm run build:debug && pnpm run start", "build:debug": "pnpm run clean && pnpm node ./scripts/dev-build.mjs && pnpm run gulp", "mocha": "pnpm --filter backend run mocha", - "test": "pnpm run mocha", + "test": "pnpm run test:ts && pnpm run test:rs", + "test:ts": "pnpm run mocha", + "test:rs": "cargo test", "format": "pnpm run format:ts; pnpm run format:rs", "format:ts": "pnpm -r --parallel run format", "format:rs": "cargo fmt --all --", diff --git a/packages/backend-rs/Cargo.toml b/packages/backend-rs/Cargo.toml index af9e10cdc1..93c57dc321 100644 --- a/packages/backend-rs/Cargo.toml +++ b/packages/backend-rs/Cargo.toml @@ -30,12 +30,14 @@ jsonschema = { workspace = true } once_cell = { workspace = true } parse-display = { workspace = true } rand = { workspace = true } +redis = { workspace = true } regex = { workspace = true } schemars = { workspace = true, features = ["chrono"] } sea-orm = { workspace = true, features = ["sqlx-postgres", "runtime-tokio-rustls"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } serde_yaml = { workspace = true } +strum = { workspace = true, features = ["derive"] } thiserror = { workspace = true } tokio = { workspace = true, features = ["full"] } url = { workspace = true } diff --git a/packages/backend-rs/Makefile b/packages/backend-rs/Makefile index eb4b30c6df..abe6045801 100644 --- a/packages/backend-rs/Makefile +++ b/packages/backend-rs/Makefile @@ -6,6 +6,7 @@ SRC += $(call recursive_wildcard, src, *) .PHONY: regenerate-entities regenerate-entities: + rm --recursive --force src/model/entity sea-orm-cli generate entity \ --output-dir='src/model/entity' \ --database-url='postgres://$(POSTGRES_USER):$(POSTGRES_PASSWORD)@localhost:25432/$(POSTGRES_DB)' \ @@ -16,8 +17,9 @@ regenerate-entities: jsname=$$(printf '%s\n' "$${base%.*}" | perl -pe 's/(^|_)./uc($$&)/ge;s/_//g'); \ attribute=$$(printf 'cfg_attr(feature = "napi", napi_derive::napi(object, js_name = "%s", use_nullable = true))' "$${jsname}"); \ sed -i "s/NAPI_EXTRA_ATTR_PLACEHOLDER/$${attribute}/" "$${file}"; \ + sed -i 's/#\[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)\]/#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)]\n#[serde(rename_all = "camelCase")]/' "$${file}"; \ done - sed -i 's/#\[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)\]/#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]\n#[cfg_attr(not(feature = "napi"), derive(Clone))]\n#[cfg_attr(feature = "napi", napi_derive::napi)]/' \ + sed -i 's/#\[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)\]/#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, serde::Serialize, serde::Deserialize)]\n#[serde(rename_all = "camelCase")]\n#[cfg_attr(not(feature = "napi"), derive(Clone))]\n#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))]/' \ src/model/entity/sea_orm_active_enums.rs cargo fmt --all -- diff --git a/packages/backend-rs/index.d.ts b/packages/backend-rs/index.d.ts index a47495508e..69de833dcc 100644 --- a/packages/backend-rs/index.d.ts +++ b/packages/backend-rs/index.d.ts @@ -3,6 +3,16 @@ /* auto-generated by NAPI-RS */ +export interface EnvConfig { + onlyQueue: boolean + onlyServer: boolean + noDaemons: boolean + disableClustering: boolean + verbose: boolean + withLogTime: boolean + slow: boolean +} +export function loadEnv(): EnvConfig export interface ServerConfig { url: string port: number @@ -19,7 +29,7 @@ export interface ServerConfig { /** `NapiValue` is not implemented for `u64` */ maxFileSize?: number accessLog?: string - clusterLimits?: WorkerConfig + clusterLimits?: WorkerConfigInternal cuid?: IdConfig outgoingAddress?: string deliverJobConcurrency?: number @@ -60,13 +70,17 @@ export interface RedisConfig { pass?: string tls?: TlsConfig db: number - prefix: string + prefix?: string } export interface TlsConfig { host: string rejectUnauthorized: boolean } export interface WorkerConfig { + web: number + queue: number +} +export interface WorkerConfigInternal { web?: number queue?: number } @@ -111,14 +125,91 @@ export interface ObjectStorageConfig { setPublicReadOnUpload?: boolean s3ForcePathStyle?: boolean } -export function readServerConfig(): ServerConfig +export interface Config { + url: string + port: number + bind?: string + disableHsts?: boolean + db: DbConfig + redis: RedisConfig + cacheServer?: RedisConfig + proxy?: string + proxySmtp?: string + proxyBypassHosts?: Array + allowedPrivateNetworks?: Array + maxFileSize?: number + accessLog?: string + clusterLimits: WorkerConfig + cuid?: IdConfig + outgoingAddress?: string + deliverJobConcurrency?: number + inboxJobConcurrency?: number + deliverJobPerSec?: number + inboxJobPerSec?: number + deliverJobMaxAttempts?: number + inboxJobMaxAttempts?: number + logLevel?: Array + syslog?: SysLogConfig + proxyRemoteFiles?: boolean + mediaProxy?: string + summalyProxyUrl?: string + reservedUsernames?: Array + maxUserSignups?: number + isManagedHosting?: boolean + maxNoteLength?: number + maxCaptionLength?: number + deepl?: DeepLConfig + libreTranslate?: LibreTranslateConfig + email?: EmailConfig + objectStorage?: ObjectStorageConfig + version: string + host: string + hostname: string + redisKeyPrefix: string + scheme: string + wsScheme: string + apiUrl: string + wsUrl: string + authUrl: string + driveUrl: string + userAgent: string + clientEntry: Manifest +} +export interface Manifest { + file: string + name: string + src: string + isEntry: boolean + isDynamicEntry: boolean + imports: Array + dynamicImports: Array + css: Array + assets: Array +} +export function loadConfig(): Config export interface Acct { username: string host: string | null } export function stringToAcct(acct: string): Acct export function acctToString(acct: Acct): string -export interface NoteLike { +/** + * @param host punycoded instance host + * @returns whether the given host should be blocked +*/ +export function isBlockedServer(host: string): Promise +/** + * @param host punycoded instance host + * @returns whether the given host should be limited +*/ +export function isSilencedServer(host: string): Promise +/** + * @param host punycoded instance host + * @returns whether the given host is allowlisted (this is always true if private mode is disabled) +*/ +export function isAllowedServer(host: string): Promise +/** TODO: handle name collisions better */ +export interface NoteLikeForCheckWordMute { fileIds: Array userId: string | null text: string | null @@ -126,7 +217,7 @@ export interface NoteLike { renoteId: string | null replyId: string | null } -export function checkWordMute(note: NoteLike, mutedWordLists: Array>, mutedPatterns: Array): Promise +export function checkWordMute(note: NoteLikeForCheckWordMute, mutedWordLists: Array>, mutedPatterns: Array): Promise export function getFullApAccount(username: string, host?: string | undefined | null): string export function isSelfHost(host?: string | undefined | null): boolean export function isSameOrigin(uri: string): boolean @@ -137,6 +228,14 @@ export function sqlLikeEscape(src: string): string export function safeForSql(src: string): boolean /** Convert milliseconds to a human readable string */ export function formatMilliseconds(milliseconds: number): string +/** TODO: handle name collisions better */ +export interface NoteLikeForGetNoteSummary { + fileIds: Array + text: string | null + cw: string | null + hasPoll: boolean +} +export function getNoteSummary(note: NoteLikeForGetNoteSummary): string export function toMastodonId(firefishId: string): string | null export function fromMastodonId(mastodonId: string): string | null export function fetchMeta(useCache: boolean): Promise @@ -329,6 +428,7 @@ export interface DriveFile { webpublicType: string | null requestHeaders: Json | null requestIp: string | null + usageHint: DriveFileUsageHintEnum | null } export interface DriveFolder { id: string @@ -534,6 +634,7 @@ export interface Meta { donationLink: string | null moreUrls: Json markLocalFilesNsfwByDefault: boolean + antennaLimit: number } export interface Migrations { id: number @@ -753,81 +854,85 @@ export interface ReplyMuting { muteeId: string muterId: string } -export const enum AntennaSrcEnum { - All = 0, - Group = 1, - Home = 2, - Instances = 3, - List = 4, - Users = 5 +export enum AntennaSrcEnum { + All = 'all', + Group = 'group', + Home = 'home', + Instances = 'instances', + List = 'list', + Users = 'users' } -export const enum MutedNoteReasonEnum { - Manual = 0, - Other = 1, - Spam = 2, - Word = 3 +export enum DriveFileUsageHintEnum { + UserAvatar = 'userAvatar', + UserBanner = 'userBanner' } -export const enum NoteVisibilityEnum { - Followers = 0, - Hidden = 1, - Home = 2, - Public = 3, - Specified = 4 +export enum MutedNoteReasonEnum { + Manual = 'manual', + Other = 'other', + Spam = 'spam', + Word = 'word' } -export const enum NotificationTypeEnum { - App = 0, - Follow = 1, - FollowRequestAccepted = 2, - GroupInvited = 3, - Mention = 4, - PollEnded = 5, - PollVote = 6, - Quote = 7, - Reaction = 8, - ReceiveFollowRequest = 9, - Renote = 10, - Reply = 11 +export enum NoteVisibilityEnum { + Followers = 'followers', + Hidden = 'hidden', + Home = 'home', + Public = 'public', + Specified = 'specified' } -export const enum PageVisibilityEnum { - Followers = 0, - Public = 1, - Specified = 2 +export enum NotificationTypeEnum { + App = 'app', + Follow = 'follow', + FollowRequestAccepted = 'followRequestAccepted', + GroupInvited = 'groupInvited', + Mention = 'mention', + PollEnded = 'pollEnded', + PollVote = 'pollVote', + Quote = 'quote', + Reaction = 'reaction', + ReceiveFollowRequest = 'receiveFollowRequest', + Renote = 'renote', + Reply = 'reply' } -export const enum PollNotevisibilityEnum { - Followers = 0, - Home = 1, - Public = 2, - Specified = 3 +export enum PageVisibilityEnum { + Followers = 'followers', + Public = 'public', + Specified = 'specified' } -export const enum RelayStatusEnum { - Accepted = 0, - Rejected = 1, - Requesting = 2 +export enum PollNotevisibilityEnum { + Followers = 'followers', + Home = 'home', + Public = 'public', + Specified = 'specified' } -export const enum UserEmojimodpermEnum { - Add = 0, - Full = 1, - Mod = 2, - Unauthorized = 3 +export enum RelayStatusEnum { + Accepted = 'accepted', + Rejected = 'rejected', + Requesting = 'requesting' } -export const enum UserProfileFfvisibilityEnum { - Followers = 0, - Private = 1, - Public = 2 +export enum UserEmojimodpermEnum { + Add = 'add', + Full = 'full', + Mod = 'mod', + Unauthorized = 'unauthorized' } -export const enum UserProfileMutingnotificationtypesEnum { - App = 0, - Follow = 1, - FollowRequestAccepted = 2, - GroupInvited = 3, - Mention = 4, - PollEnded = 5, - PollVote = 6, - Quote = 7, - Reaction = 8, - ReceiveFollowRequest = 9, - Renote = 10, - Reply = 11 +export enum UserProfileFfvisibilityEnum { + Followers = 'followers', + Private = 'private', + Public = 'public' +} +export enum UserProfileMutingnotificationtypesEnum { + App = 'app', + Follow = 'follow', + FollowRequestAccepted = 'followRequestAccepted', + GroupInvited = 'groupInvited', + Mention = 'mention', + PollEnded = 'pollEnded', + PollVote = 'pollVote', + Quote = 'quote', + Reaction = 'reaction', + ReceiveFollowRequest = 'receiveFollowRequest', + Renote = 'renote', + Reply = 'reply' } export interface Signin { id: string @@ -1014,6 +1119,7 @@ export interface Webhook { latestSentAt: Date | null latestStatus: number | null } +export function addNoteToAntenna(antennaId: string, note: Note): void /** Initializes Cuid2 generator. Must be called before any [create_id]. */ export function initIdGenerator(length: number, fingerprint: string): void export function getTimestamp(id: string): number diff --git a/packages/backend-rs/index.js b/packages/backend-rs/index.js index 6fca851430..b4d86dccdf 100644 --- a/packages/backend-rs/index.js +++ b/packages/backend-rs/index.js @@ -310,11 +310,15 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { readServerConfig, stringToAcct, acctToString, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, safeForSql, formatMilliseconds, toMastodonId, fromMastodonId, fetchMeta, metaToPugArgs, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, AntennaSrcEnum, MutedNoteReasonEnum, NoteVisibilityEnum, NotificationTypeEnum, PageVisibilityEnum, PollNotevisibilityEnum, RelayStatusEnum, UserEmojimodpermEnum, UserProfileFfvisibilityEnum, UserProfileMutingnotificationtypesEnum, initIdGenerator, getTimestamp, genId, secureRndstr } = nativeBinding +const { loadEnv, loadConfig, stringToAcct, acctToString, isBlockedServer, isSilencedServer, isAllowedServer, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, safeForSql, formatMilliseconds, getNoteSummary, toMastodonId, fromMastodonId, fetchMeta, metaToPugArgs, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, AntennaSrcEnum, DriveFileUsageHintEnum, MutedNoteReasonEnum, NoteVisibilityEnum, NotificationTypeEnum, PageVisibilityEnum, PollNotevisibilityEnum, RelayStatusEnum, UserEmojimodpermEnum, UserProfileFfvisibilityEnum, UserProfileMutingnotificationtypesEnum, addNoteToAntenna, initIdGenerator, getTimestamp, genId, secureRndstr } = nativeBinding -module.exports.readServerConfig = readServerConfig +module.exports.loadEnv = loadEnv +module.exports.loadConfig = loadConfig module.exports.stringToAcct = stringToAcct module.exports.acctToString = acctToString +module.exports.isBlockedServer = isBlockedServer +module.exports.isSilencedServer = isSilencedServer +module.exports.isAllowedServer = isAllowedServer module.exports.checkWordMute = checkWordMute module.exports.getFullApAccount = getFullApAccount module.exports.isSelfHost = isSelfHost @@ -325,6 +329,7 @@ module.exports.isUnicodeEmoji = isUnicodeEmoji module.exports.sqlLikeEscape = sqlLikeEscape module.exports.safeForSql = safeForSql module.exports.formatMilliseconds = formatMilliseconds +module.exports.getNoteSummary = getNoteSummary module.exports.toMastodonId = toMastodonId module.exports.fromMastodonId = fromMastodonId module.exports.fetchMeta = fetchMeta @@ -337,6 +342,7 @@ module.exports.decodeReaction = decodeReaction module.exports.countReactions = countReactions module.exports.toDbReaction = toDbReaction module.exports.AntennaSrcEnum = AntennaSrcEnum +module.exports.DriveFileUsageHintEnum = DriveFileUsageHintEnum module.exports.MutedNoteReasonEnum = MutedNoteReasonEnum module.exports.NoteVisibilityEnum = NoteVisibilityEnum module.exports.NotificationTypeEnum = NotificationTypeEnum @@ -346,6 +352,7 @@ module.exports.RelayStatusEnum = RelayStatusEnum module.exports.UserEmojimodpermEnum = UserEmojimodpermEnum module.exports.UserProfileFfvisibilityEnum = UserProfileFfvisibilityEnum module.exports.UserProfileMutingnotificationtypesEnum = UserProfileMutingnotificationtypesEnum +module.exports.addNoteToAntenna = addNoteToAntenna module.exports.initIdGenerator = initIdGenerator module.exports.getTimestamp = getTimestamp module.exports.genId = genId diff --git a/packages/backend-rs/package.json b/packages/backend-rs/package.json index 69ae09fa49..1f3f49e9fb 100644 --- a/packages/backend-rs/package.json +++ b/packages/backend-rs/package.json @@ -33,8 +33,8 @@ }, "scripts": { "artifacts": "napi artifacts", - "build": "napi build --features napi --platform --release ./built/", - "build:debug": "napi build --features napi --platform ./built/", + "build": "napi build --features napi --no-const-enum --platform --release ./built/", + "build:debug": "napi build --features napi --no-const-enum --platform ./built/", "prepublishOnly": "napi prepublish -t npm", "test": "pnpm run cargo:test && pnpm run build:debug && ava", "universal": "napi universal", diff --git a/packages/backend-rs/src/config/environment.rs b/packages/backend-rs/src/config/environment.rs new file mode 100644 index 0000000000..1825af326a --- /dev/null +++ b/packages/backend-rs/src/config/environment.rs @@ -0,0 +1,27 @@ +// FIXME: Are these options used? +#[crate::export(object)] +pub struct EnvConfig { + pub only_queue: bool, + pub only_server: bool, + pub no_daemons: bool, + pub disable_clustering: bool, + pub verbose: bool, + pub with_log_time: bool, + pub slow: bool, +} + +#[crate::export] +pub fn load_env() -> EnvConfig { + let node_env = std::env::var("NODE_ENV").unwrap_or_default().to_lowercase(); + let is_testing = node_env == "test"; + + EnvConfig { + only_queue: std::env::var("MK_ONLY_QUEUE").is_ok(), + only_server: std::env::var("MK_ONLY_SERVER").is_ok(), + no_daemons: is_testing || std::env::var("MK_NO_DAEMONS").is_ok(), + disable_clustering: is_testing || std::env::var("MK_DISABLE_CLUSTERING").is_ok(), + verbose: std::env::var("MK_VERBOSE").is_ok(), + with_log_time: std::env::var("MK_WITH_LOG_TIME").is_ok(), + slow: std::env::var("MK_SLOW").is_ok(), + } +} diff --git a/packages/backend-rs/src/config/mod.rs b/packages/backend-rs/src/config/mod.rs index 74f47ad347..6155f15e54 100644 --- a/packages/backend-rs/src/config/mod.rs +++ b/packages/backend-rs/src/config/mod.rs @@ -1 +1,4 @@ +pub use server::CONFIG; + +pub mod environment; pub mod server; diff --git a/packages/backend-rs/src/config/server.rs b/packages/backend-rs/src/config/server.rs index 125bae90b9..5a4ee481cd 100644 --- a/packages/backend-rs/src/config/server.rs +++ b/packages/backend-rs/src/config/server.rs @@ -6,7 +6,7 @@ use std::fs; #[derive(Clone, Debug, PartialEq, Deserialize)] #[serde(rename_all = "camelCase")] #[crate::export(object, use_nullable = false)] -pub struct ServerConfig { +struct ServerConfig { pub url: String, pub port: u16, /// host to listen on @@ -25,7 +25,7 @@ pub struct ServerConfig { /// `NapiValue` is not implemented for `u64` pub max_file_size: Option, pub access_log: Option, - pub cluster_limits: Option, + pub cluster_limits: Option, pub cuid: Option, pub outgoing_address: Option, @@ -82,8 +82,7 @@ pub struct RedisConfig { pub tls: Option, #[serde(default)] pub db: u32, - #[serde(default)] - pub prefix: String, + pub prefix: Option, } #[derive(Clone, Debug, PartialEq, Deserialize)] @@ -94,10 +93,16 @@ pub struct TlsConfig { pub reject_unauthorized: bool, } +#[crate::export(object, use_nullable = false)] +pub struct WorkerConfig { + pub web: u32, + pub queue: u32, +} + #[derive(Clone, Debug, PartialEq, Deserialize)] #[serde(rename_all = "camelCase")] #[crate::export(object, use_nullable = false)] -pub struct WorkerConfig { +pub struct WorkerConfigInternal { pub web: Option, pub queue: Option, } @@ -167,17 +172,207 @@ pub struct ObjectStorageConfig { pub s3_force_path_style: Option, } -#[crate::export] -pub fn read_server_config() -> ServerConfig { +#[crate::export(object, use_nullable = false)] +pub struct Config { + // ServerConfig (from default.yml) + pub url: String, + pub port: u16, + pub bind: Option, + pub disable_hsts: Option, + pub db: DbConfig, + pub redis: RedisConfig, + pub cache_server: Option, + pub proxy: Option, + pub proxy_smtp: Option, + pub proxy_bypass_hosts: Option>, + pub allowed_private_networks: Option>, + pub max_file_size: Option, + pub access_log: Option, + pub cluster_limits: WorkerConfig, + pub cuid: Option, + pub outgoing_address: Option, + pub deliver_job_concurrency: Option, + pub inbox_job_concurrency: Option, + pub deliver_job_per_sec: Option, + pub inbox_job_per_sec: Option, + pub deliver_job_max_attempts: Option, + pub inbox_job_max_attempts: Option, + pub log_level: Option>, + pub syslog: Option, + pub proxy_remote_files: Option, + pub media_proxy: Option, + pub summaly_proxy_url: Option, + pub reserved_usernames: Option>, + pub max_user_signups: Option, + pub is_managed_hosting: Option, + pub max_note_length: Option, + pub max_caption_length: Option, + pub deepl: Option, + pub libre_translate: Option, + pub email: Option, + pub object_storage: Option, + + // Mixin + pub version: String, + pub host: String, + pub hostname: String, + pub redis_key_prefix: String, + pub scheme: String, + pub ws_scheme: String, + pub api_url: String, + pub ws_url: String, + pub auth_url: String, + pub drive_url: String, + pub user_agent: String, + pub client_entry: Manifest, +} + +#[derive(Clone, Debug, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +struct Meta { + pub version: String, +} + +#[derive(Clone, Debug, PartialEq, Deserialize)] +struct ManifestJson { + #[serde(rename = "src/init.ts")] + pub init_ts: Manifest, +} + +#[derive(Clone, Debug, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +#[crate::export(object, use_nullable = false)] +pub struct Manifest { + pub file: String, + pub name: String, + pub src: String, + pub is_entry: bool, + pub is_dynamic_entry: bool, + pub imports: Vec, + pub dynamic_imports: Vec, + pub css: Vec, + pub assets: Vec, +} + +fn read_config_file() -> ServerConfig { let cwd = env::current_dir().unwrap(); let yml = fs::File::open(cwd.join("../../.config/default.yml")) .expect("Failed to open '.config/default.yml'"); - let mut data: ServerConfig = serde_yaml::from_reader(yml).expect("Failed to parse yaml"); + let mut data: ServerConfig = + serde_yaml::from_reader(yml).expect("Failed to parse .config/default.yml"); + data.url = url::Url::parse(&data.url) .expect("Config url is invalid") .origin() .ascii_serialization(); + + if data.bind.is_none() { + data.bind = std::env::var("BIND").ok() + } + data } -pub static SERVER_CONFIG: Lazy = Lazy::new(read_server_config); +fn read_meta() -> Meta { + let cwd = env::current_dir().unwrap(); + let meta_json = fs::File::open(cwd.join("../../built/meta.json")) + .expect("Failed to open 'built/meta.json'"); + serde_json::from_reader(meta_json).expect("Failed to parse built/meta.json") +} + +fn read_manifest() -> Manifest { + let cwd = env::current_dir().unwrap(); + let manifest_json = fs::File::open(cwd.join("../../built/_client_dist_/manifest.json")) + .expect("Failed to open 'built/_client_dist_/manifest.json'"); + let manifest: ManifestJson = serde_json::from_reader(manifest_json) + .expect("Failed to parse built/_client_dist_/manifest.json"); + + manifest.init_ts +} + +#[crate::export] +fn load_config() -> Config { + let server_config = read_config_file(); + let version = read_meta().version; + let manifest = read_manifest(); + let url = url::Url::parse(&server_config.url).expect("Config url is invalid"); + let hostname = url + .host_str() + .expect("Hostname is missing in the config url") + .to_owned(); + let host = match url.port() { + Some(port) => format!("{}:{}", hostname, port), + None => hostname.clone(), + }; + let scheme = url.scheme().to_owned(); + let ws_scheme = scheme.replace("http", "ws"); + + let cluster_limits = match server_config.cluster_limits { + Some(cl) => WorkerConfig { + web: cl.web.unwrap_or(1), + queue: cl.queue.unwrap_or(1), + }, + None => WorkerConfig { web: 1, queue: 1 }, + }; + + let redis_key_prefix = if let Some(cache_server) = &server_config.cache_server { + cache_server.prefix.clone() + } else { + server_config.redis.prefix.clone() + } + .unwrap_or(hostname.clone()); + + Config { + url: server_config.url, + port: server_config.port, + bind: server_config.bind, + disable_hsts: server_config.disable_hsts, + db: server_config.db, + redis: server_config.redis, + cache_server: server_config.cache_server, + proxy: server_config.proxy, + proxy_smtp: server_config.proxy_smtp, + proxy_bypass_hosts: server_config.proxy_bypass_hosts, + allowed_private_networks: server_config.allowed_private_networks, + max_file_size: server_config.max_file_size, + access_log: server_config.access_log, + cluster_limits, + cuid: server_config.cuid, + outgoing_address: server_config.outgoing_address, + deliver_job_concurrency: server_config.deliver_job_concurrency, + inbox_job_concurrency: server_config.inbox_job_concurrency, + deliver_job_per_sec: server_config.deliver_job_per_sec, + inbox_job_per_sec: server_config.inbox_job_per_sec, + deliver_job_max_attempts: server_config.deliver_job_max_attempts, + inbox_job_max_attempts: server_config.inbox_job_max_attempts, + log_level: server_config.log_level, + syslog: server_config.syslog, + proxy_remote_files: server_config.proxy_remote_files, + media_proxy: server_config.media_proxy, + summaly_proxy_url: server_config.summaly_proxy_url, + reserved_usernames: server_config.reserved_usernames, + max_user_signups: server_config.max_user_signups, + is_managed_hosting: server_config.is_managed_hosting, + max_note_length: server_config.max_note_length, + max_caption_length: server_config.max_caption_length, + deepl: server_config.deepl, + libre_translate: server_config.libre_translate, + email: server_config.email, + object_storage: server_config.object_storage, + + ws_url: format!("{}://{}", ws_scheme, host), + api_url: format!("{}://{}/api", scheme, host), + auth_url: format!("{}://{}/auth", scheme, host), + drive_url: format!("{}://{}/files", scheme, host), + user_agent: format!("Firefish/{} ({})", version, url), + version, + host, + hostname, + redis_key_prefix, + scheme, + ws_scheme, + client_entry: manifest, + } +} + +pub static CONFIG: Lazy = Lazy::new(load_config); diff --git a/packages/backend-rs/src/database/mod.rs b/packages/backend-rs/src/database/mod.rs index f598e35cc7..7a6277068b 100644 --- a/packages/backend-rs/src/database/mod.rs +++ b/packages/backend-rs/src/database/mod.rs @@ -1,34 +1,6 @@ -use crate::config::server::SERVER_CONFIG; -use sea_orm::{Database, DbConn, DbErr}; +pub use postgresql::db_conn; +pub use redis::key as redis_key; +pub use redis::redis_conn; -static DB_CONN: once_cell::sync::OnceCell = once_cell::sync::OnceCell::new(); - -async fn init_database() -> Result<&'static DbConn, DbErr> { - let database_uri = format!( - "postgres://{}:{}@{}:{}/{}", - SERVER_CONFIG.db.user, - urlencoding::encode(&SERVER_CONFIG.db.pass), - SERVER_CONFIG.db.host, - SERVER_CONFIG.db.port, - SERVER_CONFIG.db.db, - ); - let conn = Database::connect(database_uri).await?; - Ok(DB_CONN.get_or_init(move || conn)) -} - -pub async fn db_conn() -> Result<&'static DbConn, DbErr> { - match DB_CONN.get() { - Some(conn) => Ok(conn), - None => init_database().await, - } -} - -#[cfg(test)] -mod unit_test { - use super::db_conn; - - #[tokio::test] - async fn connect_test() { - assert!(db_conn().await.is_ok()); - } -} +pub mod postgresql; +pub mod redis; diff --git a/packages/backend-rs/src/database/postgresql.rs b/packages/backend-rs/src/database/postgresql.rs new file mode 100644 index 0000000000..ec0945fbe3 --- /dev/null +++ b/packages/backend-rs/src/database/postgresql.rs @@ -0,0 +1,35 @@ +use crate::config::CONFIG; +use sea_orm::{Database, DbConn, DbErr}; + +static DB_CONN: once_cell::sync::OnceCell = once_cell::sync::OnceCell::new(); + +async fn init_database() -> Result<&'static DbConn, DbErr> { + let database_uri = format!( + "postgres://{}:{}@{}:{}/{}", + CONFIG.db.user, + urlencoding::encode(&CONFIG.db.pass), + CONFIG.db.host, + CONFIG.db.port, + CONFIG.db.db, + ); + let conn = Database::connect(database_uri).await?; + Ok(DB_CONN.get_or_init(move || conn)) +} + +pub async fn db_conn() -> Result<&'static DbConn, DbErr> { + match DB_CONN.get() { + Some(conn) => Ok(conn), + None => init_database().await, + } +} + +#[cfg(test)] +mod unit_test { + use super::db_conn; + + #[tokio::test] + async fn connect() { + assert!(db_conn().await.is_ok()); + assert!(db_conn().await.is_ok()); + } +} diff --git a/packages/backend-rs/src/database/redis.rs b/packages/backend-rs/src/database/redis.rs new file mode 100644 index 0000000000..f8f6c712de --- /dev/null +++ b/packages/backend-rs/src/database/redis.rs @@ -0,0 +1,68 @@ +use crate::config::CONFIG; +use redis::{Client, Connection, RedisError}; + +static REDIS_CLIENT: once_cell::sync::OnceCell = once_cell::sync::OnceCell::new(); + +fn init_redis() -> Result { + let redis_url = { + let mut params = vec!["redis://".to_owned()]; + + let redis = if let Some(cache_server) = &CONFIG.cache_server { + cache_server + } else { + &CONFIG.redis + }; + + if let Some(user) = &redis.user { + params.push(user.to_string()) + } + if let Some(pass) = &redis.pass { + params.push(format!(":{}@", pass)) + } + params.push(redis.host.to_string()); + params.push(format!(":{}", redis.port)); + params.push(format!("/{}", redis.db)); + + params.concat() + }; + + Client::open(redis_url) +} + +pub fn redis_conn() -> Result { + match REDIS_CLIENT.get() { + Some(client) => Ok(client.get_connection()?), + None => init_redis()?.get_connection(), + } +} + +#[inline] +/// prefix redis key +pub fn key(key: impl ToString) -> String { + format!("{}:{}", CONFIG.redis_key_prefix, key.to_string()) +} + +#[cfg(test)] +mod unit_test { + use super::redis_conn; + use pretty_assertions::assert_eq; + use redis::Commands; + + #[test] + fn connect() { + assert!(redis_conn().is_ok()); + assert!(redis_conn().is_ok()); + } + + #[test] + fn access() { + let mut redis = redis_conn().unwrap(); + + let key = "CARGO_UNIT_TEST_KEY"; + let value = "CARGO_UNIT_TEST_VALUE"; + + assert_eq!(redis.set::<&str, &str, String>(key, value).unwrap(), "OK"); + assert_eq!(redis.get::<&str, String>(key).unwrap(), value); + assert_eq!(redis.del::<&str, u32>(key).unwrap(), 1); + } +} diff --git a/packages/backend-rs/src/lib.rs b/packages/backend-rs/src/lib.rs index bef7a41808..0dcc4e6251 100644 --- a/packages/backend-rs/src/lib.rs +++ b/packages/backend-rs/src/lib.rs @@ -4,4 +4,5 @@ pub mod config; pub mod database; pub mod misc; pub mod model; +pub mod service; pub mod util; diff --git a/packages/backend-rs/src/misc/check_server_block.rs b/packages/backend-rs/src/misc/check_server_block.rs new file mode 100644 index 0000000000..b5e262aa87 --- /dev/null +++ b/packages/backend-rs/src/misc/check_server_block.rs @@ -0,0 +1,49 @@ +use crate::misc::meta::fetch_meta; +use sea_orm::DbErr; + +/** + * @param host punycoded instance host + * @returns whether the given host should be blocked + */ +#[crate::export] +pub async fn is_blocked_server(host: &str) -> Result { + Ok(fetch_meta(true) + .await? + .blocked_hosts + .iter() + .any(|blocked_host| { + host == blocked_host || host.ends_with(format!(".{}", blocked_host).as_str()) + })) +} + +/** + * @param host punycoded instance host + * @returns whether the given host should be limited + */ +#[crate::export] +pub async fn is_silenced_server(host: &str) -> Result { + Ok(fetch_meta(true) + .await? + .silenced_hosts + .iter() + .any(|silenced_host| { + host == silenced_host || host.ends_with(format!(".{}", silenced_host).as_str()) + })) +} + +/** + * @param host punycoded instance host + * @returns whether the given host is allowlisted (this is always true if private mode is disabled) + */ +#[crate::export] +pub async fn is_allowed_server(host: &str) -> Result { + let meta = fetch_meta(true).await?; + + if !meta.private_mode.unwrap_or(false) { + return Ok(true); + } + if let Some(allowed_hosts) = meta.allowed_hosts { + return Ok(allowed_hosts.contains(&host.to_string())); + } + Ok(false) +} diff --git a/packages/backend-rs/src/misc/check_word_mute.rs b/packages/backend-rs/src/misc/check_word_mute.rs index 801175c2af..18b550c29b 100644 --- a/packages/backend-rs/src/misc/check_word_mute.rs +++ b/packages/backend-rs/src/misc/check_word_mute.rs @@ -4,7 +4,8 @@ use once_cell::sync::Lazy; use regex::Regex; use sea_orm::{prelude::*, QuerySelect}; -#[crate::export(object)] +/// TODO: handle name collisions better +#[crate::export(object, js_name = "NoteLikeForCheckWordMute")] pub struct NoteLike { pub file_ids: Vec, pub user_id: Option, diff --git a/packages/backend-rs/src/misc/convert_host.rs b/packages/backend-rs/src/misc/convert_host.rs index 34a0792c62..8f054488de 100644 --- a/packages/backend-rs/src/misc/convert_host.rs +++ b/packages/backend-rs/src/misc/convert_host.rs @@ -1,4 +1,4 @@ -use crate::config::server::SERVER_CONFIG; +use crate::config::CONFIG; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -14,21 +14,21 @@ pub enum Error { pub fn get_full_ap_account(username: &str, host: Option<&str>) -> Result { Ok(match host { Some(host) => format!("{}@{}", username, to_puny(host)?), - None => format!("{}@{}", username, extract_host(&SERVER_CONFIG.url)?), + None => format!("{}@{}", username, extract_host(&CONFIG.url)?), }) } #[crate::export] pub fn is_self_host(host: Option<&str>) -> Result { Ok(match host { - Some(host) => extract_host(&SERVER_CONFIG.url)? == to_puny(host)?, + Some(host) => extract_host(&CONFIG.url)? == to_puny(host)?, None => true, }) } #[crate::export] pub fn is_same_origin(uri: &str) -> Result { - Ok(url::Url::parse(uri)?.origin().ascii_serialization() == SERVER_CONFIG.url) + Ok(url::Url::parse(uri)?.origin().ascii_serialization() == CONFIG.url) } #[crate::export] diff --git a/packages/backend-rs/src/misc/get_note_summary.rs b/packages/backend-rs/src/misc/get_note_summary.rs new file mode 100644 index 0000000000..3b759b04f5 --- /dev/null +++ b/packages/backend-rs/src/misc/get_note_summary.rs @@ -0,0 +1,90 @@ +/// TODO: handle name collisions better +#[crate::export(object, js_name = "NoteLikeForGetNoteSummary")] +pub struct NoteLike { + pub file_ids: Vec, + pub text: Option, + pub cw: Option, + pub has_poll: bool, +} + +#[crate::export] +pub fn get_note_summary(note: NoteLike) -> String { + let mut buf: Vec = vec![]; + + if let Some(cw) = note.cw { + buf.push(cw) + } else if let Some(text) = note.text { + buf.push(text) + } + + match note.file_ids.len() { + 0 => (), + 1 => buf.push("📎".to_string()), + n => buf.push(format!("📎 ({})", n)), + }; + + if note.has_poll { + buf.push("📊".to_string()) + } + + buf.join(" ") +} + +#[cfg(test)] +mod unit_test { + use super::{get_note_summary, NoteLike}; + use pretty_assertions::assert_eq; + + #[test] + fn test_note_summary() { + let note = NoteLike { + file_ids: vec![], + text: Some("Hello world!".to_string()), + cw: None, + has_poll: false, + }; + assert_eq!(get_note_summary(note), "Hello world!"); + + let note_with_cw = NoteLike { + file_ids: vec![], + text: Some("Hello world!".to_string()), + cw: Some("Content warning".to_string()), + has_poll: false, + }; + assert_eq!(get_note_summary(note_with_cw), "Content warning"); + + let note_with_file_and_cw = NoteLike { + file_ids: vec!["9s7fmcqogiq4igin".to_string()], + text: None, + cw: Some("Selfie, no ec".to_string()), + has_poll: false, + }; + assert_eq!(get_note_summary(note_with_file_and_cw), "Selfie, no ec 📎"); + + let note_with_files_only = NoteLike { + file_ids: vec![ + "9s7fmcqogiq4igin".to_string(), + "9s7qrld5u14cey98".to_string(), + "9s7gebs5zgts4kca".to_string(), + "9s5z3e4vefqd29ee".to_string(), + ], + text: None, + cw: None, + has_poll: false, + }; + assert_eq!(get_note_summary(note_with_files_only), "📎 (4)"); + + let note_all = NoteLike { + file_ids: vec![ + "9s7fmcqogiq4igin".to_string(), + "9s7qrld5u14cey98".to_string(), + "9s7gebs5zgts4kca".to_string(), + "9s5z3e4vefqd29ee".to_string(), + ], + text: Some("Hello world!".to_string()), + cw: Some("Content warning".to_string()), + has_poll: true, + }; + assert_eq!(get_note_summary(note_all), "Content warning 📎 (4) 📊"); + } +} diff --git a/packages/backend-rs/src/misc/mod.rs b/packages/backend-rs/src/misc/mod.rs index 45fd31cdcd..56aae3b552 100644 --- a/packages/backend-rs/src/misc/mod.rs +++ b/packages/backend-rs/src/misc/mod.rs @@ -1,9 +1,11 @@ pub mod acct; +pub mod check_server_block; pub mod check_word_mute; pub mod convert_host; pub mod emoji; pub mod escape_sql; pub mod format_milliseconds; +pub mod get_note_summary; pub mod mastodon_id; pub mod meta; pub mod nyaify; diff --git a/packages/backend-rs/src/model/entity/abuse_user_report.rs b/packages/backend-rs/src/model/entity/abuse_user_report.rs index 4d781f06dd..e12b2f4d31 100644 --- a/packages/backend-rs/src/model/entity/abuse_user_report.rs +++ b/packages/backend-rs/src/model/entity/abuse_user_report.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "abuse_user_report")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/access_token.rs b/packages/backend-rs/src/model/entity/access_token.rs index 3083bb8c90..8b79877b8b 100644 --- a/packages/backend-rs/src/model/entity/access_token.rs +++ b/packages/backend-rs/src/model/entity/access_token.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "access_token")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/ad.rs b/packages/backend-rs/src/model/entity/ad.rs index 98ec50ea91..8a7b9abb86 100644 --- a/packages/backend-rs/src/model/entity/ad.rs +++ b/packages/backend-rs/src/model/entity/ad.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "ad")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/announcement.rs b/packages/backend-rs/src/model/entity/announcement.rs index 311571c113..4532614a24 100644 --- a/packages/backend-rs/src/model/entity/announcement.rs +++ b/packages/backend-rs/src/model/entity/announcement.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "announcement")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/announcement_read.rs b/packages/backend-rs/src/model/entity/announcement_read.rs index 157e402aa6..de54dae082 100644 --- a/packages/backend-rs/src/model/entity/announcement_read.rs +++ b/packages/backend-rs/src/model/entity/announcement_read.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "announcement_read")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/antenna.rs b/packages/backend-rs/src/model/entity/antenna.rs index 1edd6e8761..4074f5f106 100644 --- a/packages/backend-rs/src/model/entity/antenna.rs +++ b/packages/backend-rs/src/model/entity/antenna.rs @@ -3,7 +3,8 @@ use super::sea_orm_active_enums::AntennaSrcEnum; use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "antenna")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/antenna_note.rs b/packages/backend-rs/src/model/entity/antenna_note.rs deleted file mode 100644 index c86fb349d4..0000000000 --- a/packages/backend-rs/src/model/entity/antenna_note.rs +++ /dev/null @@ -1,49 +0,0 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3 - -use sea_orm::entity::prelude::*; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)] -#[sea_orm(table_name = "antenna_note")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub id: String, - #[sea_orm(column_name = "noteId")] - pub note_id: String, - #[sea_orm(column_name = "antennaId")] - pub antenna_id: String, - pub read: bool, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm( - belongs_to = "super::antenna::Entity", - from = "Column::AntennaId", - to = "super::antenna::Column::Id", - on_update = "NoAction", - on_delete = "Cascade" - )] - Antenna, - #[sea_orm( - belongs_to = "super::note::Entity", - from = "Column::NoteId", - to = "super::note::Column::Id", - on_update = "NoAction", - on_delete = "Cascade" - )] - Note, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::Antenna.def() - } -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::Note.def() - } -} - -impl ActiveModelBehavior for ActiveModel {} diff --git a/packages/backend-rs/src/model/entity/app.rs b/packages/backend-rs/src/model/entity/app.rs index d9517ed211..8d2aa5373a 100644 --- a/packages/backend-rs/src/model/entity/app.rs +++ b/packages/backend-rs/src/model/entity/app.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "app")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/attestation_challenge.rs b/packages/backend-rs/src/model/entity/attestation_challenge.rs index 0c131066dd..999952943d 100644 --- a/packages/backend-rs/src/model/entity/attestation_challenge.rs +++ b/packages/backend-rs/src/model/entity/attestation_challenge.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "attestation_challenge")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/auth_session.rs b/packages/backend-rs/src/model/entity/auth_session.rs index f022a5e632..fac397308d 100644 --- a/packages/backend-rs/src/model/entity/auth_session.rs +++ b/packages/backend-rs/src/model/entity/auth_session.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "auth_session")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/blocking.rs b/packages/backend-rs/src/model/entity/blocking.rs index 2f5a3e0482..d7773108ab 100644 --- a/packages/backend-rs/src/model/entity/blocking.rs +++ b/packages/backend-rs/src/model/entity/blocking.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "blocking")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/channel.rs b/packages/backend-rs/src/model/entity/channel.rs index 52f8059030..4f3ec777ee 100644 --- a/packages/backend-rs/src/model/entity/channel.rs +++ b/packages/backend-rs/src/model/entity/channel.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "channel")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/channel_following.rs b/packages/backend-rs/src/model/entity/channel_following.rs index 01a25ccb83..d45b2c9706 100644 --- a/packages/backend-rs/src/model/entity/channel_following.rs +++ b/packages/backend-rs/src/model/entity/channel_following.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "channel_following")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/channel_note_pining.rs b/packages/backend-rs/src/model/entity/channel_note_pining.rs index 0a41efd3e8..9080c0181e 100644 --- a/packages/backend-rs/src/model/entity/channel_note_pining.rs +++ b/packages/backend-rs/src/model/entity/channel_note_pining.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "channel_note_pining")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/clip.rs b/packages/backend-rs/src/model/entity/clip.rs index 6cbbcf4756..05dc66fa48 100644 --- a/packages/backend-rs/src/model/entity/clip.rs +++ b/packages/backend-rs/src/model/entity/clip.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "clip")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/clip_note.rs b/packages/backend-rs/src/model/entity/clip_note.rs index 262f4a9b50..a76b5d5d8a 100644 --- a/packages/backend-rs/src/model/entity/clip_note.rs +++ b/packages/backend-rs/src/model/entity/clip_note.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "clip_note")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/drive_file.rs b/packages/backend-rs/src/model/entity/drive_file.rs index e3e4622a62..699fa182a5 100644 --- a/packages/backend-rs/src/model/entity/drive_file.rs +++ b/packages/backend-rs/src/model/entity/drive_file.rs @@ -1,8 +1,10 @@ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 +use super::sea_orm_active_enums::DriveFileUsageHintEnum; use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "drive_file")] #[cfg_attr( feature = "napi", @@ -52,6 +54,8 @@ pub struct Model { pub request_headers: Option, #[sea_orm(column_name = "requestIp")] pub request_ip: Option, + #[sea_orm(column_name = "usageHint")] + pub usage_hint: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/packages/backend-rs/src/model/entity/drive_folder.rs b/packages/backend-rs/src/model/entity/drive_folder.rs index 727a698ce3..e4f6fccb6f 100644 --- a/packages/backend-rs/src/model/entity/drive_folder.rs +++ b/packages/backend-rs/src/model/entity/drive_folder.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "drive_folder")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/emoji.rs b/packages/backend-rs/src/model/entity/emoji.rs index 530dfe12f5..374781c4d2 100644 --- a/packages/backend-rs/src/model/entity/emoji.rs +++ b/packages/backend-rs/src/model/entity/emoji.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "emoji")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/follow_request.rs b/packages/backend-rs/src/model/entity/follow_request.rs index 4a6b572433..332eeed204 100644 --- a/packages/backend-rs/src/model/entity/follow_request.rs +++ b/packages/backend-rs/src/model/entity/follow_request.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "follow_request")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/following.rs b/packages/backend-rs/src/model/entity/following.rs index f19e46140e..6f3255ecc9 100644 --- a/packages/backend-rs/src/model/entity/following.rs +++ b/packages/backend-rs/src/model/entity/following.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "following")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/gallery_like.rs b/packages/backend-rs/src/model/entity/gallery_like.rs index f4ede4169a..db519bc91b 100644 --- a/packages/backend-rs/src/model/entity/gallery_like.rs +++ b/packages/backend-rs/src/model/entity/gallery_like.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "gallery_like")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/gallery_post.rs b/packages/backend-rs/src/model/entity/gallery_post.rs index 797bf38242..5492417911 100644 --- a/packages/backend-rs/src/model/entity/gallery_post.rs +++ b/packages/backend-rs/src/model/entity/gallery_post.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "gallery_post")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/hashtag.rs b/packages/backend-rs/src/model/entity/hashtag.rs index e2bb816b26..56b9314e08 100644 --- a/packages/backend-rs/src/model/entity/hashtag.rs +++ b/packages/backend-rs/src/model/entity/hashtag.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "hashtag")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/instance.rs b/packages/backend-rs/src/model/entity/instance.rs index 8b598f72b1..839c6206df 100644 --- a/packages/backend-rs/src/model/entity/instance.rs +++ b/packages/backend-rs/src/model/entity/instance.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "instance")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/messaging_message.rs b/packages/backend-rs/src/model/entity/messaging_message.rs index 304f876d1a..b5f5d818a0 100644 --- a/packages/backend-rs/src/model/entity/messaging_message.rs +++ b/packages/backend-rs/src/model/entity/messaging_message.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "messaging_message")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/meta.rs b/packages/backend-rs/src/model/entity/meta.rs index b9a89914bd..3bf205d040 100644 --- a/packages/backend-rs/src/model/entity/meta.rs +++ b/packages/backend-rs/src/model/entity/meta.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "meta")] #[cfg_attr( feature = "napi", @@ -173,6 +174,8 @@ pub struct Model { pub more_urls: Json, #[sea_orm(column_name = "markLocalFilesNsfwByDefault")] pub mark_local_files_nsfw_by_default: bool, + #[sea_orm(column_name = "antennaLimit")] + pub antenna_limit: i32, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/packages/backend-rs/src/model/entity/migrations.rs b/packages/backend-rs/src/model/entity/migrations.rs index 235156ba4e..f0d55ba123 100644 --- a/packages/backend-rs/src/model/entity/migrations.rs +++ b/packages/backend-rs/src/model/entity/migrations.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "migrations")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/moderation_log.rs b/packages/backend-rs/src/model/entity/moderation_log.rs index b76f2c33df..e51ac4cc0d 100644 --- a/packages/backend-rs/src/model/entity/moderation_log.rs +++ b/packages/backend-rs/src/model/entity/moderation_log.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "moderation_log")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/muted_note.rs b/packages/backend-rs/src/model/entity/muted_note.rs index 7c2880a03d..b1345565b7 100644 --- a/packages/backend-rs/src/model/entity/muted_note.rs +++ b/packages/backend-rs/src/model/entity/muted_note.rs @@ -3,7 +3,8 @@ use super::sea_orm_active_enums::MutedNoteReasonEnum; use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "muted_note")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/muting.rs b/packages/backend-rs/src/model/entity/muting.rs index 917e6a2e20..b9850b3a42 100644 --- a/packages/backend-rs/src/model/entity/muting.rs +++ b/packages/backend-rs/src/model/entity/muting.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "muting")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/note.rs b/packages/backend-rs/src/model/entity/note.rs index cb82f3d94a..85947ea35d 100644 --- a/packages/backend-rs/src/model/entity/note.rs +++ b/packages/backend-rs/src/model/entity/note.rs @@ -3,7 +3,8 @@ use super::sea_orm_active_enums::NoteVisibilityEnum; use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "note")] #[cfg_attr( feature = "napi", @@ -21,6 +22,7 @@ pub struct Model { #[sea_orm(column_type = "Text", nullable)] pub text: Option, pub name: Option, + #[sea_orm(column_type = "Text", nullable)] pub cw: Option, #[sea_orm(column_name = "userId")] pub user_id: String, diff --git a/packages/backend-rs/src/model/entity/note_edit.rs b/packages/backend-rs/src/model/entity/note_edit.rs index edfb35029a..0fb10b82a8 100644 --- a/packages/backend-rs/src/model/entity/note_edit.rs +++ b/packages/backend-rs/src/model/entity/note_edit.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "note_edit")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/note_favorite.rs b/packages/backend-rs/src/model/entity/note_favorite.rs index 76d45e9e98..12e5c641b1 100644 --- a/packages/backend-rs/src/model/entity/note_favorite.rs +++ b/packages/backend-rs/src/model/entity/note_favorite.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "note_favorite")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/note_file.rs b/packages/backend-rs/src/model/entity/note_file.rs index 2c52a4e5e8..9d13c7506d 100644 --- a/packages/backend-rs/src/model/entity/note_file.rs +++ b/packages/backend-rs/src/model/entity/note_file.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "note_file")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/note_reaction.rs b/packages/backend-rs/src/model/entity/note_reaction.rs index dd870a3e7f..9d5de3fab4 100644 --- a/packages/backend-rs/src/model/entity/note_reaction.rs +++ b/packages/backend-rs/src/model/entity/note_reaction.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "note_reaction")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/note_thread_muting.rs b/packages/backend-rs/src/model/entity/note_thread_muting.rs index 965001189e..fbbb30e948 100644 --- a/packages/backend-rs/src/model/entity/note_thread_muting.rs +++ b/packages/backend-rs/src/model/entity/note_thread_muting.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "note_thread_muting")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/note_unread.rs b/packages/backend-rs/src/model/entity/note_unread.rs index ba96aed69f..2b74e3a63c 100644 --- a/packages/backend-rs/src/model/entity/note_unread.rs +++ b/packages/backend-rs/src/model/entity/note_unread.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "note_unread")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/note_watching.rs b/packages/backend-rs/src/model/entity/note_watching.rs index 5b3b331e90..817431daaa 100644 --- a/packages/backend-rs/src/model/entity/note_watching.rs +++ b/packages/backend-rs/src/model/entity/note_watching.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "note_watching")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/notification.rs b/packages/backend-rs/src/model/entity/notification.rs index 6ed5f718aa..e2273017e5 100644 --- a/packages/backend-rs/src/model/entity/notification.rs +++ b/packages/backend-rs/src/model/entity/notification.rs @@ -3,7 +3,8 @@ use super::sea_orm_active_enums::NotificationTypeEnum; use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "notification")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/page.rs b/packages/backend-rs/src/model/entity/page.rs index e1652a372b..a88b53459d 100644 --- a/packages/backend-rs/src/model/entity/page.rs +++ b/packages/backend-rs/src/model/entity/page.rs @@ -3,7 +3,8 @@ use super::sea_orm_active_enums::PageVisibilityEnum; use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "page")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/page_like.rs b/packages/backend-rs/src/model/entity/page_like.rs index c1e59ef0e1..7f7caabde5 100644 --- a/packages/backend-rs/src/model/entity/page_like.rs +++ b/packages/backend-rs/src/model/entity/page_like.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "page_like")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/password_reset_request.rs b/packages/backend-rs/src/model/entity/password_reset_request.rs index 058eaedebe..de78019087 100644 --- a/packages/backend-rs/src/model/entity/password_reset_request.rs +++ b/packages/backend-rs/src/model/entity/password_reset_request.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "password_reset_request")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/poll.rs b/packages/backend-rs/src/model/entity/poll.rs index 6bcbcac08b..2e65674b15 100644 --- a/packages/backend-rs/src/model/entity/poll.rs +++ b/packages/backend-rs/src/model/entity/poll.rs @@ -3,7 +3,8 @@ use super::sea_orm_active_enums::PollNotevisibilityEnum; use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "poll")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/poll_vote.rs b/packages/backend-rs/src/model/entity/poll_vote.rs index 0f2df2f5db..47e68084cc 100644 --- a/packages/backend-rs/src/model/entity/poll_vote.rs +++ b/packages/backend-rs/src/model/entity/poll_vote.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "poll_vote")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/promo_note.rs b/packages/backend-rs/src/model/entity/promo_note.rs index c4e012b1a9..8b13b8987b 100644 --- a/packages/backend-rs/src/model/entity/promo_note.rs +++ b/packages/backend-rs/src/model/entity/promo_note.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "promo_note")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/promo_read.rs b/packages/backend-rs/src/model/entity/promo_read.rs index 10fe176405..3e6d822d29 100644 --- a/packages/backend-rs/src/model/entity/promo_read.rs +++ b/packages/backend-rs/src/model/entity/promo_read.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "promo_read")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/registration_ticket.rs b/packages/backend-rs/src/model/entity/registration_ticket.rs index 55d60d78b0..ab735b2422 100644 --- a/packages/backend-rs/src/model/entity/registration_ticket.rs +++ b/packages/backend-rs/src/model/entity/registration_ticket.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "registration_ticket")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/registry_item.rs b/packages/backend-rs/src/model/entity/registry_item.rs index 74ae18cef3..cb129128b7 100644 --- a/packages/backend-rs/src/model/entity/registry_item.rs +++ b/packages/backend-rs/src/model/entity/registry_item.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "registry_item")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/relay.rs b/packages/backend-rs/src/model/entity/relay.rs index c035a74bec..f2eeb571f4 100644 --- a/packages/backend-rs/src/model/entity/relay.rs +++ b/packages/backend-rs/src/model/entity/relay.rs @@ -3,7 +3,8 @@ use super::sea_orm_active_enums::RelayStatusEnum; use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "relay")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/renote_muting.rs b/packages/backend-rs/src/model/entity/renote_muting.rs index b6604cad88..308e4c1563 100644 --- a/packages/backend-rs/src/model/entity/renote_muting.rs +++ b/packages/backend-rs/src/model/entity/renote_muting.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "renote_muting")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/reply_muting.rs b/packages/backend-rs/src/model/entity/reply_muting.rs index 84fe0cbbfa..77891d1ee2 100644 --- a/packages/backend-rs/src/model/entity/reply_muting.rs +++ b/packages/backend-rs/src/model/entity/reply_muting.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "reply_muting")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/sea_orm_active_enums.rs b/packages/backend-rs/src/model/entity/sea_orm_active_enums.rs index 861b3a18d0..a9c974b9c4 100644 --- a/packages/backend-rs/src/model/entity/sea_orm_active_enums.rs +++ b/packages/backend-rs/src/model/entity/sea_orm_active_enums.rs @@ -2,9 +2,12 @@ use sea_orm::entity::prelude::*; -#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)] +#[derive( + Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, serde::Serialize, serde::Deserialize, +)] +#[serde(rename_all = "camelCase")] #[cfg_attr(not(feature = "napi"), derive(Clone))] -#[cfg_attr(feature = "napi", napi_derive::napi)] +#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))] #[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "antenna_src_enum")] pub enum AntennaSrcEnum { #[sea_orm(string_value = "all")] @@ -20,9 +23,29 @@ pub enum AntennaSrcEnum { #[sea_orm(string_value = "users")] Users, } -#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)] +#[derive( + Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, serde::Serialize, serde::Deserialize, +)] +#[serde(rename_all = "camelCase")] #[cfg_attr(not(feature = "napi"), derive(Clone))] -#[cfg_attr(feature = "napi", napi_derive::napi)] +#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))] +#[sea_orm( + rs_type = "String", + db_type = "Enum", + enum_name = "drive_file_usage_hint_enum" +)] +pub enum DriveFileUsageHintEnum { + #[sea_orm(string_value = "userAvatar")] + UserAvatar, + #[sea_orm(string_value = "userBanner")] + UserBanner, +} +#[derive( + Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, serde::Serialize, serde::Deserialize, +)] +#[serde(rename_all = "camelCase")] +#[cfg_attr(not(feature = "napi"), derive(Clone))] +#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))] #[sea_orm( rs_type = "String", db_type = "Enum", @@ -38,9 +61,12 @@ pub enum MutedNoteReasonEnum { #[sea_orm(string_value = "word")] Word, } -#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)] +#[derive( + Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, serde::Serialize, serde::Deserialize, +)] +#[serde(rename_all = "camelCase")] #[cfg_attr(not(feature = "napi"), derive(Clone))] -#[cfg_attr(feature = "napi", napi_derive::napi)] +#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))] #[sea_orm( rs_type = "String", db_type = "Enum", @@ -58,9 +84,12 @@ pub enum NoteVisibilityEnum { #[sea_orm(string_value = "specified")] Specified, } -#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)] +#[derive( + Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, serde::Serialize, serde::Deserialize, +)] +#[serde(rename_all = "camelCase")] #[cfg_attr(not(feature = "napi"), derive(Clone))] -#[cfg_attr(feature = "napi", napi_derive::napi)] +#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))] #[sea_orm( rs_type = "String", db_type = "Enum", @@ -92,9 +121,12 @@ pub enum NotificationTypeEnum { #[sea_orm(string_value = "reply")] Reply, } -#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)] +#[derive( + Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, serde::Serialize, serde::Deserialize, +)] +#[serde(rename_all = "camelCase")] #[cfg_attr(not(feature = "napi"), derive(Clone))] -#[cfg_attr(feature = "napi", napi_derive::napi)] +#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))] #[sea_orm( rs_type = "String", db_type = "Enum", @@ -108,9 +140,12 @@ pub enum PageVisibilityEnum { #[sea_orm(string_value = "specified")] Specified, } -#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)] +#[derive( + Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, serde::Serialize, serde::Deserialize, +)] +#[serde(rename_all = "camelCase")] #[cfg_attr(not(feature = "napi"), derive(Clone))] -#[cfg_attr(feature = "napi", napi_derive::napi)] +#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))] #[sea_orm( rs_type = "String", db_type = "Enum", @@ -126,9 +161,12 @@ pub enum PollNotevisibilityEnum { #[sea_orm(string_value = "specified")] Specified, } -#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)] +#[derive( + Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, serde::Serialize, serde::Deserialize, +)] +#[serde(rename_all = "camelCase")] #[cfg_attr(not(feature = "napi"), derive(Clone))] -#[cfg_attr(feature = "napi", napi_derive::napi)] +#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))] #[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "relay_status_enum")] pub enum RelayStatusEnum { #[sea_orm(string_value = "accepted")] @@ -138,9 +176,12 @@ pub enum RelayStatusEnum { #[sea_orm(string_value = "requesting")] Requesting, } -#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)] +#[derive( + Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, serde::Serialize, serde::Deserialize, +)] +#[serde(rename_all = "camelCase")] #[cfg_attr(not(feature = "napi"), derive(Clone))] -#[cfg_attr(feature = "napi", napi_derive::napi)] +#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))] #[sea_orm( rs_type = "String", db_type = "Enum", @@ -156,9 +197,12 @@ pub enum UserEmojimodpermEnum { #[sea_orm(string_value = "unauthorized")] Unauthorized, } -#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)] +#[derive( + Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, serde::Serialize, serde::Deserialize, +)] +#[serde(rename_all = "camelCase")] #[cfg_attr(not(feature = "napi"), derive(Clone))] -#[cfg_attr(feature = "napi", napi_derive::napi)] +#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))] #[sea_orm( rs_type = "String", db_type = "Enum", @@ -172,9 +216,12 @@ pub enum UserProfileFfvisibilityEnum { #[sea_orm(string_value = "public")] Public, } -#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)] +#[derive( + Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, serde::Serialize, serde::Deserialize, +)] +#[serde(rename_all = "camelCase")] #[cfg_attr(not(feature = "napi"), derive(Clone))] -#[cfg_attr(feature = "napi", napi_derive::napi)] +#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))] #[sea_orm( rs_type = "String", db_type = "Enum", diff --git a/packages/backend-rs/src/model/entity/signin.rs b/packages/backend-rs/src/model/entity/signin.rs index 491f079a82..d5584f9d84 100644 --- a/packages/backend-rs/src/model/entity/signin.rs +++ b/packages/backend-rs/src/model/entity/signin.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "signin")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/sw_subscription.rs b/packages/backend-rs/src/model/entity/sw_subscription.rs index 4aa275f77b..fe694177f3 100644 --- a/packages/backend-rs/src/model/entity/sw_subscription.rs +++ b/packages/backend-rs/src/model/entity/sw_subscription.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "sw_subscription")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/used_username.rs b/packages/backend-rs/src/model/entity/used_username.rs index 9e108a8a4f..8557955034 100644 --- a/packages/backend-rs/src/model/entity/used_username.rs +++ b/packages/backend-rs/src/model/entity/used_username.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "used_username")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/user.rs b/packages/backend-rs/src/model/entity/user.rs index 8af0b27e88..f8a2b09324 100644 --- a/packages/backend-rs/src/model/entity/user.rs +++ b/packages/backend-rs/src/model/entity/user.rs @@ -3,7 +3,8 @@ use super::sea_orm_active_enums::UserEmojimodpermEnum; use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "user")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/user_group.rs b/packages/backend-rs/src/model/entity/user_group.rs index 7d9ae71b24..387c50d422 100644 --- a/packages/backend-rs/src/model/entity/user_group.rs +++ b/packages/backend-rs/src/model/entity/user_group.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "user_group")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/user_group_invitation.rs b/packages/backend-rs/src/model/entity/user_group_invitation.rs index c449a48078..289acf89df 100644 --- a/packages/backend-rs/src/model/entity/user_group_invitation.rs +++ b/packages/backend-rs/src/model/entity/user_group_invitation.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "user_group_invitation")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/user_group_invite.rs b/packages/backend-rs/src/model/entity/user_group_invite.rs index 3df43af3f6..62fcf0c524 100644 --- a/packages/backend-rs/src/model/entity/user_group_invite.rs +++ b/packages/backend-rs/src/model/entity/user_group_invite.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "user_group_invite")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/user_group_joining.rs b/packages/backend-rs/src/model/entity/user_group_joining.rs index 2ff31b3a5f..b29bdd11eb 100644 --- a/packages/backend-rs/src/model/entity/user_group_joining.rs +++ b/packages/backend-rs/src/model/entity/user_group_joining.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "user_group_joining")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/user_ip.rs b/packages/backend-rs/src/model/entity/user_ip.rs index d6eadc7f71..9847fb8d65 100644 --- a/packages/backend-rs/src/model/entity/user_ip.rs +++ b/packages/backend-rs/src/model/entity/user_ip.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "user_ip")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/user_keypair.rs b/packages/backend-rs/src/model/entity/user_keypair.rs index d59853158a..dad29ab3b3 100644 --- a/packages/backend-rs/src/model/entity/user_keypair.rs +++ b/packages/backend-rs/src/model/entity/user_keypair.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "user_keypair")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/user_list.rs b/packages/backend-rs/src/model/entity/user_list.rs index 356ec5318c..30dc6db715 100644 --- a/packages/backend-rs/src/model/entity/user_list.rs +++ b/packages/backend-rs/src/model/entity/user_list.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "user_list")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/user_list_joining.rs b/packages/backend-rs/src/model/entity/user_list_joining.rs index 037d7af181..972cc34bb6 100644 --- a/packages/backend-rs/src/model/entity/user_list_joining.rs +++ b/packages/backend-rs/src/model/entity/user_list_joining.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "user_list_joining")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/user_note_pining.rs b/packages/backend-rs/src/model/entity/user_note_pining.rs index f0a80a66c9..26f05274fd 100644 --- a/packages/backend-rs/src/model/entity/user_note_pining.rs +++ b/packages/backend-rs/src/model/entity/user_note_pining.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "user_note_pining")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/user_pending.rs b/packages/backend-rs/src/model/entity/user_pending.rs index 1fa13c2829..43a5c2f1e2 100644 --- a/packages/backend-rs/src/model/entity/user_pending.rs +++ b/packages/backend-rs/src/model/entity/user_pending.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "user_pending")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/user_profile.rs b/packages/backend-rs/src/model/entity/user_profile.rs index a121118926..644817aa8d 100644 --- a/packages/backend-rs/src/model/entity/user_profile.rs +++ b/packages/backend-rs/src/model/entity/user_profile.rs @@ -4,7 +4,8 @@ use super::sea_orm_active_enums::UserProfileFfvisibilityEnum; use super::sea_orm_active_enums::UserProfileMutingnotificationtypesEnum; use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "user_profile")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/user_publickey.rs b/packages/backend-rs/src/model/entity/user_publickey.rs index ad6a456ead..f8bc8629eb 100644 --- a/packages/backend-rs/src/model/entity/user_publickey.rs +++ b/packages/backend-rs/src/model/entity/user_publickey.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "user_publickey")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/user_security_key.rs b/packages/backend-rs/src/model/entity/user_security_key.rs index aae6dfa8c6..a4694cf177 100644 --- a/packages/backend-rs/src/model/entity/user_security_key.rs +++ b/packages/backend-rs/src/model/entity/user_security_key.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "user_security_key")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/model/entity/webhook.rs b/packages/backend-rs/src/model/entity/webhook.rs index e7656056b9..430cf9fcc0 100644 --- a/packages/backend-rs/src/model/entity/webhook.rs +++ b/packages/backend-rs/src/model/entity/webhook.rs @@ -2,7 +2,8 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] #[sea_orm(table_name = "webhook")] #[cfg_attr( feature = "napi", diff --git a/packages/backend-rs/src/service/add_note_to_antenna.rs b/packages/backend-rs/src/service/add_note_to_antenna.rs new file mode 100644 index 0000000000..4f294cc881 --- /dev/null +++ b/packages/backend-rs/src/service/add_note_to_antenna.rs @@ -0,0 +1,23 @@ +use crate::database::{redis_conn, redis_key}; +use crate::model::entity::note; +use crate::service::stream::{publish_to_stream, Error, Stream}; +use crate::util::id::get_timestamp; +use redis::{streams::StreamMaxlen, Commands}; + +type Note = note::Model; + +#[crate::export] +pub fn add_note_to_antenna(antenna_id: String, note: &Note) -> Result<(), Error> { + redis_conn()?.xadd_maxlen( + redis_key(format!("antennaTimeline:{}", antenna_id)), + StreamMaxlen::Approx(200), + format!("{}-*", get_timestamp(¬e.id)), + &[("note", ¬e.id)], + )?; + + publish_to_stream( + &Stream::Antenna { antenna_id }, + Some("note"), + Some(serde_json::to_string(note)?), + ) +} diff --git a/packages/backend-rs/src/service/mod.rs b/packages/backend-rs/src/service/mod.rs new file mode 100644 index 0000000000..cc239e3f9e --- /dev/null +++ b/packages/backend-rs/src/service/mod.rs @@ -0,0 +1,2 @@ +pub mod add_note_to_antenna; +pub mod stream; diff --git a/packages/backend-rs/src/service/stream.rs b/packages/backend-rs/src/service/stream.rs new file mode 100644 index 0000000000..6c5e6be4dd --- /dev/null +++ b/packages/backend-rs/src/service/stream.rs @@ -0,0 +1,93 @@ +use crate::config::CONFIG; +use crate::database::redis_conn; +use redis::{Commands, RedisError}; + +#[derive(strum::Display)] +pub enum Stream { + #[strum(serialize = "internal")] + Internal, + #[strum(serialize = "broadcast")] + Broadcast, + #[strum(to_string = "adminStream:{user_id}")] + Admin { user_id: String }, + #[strum(to_string = "user:{user_id}")] + User { user_id: String }, + #[strum(to_string = "channelStream:{channel_id}")] + Channel { channel_id: String }, + #[strum(to_string = "noteStream:{note_id}")] + Note { note_id: String }, + #[strum(serialize = "notesStream")] + Notes, + #[strum(to_string = "userListStream:{list_id}")] + UserList { list_id: String }, + #[strum(to_string = "mainStream:{user_id}")] + Main { user_id: String }, + #[strum(to_string = "driveStream:{user_id}")] + Drive { user_id: String }, + #[strum(to_string = "antennaStream:{antenna_id}")] + Antenna { antenna_id: String }, + #[strum(to_string = "messagingStream:{sender_user_id}-{receiver_user_id}")] + Chat { + sender_user_id: String, + receiver_user_id: String, + }, + #[strum(to_string = "messagingStream:{group_id}")] + GroupChat { group_id: String }, + #[strum(to_string = "messagingIndexStream:{user_id}")] + MessagingIndex { user_id: String }, +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("Redis error: {0}")] + RedisError(#[from] RedisError), + #[error("Json (de)serialization error: {0}")] + JsonError(#[from] serde_json::Error), + #[error("Value error: {0}")] + ValueError(String), +} + +pub fn publish_to_stream( + stream: &Stream, + kind: Option<&str>, + value: Option, +) -> Result<(), Error> { + let message = if let Some(kind) = kind { + format!( + "{{ \"type\": \"{}\", \"body\": {} }}", + kind, + value.unwrap_or("null".to_string()), + ) + } else { + value.ok_or(Error::ValueError("Invalid streaming message".to_string()))? + }; + + redis_conn()?.publish( + &CONFIG.host, + format!( + "{{ \"channel\": \"{}\", \"message\": {} }}", + stream, message, + ), + )?; + + Ok(()) +} + +#[cfg(test)] +mod unit_test { + use super::Stream; + use pretty_assertions::assert_eq; + + #[test] + fn channel_to_string() { + assert_eq!(Stream::Internal.to_string(), "internal"); + assert_eq!(Stream::Broadcast.to_string(), "broadcast"); + assert_eq!( + Stream::Admin { + user_id: "9tb42br63g5apjcq".to_string() + } + .to_string(), + "adminStream:9tb42br63g5apjcq" + ); + } +} diff --git a/packages/backend/package.json b/packages/backend/package.json index 9289c2f7ea..0599a39d4b 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -178,6 +178,7 @@ "ts-loader": "9.5.1", "ts-node": "10.9.2", "tsconfig-paths": "4.2.0", + "type-fest": "4.15.0", "typescript": "5.4.5", "webpack": "^5.91.0", "ws": "8.16.0" diff --git a/packages/backend/src/boot/index.ts b/packages/backend/src/boot/index.ts index 4ba24c280b..3e542a6e36 100644 --- a/packages/backend/src/boot/index.ts +++ b/packages/backend/src/boot/index.ts @@ -3,7 +3,7 @@ import chalk from "chalk"; import Xev from "xev"; import Logger from "@/services/logger.js"; -import { envOption } from "../env.js"; +import { envOption } from "@/config.js"; import { inspect } from "node:util"; // for typeorm diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts index cd21c33021..f254aaea17 100644 --- a/packages/backend/src/boot/master.ts +++ b/packages/backend/src/boot/master.ts @@ -8,9 +8,9 @@ import chalkTemplate from "chalk-template"; import semver from "semver"; import Logger from "@/services/logger.js"; -import loadConfig from "@/config/load.js"; -import type { Config } from "@/config/types.js"; -import { envOption } from "@/env.js"; +import type { Config } from "backend-rs"; +import { fetchMeta } from "backend-rs"; +import { config, envOption } from "@/config.js"; import { showMachineInfo } from "@/misc/show-machine-info.js"; import { db, initDb } from "@/db/postgre.js"; import { inspect } from "node:util"; @@ -87,15 +87,12 @@ function greet() { * Init master process */ export async function masterMain() { - let config!: Config; - // initialize app try { greet(); showEnvironment(); await showMachineInfo(bootLogger); showNodejsVersion(); - config = loadConfigBoot(); await connectDb(); } catch (e) { bootLogger.error( @@ -127,6 +124,9 @@ export async function masterMain() { import("../daemons/queue-stats.js").then((x) => x.default()); import("../daemons/janitor.js").then((x) => x.default()); } + + // Update meta cache every 5 minitues + setInterval(() => fetchMeta(false), 1000 * 60 * 5); } function showEnvironment(): void { @@ -154,28 +154,6 @@ function showNodejsVersion(): void { } } -function loadConfigBoot(): Config { - const configLogger = bootLogger.createSubLogger("config"); - let config; - - try { - config = loadConfig(); - } catch (exception) { - if (exception.code === "ENOENT") { - configLogger.error("Configuration file not found", null, true); - process.exit(1); - } else if (e instanceof Error) { - configLogger.error(e.message); - process.exit(1); - } - throw exception; - } - - configLogger.succ("Loaded"); - - return config; -} - async function connectDb(): Promise { const dbLogger = bootLogger.createSubLogger("db"); @@ -195,23 +173,31 @@ async function connectDb(): Promise { } async function spawnWorkers( - clusterLimits: Required, + clusterLimits: Config["clusterLimits"], ): Promise { - const modes = ["web", "queue"]; const cpus = os.cpus().length; - for (const mode of modes.filter((mode) => clusterLimits[mode] > cpus)) { + + if (clusterLimits.queue > cpus) { bootLogger.warn( - `configuration warning: cluster limit for ${mode} exceeds number of cores (${cpus})`, + "config: queue cluster limit exceeds the number of cpu cores", ); } - const total = modes.reduce((acc, mode) => acc + clusterLimits[mode], 0); + if (clusterLimits.web > cpus) { + bootLogger.warn( + "config: web cluster limit exceeds the number of cpu cores", + ); + } + + const total = clusterLimits.queue + clusterLimits.web; + + // workers = ["web", "web", ..., "web", "queue", "queue", ..., "queue"] const workers = new Array(total); - workers.fill("web", 0, clusterLimits?.web); - workers.fill("queue", clusterLimits?.web); + workers.fill("web", 0, clusterLimits.web); + workers.fill("queue", clusterLimits.web); bootLogger.info( - `Starting ${clusterLimits?.web} web workers and ${clusterLimits?.queue} queue workers (total ${total})...`, + `Starting ${clusterLimits.web} web workers and ${clusterLimits.queue} queue workers (total ${total})...`, ); await Promise.all(workers.map((mode) => spawnWorker(mode))); bootLogger.succ("All workers started"); diff --git a/packages/backend/src/boot/worker.ts b/packages/backend/src/boot/worker.ts index 647ea40fbd..cae861230f 100644 --- a/packages/backend/src/boot/worker.ts +++ b/packages/backend/src/boot/worker.ts @@ -1,5 +1,5 @@ import cluster from "node:cluster"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import { initDb } from "@/db/postgre.js"; import { initIdGenerator } from "backend-rs"; import os from "node:os"; diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts new file mode 100644 index 0000000000..c91294b611 --- /dev/null +++ b/packages/backend/src/config.ts @@ -0,0 +1,4 @@ +import { loadConfig, loadEnv } from "backend-rs"; + +export const config = loadConfig(); +export const envOption = loadEnv(); diff --git a/packages/backend/src/config/index.ts b/packages/backend/src/config/index.ts deleted file mode 100644 index ae197b09ca..0000000000 --- a/packages/backend/src/config/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import load from "./load.js"; - -export default load(); diff --git a/packages/backend/src/config/load.ts b/packages/backend/src/config/load.ts deleted file mode 100644 index 2b286b9439..0000000000 --- a/packages/backend/src/config/load.ts +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Config loader - */ - -import * as fs from "node:fs"; -import { fileURLToPath } from "node:url"; -import { dirname } from "node:path"; -import type { Mixin } from "./types.js"; -import { readServerConfig } from "backend-rs"; - -const _filename = fileURLToPath(import.meta.url); -const _dirname = dirname(_filename); - -/** - * Path of configuration directory - */ -const dir = `${_dirname}/../../../../.config`; - -/** - * Path of configuration file - */ -const path = - process.env.NODE_ENV === "test" ? `${dir}/test.yml` : `${dir}/default.yml`; - -export default function load() { - const meta = JSON.parse( - fs.readFileSync(`${_dirname}/../../../../built/meta.json`, "utf-8"), - ); - const clientManifest = JSON.parse( - fs.readFileSync( - `${_dirname}/../../../../built/_client_dist_/manifest.json`, - "utf-8", - ), - ); - const config = readServerConfig(); - - const mixin = {} as Mixin; - - const url = tryCreateUrl(config.url); - - config.url = url.origin; - - config.port = config.port || parseInt(process.env.PORT || "", 10); - config.bind = config.bind || process.env.BIND; - - mixin.version = meta.version; - mixin.host = url.host; - mixin.hostname = url.hostname; - mixin.scheme = url.protocol.replace(/:$/, ""); - mixin.wsScheme = mixin.scheme.replace("http", "ws"); - mixin.wsUrl = `${mixin.wsScheme}://${mixin.host}`; - mixin.apiUrl = `${mixin.scheme}://${mixin.host}/api`; - mixin.authUrl = `${mixin.scheme}://${mixin.host}/auth`; - mixin.driveUrl = `${mixin.scheme}://${mixin.host}/files`; - mixin.userAgent = `Firefish/${meta.version} (${config.url})`; - mixin.clientEntry = clientManifest["src/init.ts"]; - - if (!config.redis.prefix) config.redis.prefix = mixin.hostname; - if (config.cacheServer && !config.cacheServer.prefix) - config.cacheServer.prefix = mixin.hostname; - - if (!config.clusterLimits) { - config.clusterLimits = { - web: 1, - queue: 1, - }; - } else { - config.clusterLimits = { - web: 1, - queue: 1, - ...config.clusterLimits, - }; - - if (config.clusterLimits.web! < 1 || config.clusterLimits.queue! < 1) { - throw new Error("Invalid cluster limits"); - } - } - - return Object.assign(config, mixin); -} - -function tryCreateUrl(url: string) { - try { - return new URL(url); - } catch (e) { - throw new Error(`url="${url}" is not a valid URL.`); - } -} diff --git a/packages/backend/src/config/types.ts b/packages/backend/src/config/types.ts deleted file mode 100644 index 6b593d3b2d..0000000000 --- a/packages/backend/src/config/types.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { ServerConfig } from "backend-rs"; - -/** - * Firefish が自動的に(ユーザーが設定した情報から推論して)設定する情報 - */ -export type Mixin = { - version: string; - host: string; - hostname: string; - scheme: string; - wsScheme: string; - apiUrl: string; - wsUrl: string; - authUrl: string; - driveUrl: string; - userAgent: string; - clientEntry: string; -}; - -export type Config = ServerConfig & Mixin; diff --git a/packages/backend/src/const.ts b/packages/backend/src/const.ts index 39ff99fda7..2c52e2b009 100644 --- a/packages/backend/src/const.ts +++ b/packages/backend/src/const.ts @@ -1,4 +1,4 @@ -import config from "@/config/index.js"; +import { config } from "@/config.js"; import { DB_MAX_IMAGE_COMMENT_LENGTH, DB_MAX_NOTE_TEXT_LENGTH, diff --git a/packages/backend/src/daemons/server-stats.ts b/packages/backend/src/daemons/server-stats.ts index 92265ecba7..df1f9b3032 100644 --- a/packages/backend/src/daemons/server-stats.ts +++ b/packages/backend/src/daemons/server-stats.ts @@ -13,16 +13,15 @@ const round = (num: number) => Math.round(num * 10) / 10; /** * Report server stats regularly */ -export default function () { +export default async function () { const log = [] as any[]; ev.on("requestServerStatsLog", (x) => { ev.emit(`serverStatsLog:${x.id}`, log.slice(0, x.length || 50)); }); - fetchMeta(true).then((meta) => { - if (!meta.enableServerMachineStats) return; - }); + const meta = await fetchMeta(true); + if (!meta.enableServerMachineStats) return; async function tick() { const cpu = await cpuUsage(); diff --git a/packages/backend/src/db/postgre.ts b/packages/backend/src/db/postgre.ts index 19f7e7816a..1295136054 100644 --- a/packages/backend/src/db/postgre.ts +++ b/packages/backend/src/db/postgre.ts @@ -5,7 +5,7 @@ pg.types.setTypeParser(20, Number); import type { Logger } from "typeorm"; import { DataSource } from "typeorm"; import * as highlight from "cli-highlight"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import { User } from "@/models/entities/user.js"; import { DriveFile } from "@/models/entities/drive-file.js"; @@ -77,7 +77,6 @@ import { NoteFile } from "@/models/entities/note-file.js"; import { entities as charts } from "@/services/chart/entities.js"; import { dbLogger } from "./logger.js"; -import { redisClient } from "./redis.js"; const sqlLogger = dbLogger.createSubLogger("sql", "gray", false); @@ -237,31 +236,3 @@ export async function initDb(force = false) { await db.initialize(); } } - -export async function resetDb() { - const reset = async () => { - await redisClient.flushdb(); - const tables = await db.query(`SELECT relname AS "table" - FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) - WHERE nspname NOT IN ('pg_catalog', 'information_schema') - AND C.relkind = 'r' - AND nspname !~ '^pg_toast';`); - for (const table of tables) { - await db.query(`DELETE FROM "${table.table}" CASCADE`); - } - }; - - for (let i = 1; i <= 3; i++) { - try { - await reset(); - } catch (e) { - if (i === 3) { - throw e; - } else { - await new Promise((resolve) => setTimeout(resolve, 1000)); - continue; - } - } - break; - } -} diff --git a/packages/backend/src/db/redis.ts b/packages/backend/src/db/redis.ts index 215effd8ea..72c23d84e2 100644 --- a/packages/backend/src/db/redis.ts +++ b/packages/backend/src/db/redis.ts @@ -1,5 +1,5 @@ import Redis from "ioredis"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; export function createConnection() { let source = config.redis; @@ -12,7 +12,7 @@ export function createConnection() { family: source.family ?? 0, password: source.pass, username: source.user ?? "default", - keyPrefix: `${source.prefix}:`, + keyPrefix: `${config.redisKeyPrefix}:`, db: source.db || 0, tls: source.tls, }); diff --git a/packages/backend/src/env.ts b/packages/backend/src/env.ts deleted file mode 100644 index a10952133e..0000000000 --- a/packages/backend/src/env.ts +++ /dev/null @@ -1,23 +0,0 @@ -const envOption = { - onlyQueue: false, - onlyServer: false, - noDaemons: false, - disableClustering: false, - verbose: false, - withLogTime: false, - slow: false, -}; - -for (const key of Object.keys(envOption) as (keyof typeof envOption)[]) { - if ( - process.env[ - `MK_${key.replace(/[A-Z]/g, (letter) => `_${letter}`).toUpperCase()}` - ] - ) - envOption[key] = true; -} - -if (process.env.NODE_ENV === "test") envOption.disableClustering = true; -if (process.env.NODE_ENV === "test") envOption.noDaemons = true; - -export { envOption }; diff --git a/packages/backend/src/mfm/to-html.ts b/packages/backend/src/mfm/to-html.ts index 7b7c0967a1..64aa9d7164 100644 --- a/packages/backend/src/mfm/to-html.ts +++ b/packages/backend/src/mfm/to-html.ts @@ -1,7 +1,7 @@ import { type HTMLElement, Window } from "happy-dom"; import type * as mfm from "mfm-js"; import katex from "katex"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import { intersperse } from "@/prelude/array.js"; import type { IMentionedRemoteUsers } from "@/models/entities/note.js"; diff --git a/packages/backend/src/migration/1712937600000-antennaLimit.ts b/packages/backend/src/migration/1712937600000-antennaLimit.ts new file mode 100644 index 0000000000..cd8f9ff658 --- /dev/null +++ b/packages/backend/src/migration/1712937600000-antennaLimit.ts @@ -0,0 +1,19 @@ +import type { MigrationInterface, QueryRunner } from "typeorm"; + +export class antennaLimit1712937600000 implements MigrationInterface { + async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "meta" ADD "antennaLimit" integer NOT NULL DEFAULT 5`, + undefined, + ); + await queryRunner.query( + `COMMENT ON COLUMN "meta"."antennaLimit" IS 'Antenna Limit'`, + ); + } + async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "meta" DROP COLUMN "antennaLimit"`, + undefined, + ); + } +} diff --git a/packages/backend/src/migration/1713225866247-convert-cw-varchar-to-text.ts b/packages/backend/src/migration/1713225866247-convert-cw-varchar-to-text.ts new file mode 100644 index 0000000000..93c87a98a8 --- /dev/null +++ b/packages/backend/src/migration/1713225866247-convert-cw-varchar-to-text.ts @@ -0,0 +1,21 @@ +import type { MigrationInterface, QueryRunner } from "typeorm"; + +export class ConvertCwVarcharToText1713225866247 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + queryRunner.query(`DROP INDEX "IDX_8e3bbbeb3df04d1a8105da4c8f"`); + queryRunner.query(`ALTER TABLE "note" ALTER COLUMN "cw" TYPE text`); + queryRunner.query( + `CREATE INDEX "IDX_8e3bbbeb3df04d1a8105da4c8f" ON "note" USING "pgroonga" ("cw")`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + queryRunner.query(`DROP INDEX "IDX_8e3bbbeb3df04d1a8105da4c8f"`); + queryRunner.query( + `ALTER TABLE "note" ALTER COLUMN "cw" TYPE character varying(512)`, + ); + queryRunner.query( + `CREATE INDEX "IDX_8e3bbbeb3df04d1a8105da4c8f" ON "note" USING "pgroonga" ("cw" pgroonga_varchar_full_text_search_ops_v2)`, + ); + } +} diff --git a/packages/backend/src/migration/1713451569342-AddDriveFileUsage.ts b/packages/backend/src/migration/1713451569342-AddDriveFileUsage.ts new file mode 100644 index 0000000000..3bdb1aafc8 --- /dev/null +++ b/packages/backend/src/migration/1713451569342-AddDriveFileUsage.ts @@ -0,0 +1,17 @@ +import type { MigrationInterface, QueryRunner } from "typeorm"; + +export class AddDriveFileUsage1713451569342 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TYPE drive_file_usage_hint_enum AS ENUM ('userAvatar', 'userBanner')`, + ); + await queryRunner.query( + `ALTER TABLE "drive_file" ADD "usageHint" drive_file_usage_hint_enum DEFAULT NULL`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "drive_file" DROP COLUMN "usageHint"`); + await queryRunner.query(`DROP TYPE drive_file_usage_hint_enum`); + } +} diff --git a/packages/backend/src/misc/cache.ts b/packages/backend/src/misc/cache.ts index 30a50e5714..e99b17a5f7 100644 --- a/packages/backend/src/misc/cache.ts +++ b/packages/backend/src/misc/cache.ts @@ -1,6 +1,6 @@ import { redisClient } from "@/db/redis.js"; import { encode, decode } from "msgpackr"; -import { ChainableCommander } from "ioredis"; +import type { ChainableCommander } from "ioredis"; export class Cache { private ttl: number; diff --git a/packages/backend/src/misc/captcha.ts b/packages/backend/src/misc/captcha.ts index c163d4d82d..d0969b0205 100644 --- a/packages/backend/src/misc/captcha.ts +++ b/packages/backend/src/misc/captcha.ts @@ -1,7 +1,7 @@ import fetch from "node-fetch"; import { URLSearchParams } from "node:url"; import { getAgentByUrl } from "@/misc/fetch.js"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import { inspect } from "node:util"; export async function verifyRecaptcha(secret: string, response: string) { diff --git a/packages/backend/src/misc/download-url.ts b/packages/backend/src/misc/download-url.ts index ab04e8aa9c..f516265106 100644 --- a/packages/backend/src/misc/download-url.ts +++ b/packages/backend/src/misc/download-url.ts @@ -2,8 +2,8 @@ import * as fs from "node:fs"; import * as stream from "node:stream"; import * as util from "node:util"; import got, * as Got from "got"; -import { httpAgent, httpsAgent, StatusError } from "./fetch.js"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; +import { getAgentByHostname, StatusError } from "./fetch.js"; import chalk from "chalk"; import Logger from "@/services/logger.js"; import IPCIDR from "ip-cidr"; @@ -40,10 +40,7 @@ export async function downloadUrl(url: string, path: string): Promise { send: timeout, request: operationTimeout, // whole operation timeout }, - agent: { - http: httpAgent, - https: httpsAgent, - }, + agent: getAgentByHostname(new URL(url).hostname), http2: false, // default retry: { limit: 0, diff --git a/packages/backend/src/misc/fetch.ts b/packages/backend/src/misc/fetch.ts index 4b8fc048e2..1f8c70a196 100644 --- a/packages/backend/src/misc/fetch.ts +++ b/packages/backend/src/misc/fetch.ts @@ -4,7 +4,7 @@ import type { URL } from "node:url"; import CacheableLookup from "cacheable-lookup"; import fetch, { type RequestRedirect } from "node-fetch"; import { HttpProxyAgent, HttpsProxyAgent } from "hpagent"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import { isValidUrl } from "./is-valid-url.js"; export async function getJson( @@ -171,6 +171,25 @@ export function getAgentByUrl(url: URL, bypassProxy = false) { } } +/** + * Get agent by Hostname + * @param hostname Hostname + * @param bypassProxy Allways bypass proxy + */ +export function getAgentByHostname(hostname: string, bypassProxy = false) { + if (bypassProxy || (config.proxyBypassHosts || []).includes(hostname)) { + return { + http: _http, + https: _https, + }; + } else { + return { + http: httpAgent, + https: httpsAgent, + }; + } +} + export class StatusError extends Error { public statusCode: number; public statusMessage?: string; diff --git a/packages/backend/src/misc/get-note-summary.ts b/packages/backend/src/misc/get-note-summary.ts deleted file mode 100644 index 0a662e434e..0000000000 --- a/packages/backend/src/misc/get-note-summary.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type { Packed } from "./schema.js"; - -/** - * 投稿を表す文字列を取得します。 - * @param {*} note (packされた)投稿 - */ -export const getNoteSummary = (note: Packed<"Note">): string => { - if (note.deletedAt) { - return "❌"; - } - - let summary = ""; - - // 本文 - if (note.cw != null) { - summary += note.cw; - } else { - summary += note.text ? note.text : ""; - } - - // ファイルが添付されているとき - if ((note.files || []).length !== 0) { - const len = note.files?.length; - summary += ` 📎${len !== 1 ? ` (${len})` : ""}`; - } - - // 投票が添付されているとき - if (note.poll) { - summary += " 📊"; - } - - /* - // 返信のとき - if (note.replyId) { - if (note.reply) { - summary += `\n\nRE: ${getNoteSummary(note.reply)}`; - } else { - summary += '\n\nRE: ...'; - } - } - - // Renoteのとき - if (note.renoteId) { - if (note.renote) { - summary += `\n\nRN: ${getNoteSummary(note.renote)}`; - } else { - summary += '\n\nRN: ...'; - } - } - */ - - return summary.trim(); -}; diff --git a/packages/backend/src/misc/populate-emojis.ts b/packages/backend/src/misc/populate-emojis.ts index 4ca60b222f..f2656ea28a 100644 --- a/packages/backend/src/misc/populate-emojis.ts +++ b/packages/backend/src/misc/populate-emojis.ts @@ -4,7 +4,7 @@ import type { Emoji } from "@/models/entities/emoji.js"; import type { Note } from "@/models/entities/note.js"; import { Cache } from "./cache.js"; import { decodeReaction, isSelfHost, toPuny } from "backend-rs"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import { query } from "@/prelude/url.js"; import { redisClient } from "@/db/redis.js"; import type { NoteEdit } from "@/models/entities/note-edit.js"; diff --git a/packages/backend/src/misc/schema.ts b/packages/backend/src/misc/schema.ts index c793d3b2ed..73600832ce 100644 --- a/packages/backend/src/misc/schema.ts +++ b/packages/backend/src/misc/schema.ts @@ -33,8 +33,10 @@ import { packedGalleryPostSchema } from "@/models/schema/gallery-post.js"; import { packedEmojiSchema } from "@/models/schema/emoji.js"; import { packedNoteEdit } from "@/models/schema/note-edit.js"; import { packedNoteFileSchema } from "@/models/schema/note-file.js"; +import { packedAbuseUserReportSchema } from "@/models/schema/abuse-user-report.js"; export const refs = { + AbuseUserReport: packedAbuseUserReportSchema, UserLite: packedUserLiteSchema, UserDetailedNotMeOnly: packedUserDetailedNotMeOnlySchema, MeDetailedOnly: packedMeDetailedOnlySchema, diff --git a/packages/backend/src/misc/should-block-instance.ts b/packages/backend/src/misc/should-block-instance.ts deleted file mode 100644 index 465be41f2a..0000000000 --- a/packages/backend/src/misc/should-block-instance.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { fetchMeta } from "backend-rs"; -import type { Instance } from "@/models/entities/instance.js"; -import type { Meta } from "@/models/entities/meta.js"; - -/** - * Returns whether a specific host (punycoded) should be blocked. - * - * @param host punycoded instance host - * @param meta a resolved Meta table - * @returns whether the given host should be blocked - */ -export async function shouldBlockInstance( - host: Instance["host"], - meta?: Meta, -): Promise { - const { blockedHosts } = meta ?? (await fetchMeta(true)); - return blockedHosts.some( - (blockedHost) => host === blockedHost || host.endsWith(`.${blockedHost}`), - ); -} - -/** - * Returns whether a specific host (punycoded) should be limited. - * - * @param host punycoded instance host - * @param meta a resolved Meta table - * @returns whether the given host should be limited - */ -export async function shouldSilenceInstance( - host: Instance["host"], - meta?: Meta, -): Promise { - const { silencedHosts } = meta ?? (await fetchMeta(true)); - return silencedHosts.some( - (silencedHost) => - host === silencedHost || host.endsWith(`.${silencedHost}`), - ); -} diff --git a/packages/backend/src/misc/skipped-instances.ts b/packages/backend/src/misc/skipped-instances.ts index 14b26a3032..0c87a524f6 100644 --- a/packages/backend/src/misc/skipped-instances.ts +++ b/packages/backend/src/misc/skipped-instances.ts @@ -1,9 +1,8 @@ import { Brackets } from "typeorm"; -import { fetchMeta } from "backend-rs"; +import { isBlockedServer } from "backend-rs"; import { Instances } from "@/models/index.js"; import type { Instance } from "@/models/entities/instance.js"; import { DAY } from "@/const.js"; -import { shouldBlockInstance } from "./should-block-instance.js"; // Threshold from last contact after which an instance will be considered // "dead" and should no longer get activities delivered to it. @@ -19,16 +18,16 @@ export async function skippedInstances( hosts: Instance["host"][], ): Promise { // first check for blocked instances since that info may already be in memory - const meta = await fetchMeta(true); const shouldSkip = await Promise.all( - hosts.map((host) => shouldBlockInstance(host, meta)), + hosts.map((host) => isBlockedServer(host)), ); const skipped = hosts.filter((_, i) => shouldSkip[i]); // if possible return early and skip accessing the database if (skipped.length === hosts.length) return hosts; - const deadTime = new Date(Date.now() - deadThreshold); + // FIXME: Use or remove this + // const deadTime = new Date(Date.now() - deadThreshold); return skipped.concat( await Instances.createQueryBuilder("instance") diff --git a/packages/backend/src/models/entities/drive-file.ts b/packages/backend/src/models/entities/drive-file.ts index 3c49e89fd5..81f564115f 100644 --- a/packages/backend/src/models/entities/drive-file.ts +++ b/packages/backend/src/models/entities/drive-file.ts @@ -16,6 +16,8 @@ import { DriveFolder } from "./drive-folder.js"; import { DB_MAX_IMAGE_COMMENT_LENGTH } from "@/misc/hard-limits.js"; import { NoteFile } from "./note-file.js"; +export type DriveFileUsageHint = "userAvatar" | "userBanner" | null; + @Entity() @Index(["userId", "folderId", "id"]) export class DriveFile { @@ -177,6 +179,14 @@ export class DriveFile { }) public isSensitive: boolean; + // Hint for what this file is used for + @Column({ + type: "enum", + enum: ["userAvatar", "userBanner"], + nullable: true, + }) + public usageHint: DriveFileUsageHint; + /** * 外部の(信頼されていない)URLへの直リンクか否か */ diff --git a/packages/backend/src/models/entities/meta.ts b/packages/backend/src/models/entities/meta.ts index cdb8e14c3f..5e267a8e24 100644 --- a/packages/backend/src/models/entities/meta.ts +++ b/packages/backend/src/models/entities/meta.ts @@ -276,6 +276,12 @@ export class Meta { }) public remoteDriveCapacityMb: number; + @Column("integer", { + default: 5, + comment: "Antenna Limit", + }) + public antennaLimit: number; + @Column("varchar", { length: 128, nullable: true, diff --git a/packages/backend/src/models/entities/note.ts b/packages/backend/src/models/entities/note.ts index 738e43d442..94cd8c7b66 100644 --- a/packages/backend/src/models/entities/note.ts +++ b/packages/backend/src/models/entities/note.ts @@ -72,9 +72,8 @@ export class Note { }) public name: string | null; - @Index() // USING pgroonga pgroonga_varchar_full_text_search_ops_v2 - @Column("varchar", { - length: 512, + @Index() // USING pgroonga + @Column("text", { nullable: true, }) public cw: string | null; diff --git a/packages/backend/src/models/repositories/abuse-user-report.ts b/packages/backend/src/models/repositories/abuse-user-report.ts index 16ce159955..b8d953d052 100644 --- a/packages/backend/src/models/repositories/abuse-user-report.ts +++ b/packages/backend/src/models/repositories/abuse-user-report.ts @@ -2,6 +2,7 @@ import { db } from "@/db/postgre.js"; import { Users } from "../index.js"; import { AbuseUserReport } from "@/models/entities/abuse-user-report.js"; import { awaitAll } from "@/prelude/await-all.js"; +import type { Packed } from "@/misc/schema.js"; export const AbuseUserReportRepository = db .getRepository(AbuseUserReport) @@ -10,7 +11,7 @@ export const AbuseUserReportRepository = db const report = typeof src === "object" ? src : await this.findOneByOrFail({ id: src }); - return await awaitAll({ + const packed: Packed<"AbuseUserReport"> = await awaitAll({ id: report.id, createdAt: report.createdAt.toISOString(), comment: report.comment, @@ -31,9 +32,10 @@ export const AbuseUserReportRepository = db : null, forwarded: report.forwarded, }); + return packed; }, - packMany(reports: any[]) { + packMany(reports: (AbuseUserReport["id"] | AbuseUserReport)[]) { return Promise.all(reports.map((x) => this.pack(x))); }, }); diff --git a/packages/backend/src/models/repositories/channel.ts b/packages/backend/src/models/repositories/channel.ts index 857470f4ec..809129db6c 100644 --- a/packages/backend/src/models/repositories/channel.ts +++ b/packages/backend/src/models/repositories/channel.ts @@ -40,6 +40,7 @@ export const ChannelRepository = db.getRepository(Channel).extend({ name: channel.name, description: channel.description, userId: channel.userId, + bannerId: channel.bannerId, bannerUrl: banner ? DriveFiles.getPublicUrl(banner, false) : null, usersCount: channel.usersCount, notesCount: channel.notesCount, diff --git a/packages/backend/src/models/repositories/drive-file.ts b/packages/backend/src/models/repositories/drive-file.ts index 2321f20d4c..ac0b1e5f17 100644 --- a/packages/backend/src/models/repositories/drive-file.ts +++ b/packages/backend/src/models/repositories/drive-file.ts @@ -4,7 +4,7 @@ import type { User } from "@/models/entities/user.js"; import { toPuny } from "backend-rs"; import { awaitAll } from "@/prelude/await-all.js"; import type { Packed } from "@/misc/schema.js"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import { query, appendQuery } from "@/prelude/url.js"; import { Users, DriveFolders } from "../index.js"; import { deepClone } from "@/misc/clone.js"; @@ -152,6 +152,7 @@ export const DriveFileRepository = db.getRepository(DriveFile).extend({ md5: file.md5, size: file.size, isSensitive: file.isSensitive, + usageHint: file.usageHint, blurhash: file.blurhash, properties: opts.self ? file.properties : this.getPublicProperties(file), url: opts.self ? file.url : this.getPublicUrl(file, false), @@ -193,6 +194,7 @@ export const DriveFileRepository = db.getRepository(DriveFile).extend({ md5: file.md5, size: file.size, isSensitive: file.isSensitive, + usageHint: file.usageHint, blurhash: file.blurhash, properties: opts.self ? file.properties : this.getPublicProperties(file), url: opts.self ? file.url : this.getPublicUrl(file, false), diff --git a/packages/backend/src/models/repositories/gallery-post.ts b/packages/backend/src/models/repositories/gallery-post.ts index d91fb9de2a..c4078e9091 100644 --- a/packages/backend/src/models/repositories/gallery-post.ts +++ b/packages/backend/src/models/repositories/gallery-post.ts @@ -19,7 +19,9 @@ export const GalleryPostRepository = db.getRepository(GalleryPost).extend({ createdAt: post.createdAt.toISOString(), updatedAt: post.updatedAt.toISOString(), userId: post.userId, - user: Users.pack(post.user || post.userId, me), + user: Users.pack(post.user || post.userId, me, { + detail: true, + }), title: post.title, description: post.description, fileIds: post.fileIds, diff --git a/packages/backend/src/models/repositories/instance.ts b/packages/backend/src/models/repositories/instance.ts index 667ec948de..8f479e8ea4 100644 --- a/packages/backend/src/models/repositories/instance.ts +++ b/packages/backend/src/models/repositories/instance.ts @@ -1,10 +1,7 @@ import { db } from "@/db/postgre.js"; import { Instance } from "@/models/entities/instance.js"; import type { Packed } from "@/misc/schema.js"; -import { - shouldBlockInstance, - shouldSilenceInstance, -} from "@/misc/should-block-instance.js"; +import { isBlockedServer, isSilencedServer } from "backend-rs"; export const InstanceRepository = db.getRepository(Instance).extend({ async pack(instance: Instance): Promise> { @@ -22,8 +19,8 @@ export const InstanceRepository = db.getRepository(Instance).extend({ lastCommunicatedAt: instance.lastCommunicatedAt.toISOString(), isNotResponding: instance.isNotResponding, isSuspended: instance.isSuspended, - isBlocked: await shouldBlockInstance(instance.host), - isSilenced: await shouldSilenceInstance(instance.host), + isBlocked: await isBlockedServer(instance.host), + isSilenced: await isSilencedServer(instance.host), softwareName: instance.softwareName, softwareVersion: instance.softwareVersion, openRegistrations: instance.openRegistrations, diff --git a/packages/backend/src/models/repositories/note.ts b/packages/backend/src/models/repositories/note.ts index 1bf4ef657f..c877048709 100644 --- a/packages/backend/src/models/repositories/note.ts +++ b/packages/backend/src/models/repositories/note.ts @@ -232,6 +232,7 @@ export const NoteRepository = db.getRepository(Note).extend({ uri: note.uri || undefined, url: note.url || undefined, updatedAt: note.updatedAt?.toISOString() || undefined, + hasPoll: note.hasPoll, poll: note.hasPoll ? populatePoll(note, meId) : undefined, ...(meId ? { diff --git a/packages/backend/src/models/repositories/user.ts b/packages/backend/src/models/repositories/user.ts index 3bc943965b..040106b410 100644 --- a/packages/backend/src/models/repositories/user.ts +++ b/packages/backend/src/models/repositories/user.ts @@ -2,7 +2,7 @@ import { In, Not } from "typeorm"; import Ajv from "ajv"; import type { ILocalUser, IRemoteUser } from "@/models/entities/user.js"; import { User } from "@/models/entities/user.js"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import type { Packed } from "@/misc/schema.js"; import type { Promiseable } from "@/prelude/await-all.js"; import { awaitAll } from "@/prelude/await-all.js"; diff --git a/packages/backend/src/models/schema/abuse-user-report.ts b/packages/backend/src/models/schema/abuse-user-report.ts new file mode 100644 index 0000000000..47e56c7415 --- /dev/null +++ b/packages/backend/src/models/schema/abuse-user-report.ts @@ -0,0 +1,69 @@ +export const packedAbuseUserReportSchema = { + type: "object", + properties: { + id: { + type: "string", + optional: false, + nullable: false, + format: "id", + example: "xxxxxxxxxx", + }, + createdAt: { + type: "string", + optional: false, + nullable: false, + format: "date-time", + }, + comment: { + type: "string", + optional: false, + nullable: false, + }, + resolved: { + type: "boolean", + optional: false, + nullable: false, + }, + reporterId: { + type: "string", + optional: false, + nullable: false, + format: "id", + }, + targetUserId: { + type: "string", + optional: false, + nullable: false, + format: "id", + }, + assigneeId: { + type: "string", + optional: false, + nullable: true, + format: "id", + }, + reporter: { + type: "object", + optional: false, + nullable: false, + ref: "UserDetailed", + }, + targetUser: { + type: "object", + optional: false, + nullable: false, + ref: "UserDetailed", + }, + assignee: { + type: "object", + optional: true, + nullable: true, + ref: "UserDetailed", + }, + forwarded: { + type: "boolean", + optional: false, + nullable: false, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/channel.ts b/packages/backend/src/models/schema/channel.ts index 67833cb0dd..d3ec222c8d 100644 --- a/packages/backend/src/models/schema/channel.ts +++ b/packages/backend/src/models/schema/channel.ts @@ -36,6 +36,13 @@ export const packedChannelSchema = { nullable: true, optional: false, }, + bannerId: { + type: "string", + optional: false, + nullable: true, + format: "id", + example: "xxxxxxxxxx", + }, notesCount: { type: "number", nullable: false, @@ -57,5 +64,10 @@ export const packedChannelSchema = { optional: false, format: "id", }, + hasUnreadNote: { + type: "boolean", + optional: true, + nullable: false, + }, }, } as const; diff --git a/packages/backend/src/models/schema/drive-file.ts b/packages/backend/src/models/schema/drive-file.ts index 30db9e7d48..929dbb472e 100644 --- a/packages/backend/src/models/schema/drive-file.ts +++ b/packages/backend/src/models/schema/drive-file.ts @@ -44,6 +44,12 @@ export const packedDriveFileSchema = { optional: false, nullable: false, }, + usageHint: { + type: "string", + optional: false, + nullable: true, + enum: ["userAvatar", "userBanner"], + }, blurhash: { type: "string", optional: false, diff --git a/packages/backend/src/models/schema/federation-instance.ts b/packages/backend/src/models/schema/federation-instance.ts index 7a8af7f51d..338e079e28 100644 --- a/packages/backend/src/models/schema/federation-instance.ts +++ b/packages/backend/src/models/schema/federation-instance.ts @@ -1,4 +1,4 @@ -import config from "@/config/index.js"; +import { config } from "@/config.js"; export const packedFederationInstanceSchema = { type: "object", diff --git a/packages/backend/src/models/schema/gallery-post.ts b/packages/backend/src/models/schema/gallery-post.ts index 9ac348e1fb..ae22507643 100644 --- a/packages/backend/src/models/schema/gallery-post.ts +++ b/packages/backend/src/models/schema/gallery-post.ts @@ -38,7 +38,7 @@ export const packedGalleryPostSchema = { }, user: { type: "object", - ref: "UserLite", + ref: "UserDetailed", optional: false, nullable: false, }, @@ -79,5 +79,15 @@ export const packedGalleryPostSchema = { optional: false, nullable: false, }, + isLiked: { + type: "boolean", + optional: true, + nullable: false, + }, + likedCount: { + type: "number", + optional: false, + nullable: false, + }, }, } as const; diff --git a/packages/backend/src/models/schema/note.ts b/packages/backend/src/models/schema/note.ts index 7dcdbc9b03..fff872b69f 100644 --- a/packages/backend/src/models/schema/note.ts +++ b/packages/backend/src/models/schema/note.ts @@ -28,7 +28,7 @@ export const packedNoteSchema = { }, cw: { type: "string", - optional: true, + optional: false, nullable: true, }, userId: { @@ -98,7 +98,7 @@ export const packedNoteSchema = { }, fileIds: { type: "array", - optional: true, + optional: false, nullable: false, items: { type: "string", @@ -128,6 +128,11 @@ export const packedNoteSchema = { nullable: false, }, }, + hasPoll: { + type: "boolean", + optional: false, + nullable: false, + }, poll: { type: "object", optional: true, diff --git a/packages/backend/src/ormconfig.ts b/packages/backend/src/ormconfig.ts index a1891e00df..293be57cae 100644 --- a/packages/backend/src/ormconfig.ts +++ b/packages/backend/src/ormconfig.ts @@ -1,6 +1,6 @@ import { DataSource } from "typeorm"; -import config from "./config/index.js"; -import { entities } from "./db/postgre.js"; +import { config } from "@/config.js"; +import { entities } from "@/db/postgre.js"; export default new DataSource({ type: "postgres", diff --git a/packages/backend/src/prelude/undefined-to-null.ts b/packages/backend/src/prelude/undefined-to-null.ts new file mode 100644 index 0000000000..013be3cf9e --- /dev/null +++ b/packages/backend/src/prelude/undefined-to-null.ts @@ -0,0 +1,76 @@ +// https://gist.github.com/tkrotoff/a6baf96eb6b61b445a9142e5555511a0 +import type { Primitive } from "type-fest"; + +type NullToUndefined = T extends null + ? undefined + : T extends Primitive | Function | Date | RegExp + ? T + : T extends Array + ? Array> + : T extends Map + ? Map> + : T extends Set + ? Set> + : T extends object + ? { [K in keyof T]: NullToUndefined } + : unknown; + +type UndefinedToNull = T extends undefined + ? null + : T extends Primitive | Function | Date | RegExp + ? T + : T extends Array + ? Array> + : T extends Map + ? Map> + : T extends Set + ? Set> + : T extends object + ? { [K in keyof T]: UndefinedToNull } + : unknown; + +function _nullToUndefined(obj: T): NullToUndefined { + if (obj === null) { + return undefined as any; + } + + if (typeof obj === "object") { + if (obj instanceof Map) { + obj.forEach((value, key) => obj.set(key, _nullToUndefined(value))); + } else { + for (const key in obj) { + obj[key] = _nullToUndefined(obj[key]) as any; + } + } + } + + return obj as any; +} + +function _undefinedToNull(obj: T): UndefinedToNull { + if (obj === undefined) { + return null as any; + } + + if (typeof obj === "object") { + if (obj instanceof Map) { + obj.forEach((value, key) => obj.set(key, _undefinedToNull(value))); + } else { + for (const key in obj) { + obj[key] = _undefinedToNull(obj[key]) as any; + } + } + } + + return obj as any; +} + +/** + * Recursively converts all undefined values to null. + * + * @param obj object to convert + * @returns a copy of the object with all its undefined values converted to null + */ +export function undefinedToNull(obj: T) { + return _undefinedToNull(structuredClone(obj)); +} diff --git a/packages/backend/src/queue/index.ts b/packages/backend/src/queue/index.ts index 58a0ae7486..6272a5e668 100644 --- a/packages/backend/src/queue/index.ts +++ b/packages/backend/src/queue/index.ts @@ -1,11 +1,10 @@ import type httpSignature from "@peertube/http-signature"; import { v4 as uuid } from "uuid"; -import config from "@/config/index.js"; +import { config, envOption } from "@/config.js"; import type { DriveFile } from "@/models/entities/drive-file.js"; import type { IActivity } from "@/remote/activitypub/type.js"; import type { Webhook, webhookEventTypes } from "@/models/entities/webhook.js"; -import { envOption } from "../env.js"; import processDeliver from "./processors/deliver.js"; import processInbox from "./processors/inbox.js"; @@ -24,10 +23,9 @@ import { objectStorageQueue, endedPollNotificationQueue, webhookDeliverQueue, - backgroundQueue, } from "./queues.js"; import type { ThinUser } from "./types.js"; -import { Note } from "@/models/entities/note.js"; +import type { Note } from "@/models/entities/note.js"; function renderError(e: Error): any { return { diff --git a/packages/backend/src/queue/initialize.ts b/packages/backend/src/queue/initialize.ts index 0f9c83132f..32cc0005e9 100644 --- a/packages/backend/src/queue/initialize.ts +++ b/packages/backend/src/queue/initialize.ts @@ -1,5 +1,5 @@ import Bull from "bull"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; export function initialize(name: string, limitPerSec = -1) { return new Bull(name, { @@ -34,7 +34,7 @@ export function initialize(name: string, limitPerSec = -1) { function apBackoff(attemptsMade: number, err: Error) { const baseDelay = 60 * 1000; // 1min const maxBackoff = 8 * 60 * 60 * 1000; // 8hours - let backoff = (Math.pow(2, attemptsMade) - 1) * baseDelay; + let backoff = (2 ** attemptsMade - 1) * baseDelay; backoff = Math.min(backoff, maxBackoff); backoff += Math.round(backoff * Math.random() * 0.2); return backoff; diff --git a/packages/backend/src/queue/processors/db/export-custom-emojis.ts b/packages/backend/src/queue/processors/db/export-custom-emojis.ts index a1ca3a91c5..157751c1aa 100644 --- a/packages/backend/src/queue/processors/db/export-custom-emojis.ts +++ b/packages/backend/src/queue/processors/db/export-custom-emojis.ts @@ -9,7 +9,7 @@ import { format as dateFormat } from "date-fns"; import { Users, Emojis } from "@/models/index.js"; import { createTemp, createTempDir } from "@/misc/create-temp.js"; import { downloadUrl } from "@/misc/download-url.js"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import { IsNull } from "typeorm"; import { inspect } from "node:util"; diff --git a/packages/backend/src/queue/processors/inbox.ts b/packages/backend/src/queue/processors/inbox.ts index 0ea72306b6..898c68cff3 100644 --- a/packages/backend/src/queue/processors/inbox.ts +++ b/packages/backend/src/queue/processors/inbox.ts @@ -5,7 +5,7 @@ import perform from "@/remote/activitypub/perform.js"; import Logger from "@/services/logger.js"; import { registerOrFetchInstanceDoc } from "@/services/register-or-fetch-instance-doc.js"; import { Instances } from "@/models/index.js"; -import { fetchMeta } from "backend-rs"; +import { isAllowedServer, isBlockedServer } from "backend-rs"; import { toPuny, extractHost } from "backend-rs"; import { getApId } from "@/remote/activitypub/type.js"; import { fetchInstanceMetadata } from "@/services/fetch-instance-metadata.js"; @@ -16,7 +16,6 @@ import { LdSignature } from "@/remote/activitypub/misc/ld-signature.js"; import { StatusError } from "@/misc/fetch.js"; import type { CacheableRemoteUser } from "@/models/entities/user.js"; import type { UserPublickey } from "@/models/entities/user-publickey.js"; -import { shouldBlockInstance } from "@/misc/should-block-instance.js"; import { verifySignature } from "@/remote/activitypub/check-fetch.js"; import { inspect } from "node:util"; @@ -41,13 +40,12 @@ export default async (job: Bull.Job): Promise => { const host = toPuny(new URL(signature.keyId).hostname); // interrupt if blocked - const meta = await fetchMeta(true); - if (await shouldBlockInstance(host, meta)) { + if (await isBlockedServer(host)) { return `Blocked request: ${host}`; } // only whitelisted instances in private mode - if (meta.privateMode && !meta.allowedHosts.includes(host)) { + if (!isAllowedServer(host)) { return `Blocked request: ${host}`; } @@ -158,7 +156,7 @@ export default async (job: Bull.Job): Promise => { // ブロックしてたら中断 const ldHost = extractHost(authUser.user.uri); - if (await shouldBlockInstance(ldHost, meta)) { + if (await isBlockedServer(ldHost)) { return `Blocked request: ${ldHost}`; } } else { diff --git a/packages/backend/src/queue/processors/webhook-deliver.ts b/packages/backend/src/queue/processors/webhook-deliver.ts index 12c9a05498..63a16c373b 100644 --- a/packages/backend/src/queue/processors/webhook-deliver.ts +++ b/packages/backend/src/queue/processors/webhook-deliver.ts @@ -3,7 +3,7 @@ import Logger from "@/services/logger.js"; import type { WebhookDeliverJobData } from "../types.js"; import { getResponse, StatusError } from "@/misc/fetch.js"; import { Webhooks } from "@/models/index.js"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; const logger = new Logger("webhook"); diff --git a/packages/backend/src/queue/queues.ts b/packages/backend/src/queue/queues.ts index 6b0eb2de42..06b9567e74 100644 --- a/packages/backend/src/queue/queues.ts +++ b/packages/backend/src/queue/queues.ts @@ -1,4 +1,4 @@ -import config from "@/config/index.js"; +import { config } from "@/config.js"; import { initialize as initializeQueue } from "./initialize.js"; import type { DeliverJobData, diff --git a/packages/backend/src/remote/activitypub/check-fetch.ts b/packages/backend/src/remote/activitypub/check-fetch.ts index 12ea63a931..726d996ad8 100644 --- a/packages/backend/src/remote/activitypub/check-fetch.ts +++ b/packages/backend/src/remote/activitypub/check-fetch.ts @@ -1,11 +1,10 @@ import { URL } from "url"; -import httpSignature, { IParsedSignature } from "@peertube/http-signature"; -import config from "@/config/index.js"; -import { fetchMeta } from "backend-rs"; +import httpSignature, { type IParsedSignature } from "@peertube/http-signature"; +import { config } from "@/config.js"; +import { fetchMeta, isAllowedServer, isBlockedServer } from "backend-rs"; import { toPuny } from "backend-rs"; import DbResolver from "@/remote/activitypub/db-resolver.js"; import { getApId } from "@/remote/activitypub/type.js"; -import { shouldBlockInstance } from "@/misc/should-block-instance.js"; import type { IncomingMessage } from "http"; import type { CacheableRemoteUser } from "@/models/entities/user.js"; import type { UserPublickey } from "@/models/entities/user-publickey.js"; @@ -44,15 +43,11 @@ export async function checkFetch(req: IncomingMessage): Promise { const keyId = new URL(signature.keyId); const host = toPuny(keyId.hostname); - if (await shouldBlockInstance(host, meta)) { + if (await isBlockedServer(host)) { return 403; } - if ( - meta.privateMode && - host !== config.host && - !meta.allowedHosts.includes(host) - ) { + if (host !== config.host && !isAllowedServer(host)) { return 403; } diff --git a/packages/backend/src/remote/activitypub/db-resolver.ts b/packages/backend/src/remote/activitypub/db-resolver.ts index a753606a14..088a2db9da 100644 --- a/packages/backend/src/remote/activitypub/db-resolver.ts +++ b/packages/backend/src/remote/activitypub/db-resolver.ts @@ -1,5 +1,5 @@ import escapeRegexp from "escape-regexp"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import type { Note } from "@/models/entities/note.js"; import type { CacheableRemoteUser, diff --git a/packages/backend/src/remote/activitypub/kernel/announce/note.ts b/packages/backend/src/remote/activitypub/kernel/announce/note.ts index ae16c77dbd..41d1319dbd 100644 --- a/packages/backend/src/remote/activitypub/kernel/announce/note.ts +++ b/packages/backend/src/remote/activitypub/kernel/announce/note.ts @@ -10,7 +10,7 @@ import { getApLock } from "@/misc/app-lock.js"; import { parseAudience } from "../../audience.js"; import { StatusError } from "@/misc/fetch.js"; import { Notes } from "@/models/index.js"; -import { shouldBlockInstance } from "@/misc/should-block-instance.js"; +import { isBlockedServer } from "backend-rs"; import { inspect } from "node:util"; const logger = apLogger; @@ -31,7 +31,7 @@ export default async function ( } // Interrupt if you block the announcement destination - if (await shouldBlockInstance(extractHost(uri))) return; + if (await isBlockedServer(extractHost(uri))) return; const lock = await getApLock(uri); diff --git a/packages/backend/src/remote/activitypub/kernel/flag/index.ts b/packages/backend/src/remote/activitypub/kernel/flag/index.ts index 0f83f6b449..c556605865 100644 --- a/packages/backend/src/remote/activitypub/kernel/flag/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/flag/index.ts @@ -1,5 +1,5 @@ import type { CacheableRemoteUser } from "@/models/entities/user.js"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import type { IFlag } from "../../type.js"; import { getApIds } from "../../type.js"; import { AbuseUserReports, Users } from "@/models/index.js"; diff --git a/packages/backend/src/remote/activitypub/kernel/index.ts b/packages/backend/src/remote/activitypub/kernel/index.ts index f64a0e2ee8..b556550c5f 100644 --- a/packages/backend/src/remote/activitypub/kernel/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/index.ts @@ -38,8 +38,7 @@ import block from "./block/index.js"; import flag from "./flag/index.js"; import move from "./move/index.js"; import type { IObject, IActivity } from "../type.js"; -import { extractHost } from "backend-rs"; -import { shouldBlockInstance } from "@/misc/should-block-instance.js"; +import { extractHost, isBlockedServer } from "backend-rs"; import { inspect } from "node:util"; export async function performActivity( @@ -71,7 +70,7 @@ async function performOneActivity( if (typeof activity.id !== "undefined") { const host = extractHost(getApId(activity)); - if (await shouldBlockInstance(host)) return; + if (await isBlockedServer(host)) return; } if (isCreate(activity)) { diff --git a/packages/backend/src/remote/activitypub/models/image.ts b/packages/backend/src/remote/activitypub/models/image.ts index e2072a963a..a6ac698feb 100644 --- a/packages/backend/src/remote/activitypub/models/image.ts +++ b/packages/backend/src/remote/activitypub/models/image.ts @@ -3,7 +3,10 @@ import type { CacheableRemoteUser } from "@/models/entities/user.js"; import Resolver from "../resolver.js"; import { fetchMeta } from "backend-rs"; import { apLogger } from "../logger.js"; -import type { DriveFile } from "@/models/entities/drive-file.js"; +import type { + DriveFile, + DriveFileUsageHint, +} from "@/models/entities/drive-file.js"; import { DriveFiles } from "@/models/index.js"; import { truncate } from "@/misc/truncate.js"; import { DB_MAX_IMAGE_COMMENT_LENGTH } from "@/misc/hard-limits.js"; @@ -16,6 +19,7 @@ const logger = apLogger; export async function createImage( actor: CacheableRemoteUser, value: any, + usage: DriveFileUsageHint, ): Promise { // Skip if author is frozen. if (actor.isSuspended) { @@ -43,6 +47,7 @@ export async function createImage( sensitive: image.sensitive, isLink: !instance.cacheRemoteFiles, comment: truncate(image.name, DB_MAX_IMAGE_COMMENT_LENGTH), + usageHint: usage, }); if (file.isLink) { @@ -73,9 +78,10 @@ export async function createImage( export async function resolveImage( actor: CacheableRemoteUser, value: any, + usage: DriveFileUsageHint, ): Promise { // TODO // Fetch from remote server and register - return await createImage(actor, value); + return await createImage(actor, value, usage); } diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts index ad59930457..4b685747ba 100644 --- a/packages/backend/src/remote/activitypub/models/note.ts +++ b/packages/backend/src/remote/activitypub/models/note.ts @@ -12,7 +12,7 @@ import { unique, toArray, toSingle } from "@/prelude/array.js"; import { extractPollFromQuestion } from "./question.js"; import vote from "@/services/note/polls/vote.js"; import { apLogger } from "../logger.js"; -import { DriveFile } from "@/models/entities/drive-file.js"; +import type { DriveFile } from "@/models/entities/drive-file.js"; import { extractHost, isSameOrigin, toPuny } from "backend-rs"; import { Emojis, @@ -33,14 +33,13 @@ import { getApType, } from "../type.js"; import type { Emoji } from "@/models/entities/emoji.js"; -import { genId } from "backend-rs"; +import { genId, isBlockedServer } from "backend-rs"; import { getApLock } from "@/misc/app-lock.js"; import { createMessage } from "@/services/messages/create.js"; import { parseAudience } from "../audience.js"; import { extractApMentions } from "./mention.js"; import DbResolver from "../db-resolver.js"; import { StatusError } from "@/misc/fetch.js"; -import { shouldBlockInstance } from "@/misc/should-block-instance.js"; import { publishNoteStream } from "@/services/stream.js"; import { extractHashtags } from "@/misc/extract-hashtags.js"; import { UserProfiles } from "@/models/index.js"; @@ -213,7 +212,8 @@ export async function createNote( ? ( await Promise.all( note.attachment.map( - (x) => limit(() => resolveImage(actor, x)) as Promise, + (x) => + limit(() => resolveImage(actor, x, null)) as Promise, ), ) ).filter((image) => image != null) @@ -420,7 +420,7 @@ export async function resolveNote( if (uri == null) throw new Error("missing uri"); // Abort if origin host is blocked - if (await shouldBlockInstance(extractHost(uri))) + if (await isBlockedServer(extractHost(uri))) throw new StatusError( "host blocked", 451, @@ -616,7 +616,7 @@ export async function updateNote(value: string | IObject, resolver?: Resolver) { fileList.map( (x) => limit(async () => { - const file = await resolveImage(actor, x); + const file = await resolveImage(actor, x, null); const update: Partial = {}; const altText = truncate(x.name, DB_MAX_IMAGE_COMMENT_LENGTH); diff --git a/packages/backend/src/remote/activitypub/models/person.ts b/packages/backend/src/remote/activitypub/models/person.ts index e91280125f..4baa2c021b 100644 --- a/packages/backend/src/remote/activitypub/models/person.ts +++ b/packages/backend/src/remote/activitypub/models/person.ts @@ -10,6 +10,7 @@ import { Followings, UserProfiles, UserPublickeys, + DriveFiles, } from "@/models/index.js"; import type { IRemoteUser, CacheableUser } from "@/models/entities/user.js"; import { User } from "@/models/entities/user.js"; @@ -362,10 +363,14 @@ export async function createPerson( //#region Fetch avatar and header image const [avatar, banner] = await Promise.all( - [person.icon, person.image].map((img) => + [person.icon, person.image].map((img, index) => img == null ? Promise.resolve(null) - : resolveImage(user!, img).catch(() => null), + : resolveImage( + user, + img, + index === 0 ? "userAvatar" : index === 1 ? "userBanner" : null, + ).catch(() => null), ), ); @@ -438,10 +443,14 @@ export async function updatePerson( // Fetch avatar and header image const [avatar, banner] = await Promise.all( - [person.icon, person.image].map((img) => + [person.icon, person.image].map((img, index) => img == null ? Promise.resolve(null) - : resolveImage(user, img).catch(() => null), + : resolveImage( + user, + img, + index === 0 ? "userAvatar" : index === 1 ? "userBanner" : null, + ).catch(() => null), ), ); @@ -561,10 +570,14 @@ export async function updatePerson( } as Partial; if (avatar) { + if (user?.avatarId) + await DriveFiles.update(user.avatarId, { usageHint: null }); updates.avatarId = avatar.id; } if (banner) { + if (user?.bannerId) + await DriveFiles.update(user.bannerId, { usageHint: null }); updates.bannerId = banner.id; } diff --git a/packages/backend/src/remote/activitypub/renderer/accept.ts b/packages/backend/src/remote/activitypub/renderer/accept.ts index fd145dcf97..2d27a1b29d 100644 --- a/packages/backend/src/remote/activitypub/renderer/accept.ts +++ b/packages/backend/src/remote/activitypub/renderer/accept.ts @@ -1,4 +1,4 @@ -import config from "@/config/index.js"; +import { config } from "@/config.js"; import type { User } from "@/models/entities/user.js"; export default (object: any, user: { id: User["id"]; host: null }) => ({ diff --git a/packages/backend/src/remote/activitypub/renderer/add.ts b/packages/backend/src/remote/activitypub/renderer/add.ts index d8203ac1ea..14c71694e1 100644 --- a/packages/backend/src/remote/activitypub/renderer/add.ts +++ b/packages/backend/src/remote/activitypub/renderer/add.ts @@ -1,4 +1,4 @@ -import config from "@/config/index.js"; +import { config } from "@/config.js"; import type { ILocalUser } from "@/models/entities/user.js"; export default (user: ILocalUser, target: any, object: any) => ({ diff --git a/packages/backend/src/remote/activitypub/renderer/announce.ts b/packages/backend/src/remote/activitypub/renderer/announce.ts index 1fd1842acf..0cc9bec6f4 100644 --- a/packages/backend/src/remote/activitypub/renderer/announce.ts +++ b/packages/backend/src/remote/activitypub/renderer/announce.ts @@ -1,4 +1,4 @@ -import config from "@/config/index.js"; +import { config } from "@/config.js"; import type { Note } from "@/models/entities/note.js"; export default (object: any, note: Note) => { diff --git a/packages/backend/src/remote/activitypub/renderer/block.ts b/packages/backend/src/remote/activitypub/renderer/block.ts index c2ea267f38..5169e0d550 100644 --- a/packages/backend/src/remote/activitypub/renderer/block.ts +++ b/packages/backend/src/remote/activitypub/renderer/block.ts @@ -1,4 +1,4 @@ -import config from "@/config/index.js"; +import { config } from "@/config.js"; import type { Blocking } from "@/models/entities/blocking.js"; /** diff --git a/packages/backend/src/remote/activitypub/renderer/create.ts b/packages/backend/src/remote/activitypub/renderer/create.ts index 857f5722cc..89b14b88b2 100644 --- a/packages/backend/src/remote/activitypub/renderer/create.ts +++ b/packages/backend/src/remote/activitypub/renderer/create.ts @@ -1,4 +1,4 @@ -import config from "@/config/index.js"; +import { config } from "@/config.js"; import type { Note } from "@/models/entities/note.js"; export default (object: any, note: Note) => { diff --git a/packages/backend/src/remote/activitypub/renderer/delete.ts b/packages/backend/src/remote/activitypub/renderer/delete.ts index 70bdc34922..0d2105941e 100644 --- a/packages/backend/src/remote/activitypub/renderer/delete.ts +++ b/packages/backend/src/remote/activitypub/renderer/delete.ts @@ -1,4 +1,4 @@ -import config from "@/config/index.js"; +import { config } from "@/config.js"; import type { User } from "@/models/entities/user.js"; export default (object: any, user: { id: User["id"]; host: null }) => ({ diff --git a/packages/backend/src/remote/activitypub/renderer/emoji.ts b/packages/backend/src/remote/activitypub/renderer/emoji.ts index 3d9b8cd55b..dab0e8cac7 100644 --- a/packages/backend/src/remote/activitypub/renderer/emoji.ts +++ b/packages/backend/src/remote/activitypub/renderer/emoji.ts @@ -1,4 +1,4 @@ -import config from "@/config/index.js"; +import { config } from "@/config.js"; import type { Emoji } from "@/models/entities/emoji.js"; export default (emoji: Emoji) => ({ diff --git a/packages/backend/src/remote/activitypub/renderer/flag.ts b/packages/backend/src/remote/activitypub/renderer/flag.ts index 44da33f5c2..1fa260be5e 100644 --- a/packages/backend/src/remote/activitypub/renderer/flag.ts +++ b/packages/backend/src/remote/activitypub/renderer/flag.ts @@ -1,4 +1,4 @@ -import config from "@/config/index.js"; +import { config } from "@/config.js"; import type { ILocalUser } from "@/models/entities/user.js"; // to anonymise reporters, the reporting actor must be a system user diff --git a/packages/backend/src/remote/activitypub/renderer/follow-relay.ts b/packages/backend/src/remote/activitypub/renderer/follow-relay.ts index ad7f05bf84..b62d9c4011 100644 --- a/packages/backend/src/remote/activitypub/renderer/follow-relay.ts +++ b/packages/backend/src/remote/activitypub/renderer/follow-relay.ts @@ -1,4 +1,4 @@ -import config from "@/config/index.js"; +import { config } from "@/config.js"; import type { Relay } from "@/models/entities/relay.js"; import type { ILocalUser } from "@/models/entities/user.js"; diff --git a/packages/backend/src/remote/activitypub/renderer/follow-user.ts b/packages/backend/src/remote/activitypub/renderer/follow-user.ts index 22ee429ff6..93228a6327 100644 --- a/packages/backend/src/remote/activitypub/renderer/follow-user.ts +++ b/packages/backend/src/remote/activitypub/renderer/follow-user.ts @@ -1,4 +1,4 @@ -import config from "@/config/index.js"; +import { config } from "@/config.js"; import { Users } from "@/models/index.js"; import type { User } from "@/models/entities/user.js"; diff --git a/packages/backend/src/remote/activitypub/renderer/follow.ts b/packages/backend/src/remote/activitypub/renderer/follow.ts index 3ff89c12aa..30ad799507 100644 --- a/packages/backend/src/remote/activitypub/renderer/follow.ts +++ b/packages/backend/src/remote/activitypub/renderer/follow.ts @@ -1,4 +1,4 @@ -import config from "@/config/index.js"; +import { config } from "@/config.js"; import type { User } from "@/models/entities/user.js"; import { Users } from "@/models/index.js"; diff --git a/packages/backend/src/remote/activitypub/renderer/hashtag.ts b/packages/backend/src/remote/activitypub/renderer/hashtag.ts index a00cd1ff5e..ab6651f55d 100644 --- a/packages/backend/src/remote/activitypub/renderer/hashtag.ts +++ b/packages/backend/src/remote/activitypub/renderer/hashtag.ts @@ -1,4 +1,4 @@ -import config from "@/config/index.js"; +import { config } from "@/config.js"; export default (tag: string) => ({ type: "Hashtag", diff --git a/packages/backend/src/remote/activitypub/renderer/index.ts b/packages/backend/src/remote/activitypub/renderer/index.ts index 2b6229b3e4..a085443d23 100644 --- a/packages/backend/src/remote/activitypub/renderer/index.ts +++ b/packages/backend/src/remote/activitypub/renderer/index.ts @@ -1,5 +1,5 @@ import { v4 as uuid } from "uuid"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import { getUserKeypair } from "@/misc/keypair-store.js"; import type { User } from "@/models/entities/user.js"; import { LdSignature } from "../misc/ld-signature.js"; diff --git a/packages/backend/src/remote/activitypub/renderer/key.ts b/packages/backend/src/remote/activitypub/renderer/key.ts index 084bb5361a..1e01640e59 100644 --- a/packages/backend/src/remote/activitypub/renderer/key.ts +++ b/packages/backend/src/remote/activitypub/renderer/key.ts @@ -1,4 +1,4 @@ -import config from "@/config/index.js"; +import { config } from "@/config.js"; import type { ILocalUser } from "@/models/entities/user.js"; import type { UserKeypair } from "@/models/entities/user-keypair.js"; import { createPublicKey } from "node:crypto"; diff --git a/packages/backend/src/remote/activitypub/renderer/like.ts b/packages/backend/src/remote/activitypub/renderer/like.ts index 6f810cd201..ea0df3e359 100644 --- a/packages/backend/src/remote/activitypub/renderer/like.ts +++ b/packages/backend/src/remote/activitypub/renderer/like.ts @@ -1,5 +1,5 @@ import { IsNull } from "typeorm"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import type { NoteReaction } from "@/models/entities/note-reaction.js"; import type { Note } from "@/models/entities/note.js"; import { Emojis } from "@/models/index.js"; diff --git a/packages/backend/src/remote/activitypub/renderer/mention.ts b/packages/backend/src/remote/activitypub/renderer/mention.ts index e7f0435c16..c935c7d325 100644 --- a/packages/backend/src/remote/activitypub/renderer/mention.ts +++ b/packages/backend/src/remote/activitypub/renderer/mention.ts @@ -1,4 +1,4 @@ -import config from "@/config/index.js"; +import { config } from "@/config.js"; import type { User, ILocalUser } from "@/models/entities/user.js"; import { Users } from "@/models/index.js"; diff --git a/packages/backend/src/remote/activitypub/renderer/note.ts b/packages/backend/src/remote/activitypub/renderer/note.ts index f1344c1b03..dc978f1e0e 100644 --- a/packages/backend/src/remote/activitypub/renderer/note.ts +++ b/packages/backend/src/remote/activitypub/renderer/note.ts @@ -1,5 +1,5 @@ import { In, IsNull } from "typeorm"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import type { Note, IMentionedRemoteUsers } from "@/models/entities/note.js"; import type { DriveFile } from "@/models/entities/drive-file.js"; import { DriveFiles, Notes, Users, Emojis, Polls } from "@/models/index.js"; diff --git a/packages/backend/src/remote/activitypub/renderer/person.ts b/packages/backend/src/remote/activitypub/renderer/person.ts index bba963d72e..c6371440b4 100644 --- a/packages/backend/src/remote/activitypub/renderer/person.ts +++ b/packages/backend/src/remote/activitypub/renderer/person.ts @@ -1,6 +1,6 @@ import { URL } from "node:url"; import * as mfm from "mfm-js"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import type { ILocalUser } from "@/models/entities/user.js"; import { DriveFiles, UserProfiles } from "@/models/index.js"; import { getUserKeypair } from "@/misc/keypair-store.js"; diff --git a/packages/backend/src/remote/activitypub/renderer/question.ts b/packages/backend/src/remote/activitypub/renderer/question.ts index cb89aa7583..ac62734395 100644 --- a/packages/backend/src/remote/activitypub/renderer/question.ts +++ b/packages/backend/src/remote/activitypub/renderer/question.ts @@ -1,4 +1,4 @@ -import config from "@/config/index.js"; +import { config } from "@/config.js"; import type { User } from "@/models/entities/user.js"; import type { Note } from "@/models/entities/note.js"; import type { Poll } from "@/models/entities/poll.js"; diff --git a/packages/backend/src/remote/activitypub/renderer/read.ts b/packages/backend/src/remote/activitypub/renderer/read.ts index 212e7e8ddf..9ea15b10f7 100644 --- a/packages/backend/src/remote/activitypub/renderer/read.ts +++ b/packages/backend/src/remote/activitypub/renderer/read.ts @@ -1,4 +1,4 @@ -import config from "@/config/index.js"; +import { config } from "@/config.js"; import type { User } from "@/models/entities/user.js"; import type { MessagingMessage } from "@/models/entities/messaging-message.js"; diff --git a/packages/backend/src/remote/activitypub/renderer/reject.ts b/packages/backend/src/remote/activitypub/renderer/reject.ts index 7ac4452411..75c3b9d065 100644 --- a/packages/backend/src/remote/activitypub/renderer/reject.ts +++ b/packages/backend/src/remote/activitypub/renderer/reject.ts @@ -1,4 +1,4 @@ -import config from "@/config/index.js"; +import { config } from "@/config.js"; import type { User } from "@/models/entities/user.js"; export default (object: any, user: { id: User["id"] }) => ({ diff --git a/packages/backend/src/remote/activitypub/renderer/remove.ts b/packages/backend/src/remote/activitypub/renderer/remove.ts index e3b3fef856..270744dd30 100644 --- a/packages/backend/src/remote/activitypub/renderer/remove.ts +++ b/packages/backend/src/remote/activitypub/renderer/remove.ts @@ -1,4 +1,4 @@ -import config from "@/config/index.js"; +import { config } from "@/config.js"; import type { User } from "@/models/entities/user.js"; export default (user: { id: User["id"] }, target: any, object: any) => ({ diff --git a/packages/backend/src/remote/activitypub/renderer/undo.ts b/packages/backend/src/remote/activitypub/renderer/undo.ts index 4394c4bf2f..a0285ea9c7 100644 --- a/packages/backend/src/remote/activitypub/renderer/undo.ts +++ b/packages/backend/src/remote/activitypub/renderer/undo.ts @@ -1,4 +1,4 @@ -import config from "@/config/index.js"; +import { config } from "@/config.js"; import type { User } from "@/models/entities/user.js"; export default (object: any, user: { id: User["id"] }) => { diff --git a/packages/backend/src/remote/activitypub/renderer/update.ts b/packages/backend/src/remote/activitypub/renderer/update.ts index ecb0ed2192..b47f7c5cda 100644 --- a/packages/backend/src/remote/activitypub/renderer/update.ts +++ b/packages/backend/src/remote/activitypub/renderer/update.ts @@ -1,4 +1,4 @@ -import config from "@/config/index.js"; +import { config } from "@/config.js"; import type { User } from "@/models/entities/user.js"; export default (object: any, user: { id: User["id"] }) => { diff --git a/packages/backend/src/remote/activitypub/renderer/vote.ts b/packages/backend/src/remote/activitypub/renderer/vote.ts index 21234a112d..118e6761ab 100644 --- a/packages/backend/src/remote/activitypub/renderer/vote.ts +++ b/packages/backend/src/remote/activitypub/renderer/vote.ts @@ -1,4 +1,4 @@ -import config from "@/config/index.js"; +import { config } from "@/config.js"; import type { Note } from "@/models/entities/note.js"; import type { IRemoteUser, User } from "@/models/entities/user.js"; import type { PollVote } from "@/models/entities/poll-vote.js"; diff --git a/packages/backend/src/remote/activitypub/request.ts b/packages/backend/src/remote/activitypub/request.ts index f6d33b8549..8e3bc764d3 100644 --- a/packages/backend/src/remote/activitypub/request.ts +++ b/packages/backend/src/remote/activitypub/request.ts @@ -1,4 +1,4 @@ -import config from "@/config/index.js"; +import { config } from "@/config.js"; import { getUserKeypair } from "@/misc/keypair-store.js"; import type { User, ILocalUser } from "@/models/entities/user.js"; import { StatusError, getResponse } from "@/misc/fetch.js"; diff --git a/packages/backend/src/remote/activitypub/resolver.ts b/packages/backend/src/remote/activitypub/resolver.ts index 79b7962b72..51419deecf 100644 --- a/packages/backend/src/remote/activitypub/resolver.ts +++ b/packages/backend/src/remote/activitypub/resolver.ts @@ -1,8 +1,12 @@ -import config from "@/config/index.js"; +import { config } from "@/config.js"; import type { ILocalUser } from "@/models/entities/user.js"; import { getInstanceActor } from "@/services/instance-actor.js"; -import { fetchMeta } from "backend-rs"; -import { extractHost, isSelfHost } from "backend-rs"; +import { + extractHost, + isAllowedServer, + isBlockedServer, + isSelfHost, +} from "backend-rs"; import { apGet } from "./request.js"; import type { IObject, ICollection, IOrderedCollection } from "./type.js"; import { isCollectionOrOrderedCollection, getApId } from "./type.js"; @@ -21,7 +25,6 @@ import renderQuestion from "@/remote/activitypub/renderer/question.js"; import renderCreate from "@/remote/activitypub/renderer/create.js"; import { renderActivity } from "@/remote/activitypub/renderer/index.js"; import renderFollow from "@/remote/activitypub/renderer/follow.js"; -import { shouldBlockInstance } from "@/misc/should-block-instance.js"; import { apLogger } from "@/remote/activitypub/logger.js"; import { IsNull, Not } from "typeorm"; @@ -69,7 +72,7 @@ export default class Resolver { apLogger.debug("Object to resolve is not a string"); if (typeof value.id !== "undefined") { const host = extractHost(getApId(value)); - if (await shouldBlockInstance(host)) { + if (await isBlockedServer(host)) { throw new Error("instance is blocked"); } } @@ -100,17 +103,12 @@ export default class Resolver { return await this.resolveLocal(value); } - const meta = await fetchMeta(true); - if (await shouldBlockInstance(host, meta)) { - throw new Error("Instance is blocked"); + if (await isBlockedServer(host)) { + throw new Error("This instance is blocked"); } - if ( - meta.privateMode && - config.host !== host && - !meta.allowedHosts.includes(host) - ) { - throw new Error("Instance is not allowed"); + if (config.host !== host && !isAllowedServer(host)) { + throw new Error("This instance is not allowed"); } if (!this.user) { diff --git a/packages/backend/src/remote/resolve-user.ts b/packages/backend/src/remote/resolve-user.ts index 0883386371..69a99c767a 100644 --- a/packages/backend/src/remote/resolve-user.ts +++ b/packages/backend/src/remote/resolve-user.ts @@ -1,7 +1,7 @@ import { URL } from "node:url"; import chalk from "chalk"; import { IsNull } from "typeorm"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import type { User, IRemoteUser } from "@/models/entities/user.js"; import { Users } from "@/models/index.js"; import { toPuny } from "backend-rs"; diff --git a/packages/backend/src/server/activitypub.ts b/packages/backend/src/server/activitypub.ts index 00c8a6babe..1e070f91f9 100644 --- a/packages/backend/src/server/activitypub.ts +++ b/packages/backend/src/server/activitypub.ts @@ -31,8 +31,8 @@ import Following from "./activitypub/following.js"; import Followers from "./activitypub/followers.js"; import Outbox, { packActivity } from "./activitypub/outbox.js"; import { serverLogger } from "./index.js"; -import config from "@/config/index.js"; -import Koa from "koa"; +import { config } from "@/config.js"; +import type Koa from "koa"; import * as crypto from "node:crypto"; import { inspect } from "node:util"; import type { IActivity } from "@/remote/activitypub/type.js"; diff --git a/packages/backend/src/server/activitypub/featured.ts b/packages/backend/src/server/activitypub/featured.ts index e7ea6f238e..671c7ac67e 100644 --- a/packages/backend/src/server/activitypub/featured.ts +++ b/packages/backend/src/server/activitypub/featured.ts @@ -1,5 +1,5 @@ import { IsNull } from "typeorm"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import { renderActivity } from "@/remote/activitypub/renderer/index.js"; import renderOrderedCollection from "@/remote/activitypub/renderer/ordered-collection.js"; import renderNote from "@/remote/activitypub/renderer/note.js"; diff --git a/packages/backend/src/server/activitypub/followers.ts b/packages/backend/src/server/activitypub/followers.ts index 576a672d6d..603e93ebe8 100644 --- a/packages/backend/src/server/activitypub/followers.ts +++ b/packages/backend/src/server/activitypub/followers.ts @@ -1,5 +1,5 @@ import { IsNull, LessThan } from "typeorm"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import * as url from "@/prelude/url.js"; import { renderActivity } from "@/remote/activitypub/renderer/index.js"; import renderOrderedCollection from "@/remote/activitypub/renderer/ordered-collection.js"; diff --git a/packages/backend/src/server/activitypub/following.ts b/packages/backend/src/server/activitypub/following.ts index 76b4e79716..be5a4e9643 100644 --- a/packages/backend/src/server/activitypub/following.ts +++ b/packages/backend/src/server/activitypub/following.ts @@ -1,5 +1,5 @@ import { LessThan, IsNull } from "typeorm"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import * as url from "@/prelude/url.js"; import { renderActivity } from "@/remote/activitypub/renderer/index.js"; import renderOrderedCollection from "@/remote/activitypub/renderer/ordered-collection.js"; diff --git a/packages/backend/src/server/activitypub/outbox.ts b/packages/backend/src/server/activitypub/outbox.ts index 305102cf12..06319565e5 100644 --- a/packages/backend/src/server/activitypub/outbox.ts +++ b/packages/backend/src/server/activitypub/outbox.ts @@ -1,5 +1,5 @@ import { Brackets, IsNull } from "typeorm"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import { renderActivity } from "@/remote/activitypub/renderer/index.js"; import renderOrderedCollection from "@/remote/activitypub/renderer/ordered-collection.js"; import renderOrderedCollectionPage from "@/remote/activitypub/renderer/ordered-collection-page.js"; diff --git a/packages/backend/src/server/api/2fa.ts b/packages/backend/src/server/api/2fa.ts index 7318f0f433..5a6479939d 100644 --- a/packages/backend/src/server/api/2fa.ts +++ b/packages/backend/src/server/api/2fa.ts @@ -1,6 +1,6 @@ import * as crypto from "node:crypto"; import * as jsrsasign from "jsrsasign"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; const ECC_PRELUDE = Buffer.from([0x04]); const NULL_BYTE = Buffer.from([0]); diff --git a/packages/backend/src/server/api/common/signin.ts b/packages/backend/src/server/api/common/signin.ts index e59a39ac41..e5ca09df95 100644 --- a/packages/backend/src/server/api/common/signin.ts +++ b/packages/backend/src/server/api/common/signin.ts @@ -1,6 +1,6 @@ import type Koa from "koa"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import type { ILocalUser } from "@/models/entities/user.js"; import { Signins } from "@/models/index.js"; import { genId } from "backend-rs"; diff --git a/packages/backend/src/server/api/common/signup.ts b/packages/backend/src/server/api/common/signup.ts index 58b88b7d02..40b59c8ed5 100644 --- a/packages/backend/src/server/api/common/signup.ts +++ b/packages/backend/src/server/api/common/signup.ts @@ -8,7 +8,7 @@ import { genId, hashPassword, toPuny } from "backend-rs"; import { UserKeypair } from "@/models/entities/user-keypair.js"; import { UsedUsername } from "@/models/entities/used-username.js"; import { db } from "@/db/postgre.js"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; export async function signup(opts: { username: User["username"]; diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts index 78034917f0..4063af5c5c 100644 --- a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts +++ b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts @@ -16,68 +16,7 @@ export const meta = { type: "object", optional: false, nullable: false, - properties: { - id: { - type: "string", - nullable: false, - optional: false, - format: "id", - example: "xxxxxxxxxx", - }, - createdAt: { - type: "string", - nullable: false, - optional: false, - format: "date-time", - }, - comment: { - type: "string", - nullable: false, - optional: false, - }, - resolved: { - type: "boolean", - nullable: false, - optional: false, - example: false, - }, - reporterId: { - type: "string", - nullable: false, - optional: false, - format: "id", - }, - targetUserId: { - type: "string", - nullable: false, - optional: false, - format: "id", - }, - assigneeId: { - type: "string", - nullable: true, - optional: false, - format: "id", - }, - reporter: { - type: "object", - nullable: false, - optional: false, - ref: "User", - }, - targetUser: { - type: "object", - nullable: false, - optional: false, - ref: "User", - }, - assignee: { - type: "object", - nullable: true, - optional: true, - ref: "User", - }, - }, + ref: "AbuseUserReport", }, }, } as const; diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/hosted.ts b/packages/backend/src/server/api/endpoints/admin/accounts/hosted.ts index fd4ad9401a..01d08f7a3d 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/hosted.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/hosted.ts @@ -1,4 +1,4 @@ -import config from "@/config/index.js"; +import { config } from "@/config.js"; import { Meta } from "@/models/entities/meta.js"; import { insertModerationLog } from "@/services/insert-moderation-log.js"; import { db } from "@/db/postgre.js"; diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index c7731c6c81..ecfed950d3 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -1,4 +1,4 @@ -import config from "@/config/index.js"; +import { config } from "@/config.js"; import { fetchMeta } from "backend-rs"; import { MAX_NOTE_TEXT_LENGTH, MAX_CAPTION_TEXT_LENGTH } from "@/const.js"; import define from "@/server/api/define.js"; @@ -24,6 +24,11 @@ export const meta = { optional: false, nullable: false, }, + antennaLimit: { + type: "number", + optional: false, + nullable: false, + }, cacheRemoteFiles: { type: "boolean", optional: false, @@ -487,6 +492,7 @@ export default define(meta, paramDef, async () => { enableGuestTimeline: instance.enableGuestTimeline, driveCapacityPerLocalUserMb: instance.localDriveCapacityMb, driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb, + antennaLimit: instance.antennaLimit, emailRequiredForSignup: instance.emailRequiredForSignup, enableHcaptcha: instance.enableHcaptcha, hcaptchaSiteKey: instance.hcaptchaSiteKey, diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index 604ef3a0fc..e5234ea720 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -94,6 +94,7 @@ export const paramDef = { defaultDarkTheme: { type: "string", nullable: true }, localDriveCapacityMb: { type: "integer" }, remoteDriveCapacityMb: { type: "integer" }, + antennaLimit: { type: "integer" }, cacheRemoteFiles: { type: "boolean" }, markLocalFilesNsfwByDefault: { type: "boolean" }, emailRequiredForSignup: { type: "boolean" }, @@ -327,6 +328,10 @@ export default define(meta, paramDef, async (ps, me) => { set.remoteDriveCapacityMb = ps.remoteDriveCapacityMb; } + if (ps.antennaLimit !== undefined) { + set.antennaLimit = ps.antennaLimit; + } + if (ps.cacheRemoteFiles !== undefined) { set.cacheRemoteFiles = ps.cacheRemoteFiles; } diff --git a/packages/backend/src/server/api/endpoints/antennas/create.ts b/packages/backend/src/server/api/endpoints/antennas/create.ts index 792301d4de..aa5dcee044 100644 --- a/packages/backend/src/server/api/endpoints/antennas/create.ts +++ b/packages/backend/src/server/api/endpoints/antennas/create.ts @@ -1,5 +1,5 @@ import define from "@/server/api/define.js"; -import { genId } from "backend-rs"; +import { fetchMeta, genId } from "backend-rs"; import { Antennas, UserLists, UserGroupJoinings } from "@/models/index.js"; import { ApiError } from "@/server/api/error.js"; import { publishInternalEvent } from "@/services/stream.js"; @@ -109,10 +109,12 @@ export default define(meta, paramDef, async (ps, user) => { let userList; let userGroupJoining; + const instance = await fetchMeta(true); + const antennas = await Antennas.findBy({ userId: user.id, }); - if (antennas.length > 5 && !user.isAdmin) { + if (antennas.length >= instance.antennaLimit) { throw new ApiError(meta.errors.tooManyAntennas); } diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index f1d42d8d93..56307311b2 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -4,14 +4,13 @@ import { createNote } from "@/remote/activitypub/models/note.js"; import DbResolver from "@/remote/activitypub/db-resolver.js"; import Resolver from "@/remote/activitypub/resolver.js"; import { ApiError } from "@/server/api/error.js"; -import { extractHost } from "backend-rs"; +import { extractHost, isBlockedServer } from "backend-rs"; import { Users, Notes } from "@/models/index.js"; import type { Note } from "@/models/entities/note.js"; import type { CacheableLocalUser, User } from "@/models/entities/user.js"; import { isActor, isPost, getApId } from "@/remote/activitypub/type.js"; import type { SchemaType } from "@/misc/schema.js"; import { MINUTE } from "@/const.js"; -import { shouldBlockInstance } from "@/misc/should-block-instance.js"; import { updateQuestion } from "@/remote/activitypub/models/question.js"; import { populatePoll } from "@/models/repositories/note.js"; import { redisClient } from "@/db/redis.js"; @@ -101,7 +100,7 @@ async function fetchAny( me: CacheableLocalUser | null | undefined, ): Promise | null> { // Wait if blocked. - if (await shouldBlockInstance(extractHost(uri))) return null; + if (await isBlockedServer(extractHost(uri))) return null; const dbResolver = new DbResolver(); diff --git a/packages/backend/src/server/api/endpoints/auth/session/generate.ts b/packages/backend/src/server/api/endpoints/auth/session/generate.ts index 26a1fddfcb..f20219aa82 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/generate.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/generate.ts @@ -1,5 +1,5 @@ import { v4 as uuid } from "uuid"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import define from "@/server/api/define.js"; import { ApiError } from "@/server/api/error.js"; import { Apps, AuthSessions } from "@/models/index.js"; diff --git a/packages/backend/src/server/api/endpoints/channels/update.ts b/packages/backend/src/server/api/endpoints/channels/update.ts index 0de7a837a1..fdd21da65f 100644 --- a/packages/backend/src/server/api/endpoints/channels/update.ts +++ b/packages/backend/src/server/api/endpoints/channels/update.ts @@ -83,7 +83,7 @@ export default define(meta, paramDef, async (ps, me) => { await Channels.update(channel.id, { ...(ps.name !== undefined ? { name: ps.name } : {}), ...(ps.description !== undefined ? { description: ps.description } : {}), - ...(banner ? { bannerId: banner.id } : {}), + ...(banner ? { bannerId: banner.id } : { bannerId: null }), }); return await Channels.pack(channel.id, me); diff --git a/packages/backend/src/server/api/endpoints/fetch-rss.ts b/packages/backend/src/server/api/endpoints/fetch-rss.ts index bda3c455d1..489aaab0f5 100644 --- a/packages/backend/src/server/api/endpoints/fetch-rss.ts +++ b/packages/backend/src/server/api/endpoints/fetch-rss.ts @@ -1,6 +1,6 @@ import Parser from "rss-parser"; import { getResponse } from "@/misc/fetch.js"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import define from "@/server/api/define.js"; const rssParser = new Parser(); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts index 6c99217e7d..0951369dd8 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts @@ -6,7 +6,7 @@ import { AttestationChallenges, Users, } from "@/models/index.js"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import { procedures, hash } from "@/server/api/2fa.js"; import { publishMainStream } from "@/services/stream.js"; import { verifyPassword } from "backend-rs"; diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register.ts b/packages/backend/src/server/api/endpoints/i/2fa/register.ts index c0e6137d5d..eb926a6098 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register.ts @@ -1,6 +1,6 @@ import * as OTPAuth from "otpauth"; import * as QRCode from "qrcode"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import { UserProfiles } from "@/models/index.js"; import define from "@/server/api/define.js"; import { verifyPassword } from "backend-rs"; diff --git a/packages/backend/src/server/api/endpoints/i/move.ts b/packages/backend/src/server/api/endpoints/i/move.ts index 4784d3ee20..381bb1dea9 100644 --- a/packages/backend/src/server/api/endpoints/i/move.ts +++ b/packages/backend/src/server/api/endpoints/i/move.ts @@ -10,7 +10,7 @@ import deleteFollowing from "@/services/following/delete.js"; import create from "@/services/following/create.js"; import { getUser } from "@/server/api/common/getters.js"; import { Followings, Users } from "@/models/index.js"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import { publishMainStream } from "@/services/stream.js"; import { stringToAcct } from "backend-rs"; import { inspect } from "node:util"; diff --git a/packages/backend/src/server/api/endpoints/i/update-email.ts b/packages/backend/src/server/api/endpoints/i/update-email.ts index 234127f584..185d1c8dc9 100644 --- a/packages/backend/src/server/api/endpoints/i/update-email.ts +++ b/packages/backend/src/server/api/endpoints/i/update-email.ts @@ -1,7 +1,7 @@ import { publishMainStream } from "@/services/stream.js"; import define from "@/server/api/define.js"; import rndstr from "rndstr"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import { Users, UserProfiles } from "@/models/index.js"; import { sendEmail } from "@/services/send-email.js"; import { ApiError } from "@/server/api/error.js"; diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index 4389688a12..4f65c59a9e 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -13,6 +13,7 @@ import { normalizeForSearch } from "@/misc/normalize-for-search.js"; import { verifyLink } from "@/services/fetch-rel-me.js"; import { ApiError } from "@/server/api/error.js"; import define from "@/server/api/define.js"; +import { DriveFile } from "@/models/entities/drive-file"; export const meta = { tags: ["account"], @@ -241,8 +242,9 @@ export default define(meta, paramDef, async (ps, _user, token) => { if (ps.emailNotificationTypes !== undefined) profileUpdates.emailNotificationTypes = ps.emailNotificationTypes; + let avatar: DriveFile | null = null; if (ps.avatarId) { - const avatar = await DriveFiles.findOneBy({ id: ps.avatarId }); + avatar = await DriveFiles.findOneBy({ id: ps.avatarId }); if (avatar == null || avatar.userId !== user.id) throw new ApiError(meta.errors.noSuchAvatar); @@ -250,8 +252,9 @@ export default define(meta, paramDef, async (ps, _user, token) => { throw new ApiError(meta.errors.avatarNotAnImage); } + let banner: DriveFile | null = null; if (ps.bannerId) { - const banner = await DriveFiles.findOneBy({ id: ps.bannerId }); + banner = await DriveFiles.findOneBy({ id: ps.bannerId }); if (banner == null || banner.userId !== user.id) throw new ApiError(meta.errors.noSuchBanner); @@ -328,6 +331,20 @@ export default define(meta, paramDef, async (ps, _user, token) => { updateUsertags(user, tags); //#endregion + // Update old/new avatar usage hints + if (avatar) { + if (user.avatarId) + await DriveFiles.update(user.avatarId, { usageHint: null }); + await DriveFiles.update(avatar.id, { usageHint: "userAvatar" }); + } + + // Update old/new banner usage hints + if (banner) { + if (user.bannerId) + await DriveFiles.update(user.bannerId, { usageHint: null }); + await DriveFiles.update(banner.id, { usageHint: "userBanner" }); + } + if (Object.keys(updates).length > 0) await Users.update(user.id, updates); if (Object.keys(profileUpdates).length > 0) await UserProfiles.update(user.id, profileUpdates); diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index 31677ee2ef..f35ae9cc6b 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -1,6 +1,6 @@ import JSON5 from "json5"; import { IsNull, MoreThan } from "typeorm"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import { fetchMeta } from "backend-rs"; import { Ads, Emojis, Users } from "@/models/index.js"; import { MAX_NOTE_TEXT_LENGTH, MAX_CAPTION_TEXT_LENGTH } from "@/const.js"; @@ -126,6 +126,11 @@ export const meta = { optional: false, nullable: false, }, + antennaLimit: { + type: "number", + optional: false, + nullable: false, + }, cacheRemoteFiles: { type: "boolean", optional: false, @@ -445,6 +450,7 @@ export default define(meta, paramDef, async (ps, me) => { enableGuestTimeline: instance.enableGuestTimeline, driveCapacityPerLocalUserMb: instance.localDriveCapacityMb, driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb, + antennaLimit: instance.antennaLimit, emailRequiredForSignup: instance.emailRequiredForSignup, enableHcaptcha: instance.enableHcaptcha, hcaptchaSiteKey: instance.hcaptchaSiteKey, diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index 270c33abd0..c2302f4c8d 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -114,7 +114,7 @@ export const paramDef = { enum: Object.keys(langmap), nullable: true, }, - cw: { type: "string", nullable: true, maxLength: 100 }, + cw: { type: "string", nullable: true, maxLength: MAX_NOTE_TEXT_LENGTH }, localOnly: { type: "boolean", default: false }, noExtractMentions: { type: "boolean", default: false }, noExtractHashtags: { type: "boolean", default: false }, diff --git a/packages/backend/src/server/api/endpoints/pinned-users.ts b/packages/backend/src/server/api/endpoints/pinned-users.ts index 325b54f350..65241becae 100644 --- a/packages/backend/src/server/api/endpoints/pinned-users.ts +++ b/packages/backend/src/server/api/endpoints/pinned-users.ts @@ -44,9 +44,7 @@ export default define(meta, paramDef, async (ps, me) => { ), ); - return await Users.packMany( - users.filter((x) => x !== undefined) as User[], - me, - { detail: true }, - ); + return await Users.packMany(users.filter((x) => x != null) as User[], me, { + detail: true, + }); }); diff --git a/packages/backend/src/server/api/endpoints/request-reset-password.ts b/packages/backend/src/server/api/endpoints/request-reset-password.ts index 9855b06513..00936c9d22 100644 --- a/packages/backend/src/server/api/endpoints/request-reset-password.ts +++ b/packages/backend/src/server/api/endpoints/request-reset-password.ts @@ -1,6 +1,6 @@ import rndstr from "rndstr"; import { IsNull } from "typeorm"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import { Users, UserProfiles, PasswordResetRequests } from "@/models/index.js"; import { sendEmail } from "@/services/send-email.js"; import { genId } from "backend-rs"; diff --git a/packages/backend/src/server/api/endpoints/username/available.ts b/packages/backend/src/server/api/endpoints/username/available.ts index cd634a798a..3818a3c28f 100644 --- a/packages/backend/src/server/api/endpoints/username/available.ts +++ b/packages/backend/src/server/api/endpoints/username/available.ts @@ -1,6 +1,6 @@ import { IsNull } from "typeorm"; import { Users, UsedUsernames } from "@/models/index.js"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import define from "@/server/api/define.js"; export const meta = { diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts index 493661b2a2..7e5cd892bc 100644 --- a/packages/backend/src/server/api/index.ts +++ b/packages/backend/src/server/api/index.ts @@ -12,7 +12,7 @@ import { getClient, } from "./mastodon/ApiMastodonCompatibleService.js"; import { AccessTokens, Users } from "@/models/index.js"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import endpoints from "./endpoints.js"; import compatibility from "./compatibility.js"; import handler from "./api-handler.js"; diff --git a/packages/backend/src/server/api/mastodon/converters.ts b/packages/backend/src/server/api/mastodon/converters.ts index b392403578..b2259e6ed5 100644 --- a/packages/backend/src/server/api/mastodon/converters.ts +++ b/packages/backend/src/server/api/mastodon/converters.ts @@ -1,4 +1,4 @@ -import { Entity } from "megalodon"; +import type { Entity } from "megalodon"; import { toMastodonId } from "backend-rs"; function simpleConvert(data: any) { @@ -15,7 +15,19 @@ export function convertAnnouncement(announcement: Entity.Announcement) { return simpleConvert(announcement); } export function convertAttachment(attachment: Entity.Attachment) { - return simpleConvert(attachment); + const converted = simpleConvert(attachment); + // ref: https://github.com/whitescent/Mastify/pull/102 + if (converted.meta == null) return converted; + const result = { + ...converted, + meta: { + ...converted.meta, + original: { + ...converted.meta, + }, + }, + }; + return result; } export function convertFilter(filter: Entity.Filter) { return simpleConvert(filter); diff --git a/packages/backend/src/server/api/mastodon/endpoints/meta.ts b/packages/backend/src/server/api/mastodon/endpoints/meta.ts index 5c304929a1..862310fb3e 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/meta.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/meta.ts @@ -1,5 +1,5 @@ import { Entity } from "megalodon"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import { fetchMeta } from "backend-rs"; import { Users, Notes } from "@/models/index.js"; import { IsNull } from "typeorm"; diff --git a/packages/backend/src/server/api/openapi/gen-spec.ts b/packages/backend/src/server/api/openapi/gen-spec.ts index 8b2f92b745..79dc48a8bb 100644 --- a/packages/backend/src/server/api/openapi/gen-spec.ts +++ b/packages/backend/src/server/api/openapi/gen-spec.ts @@ -1,5 +1,5 @@ import endpoints from "@/server/api/endpoints.js"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import { errors as basicErrors } from "./errors.js"; import { schemas, convertSchemaToOpenApiSchema } from "./schemas.js"; diff --git a/packages/backend/src/server/api/private/signin.ts b/packages/backend/src/server/api/private/signin.ts index a7eb623062..d8bbbf74ad 100644 --- a/packages/backend/src/server/api/private/signin.ts +++ b/packages/backend/src/server/api/private/signin.ts @@ -1,7 +1,7 @@ import type Koa from "koa"; import * as OTPAuth from "otpauth"; import signin from "@/server/api/common/signin.js"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import { Users, Signins, diff --git a/packages/backend/src/server/api/private/signup.ts b/packages/backend/src/server/api/private/signup.ts index 5af5d65b50..4dd4ffb231 100644 --- a/packages/backend/src/server/api/private/signup.ts +++ b/packages/backend/src/server/api/private/signup.ts @@ -3,7 +3,7 @@ import rndstr from "rndstr"; import { verifyHcaptcha, verifyRecaptcha } from "@/misc/captcha.js"; import { Users, RegistrationTickets, UserPendings } from "@/models/index.js"; import { signup } from "@/server/api/common/signup.js"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import { sendEmail } from "@/services/send-email.js"; import { fetchMeta, genId, hashPassword } from "backend-rs"; import { validateEmailForAccount } from "@/services/validate-email-for-account.js"; diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts index fcfb7ecda3..2722a6610a 100644 --- a/packages/backend/src/server/api/stream/channels/user-list.ts +++ b/packages/backend/src/server/api/stream/channels/user-list.ts @@ -22,7 +22,7 @@ export default class extends Channel { this.listId = params.listId as string; // Check existence and owner - const exist = await UserLists.exist({ + const exist = await UserLists.exists({ where: { id: this.listId, userId: this.user!.id, diff --git a/packages/backend/src/server/api/stream/index.ts b/packages/backend/src/server/api/stream/index.ts index 416a041b52..313719e207 100644 --- a/packages/backend/src/server/api/stream/index.ts +++ b/packages/backend/src/server/api/stream/index.ts @@ -1,4 +1,4 @@ -import type { EventEmitter } from "events"; +import type { EventEmitter } from "node:events"; import type * as websocket from "websocket"; import readNote from "@/services/note/read.js"; import type { User } from "@/models/entities/user.js"; diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts index 91095a46d3..8b4ac9e502 100644 --- a/packages/backend/src/server/api/stream/types.ts +++ b/packages/backend/src/server/api/stream/types.ts @@ -1,4 +1,4 @@ -import type { EventEmitter } from "events"; +import type { EventEmitter } from "node:events"; import type Emitter from "strict-event-emitter-types"; import type { Channel } from "@/models/entities/channel.js"; import type { User } from "@/models/entities/user.js"; diff --git a/packages/backend/src/server/api/streaming.ts b/packages/backend/src/server/api/streaming.ts index a25984ec3e..12f97d8018 100644 --- a/packages/backend/src/server/api/streaming.ts +++ b/packages/backend/src/server/api/streaming.ts @@ -1,6 +1,6 @@ import type * as http from "node:http"; -import { EventEmitter } from "events"; -import type { ParsedUrlQuery } from "querystring"; +import { EventEmitter } from "node:events"; +import type { ParsedUrlQuery } from "node:querystring"; import * as websocket from "websocket"; import { subscriber as redisClient } from "@/db/redis.js"; diff --git a/packages/backend/src/server/file/byte-range-readable.ts b/packages/backend/src/server/file/byte-range-readable.ts index 96dcbc4a52..9699f95092 100644 --- a/packages/backend/src/server/file/byte-range-readable.ts +++ b/packages/backend/src/server/file/byte-range-readable.ts @@ -1,4 +1,4 @@ -import { Readable, ReadableOptions } from "node:stream"; +import { Readable, type ReadableOptions } from "node:stream"; import { Buffer } from "node:buffer"; import * as fs from "node:fs"; diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts index 6cf837b4ed..54d6e8bf5f 100644 --- a/packages/backend/src/server/index.ts +++ b/packages/backend/src/server/index.ts @@ -13,15 +13,14 @@ import koaLogger from "koa-logger"; import * as slow from "koa-slow"; import { IsNull } from "typeorm"; -import config from "@/config/index.js"; +import { config, envOption } from "@/config.js"; import Logger from "@/services/logger.js"; import { Users } from "@/models/index.js"; import { fetchMeta } from "backend-rs"; import { genIdenticon } from "@/misc/gen-identicon.js"; import { createTemp } from "@/misc/create-temp.js"; import { stringToAcct } from "backend-rs"; -import { envOption } from "@/env.js"; -import megalodon, { MegalodonInterface } from "megalodon"; +import megalodon, { type MegalodonInterface } from "megalodon"; import activityPub from "./activitypub.js"; import nodeinfo from "./nodeinfo.js"; import wellKnown from "./well-known.js"; diff --git a/packages/backend/src/server/nodeinfo.ts b/packages/backend/src/server/nodeinfo.ts index 7359878b19..91e5fd8034 100644 --- a/packages/backend/src/server/nodeinfo.ts +++ b/packages/backend/src/server/nodeinfo.ts @@ -1,5 +1,5 @@ import Router from "@koa/router"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import { fetchMeta } from "backend-rs"; import { Users, Notes } from "@/models/index.js"; import { IsNull, MoreThan } from "typeorm"; diff --git a/packages/backend/src/server/web/feed.ts b/packages/backend/src/server/web/feed.ts index e6b09b4f4f..3beffc82f0 100644 --- a/packages/backend/src/server/web/feed.ts +++ b/packages/backend/src/server/web/feed.ts @@ -1,8 +1,17 @@ import { Feed } from "feed"; import { In, IsNull } from "typeorm"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import type { User } from "@/models/entities/user.js"; +import type { Note } from "@/models/entities/note.js"; import { Notes, DriveFiles, UserProfiles, Users } from "@/models/index.js"; +import getNoteHtml from "@/remote/activitypub/misc/get-note-html.js"; + +/** + * If there is this part in the note, it will cause CDATA to be terminated early. + */ +function escapeCDATA(str: string) { + return str.replaceAll("]]>", "]]]]>"); +} export default async function ( user: User, @@ -15,7 +24,7 @@ export default async function ( const author = { link: `${config.url}/@${user.username}`, email: `${user.username}@${config.host}`, - name: user.name || user.username, + name: escapeCDATA(user.name || user.username), }; const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); @@ -44,11 +53,13 @@ export default async function ( title: `${author.name} (@${user.username}@${config.host})`, updated: notes[0].createdAt, generator: "Firefish", - description: `${user.notesCount} Notes, ${ - profile.ffVisibility === "public" ? user.followingCount : "?" - } Following, ${ - profile.ffVisibility === "public" ? user.followersCount : "?" - } Followers${profile.description ? ` · ${profile.description}` : ""}`, + description: escapeCDATA( + `${user.notesCount} Notes, ${ + profile.ffVisibility === "public" ? user.followingCount : "?" + } Following, ${ + profile.ffVisibility === "public" ? user.followersCount : "?" + } Followers${profile.description ? ` · ${profile.description}` : ""}`, + ), link: author.link, image: await Users.getAvatarUrl(user), feedLinks: { @@ -88,19 +99,23 @@ export default async function ( } feed.addItem({ - title: title - .replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "") - .substring(0, 100), + title: escapeCDATA( + title + .replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "") + .substring(0, 100), + ), link: `${config.url}/notes/${note.id}`, date: note.createdAt, description: note.cw - ? note.cw.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "") + ? escapeCDATA(note.cw.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "")) : undefined, - content: contentStr.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, ""), + content: escapeCDATA( + contentStr.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, ""), + ), }); } - async function noteToString(note, isTheNote = false) { + async function noteToString(note: Note, isTheNote = false) { const author = isTheNote ? null : await Users.findOneBy({ id: note.userId }); @@ -135,7 +150,10 @@ export default async function ( }">${file.name}`; } } - outstr += `${note.cw ? note.cw + "
" : ""}${note.text || ""}${fileEle}`; + + outstr += `${note.cw ? note.cw + "
" : ""}${ + getNoteHtml(note) || "" + }${fileEle}`; if (isTheNote) { outstr += ` { const url = decodeURI(ctx.path); if (url === bullBoardPath || url.startsWith(`${bullBoardPath}/`)) { + if (!url.startsWith(`${bullBoardPath}/static/`)) { + ctx.set("Cache-Control", "private, max-age=0, must-revalidate"); + } + const token = ctx.cookies.get("token"); if (token == null) { ctx.status = 401; @@ -517,8 +520,8 @@ router.get("/notes/:note", async (ctx, next) => { }); try { - if (note) { - const _note = await Notes.pack(note); + if (note != null) { + const packedNote = await Notes.pack(note); const profile = await UserProfiles.findOneByOrFail({ userId: note.userId, @@ -526,13 +529,13 @@ router.get("/notes/:note", async (ctx, next) => { const meta = await fetchMeta(true); await ctx.render("note", { ...metaToPugArgs(meta), - note: _note, + note: packedNote, profile, avatarUrl: await Users.getAvatarUrl( await Users.findOneByOrFail({ id: note.userId }), ), // TODO: Let locale changeable by instance setting - summary: getNoteSummary(_note), + summary: getNoteSummary(note), }); ctx.set("Cache-Control", "public, max-age=15"); diff --git a/packages/backend/src/server/web/manifest.ts b/packages/backend/src/server/web/manifest.ts index a4c615c7ab..77c8a57cf4 100644 --- a/packages/backend/src/server/web/manifest.ts +++ b/packages/backend/src/server/web/manifest.ts @@ -1,6 +1,6 @@ import type Koa from "koa"; import { fetchMeta } from "backend-rs"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import manifest from "./manifest.json" assert { type: "json" }; export const manifestHandler = async (ctx: Koa.Context) => { diff --git a/packages/backend/src/server/web/url-preview.ts b/packages/backend/src/server/web/url-preview.ts index f59f3f357a..8fd757ef14 100644 --- a/packages/backend/src/server/web/url-preview.ts +++ b/packages/backend/src/server/web/url-preview.ts @@ -2,7 +2,7 @@ import type Koa from "koa"; import summaly from "summaly"; import { fetchMeta } from "backend-rs"; import Logger from "@/services/logger.js"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import { query } from "@/prelude/url.js"; import { getJson } from "@/misc/fetch.js"; import { inspect } from "node:util"; diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug index bdbe153fbe..738d3ffc01 100644 --- a/packages/backend/src/server/web/views/base.pug +++ b/packages/backend/src/server/web/views/base.pug @@ -72,8 +72,8 @@ html div#splash img#splashIcon(src= splashIcon || `/static-assets/splash.svg?${ timestamp }`) span#splashText - block randomMOTD - = randomMOTD + block randomMotd + = randomMotd div#splashSpinner diff --git a/packages/backend/src/server/well-known.ts b/packages/backend/src/server/well-known.ts index fc339eaad6..4a244db57f 100644 --- a/packages/backend/src/server/well-known.ts +++ b/packages/backend/src/server/well-known.ts @@ -1,6 +1,6 @@ import Router from "@koa/router"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import { type Acct, stringToAcct } from "backend-rs"; import { links } from "./nodeinfo.js"; import { escapeAttribute, escapeValue } from "@/prelude/xml.js"; diff --git a/packages/backend/src/services/add-note-to-antenna.ts b/packages/backend/src/services/add-note-to-antenna.ts deleted file mode 100644 index 66bc898263..0000000000 --- a/packages/backend/src/services/add-note-to-antenna.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { Antenna } from "@/models/entities/antenna.js"; -import type { Note } from "@/models/entities/note.js"; -import { getTimestamp } from "backend-rs"; -import { redisClient } from "@/db/redis.js"; -import { publishAntennaStream } from "@/services/stream.js"; -import type { User } from "@/models/entities/user.js"; - -export async function addNoteToAntenna( - antenna: Antenna, - note: Note, - _noteUser: { id: User["id"] }, -) { - redisClient.xadd( - `antennaTimeline:${antenna.id}`, - "MAXLEN", - "~", - "200", - `${getTimestamp(note.id)}-*`, - "note", - note.id, - ); - - publishAntennaStream(antenna.id, "note", note); -} diff --git a/packages/backend/src/services/chart/charts/active-users.ts b/packages/backend/src/services/chart/charts/active-users.ts index 3f4b7e3381..067334005e 100644 --- a/packages/backend/src/services/chart/charts/active-users.ts +++ b/packages/backend/src/services/chart/charts/active-users.ts @@ -1,4 +1,3 @@ -import type { KVs } from "../core.js"; import Chart from "../core.js"; import type { User } from "@/models/entities/user.js"; import { name, schema } from "./entities/active-users.js"; diff --git a/packages/backend/src/services/create-notification.ts b/packages/backend/src/services/create-notification.ts index a108d4f5de..93fec126d3 100644 --- a/packages/backend/src/services/create-notification.ts +++ b/packages/backend/src/services/create-notification.ts @@ -8,11 +8,10 @@ import { Users, Followings, } from "@/models/index.js"; -import { genId } from "backend-rs"; +import { genId, isSilencedServer } from "backend-rs"; import type { User } from "@/models/entities/user.js"; import type { Notification } from "@/models/entities/notification.js"; import { sendEmailNotification } from "./send-email-notification.js"; -import { shouldSilenceInstance } from "@/misc/should-block-instance.js"; export async function createNotification( notifieeId: User["id"], @@ -35,8 +34,8 @@ export async function createNotification( if ( (notifier.isSilenced || (Users.isRemoteUser(notifier) && - (await shouldSilenceInstance(notifier.host)))) && - !(await Followings.exist({ + (await isSilencedServer(notifier.host)))) && + !(await Followings.exists({ where: { followerId: notifieeId, followeeId: data.notifierId }, })) ) diff --git a/packages/backend/src/services/drive/add-file.ts b/packages/backend/src/services/drive/add-file.ts index 24ad9f8f02..d180bbabf3 100644 --- a/packages/backend/src/services/drive/add-file.ts +++ b/packages/backend/src/services/drive/add-file.ts @@ -16,6 +16,7 @@ import { UserProfiles, } from "@/models/index.js"; import { DriveFile } from "@/models/entities/drive-file.js"; +import type { DriveFileUsageHint } from "@/models/entities/drive-file.js"; import type { IRemoteUser, User } from "@/models/entities/user.js"; import { genId } from "backend-rs"; import { isDuplicateKeyValueError } from "@/misc/is-duplicate-key-value-error.js"; @@ -65,6 +66,7 @@ function urlPathJoin( * @param type Content-Type for original * @param hash Hash for original * @param size Size for original + * @param usage Optional usage hint for file (f.e. "userAvatar") */ async function save( file: DriveFile, @@ -73,6 +75,7 @@ async function save( type: string, hash: string, size: number, + usage: DriveFileUsageHint = null, ): Promise { // thunbnail, webpublic を必要なら生成 const alts = await generateAlts(path, type, !file.uri); @@ -161,6 +164,7 @@ async function save( file.md5 = hash; file.size = size; file.storedInternal = false; + file.usageHint = usage ?? null; return await DriveFiles.insert(file).then((x) => DriveFiles.findOneByOrFail(x.identifiers[0]), @@ -204,6 +208,7 @@ async function save( file.type = type; file.md5 = hash; file.size = size; + file.usageHint = usage ?? null; return await DriveFiles.insert(file).then((x) => DriveFiles.findOneByOrFail(x.identifiers[0]), @@ -450,6 +455,9 @@ type AddFileArgs = { requestIp?: string | null; requestHeaders?: Record | null; + + /** Whether this file has a known use case, like user avatar or instance icon */ + usageHint?: DriveFileUsageHint; }; /** @@ -469,6 +477,7 @@ export async function addFile({ sensitive = null, requestIp = null, requestHeaders = null, + usageHint = null, }: AddFileArgs): Promise { const info = await getFileInfo(path); logger.info(`${JSON.stringify(info)}`); @@ -581,6 +590,7 @@ export async function addFile({ file.isLink = isLink; file.requestIp = requestIp; file.requestHeaders = requestHeaders; + file.usageHint = usageHint; file.isSensitive = user ? Users.isLocalUser(user) && (instance!.markLocalFilesNsfwByDefault || profile!.alwaysMarkNsfw) @@ -639,6 +649,7 @@ export async function addFile({ info.type.mime, info.md5, info.size, + usageHint, ); } diff --git a/packages/backend/src/services/drive/internal-storage.ts b/packages/backend/src/services/drive/internal-storage.ts index b2a663b3ea..6413f7920e 100644 --- a/packages/backend/src/services/drive/internal-storage.ts +++ b/packages/backend/src/services/drive/internal-storage.ts @@ -3,7 +3,7 @@ import * as fsPromises from "node:fs/promises"; import * as Path from "node:path"; import { fileURLToPath } from "node:url"; import { dirname } from "node:path"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); diff --git a/packages/backend/src/services/drive/upload-from-url.ts b/packages/backend/src/services/drive/upload-from-url.ts index 551d3757ca..e7b084bda1 100644 --- a/packages/backend/src/services/drive/upload-from-url.ts +++ b/packages/backend/src/services/drive/upload-from-url.ts @@ -3,7 +3,10 @@ import type { User } from "@/models/entities/user.js"; import { createTemp } from "@/misc/create-temp.js"; import { downloadUrl, isPrivateIp } from "@/misc/download-url.js"; import type { DriveFolder } from "@/models/entities/drive-folder.js"; -import type { DriveFile } from "@/models/entities/drive-file.js"; +import type { + DriveFile, + DriveFileUsageHint, +} from "@/models/entities/drive-file.js"; import { DriveFiles } from "@/models/index.js"; import { driveLogger } from "./logger.js"; import { addFile } from "./add-file.js"; @@ -13,7 +16,11 @@ const logger = driveLogger.createSubLogger("downloader"); type Args = { url: string; - user: { id: User["id"]; host: User["host"] } | null; + user: { + id: User["id"]; + host: User["host"]; + driveCapacityOverrideMb: User["driveCapacityOverrideMb"]; + } | null; folderId?: DriveFolder["id"] | null; uri?: string | null; sensitive?: boolean; @@ -22,6 +29,7 @@ type Args = { comment?: string | null; requestIp?: string | null; requestHeaders?: Record | null; + usageHint?: DriveFileUsageHint; }; export async function uploadFromUrl({ @@ -35,6 +43,7 @@ export async function uploadFromUrl({ comment = null, requestIp = null, requestHeaders = null, + usageHint = null, }: Args): Promise { const parsedUrl = new URL(url); if ( @@ -75,9 +84,10 @@ export async function uploadFromUrl({ sensitive, requestIp, requestHeaders, + usageHint, }); logger.succ(`Got: ${driveFile.id}`); - return driveFile!; + return driveFile; } catch (e) { logger.error(`Failed to create drive file:\n${inspect(e)}`); throw e; diff --git a/packages/backend/src/services/fetch-rel-me.ts b/packages/backend/src/services/fetch-rel-me.ts index c9a37d1c88..88f9c2ab1e 100644 --- a/packages/backend/src/services/fetch-rel-me.ts +++ b/packages/backend/src/services/fetch-rel-me.ts @@ -1,5 +1,6 @@ import { Window } from "happy-dom"; -import config from "@/config/index.js"; +import type { HTMLAnchorElement, HTMLLinkElement } from "happy-dom"; +import { config } from "@/config.js"; async function getRelMeLinks(url: string): Promise { try { diff --git a/packages/backend/src/services/following/create.ts b/packages/backend/src/services/following/create.ts index 7387346d1a..49852ee9ef 100644 --- a/packages/backend/src/services/following/create.ts +++ b/packages/backend/src/services/following/create.ts @@ -17,13 +17,12 @@ import { Instances, UserProfiles, } from "@/models/index.js"; -import { genId } from "backend-rs"; +import { genId, isSilencedServer } from "backend-rs"; import { createNotification } from "@/services/create-notification.js"; import { isDuplicateKeyValueError } from "@/misc/is-duplicate-key-value-error.js"; import type { Packed } from "@/misc/schema.js"; import { getActiveWebhooks } from "@/misc/webhook-cache.js"; import { webhookDeliver } from "@/queue/index.js"; -import { shouldSilenceInstance } from "@/misc/should-block-instance.js"; const logger = new Logger("following/create"); @@ -231,7 +230,7 @@ export default async function ( (Users.isLocalUser(follower) && Users.isRemoteUser(followee)) || (Users.isRemoteUser(follower) && Users.isLocalUser(followee) && - (await shouldSilenceInstance(follower.host))) + (await isSilencedServer(follower.host))) ) { let autoAccept = false; diff --git a/packages/backend/src/services/following/requests/create.ts b/packages/backend/src/services/following/requests/create.ts index d2f2c1ca41..146e20efd9 100644 --- a/packages/backend/src/services/following/requests/create.ts +++ b/packages/backend/src/services/following/requests/create.ts @@ -6,7 +6,7 @@ import type { User } from "@/models/entities/user.js"; import { Blockings, FollowRequests, Users } from "@/models/index.js"; import { genId } from "backend-rs"; import { createNotification } from "@/services/create-notification.js"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; export default async function ( follower: { diff --git a/packages/backend/src/services/i/pin.ts b/packages/backend/src/services/i/pin.ts index 2d2675a535..b44ba5f9fa 100644 --- a/packages/backend/src/services/i/pin.ts +++ b/packages/backend/src/services/i/pin.ts @@ -1,4 +1,4 @@ -import config from "@/config/index.js"; +import { config } from "@/config.js"; import renderAdd from "@/remote/activitypub/renderer/add.js"; import renderRemove from "@/remote/activitypub/renderer/remove.js"; import { renderActivity } from "@/remote/activitypub/renderer/index.js"; diff --git a/packages/backend/src/services/logger.ts b/packages/backend/src/services/logger.ts index 47a1fe82f8..aec4542b82 100644 --- a/packages/backend/src/services/logger.ts +++ b/packages/backend/src/services/logger.ts @@ -2,8 +2,7 @@ import cluster from "node:cluster"; import chalk from "chalk"; import { default as convertColor } from "color-convert"; import { format as dateFormat } from "date-fns"; -import { envOption } from "@/env.js"; -import config from "@/config/index.js"; +import { config, envOption } from "@/config.js"; import * as SyslogPro from "syslog-pro"; @@ -29,9 +28,9 @@ export default class Logger { if (config.syslog) { this.syslogClient = new SyslogPro.RFC5424({ - applacationName: "Firefish", + applicationName: "Firefish", timestamp: true, - encludeStructuredData: true, + includeStructuredData: true, color: true, extendedColor: true, server: { @@ -145,12 +144,12 @@ export default class Logger { } } + // Used when the process can't continue (fatal error) public error( x: string | Error, data?: Record | null, important = false, ): void { - // 実行を継続できない状況で使う if (x instanceof Error) { data = data || {}; data.e = x; @@ -167,30 +166,30 @@ export default class Logger { } } + // Used when the process can continue but some action should be taken public warn( message: string, data?: Record | null, important = false, ): void { - // 実行を継続できるが改善すべき状況で使う this.log("warning", message, data, important); } + // Used when something is successful public succ( message: string, data?: Record | null, important = false, ): void { - // 何かに成功した状況で使う this.log("success", message, data, important); } + // Used for debugging (information necessary for developers but unnecessary for users) public debug( message: string, data?: Record | null, important = false, ): void { - // Used for debugging (information necessary for developers but unnecessary for users) // Fixed if statement is ignored when logLevel includes debug if ( config.logLevel?.includes("debug") || @@ -201,12 +200,12 @@ export default class Logger { } } + // Other generic logs public info( message: string, data?: Record | null, important = false, ): void { - // それ以外 this.log("info", message, data, important); } } diff --git a/packages/backend/src/services/messages/delete.ts b/packages/backend/src/services/messages/delete.ts index 77caba80ce..2d8f6b9baf 100644 --- a/packages/backend/src/services/messages/delete.ts +++ b/packages/backend/src/services/messages/delete.ts @@ -1,4 +1,4 @@ -import config from "@/config/index.js"; +import { config } from "@/config.js"; import { MessagingMessages, Users } from "@/models/index.js"; import type { MessagingMessage } from "@/models/entities/messaging-message.js"; import { diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts index 0a4ddc517f..2135847a17 100644 --- a/packages/backend/src/services/note/create.ts +++ b/packages/backend/src/services/note/create.ts @@ -10,7 +10,7 @@ import renderCreate from "@/remote/activitypub/renderer/create.js"; import renderAnnounce from "@/remote/activitypub/renderer/announce.js"; import { renderActivity } from "@/remote/activitypub/renderer/index.js"; import { resolveUser } from "@/remote/resolve-user.js"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import { updateHashtags } from "@/services/update-hashtag.js"; import { concat } from "@/prelude/array.js"; import { insertNoteUnread } from "@/services/note/unread.js"; @@ -37,15 +37,18 @@ import type { DriveFile } from "@/models/entities/drive-file.js"; import type { App } from "@/models/entities/app.js"; import { Not, In } from "typeorm"; import type { User, ILocalUser, IRemoteUser } from "@/models/entities/user.js"; -import { genId } from "backend-rs"; import { activeUsersChart } from "@/services/chart/index.js"; import type { IPoll } from "@/models/entities/poll.js"; import { Poll } from "@/models/entities/poll.js"; import { createNotification } from "@/services/create-notification.js"; import { isDuplicateKeyValueError } from "@/misc/is-duplicate-key-value-error.js"; import { checkHitAntenna } from "@/misc/check-hit-antenna.js"; -import { checkWordMute } from "backend-rs"; -import { addNoteToAntenna } from "@/services/add-note-to-antenna.js"; +import { + addNoteToAntenna, + checkWordMute, + genId, + isSilencedServer, +} from "backend-rs"; import { countSameRenotes } from "@/misc/count-same-renotes.js"; import { deliverToRelays, getCachedRelays } from "../relay.js"; import type { Channel } from "@/models/entities/channel.js"; @@ -57,12 +60,12 @@ import { Cache } from "@/misc/cache.js"; import type { UserProfile } from "@/models/entities/user-profile.js"; import { db } from "@/db/postgre.js"; import { getActiveWebhooks } from "@/misc/webhook-cache.js"; -import { shouldSilenceInstance } from "@/misc/should-block-instance.js"; import { redisClient } from "@/db/redis.js"; import { Mutex } from "redis-semaphore"; import { langmap } from "@/misc/langmap.js"; import Logger from "@/services/logger.js"; import { inspect } from "node:util"; +import { undefinedToNull } from "@/prelude/undefined-to-null.js"; const logger = new Logger("create-note"); @@ -225,7 +228,7 @@ export default async ( if ( data.visibility === "public" && Users.isRemoteUser(user) && - (await shouldSilenceInstance(user.host)) + (await isSilencedServer(user.host)) ) { data.visibility = "home"; } @@ -399,7 +402,8 @@ export default async ( for (const antenna of await getAntennas()) { checkHitAntenna(antenna, note, user).then((hit) => { if (hit) { - addNoteToAntenna(antenna, note, user); + // TODO: do this more sanely + addNoteToAntenna(antenna.id, undefinedToNull(note) as Note); } }); } diff --git a/packages/backend/src/services/note/delete.ts b/packages/backend/src/services/note/delete.ts index be3bf1e8b2..c709792fef 100644 --- a/packages/backend/src/services/note/delete.ts +++ b/packages/backend/src/services/note/delete.ts @@ -5,7 +5,7 @@ import renderAnnounce from "@/remote/activitypub/renderer/announce.js"; import renderUndo from "@/remote/activitypub/renderer/undo.js"; import { renderActivity } from "@/remote/activitypub/renderer/index.js"; import renderTombstone from "@/remote/activitypub/renderer/tombstone.js"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import type { User, ILocalUser, IRemoteUser } from "@/models/entities/user.js"; import type { Note, IMentionedRemoteUsers } from "@/models/entities/note.js"; import { Notes, Users, Instances } from "@/models/index.js"; diff --git a/packages/backend/src/services/note/read.ts b/packages/backend/src/services/note/read.ts index 3c49501416..d7fda27a85 100644 --- a/packages/backend/src/services/note/read.ts +++ b/packages/backend/src/services/note/read.ts @@ -1,12 +1,7 @@ import { publishMainStream } from "@/services/stream.js"; import type { Note } from "@/models/entities/note.js"; import type { User } from "@/models/entities/user.js"; -import { - NoteUnreads, - Users, - Followings, - ChannelFollowings, -} from "@/models/index.js"; +import { NoteUnreads, Followings, ChannelFollowings } from "@/models/index.js"; import { Not, IsNull, In } from "typeorm"; import type { Channel } from "@/models/entities/channel.js"; import { readNotificationByQuery } from "@/server/api/common/read-notification.js"; @@ -120,34 +115,4 @@ export default async function ( ]), }); } - - // if (readAntennaNotes.length > 0) { - // await AntennaNotes.update( - // { - // antennaId: In(myAntennas.map((a) => a.id)), - // noteId: In(readAntennaNotes.map((n) => n.id)), - // }, - // { - // read: true, - // }, - // ); - - // // TODO: まとめてクエリしたい - // for (const antenna of myAntennas) { - // const count = await AntennaNotes.countBy({ - // antennaId: antenna.id, - // read: false, - // }); - - // if (count === 0) { - // publishMainStream(userId, "readAntenna", antenna); - // } - // } - - // Users.getHasUnreadAntenna(userId).then((unread) => { - // if (!unread) { - // publishMainStream(userId, "readAllAntennas"); - // } - // }); - // } } diff --git a/packages/backend/src/services/push-notification.ts b/packages/backend/src/services/push-notification.ts index 1a772ff9c5..86dd2a32e2 100644 --- a/packages/backend/src/services/push-notification.ts +++ b/packages/backend/src/services/push-notification.ts @@ -1,9 +1,8 @@ import push from "web-push"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import { SwSubscriptions } from "@/models/index.js"; -import { fetchMeta } from "backend-rs"; +import { fetchMeta, getNoteSummary } from "backend-rs"; import type { Packed } from "@/misc/schema.js"; -import { getNoteSummary } from "@/misc/get-note-summary.js"; // Defined also packages/sw/types.ts#L14-L21 type pushNotificationsTypes = { @@ -17,15 +16,15 @@ type pushNotificationsTypes = { // プッシュメッセージサーバーには文字数制限があるため、内容を削減します function truncateNotification(notification: Packed<"Notification">): any { - if (notification.note) { + if (notification.note != null) { return { ...notification, note: { ...notification.note, - // textをgetNoteSummaryしたものに置き換える + // replace the text with summary text: getNoteSummary( - notification.type === "renote" - ? (notification.note.renote as Packed<"Note">) + notification.type === "renote" && notification.note.renote != null + ? notification.note.renote : notification.note, ), diff --git a/packages/backend/src/services/send-email.ts b/packages/backend/src/services/send-email.ts index 11a899d267..bcbecce356 100644 --- a/packages/backend/src/services/send-email.ts +++ b/packages/backend/src/services/send-email.ts @@ -1,7 +1,7 @@ import * as nodemailer from "nodemailer"; import { fetchMeta } from "backend-rs"; import Logger from "@/services/logger.js"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import { inspect } from "node:util"; export const logger = new Logger("email"); diff --git a/packages/backend/src/services/stream.ts b/packages/backend/src/services/stream.ts index bd09bc3f2c..7f1862b74d 100644 --- a/packages/backend/src/services/stream.ts +++ b/packages/backend/src/services/stream.ts @@ -3,13 +3,13 @@ import type { User } from "@/models/entities/user.js"; import type { Note } from "@/models/entities/note.js"; import type { UserList } from "@/models/entities/user-list.js"; import type { UserGroup } from "@/models/entities/user-group.js"; -import config from "@/config/index.js"; -import type { Antenna } from "@/models/entities/antenna.js"; +import { config } from "@/config.js"; +// import type { Antenna } from "@/models/entities/antenna.js"; import type { Channel } from "@/models/entities/channel.js"; import type { StreamChannels, AdminStreamTypes, - AntennaStreamTypes, + // AntennaStreamTypes, BroadcastTypes, ChannelStreamTypes, DriveStreamTypes, @@ -134,17 +134,18 @@ class Publisher { ); }; - public publishAntennaStream = ( - antennaId: Antenna["id"], - type: K, - value?: AntennaStreamTypes[K], - ): void => { - this.publish( - `antennaStream:${antennaId}`, - type, - typeof value === "undefined" ? null : value, - ); - }; + /* ported to backend-rs */ + // public publishAntennaStream = ( + // antennaId: Antenna["id"], + // type: K, + // value?: AntennaStreamTypes[K], + // ): void => { + // this.publish( + // `antennaStream:${antennaId}`, + // type, + // typeof value === "undefined" ? null : value, + // ); + // }; public publishMessagingStream = ( userId: User["id"], @@ -217,7 +218,7 @@ export const publishNoteStream = publisher.publishNoteStream; export const publishNotesStream = publisher.publishNotesStream; export const publishChannelStream = publisher.publishChannelStream; export const publishUserListStream = publisher.publishUserListStream; -export const publishAntennaStream = publisher.publishAntennaStream; +// export const publishAntennaStream = publisher.publishAntennaStream; export const publishMessagingStream = publisher.publishMessagingStream; export const publishGroupMessagingStream = publisher.publishGroupMessagingStream; diff --git a/packages/backend/src/services/suspend-user.ts b/packages/backend/src/services/suspend-user.ts index f72b8ffcb1..0babd31bc5 100644 --- a/packages/backend/src/services/suspend-user.ts +++ b/packages/backend/src/services/suspend-user.ts @@ -1,7 +1,7 @@ import renderDelete from "@/remote/activitypub/renderer/delete.js"; import { renderActivity } from "@/remote/activitypub/renderer/index.js"; import { deliver } from "@/queue/index.js"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import type { User } from "@/models/entities/user.js"; import { Users, Followings } from "@/models/index.js"; import { Not, IsNull } from "typeorm"; diff --git a/packages/backend/src/services/unsuspend-user.ts b/packages/backend/src/services/unsuspend-user.ts index 69447a4a26..72d7e30d66 100644 --- a/packages/backend/src/services/unsuspend-user.ts +++ b/packages/backend/src/services/unsuspend-user.ts @@ -2,7 +2,7 @@ import renderDelete from "@/remote/activitypub/renderer/delete.js"; import renderUndo from "@/remote/activitypub/renderer/undo.js"; import { renderActivity } from "@/remote/activitypub/renderer/index.js"; import { deliver } from "@/queue/index.js"; -import config from "@/config/index.js"; +import { config } from "@/config.js"; import type { User } from "@/models/entities/user.js"; import { Users, Followings } from "@/models/index.js"; import { Not, IsNull } from "typeorm"; diff --git a/packages/client/.eslintrc.json b/packages/client/.eslintrc.json index b0e97b2fa6..37d80f6588 100644 --- a/packages/client/.eslintrc.json +++ b/packages/client/.eslintrc.json @@ -4,10 +4,10 @@ "ignorePatterns": ["**/*.json5"], "rules": { "file-progress/activate": 1, - "prettier/prettier": 0, - "one-var": ["error", "never"], + "prettier/prettier": "off", + "one-var": ["warn", "never"], "@typescript-eslint/no-unused-vars": [ - "error", + "warn", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_", diff --git a/packages/client/@types/global.d.ts b/packages/client/@types/global.d.ts index c757482900..3ac4f09b0c 100644 --- a/packages/client/@types/global.d.ts +++ b/packages/client/@types/global.d.ts @@ -1,3 +1,4 @@ +// biome-ignore lint/suspicious/noExplicitAny: type FIXME = any; declare const _LANGS_: string[][]; diff --git a/packages/client/@types/window.d.ts b/packages/client/@types/window.d.ts new file mode 100644 index 0000000000..1ae20c0d20 --- /dev/null +++ b/packages/client/@types/window.d.ts @@ -0,0 +1,6 @@ +declare global { + interface Window { + __misskey_input_ref__?: HTMLInputElement | null; + } +} +export type {}; diff --git a/packages/client/src/components/MkAbuseReport.vue b/packages/client/src/components/MkAbuseReport.vue index 5536523948..b190652052 100644 --- a/packages/client/src/components/MkAbuseReport.vue +++ b/packages/client/src/components/MkAbuseReport.vue @@ -67,6 +67,7 @@ {{ emoji.emoji }} ({{ emoji.aliasOf }}) import { onMounted, ref } from "vue"; +import type { entities } from "firefish-js"; import * as os from "@/os"; const props = defineProps<{ userIds: string[]; }>(); -const users = ref([]); +const users = ref([]); onMounted(async () => { users.value = await os.api("users/show", { diff --git a/packages/client/src/components/MkButton.vue b/packages/client/src/components/MkButton.vue index 48864be62a..ab62fd166c 100644 --- a/packages/client/src/components/MkButton.vue +++ b/packages/client/src/components/MkButton.vue @@ -16,7 +16,7 @@ v-else class="bghgjjyj _button" :class="{ inline, primary, gradate, danger, rounded, full, mini }" - :to="to" + :to="to!" @mousedown="onMousedown" >
@@ -36,6 +36,7 @@ const props = defineProps<{ gradate?: boolean; rounded?: boolean; inline?: boolean; + // FIXME: if `link`, `to` is necessary link?: boolean; to?: string; autofocus?: boolean; @@ -47,7 +48,7 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (ev: "click", payload: MouseEvent): void; + click: [payload: MouseEvent]; }>(); const el = ref(null); @@ -61,11 +62,19 @@ onMounted(() => { } }); -function distance(p, q): number { +function distance( + p: { x: number; y: number }, + q: { x: number; y: number }, +): number { return Math.hypot(p.x - q.x, p.y - q.y); } -function calcCircleScale(boxW, boxH, circleCenterX, circleCenterY): number { +function calcCircleScale( + boxW: number, + boxH: number, + circleCenterX: number, + circleCenterY: number, +): number { const origin = { x: circleCenterX, y: circleCenterY }; const dist1 = distance({ x: 0, y: 0 }, origin); const dist2 = distance({ x: boxW, y: 0 }, origin); @@ -79,8 +88,8 @@ function onMousedown(evt: MouseEvent): void { const rect = target.getBoundingClientRect(); const ripple = document.createElement("div"); - ripple.style.top = (evt.clientY - rect.top - 1).toString() + "px"; - ripple.style.left = (evt.clientX - rect.left - 1).toString() + "px"; + ripple.style.top = `${(evt.clientY - rect.top - 1).toString()}px`; + ripple.style.left = `${(evt.clientX - rect.left - 1).toString()}px`; ripples.value!.appendChild(ripple); @@ -97,7 +106,7 @@ function onMousedown(evt: MouseEvent): void { vibrate(10); window.setTimeout(() => { - ripple.style.transform = "scale(" + scale / 2 + ")"; + ripple.style.transform = `scale(${scale / 2})`; }, 1); window.setTimeout(() => { ripple.style.transition = "all 1s ease"; diff --git a/packages/client/src/components/MkCaptcha.vue b/packages/client/src/components/MkCaptcha.vue index 146c512fb8..135eee8e90 100644 --- a/packages/client/src/components/MkCaptcha.vue +++ b/packages/client/src/components/MkCaptcha.vue @@ -50,7 +50,7 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (ev: "update:modelValue", v: string | null): void; + "update:modelValue": [v: string | null]; }>(); const available = ref(false); @@ -93,7 +93,9 @@ if (loaded) { src: src.value, }), ) - ).addEventListener("load", () => (available.value = true)); + ) + // biome-ignore lint/suspicious/noAssignInExpressions: assign it intentially + .addEventListener("load", () => (available.value = true)); } function reset() { diff --git a/packages/client/src/components/MkChannelFollowButton.vue b/packages/client/src/components/MkChannelFollowButton.vue index c1910bc595..3ff907b25b 100644 --- a/packages/client/src/components/MkChannelFollowButton.vue +++ b/packages/client/src/components/MkChannelFollowButton.vue @@ -24,13 +24,14 @@ diff --git a/packages/client/src/components/MkChannelPreview.vue b/packages/client/src/components/MkChannelPreview.vue index f824a1b2f5..b7462f5504 100644 --- a/packages/client/src/components/MkChannelPreview.vue +++ b/packages/client/src/components/MkChannelPreview.vue @@ -52,11 +52,12 @@ diff --git a/packages/client/src/components/MkCode.core.vue b/packages/client/src/components/MkCode.core.vue index 11720b4f62..7e3cdac562 100644 --- a/packages/client/src/components/MkCode.core.vue +++ b/packages/client/src/components/MkCode.core.vue @@ -29,6 +29,7 @@ if (props.lang != null && !(props.lang in Prism.languages)) { const { lang } = props; loadLanguage(props.lang).then( // onLoaded + // biome-ignore lint/suspicious/noAssignInExpressions: assign intentionally () => (prismLang.value = lang), // onError () => {}, diff --git a/packages/client/src/components/MkContainer.vue b/packages/client/src/components/MkContainer.vue index 404f984415..6f7f19dbf0 100644 --- a/packages/client/src/components/MkContainer.vue +++ b/packages/client/src/components/MkContainer.vue @@ -1,5 +1,6 @@ - diff --git a/packages/client/src/components/MkFollowButton.vue b/packages/client/src/components/MkFollowButton.vue index ffe1de72af..0920001c7b 100644 --- a/packages/client/src/components/MkFollowButton.vue +++ b/packages/client/src/components/MkFollowButton.vue @@ -8,7 +8,7 @@