Compare commits

...

81 Commits

Author SHA1 Message Date
laozhoubuluo acfa64772d Merge branch 'feat/post_import_export' into 'develop'
feat: import firefish renote and reply from export, import self-reply from mastodon export

Closes #9947, #10661, and #10807

See merge request firefish/firefish!10689
2024-04-22 01:15:30 +00:00
naskya 9acd130a22
chore (backend): update meta cache every 5 mins 2024-04-22 08:57:00 +09:00
naskya 0c1e7cdd72
refactor (backend): port should-block-instance to backend-rs 2024-04-22 08:31:28 +09:00
naskya cbd15fb2ca Merge branch 'redis' into 'develop'
Make Redis accessible from backend-rs


See merge request firefish/firefish!10753
2024-04-21 22:23:29 +00:00
naskya a6498b0491 Merge branch 'feat/alt_warning' into 'develop'
feat(client): attachment without description opened when choose add alt

Co-authored-by: 老周部落 <laozhoubuluo@gmail.com>

See merge request firefish/firefish!10699
2024-04-21 22:20:45 +00:00
naskya 9ced0d96ad
chore (client): don't make a new post as soon as you add descriptions 2024-04-22 07:20:07 +09:00
naskya 9a4988eaad
chore (client): fix type errors 2024-04-22 07:14:56 +09:00
naskya fbdc068115
locale: update en-US.yml 2024-04-22 07:09:54 +09:00
naskya 23ec206aee
Merge branch 'develop' into feat/alt_warning 2024-04-22 06:59:58 +09:00
naskya 07444ae7c1
Merge branch 'develop' into redis 2024-04-22 06:42:53 +09:00
naskya 455ecdf743 Merge branch 'docs/add_dependencies' into 'develop'
docs: add minimum dependencies

Co-authored-by: 老周部落 <laozhoubuluo@gmail.com>

See merge request firefish/firefish!10703
2024-04-21 21:40:34 +00:00
naskya d98c564ead
docs: move the dependencies section to the top 2024-04-22 06:39:20 +09:00
naskya 56aac15a6b
docs (minor): paraphrase a bit 2024-04-22 06:32:07 +09:00
naskya 280dddf464 Merge branch 'fix/download-url-agent' into 'develop'
fix: download-url should use proxy bypass hosts

Co-authored-by: 老周部落 <laozhoubuluo@gmail.com>

See merge request firefish/firefish!10739
2024-04-21 21:27:55 +00:00
naskya 1347c6ff04
Merge branch 'develop' into redis 2024-04-22 06:23:17 +09:00
naskya b3cc01c440 Merge branch 'feat/show-MkRemoteCaution-in-history' into 'develop'
feat: show MkRemoteCaution in note history page

Co-authored-by: Lhcfl <Lhcfl@outlook.com>

See merge request firefish/firefish!10743
2024-04-21 21:23:02 +00:00
naskya ebaefb9697
chore (minor, client): remove redundant attribute 2024-04-22 06:02:08 +09:00
naskya d9982a0b6a
Merge branch 'develop' into feat/show-MkRemoteCaution-in-history 2024-04-22 06:01:19 +09:00
naskya 0cb2e94d99 Merge branch 'fix/feed' into 'develop'
fix: notes in rss feed do not display HTML

Co-authored-by: Lhcfl <Lhcfl@outlook.com>

See merge request firefish/firefish!10744
2024-04-21 21:00:18 +00:00
naskya d1817d9a22 Merge branch 'feat/antenna_limit' into 'develop'
feat: antenna limit

Co-authored-by: 老周部落 <laozhoubuluo@gmail.com>

Closes #10894

See merge request firefish/firefish!10740
2024-04-21 20:58:09 +00:00
naskya c9de5f6095
docs: update api-changes.md 2024-04-22 05:56:46 +09:00
naskya c4658801aa
chore: regenerate entities 2024-04-22 05:54:32 +09:00
naskya a107d8c1ec
fix (backend): update import 2024-04-22 05:52:56 +09:00
naskya 4c91e8e37f
Merge branch 'develop' into feat/antenna_limit 2024-04-22 05:51:22 +09:00
naskya 0c9dc92f07
fix (backend-rs): use hostname for Redis key prefix and host for Redis streams
I suspect this is a bug, but I keep this behavior for compatibility for now
2024-04-22 05:16:14 +09:00
naskya 9fef36e80c
chore (backend): add comment 2024-04-22 01:56:34 +09:00
naskya b4ae877462
chore: format 2024-04-22 01:54:44 +09:00
naskya 2a30b4a536
Revert "refactor (backend): it turns out that sending the entire note object was redundant"
This reverts commit 3b65ebcb3e.
2024-04-22 01:47:56 +09:00
naskya 3039458c4c
chore (backend-rs): config::server::CONFIG -> config::CONFIG 2024-04-22 00:36:06 +09:00
naskya 574d3b3fe5
chore (backend-rs): in the current implementation we need to call to_string() anyway
There's room for refactoring
2024-04-22 00:30:59 +09:00
naskya 137d0fe3e5
chore (backend-rs): make variable names clearer 2024-04-22 00:29:37 +09:00
naskya 5c6f1c818a
chore (backend-rs): clean redundant match expression 2024-04-22 00:21:10 +09:00
naskya 2d8f4b945f
chore (backend-rs): remove unneeded serialize derivation for now 2024-04-22 00:17:33 +09:00
naskya 77e6479a67
chore (backend-rs): separate one-to-one/group chat stream types 2024-04-22 00:14:19 +09:00
naskya ec940bb068
chore (backend-rs): publish -> publish_to_stream 2024-04-21 22:43:09 +09:00
naskya 9b91035a79
chore (backend-rs): remove unneeded reference 2024-04-21 22:40:58 +09:00
naskya ce672f4edd
dev: add cargo test to pnpm scripts
mocha test has been unmaintained for a long time and is very broken :(
2024-04-21 22:36:05 +09:00
naskya 2fa0ca355d
Merge branch 'develop' into redis 2024-04-21 22:25:50 +09:00
naskya 131b3686d4 Merge branch 'feat/drive-file-usage-hints' into 'develop'
feat: Add usageHint field to DriveFile

Co-authored-by: yumeko <yumeko@mainichi.social>

See merge request firefish/firefish!10750
2024-04-21 12:58:37 +00:00
naskya 6b008c651a
chore (backend): remove (technically) incorrect TypeORM decorator field 2024-04-21 11:09:18 +09:00
naskya d2dbfb37c7
chore (backend): reflect entity changes to the schema and repository 2024-04-21 10:59:02 +09:00
naskya 96481f1353
chore: update downgrade.sql 2024-04-21 10:48:31 +09:00
naskya c936102a4c
chore (backend-rs): regenerate entities and index.js/d.ts 2024-04-21 10:45:47 +09:00
naskya 43570a54aa
chore: format 2024-04-21 10:44:54 +09:00
naskya 4d34e14dd8
Merge branch 'develop' into feat/drive-file-usage-hints 2024-04-21 10:42:25 +09:00
naskya 3b65ebcb3e
refactor (backend): it turns out that sending the entire note object was redundant 2024-04-21 05:55:45 +09:00
naskya 6349705fb2
test (backend-rs): add db connection tests 2024-04-21 00:48:03 +09:00
naskya b08175fb83
Merge branch 'develop' into redis 2024-04-20 23:15:58 +09:00
naskya 77ded03330
Merge branch 'develop' into redis 2024-04-20 10:17:11 +09:00
naskya 2b0668dacd
fix (backend-rs): Redis streaming 2024-04-20 09:54:02 +09:00
naskya f13df7e202
chore (backend-rs): make entities deserializable 2024-04-20 09:27:58 +09:00
naskya f33c7c6c94
fix (backend): host and hostname was messed up 2024-04-20 08:48:48 +09:00
naskya d41b462a89
fix (backend): fix redis key prefix 2024-04-20 07:43:09 +09:00
naskya ccbd6178e4
refactor (backend): port add-note-to-antenna to backend-rs
I hit this bug: https://github.com/napi-rs/napi-rs/issues/2060
2024-04-20 07:18:39 +09:00
naskya f486caf244
fix (backend-rs): use correct Redis key prefix 2024-04-20 06:53:45 +09:00
naskya b017f9ce94
fix (backend-rs): use cache server if present 2024-04-20 06:53:34 +09:00
naskya b157cbe79a
chore (backend-rs): make entities serializable 2024-04-20 04:41:47 +09:00
naskya ccb17977c3
chore (minor, backend): use type import 2024-04-20 03:08:35 +09:00
naskya def62ff1ce
chore (minor, backend): remove unused import 2024-04-20 03:04:34 +09:00
naskya 56038b174d
fix (backend): correct import path 2024-04-20 03:01:13 +09:00
naskya 037b7950a2
refactor (backend): clean the booting process a bit 2024-04-20 02:58:39 +09:00
naskya e82a07c03d
dev (backend-rs): make backend-rs accessible to Redis 2024-04-20 02:47:32 +09:00
naskya e60898b4b0
refactor (backend-rs): separate postgresql connector from database/mod.rs 2024-04-20 02:46:44 +09:00
naskya 3b89a8bfa6
refactor (backend): port config loader to backend-rs completely 2024-04-20 02:46:37 +09:00
yumeko 6c46bb56fd
Switch DriveFile's usageHint field to an enum type 2024-04-19 18:24:48 +03:00
yumeko 968657d26e
Run format 2024-04-19 07:54:11 +03:00
yumeko 913de651db
When updating (remote) user avatar/banner, clear usageHint for the previous drivefile, if any 2024-04-19 07:25:42 +03:00
yumeko 4aeb0d95cc
Add DriveFile usageHint field to rust model as well 2024-04-19 07:03:09 +03:00
yumeko c0f93de94b
Set file usage hints on local avatar/banner uploads as well + export "valid" values as type 2024-04-19 06:29:28 +03:00
yumeko 4823abd3a9
Add usageHint field to DriveFile, and fill accordingly when operating on Persons 2024-04-19 03:41:36 +03:00
Lhcfl 241c824ab5 fix: use better `]]>` replacer 2024-04-14 16:44:12 +08:00
Lhcfl 54d9916fec fix: rss feed no HTML 2024-04-14 16:34:33 +08:00
Lhcfl f0a50bc288 feat: show MkRemoteCaution in note history page 2024-04-14 13:59:27 +08:00
老周部落 e4f9902945
feat: import firefish renote and reply from export, import self-reply from mastodon export 2024-04-13 10:22:24 +08:00
老周部落 5eff4da27b
feat: antenna limit 2024-04-13 10:02:32 +08:00
老周部落 f44a2937d4
fix: download-url should use proxy bypass hosts 2024-04-13 09:00:08 +08:00
老周部落 46bf02cdd5
chore: format 2024-03-31 06:44:00 +08:00
老周部落 f346d2e2f6
feat: add showAddFileDescriptionAtFirstPost and allow cancel adding alt-text midway 2024-03-31 06:42:33 +08:00
naskya 2267e90d3b
refactor (client): await asynchronous processes, remove duplicate code 2024-03-31 06:41:20 +08:00
老周部落 db0bd21edc
feat(client): attachment without description opened when choose add alt 2024-03-31 06:41:07 +08:00
老周部落 de4da3c1fd
docs: add minimum dependencies 2024-03-19 22:51:05 +08:00
237 changed files with 1694 additions and 670 deletions

63
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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

View File

@ -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.

View File

@ -1,6 +1,7 @@
BEGIN;
DELETE FROM "migrations" WHERE name IN (
'AddDriveFileUsage1713451569342',
'ConvertCwVarcharToText1713225866247',
'FixChatFileConstraint1712855579316',
'DropTimeZone1712425488543',
@ -23,7 +24,11 @@ DELETE FROM "migrations" WHERE name IN (
'RemoveNativeUtilsMigration1705877093218'
);
--convert-cw-varchar-to-text
-- 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);

View File

@ -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

View File

@ -74,6 +74,34 @@ mentions: "Mentions"
directNotes: "Direct messages"
cw: "Content warning"
importAndExport: "Import/Export Data"
importAndExportWarn: "The Import/Export Data feature is an experimental feature and
implementation may change at any time without prior notice.\n
Due to differences in the exported data of different software versions, the actual
conditions of the import program, and the server health of the exported data link,
the imported data may be incomplete or the access permissions may not be set
correctly (for example, there is no access permission mark in the
Mastodon/Akkoma/Pleroma exported data, so all posts makes public after import),
so please be sure to check the imported data carefully integrity and configure
the correct access permissions for it."
importAndExportInfo: "Since some data cannot be obtained after the original account is
frozen or the original server goes offline, it is strongly recommendedthat you import
the data before the original account is frozen (migrated, logged out) or the original
server goes offline.\n
If the original account is frozen or the original server is offline but you have the
original images, you can try uploading them to the network disk before importing the
data, which may help with data import.\n
Since some data is obtained from its server using your current account when importing
data, data that the current account does not have permission to access will be regarded
as broken. Please make adjustments including but not limited to access permissions,
Manually following accounts and other methods allow the current account to obtain
relevant data, so that the import program can normally obtain the data it needs to
obtain to help you import.\n
Since it is impossible to confirm whether the broken link content posted by someone other
than you is posted by him/her, if there is broken link content posted by others in the
discussion thread, the related content and subsequent replies will not be imported.\n
Since data import is greatly affected by network communication, it is recommended that you
pay attention to data recovery after a period of time. If the data is still not restored,
you can try importing the same backup file again and try again."
import: "Import"
export: "Export"
files: "Files"
@ -394,6 +422,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 +1255,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"

View File

@ -61,6 +61,16 @@ mention: "提及"
mentions: "提及"
directNotes: "私信"
importAndExport: "导入 / 导出数据"
importAndExportWarn: "导入 / 导出数据功能是一项实验性功能,实现可能会随时变化而无预先通知。\n
由于不同软件不同版本的导出数据、导入程序实际情况以及导出数据链接的服务器运行状况不同,导入的数据可能会不完整或未被正确设置访问权限
(例如 Mastodon/Akkoma/Pleroma 导出数据内无访问权限标记,因此所有帖子导入后均为公开状态),因此请务必谨慎核对导入数据的完整性,
并为其配置正确的访问权限。"
importAndExportInfo: "由于原账号冻结或者原服务器下线后部分数据无法获取,因此强烈建议您在原账号冻结(迁移、注销)或原服务器下线前导入数据。\n
在原账号冻结或者原服务器下线但您拥有原始图片的情况下,可以尝试在导入数据之前将其上传到网盘上,可能对数据导入有所帮助。\n
由于导入数据时部分数据是使用您当前账号到其服务器上获取,因此当前账号无权访问的数据会视为断链。请通过包括但不限于访问权限调整、
手动关注账户等方式让当前帐号可以获取到相关数据,以便导入程序能够正常获取到需要获取的数据从而帮助您进行导入。\n
由于无法确认非您本人发表的断链内容的是否由其本人发表,因此如果讨论串内有其他人发表的断链内容,则相关内容以及后续回复不会被导入。\n
由于数据导入受网络通信影响较大,因此建议您一段时间之后再关注数据恢复情况。如果数据仍未恢复可以尝试再次导入同样的备份文件重试一次。"
import: "导入"
export: "导出"
files: "文件"
@ -340,6 +350,7 @@ invite: "邀请"
driveCapacityPerLocalAccount: "每个本地用户的网盘容量"
driveCapacityPerRemoteAccount: "每个远程用户的网盘容量"
inMb: "以兆字节 (MegaByte) 为单位"
antennaLimit: "每个用户最多可以创建的天线数量"
iconUrl: "图标 URL"
bannerUrl: "横幅图 URL"
backgroundImageUrl: "背景图 URL"
@ -2053,6 +2064,7 @@ searchRangeDescription: "如果您要过滤时间段,请按以下格式输入
messagingUnencryptedInfo: "Firefish 上的聊天没有经过端到端加密,请不要在聊天中分享您的敏感信息。"
noAltTextWarning: 有些附件没有描述。您是否忘记写描述了?
showNoAltTextWarning: 当您尝试发布没有描述的帖子附件时显示警告
showAddFileDescriptionAtFirstPost: 当您首次尝试发布没有描述的帖子附件时自动弹出添加描述页面
autocorrectNoteLanguage: 当帖子语言不符合自动检测的结果的时候显示警告
incorrectLanguageWarning: "看上去您帖子使用的语言是{detected},但您选择的语言是{current}。\n要改为以{detected}发帖吗?"
noteEditHistory: "帖子编辑历史"

View File

@ -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 --",

View File

@ -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 }

View File

@ -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(string_enum = "camelCase"))]/' \
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 --

View File

@ -12,7 +12,7 @@ export interface EnvConfig {
withLogTime: boolean
slow: boolean
}
export function readEnvironmentConfig(): EnvConfig
export function loadEnv(): EnvConfig
export interface ServerConfig {
url: string
port: number
@ -29,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
@ -70,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
}
@ -121,13 +125,89 @@ 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<string>
allowedPrivateNetworks?: Array<string>
maxFileSize?: number
accessLog?: string
clusterLimits: WorkerConfig
cuid?: IdConfig
outgoingAddress?: string
deliverJobConcurrency?: number
inboxJobConcurrency?: number
deliverJobPerSec?: number
inboxJobPerSec?: number
deliverJobMaxAttempts?: number
inboxJobMaxAttempts?: number
logLevel?: Array<string>
syslog?: SysLogConfig
proxyRemoteFiles?: boolean
mediaProxy?: string
summalyProxyUrl?: string
reservedUsernames?: Array<string>
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<string>
dynamicImports: Array<string>
css: Array<string>
assets: Array<string>
}
export function loadConfig(): Config
export interface Acct {
username: string
host: string | null
}
export function stringToAcct(acct: string): Acct
export function acctToString(acct: Acct): string
/**
* @param host punycoded instance host
* @returns whether the given host should be blocked
*/
export function isBlockedServer(host: string): Promise<boolean>
/**
* @param host punycoded instance host
* @returns whether the given host should be limited
*/
export function isSilencedServer(host: string): Promise<boolean>
/**
* @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<boolean>
/** TODO: handle name collisions better */
export interface NoteLikeForCheckWordMute {
fileIds: Array<string>
@ -348,6 +428,7 @@ export interface DriveFile {
webpublicType: string | null
requestHeaders: Json | null
requestIp: string | null
usageHint: DriveFileUsageHintEnum | null
}
export interface DriveFolder {
id: string
@ -553,6 +634,7 @@ export interface Meta {
donationLink: string | null
moreUrls: Json
markLocalFilesNsfwByDefault: boolean
antennaLimit: number
}
export interface Migrations {
id: number
@ -780,6 +862,10 @@ export enum AntennaSrcEnum {
List = 'list',
Users = 'users'
}
export enum DriveFileUsageHintEnum {
UserAvatar = 'userAvatar',
UserBanner = 'userBanner'
}
export enum MutedNoteReasonEnum {
Manual = 'manual',
Other = 'other',
@ -1033,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

View File

@ -310,12 +310,15 @@ if (!nativeBinding) {
throw new Error(`Failed to load native binding`)
}
const { readEnvironmentConfig, readServerConfig, stringToAcct, acctToString, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, safeForSql, formatMilliseconds, getNoteSummary, 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.readEnvironmentConfig = readEnvironmentConfig
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
@ -339,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
@ -348,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

View File

@ -11,7 +11,7 @@ pub struct EnvConfig {
}
#[crate::export]
pub fn read_environment_config() -> EnvConfig {
pub fn load_env() -> EnvConfig {
let node_env = std::env::var("NODE_ENV").unwrap_or_default().to_lowercase();
let is_testing = node_env == "test";

View File

@ -1,2 +1,4 @@
pub use server::CONFIG;
pub mod environment;
pub mod server;

View File

@ -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<i64>,
pub access_log: Option<String>,
pub cluster_limits: Option<WorkerConfig>,
pub cluster_limits: Option<WorkerConfigInternal>,
pub cuid: Option<IdConfig>,
pub outgoing_address: Option<String>,
@ -82,8 +82,7 @@ pub struct RedisConfig {
pub tls: Option<TlsConfig>,
#[serde(default)]
pub db: u32,
#[serde(default)]
pub prefix: String,
pub prefix: Option<String>,
}
#[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<u32>,
pub queue: Option<u32>,
}
@ -167,17 +172,207 @@ pub struct ObjectStorageConfig {
pub s3_force_path_style: Option<bool>,
}
#[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<String>,
pub disable_hsts: Option<bool>,
pub db: DbConfig,
pub redis: RedisConfig,
pub cache_server: Option<RedisConfig>,
pub proxy: Option<String>,
pub proxy_smtp: Option<String>,
pub proxy_bypass_hosts: Option<Vec<String>>,
pub allowed_private_networks: Option<Vec<String>>,
pub max_file_size: Option<i64>,
pub access_log: Option<String>,
pub cluster_limits: WorkerConfig,
pub cuid: Option<IdConfig>,
pub outgoing_address: Option<String>,
pub deliver_job_concurrency: Option<u32>,
pub inbox_job_concurrency: Option<u32>,
pub deliver_job_per_sec: Option<u32>,
pub inbox_job_per_sec: Option<u32>,
pub deliver_job_max_attempts: Option<u32>,
pub inbox_job_max_attempts: Option<u32>,
pub log_level: Option<Vec<String>>,
pub syslog: Option<SysLogConfig>,
pub proxy_remote_files: Option<bool>,
pub media_proxy: Option<String>,
pub summaly_proxy_url: Option<String>,
pub reserved_usernames: Option<Vec<String>>,
pub max_user_signups: Option<u32>,
pub is_managed_hosting: Option<bool>,
pub max_note_length: Option<u32>,
pub max_caption_length: Option<u32>,
pub deepl: Option<DeepLConfig>,
pub libre_translate: Option<LibreTranslateConfig>,
pub email: Option<EmailConfig>,
pub object_storage: Option<ObjectStorageConfig>,
// 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<String>,
pub dynamic_imports: Vec<String>,
pub css: Vec<String>,
pub assets: Vec<String>,
}
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<ServerConfig> = 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<Config> = Lazy::new(load_config);

View File

@ -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<DbConn> = 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;

View File

@ -0,0 +1,35 @@
use crate::config::CONFIG;
use sea_orm::{Database, DbConn, DbErr};
static DB_CONN: once_cell::sync::OnceCell<DbConn> = 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());
}
}

View File

@ -0,0 +1,68 @@
use crate::config::CONFIG;
use redis::{Client, Connection, RedisError};
static REDIS_CLIENT: once_cell::sync::OnceCell<Client> = once_cell::sync::OnceCell::new();
fn init_redis() -> Result<Client, RedisError> {
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<Connection, RedisError> {
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);
}
}

View File

@ -4,4 +4,5 @@ pub mod config;
pub mod database;
pub mod misc;
pub mod model;
pub mod service;
pub mod util;

View File

@ -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<bool, DbErr> {
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<bool, DbErr> {
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<bool, DbErr> {
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)
}

View File

@ -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<String, Error> {
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<bool, Error> {
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<bool, Error> {
Ok(url::Url::parse(uri)?.origin().ascii_serialization() == SERVER_CONFIG.url)
Ok(url::Url::parse(uri)?.origin().ascii_serialization() == CONFIG.url)
}
#[crate::export]

View File

@ -1,4 +1,5 @@
pub mod acct;
pub mod check_server_block;
pub mod check_word_mute;
pub mod convert_host;
pub mod emoji;

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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<super::antenna::Entity> for Entity {
fn to() -> RelationDef {
Relation::Antenna.def()
}
}
impl Related<super::note::Entity> for Entity {
fn to() -> RelationDef {
Relation::Note.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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<Json>,
#[sea_orm(column_name = "requestIp")]
pub request_ip: Option<String>,
#[sea_orm(column_name = "usageHint")]
pub usage_hint: Option<DriveFileUsageHintEnum>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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)]

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -2,7 +2,10 @@
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(string_enum = "camelCase"))]
#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "antenna_src_enum")]
@ -20,7 +23,27 @@ 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(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(
@ -38,7 +61,10 @@ 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(string_enum = "camelCase"))]
#[sea_orm(
@ -58,7 +84,10 @@ 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(string_enum = "camelCase"))]
#[sea_orm(
@ -92,7 +121,10 @@ 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(string_enum = "camelCase"))]
#[sea_orm(
@ -108,7 +140,10 @@ 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(string_enum = "camelCase"))]
#[sea_orm(
@ -126,7 +161,10 @@ 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(string_enum = "camelCase"))]
#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "relay_status_enum")]
@ -138,7 +176,10 @@ 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(string_enum = "camelCase"))]
#[sea_orm(
@ -156,7 +197,10 @@ 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(string_enum = "camelCase"))]
#[sea_orm(
@ -172,7 +216,10 @@ 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(string_enum = "camelCase"))]
#[sea_orm(

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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(&note.id)),
&[("note", &note.id)],
)?;
publish_to_stream(
&Stream::Antenna { antenna_id },
Some("note"),
Some(serde_json::to_string(note)?),
)
}

View File

@ -0,0 +1,2 @@
pub mod add_note_to_antenna;
pub mod stream;

View File

@ -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<String>,
) -> 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"
);
}
}

View File

@ -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"

View File

@ -3,7 +3,7 @@ import chalk from "chalk";
import Xev from "xev";
import Logger from "@/services/logger.js";
import { envOption } from "@/config/index.js";
import { envOption } from "@/config.js";
import { inspect } from "node:util";
// for typeorm

View File

@ -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 "@/config/index.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<void> {
const dbLogger = bootLogger.createSubLogger("db");
@ -195,23 +173,31 @@ async function connectDb(): Promise<void> {
}
async function spawnWorkers(
clusterLimits: Required<Config["clusterLimits"]>,
clusterLimits: Config["clusterLimits"],
): Promise<void> {
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");

Some files were not shown because too many files have changed in this diff Show More