Compare commits

...
Sign in to create a new pull request.

17 commits

Author SHA1 Message Date
Kainoa Kanter
bdc391caa7 Merge pull request '[rust] refactor: config' (#10109) from nmkj/calckey:refactor/rustify into refactor/rocket
Reviewed-on: https://codeberg.org/calckey/calckey/pulls/10109
2023-05-14 20:03:25 +00:00
Namekuji
04a9dc8c97
add url 2023-05-14 04:09:05 -04:00
Namekuji
e69f3b54a9
add default values 2023-05-14 03:30:10 -04:00
Namekuji
bee531ddca remove tokio main 2023-05-14 03:29:05 -04:00
Namekuji
e1a0ab7d25 use thiserror for impl Error and Display 2023-05-14 03:29:05 -04:00
s1idewhist1e
31c425995f
some boilerplate server/tokio runtime stuff 2023-05-13 23:25:50 -07:00
s1idewhist1e
4fb4597e7b
Add macros crate for utility macros 2023-05-13 16:56:38 -07:00
s1idewhist1e
c34aab6ac8
more config initialization 2023-05-09 22:06:08 -07:00
s1idewhist1e
d61fd0f418
Fix clean.js to remove rust target dir 2023-05-09 00:14:15 -07:00
s1idewhist1e
d86a5a7147
Add config iniitialization (I can totally spell) 2023-05-09 00:13:21 -07:00
s1idewhist1e
6033a63446
Move config to its own crate + fix config parsing 2023-05-08 22:42:34 -07:00
s1idewhist1e
393c8c9427
fix pnpm test script 2023-05-08 09:33:46 -07:00
s1idewhist1e
92fcfc1a08
more config stuff 2023-05-08 09:33:16 -07:00
s1idewhist1e
ce685af31a
change rust-toolchain file to rust-toolchain.toml 2023-05-08 09:24:53 -07:00
s1idewhist1e
993befbd9c
config parsing 2023-05-04 00:58:15 -07:00
s1idewhist1e
c8b7584fee
add backend .editorconfig that complies with the rust style guide 2023-05-03 18:26:52 -07:00
s1idewhist1e
e3ab697e66
Remove ts from backend and prepare rust environment 2023-04-29 23:10:20 -07:00
980 changed files with 1891 additions and 80697 deletions

View file

@ -106,7 +106,7 @@ id: 'aid'
# Max note length, should be < 8000. # Max note length, should be < 8000.
#maxNoteLength: 3000 #maxNoteLength: 3000
# Maximum lenght of an image caption or file comment (default 1500, max 8192) # Maximum length of an image caption or file comment (default 1500, max 8192)
#maxCaptionLength: 1500 #maxCaptionLength: 1500
# Whether disable HSTS # Whether disable HSTS

View file

@ -25,8 +25,8 @@
"cy:open": "cypress open --browser --e2e --config-file=cypress.config.ts", "cy:open": "cypress open --browser --e2e --config-file=cypress.config.ts",
"cy:run": "cypress run", "cy:run": "cypress run",
"e2e": "start-server-and-test start:test http://localhost:61812 cy:run", "e2e": "start-server-and-test start:test http://localhost:61812 cy:run",
"mocha": "pnpm --filter backend run mocha", "test": "pnpm run -r test",
"test": "pnpm run mocha", "test:backend": "pnpm run --filter backend test",
"format": "pnpm rome format packages/**/* --write && pnpm --filter client run format", "format": "pnpm rome format packages/**/* --write && pnpm --filter client run format",
"clean": "pnpm node ./scripts/clean.js", "clean": "pnpm node ./scripts/clean.js",
"clean-all": "pnpm node ./scripts/clean-all.js", "clean-all": "pnpm node ./scripts/clean-all.js",

View file

@ -0,0 +1,4 @@
[*.rs]
indent_style = space
indent_size = 4

7
packages/backend/.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
# profiling data
*.profraw
*.profdata
# rust build dir
target/

View file

@ -1,10 +0,0 @@
{
"extension": ["ts","js","cjs","mjs"],
"node-option": [
"experimental-specifier-resolution=node",
"loader=./test/loader.js"
],
"slow": 1000,
"timeout": 30000,
"exit": true
}

View file

@ -1,25 +0,0 @@
{
"$schema": "https://json.schemastore.org/swcrc",
"jsc": {
"parser": {
"syntax": "typescript",
"dynamicImport": true,
"decorators": true
},
"transform": {
"legacyDecorator": true,
"decoratorMetadata": true
},
"experimental": {
"keepImportAssertions": true
},
"baseUrl": ".",
"paths": {
"@/*": [
"./src/*"
]
},
"target": "es2022"
},
"minify": false
}

View file

@ -0,0 +1,5 @@
{
"workspace.workspaceFolderCheckCwd": false,
"rust-analyzer.check.command": "clippy",
"rust-analyzer.check.extraArgs": "--profile test"
}

832
packages/backend/Cargo.lock generated Normal file
View file

@ -0,0 +1,832 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "async-trait"
version = "0.1.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "axum"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8175979259124331c1d7bf6586ee7e0da434155e4b2d48ec2c8386281d8df39"
dependencies = [
"async-trait",
"axum-core",
"bitflags",
"bytes",
"futures-util",
"http",
"http-body",
"hyper",
"itoa",
"matchit",
"memchr",
"mime",
"percent-encoding",
"pin-project-lite",
"rustversion",
"serde",
"serde_json",
"serde_path_to_error",
"serde_urlencoded",
"sync_wrapper",
"tokio",
"tower",
"tower-layer",
"tower-service",
]
[[package]]
name = "axum-core"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c"
dependencies = [
"async-trait",
"bytes",
"futures-util",
"http",
"http-body",
"mime",
"rustversion",
"tower-layer",
"tower-service",
]
[[package]]
name = "backend"
version = "0.0.0"
dependencies = [
"config",
"lazy_static",
"logging",
"macros",
"queue",
"server",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bytes"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "config"
version = "0.1.0"
dependencies = [
"once_cell",
"serde",
"serde_yaml",
]
[[package]]
name = "db"
version = "0.1.0"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "form_urlencoded"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
dependencies = [
"percent-encoding",
]
[[package]]
name = "futures-channel"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
dependencies = [
"futures-core",
]
[[package]]
name = "futures-core"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
[[package]]
name = "futures-task"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
[[package]]
name = "futures-util"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
dependencies = [
"futures-core",
"futures-task",
"pin-project-lite",
"pin-utils",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hermit-abi"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
dependencies = [
"libc",
]
[[package]]
name = "http"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]]
name = "http-body"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
dependencies = [
"bytes",
"http",
"pin-project-lite",
]
[[package]]
name = "httparse"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
[[package]]
name = "httpdate"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
[[package]]
name = "hyper"
version = "0.14.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4"
dependencies = [
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"http",
"http-body",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"socket2",
"tokio",
"tower-service",
"tracing",
"want",
]
[[package]]
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]]
name = "itoa"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.144"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1"
[[package]]
name = "lock_api"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "logging"
version = "0.1.0"
dependencies = [
"config",
]
[[package]]
name = "macros"
version = "0.1.0"
dependencies = [
"lazy_static",
]
[[package]]
name = "matchit"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40"
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mio"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
dependencies = [
"libc",
"log",
"wasi",
"windows-sys 0.45.0",
]
[[package]]
name = "num_cpus"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "once_cell"
version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "parking_lot"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-sys 0.45.0",
]
[[package]]
name = "percent-encoding"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
[[package]]
name = "pin-project"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pin-project-lite"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "proc-macro2"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
dependencies = [
"unicode-ident",
]
[[package]]
name = "queue"
version = "0.1.0"
[[package]]
name = "quote"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
]
[[package]]
name = "rustversion"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06"
[[package]]
name = "ryu"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.160"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.160"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "serde_path_to_error"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7f05c1d5476066defcdfacce1f52fc3cae3af1d3089727100c02ae92e5abbe0"
dependencies = [
"serde",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "serde_yaml"
version = "0.9.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9d684e3ec7de3bf5466b32bd75303ac16f0736426e5a4e0d6e489559ce1249c"
dependencies = [
"indexmap",
"itoa",
"ryu",
"serde",
"unsafe-libyaml",
]
[[package]]
name = "server"
version = "0.1.0"
dependencies = [
"axum",
"config",
"tokio",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
dependencies = [
"libc",
]
[[package]]
name = "smallvec"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "socket2"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "syn"
version = "2.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "sync_wrapper"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
[[package]]
name = "tokio"
version = "1.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105"
dependencies = [
"autocfg",
"bytes",
"libc",
"mio",
"num_cpus",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.48.0",
]
[[package]]
name = "tokio-macros"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tower"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
dependencies = [
"futures-core",
"futures-util",
"pin-project",
"pin-project-lite",
"tokio",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tower-layer"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
[[package]]
name = "tower-service"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
[[package]]
name = "tracing"
version = "0.1.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
dependencies = [
"cfg-if",
"log",
"pin-project-lite",
"tracing-core",
]
[[package]]
name = "tracing-core"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
dependencies = [
"once_cell",
]
[[package]]
name = "try-lock"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
[[package]]
name = "unicode-ident"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
[[package]]
name = "unsafe-libyaml"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1865806a559042e51ab5414598446a5871b561d21b6764f2eabb0dd481d880a6"
[[package]]
name = "want"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
dependencies = [
"log",
"try-lock",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets 0.42.2",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.0",
]
[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
]
[[package]]
name = "windows-targets"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
dependencies = [
"windows_aarch64_gnullvm 0.48.0",
"windows_aarch64_msvc 0.48.0",
"windows_i686_gnu 0.48.0",
"windows_i686_msvc 0.48.0",
"windows_x86_64_gnu 0.48.0",
"windows_x86_64_gnullvm 0.48.0",
"windows_x86_64_msvc 0.48.0",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_i686_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"

View file

@ -0,0 +1,31 @@
[package]
name = "backend"
version = "0.0.0"
edition = "2021"
default-run = "backend"
[workspace]
members = ["crates/*"]
[profile.development]
inherits = "dev"
[profile.production]
inherits = "release"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
server = { path = "crates/server" }
logging = { path = "crates/logging" }
queue = { path = "crates/queue" }
config = { path = "crates/config" }
macros = { path = "crates/macros" }
lazy_static = "1.4.0"
tracing = "0.1.37"
tracing-subscriber = "0.3.17"
tokio = { version = "1.28.1", features = ["full"] }
anyhow = "1.0.71"
[dev-dependencies]

View file

@ -0,0 +1,14 @@
[package]
name = "config"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
once_cell = "1.17.1"
serde = { version = "1.0.160", features = [ "derive" ] }
serde_yaml = "0.9.21"
thiserror = "1.0.40"
url = "2.3.1"

View file

@ -0,0 +1,311 @@
use serde::Deserialize;
type Port = u16;
#[derive(Debug, PartialEq, Deserialize)]
pub struct MaxNoteLength(pub u16);
#[derive(Debug, PartialEq, Deserialize)]
pub struct MaxCommentLength(pub u16);
#[derive(Debug, PartialEq)]
pub enum IpFamily {
Both,
IPv4,
IPv6,
}
impl<'de> Deserialize<'de> for IpFamily {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct IpFamilyVisitor;
use serde::de::Visitor;
impl<'de> Visitor<'de> for IpFamilyVisitor {
type Value = IpFamily;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("One of `4` `6` `0`")
}
fn visit_u8<E>(self, v: u8) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
{
match v {
0 => Ok(IpFamily::Both),
4 => Ok(IpFamily::IPv4),
6 => Ok(IpFamily::IPv6),
_ => Err(E::unknown_variant(&v.to_string(), &["0", "4", "6"])),
}
}
}
}
deserializer.deserialize_u8(IpFamilyVisitor)
}
}
impl Default for IpFamily {
fn default() -> Self {
Self::Both
}
}
impl<'de> Deserialize<'de> for Host {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct HostVisitor;
use serde::de::Visitor;
impl<'de> Visitor<'de> for HostVisitor {
type Value = Host;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("(proto://)host(.tld)(/)")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
let components: Vec<&str> = v.split("://").collect();
match components.len() {
1 => Ok(Host(None, components[0].into())),
2 => Ok(Host(
Some(components[0].into()),
components[1].trim_end_matches('/').into(),
)),
_ => Err(E::custom(format!("Invalid url: {}", v))), // FIXME: more descriptive
// error message
}
}
}
deserializer.deserialize_str(HostVisitor)
}
}
#[derive(Debug, PartialEq, Default)]
// TODO: Convert to uri later and maybe some more enums
pub struct Host(pub Option<String>, pub String);
#[derive(Debug, PartialEq, Deserialize)]
#[serde(rename = "camelCase")]
pub struct Config {
pub repository_url: Option<String>,
pub feedback_url: Option<String>,
pub url: Host,
pub port: Port,
pub db: db::DbConfig,
pub redis: redis::RedisConfig,
// pub sonic: sonic::SonicConfig,
// pub elasticsearch: elasticsearch::ElasticsearchConfig,
// pub id: IdGenerator,
#[serde(default)]
pub max_note_length: MaxNoteLength,
#[serde(default)]
pub max_caption_length: MaxCommentLength,
// pub disable_hsts: bool,
#[serde(default = "cluster_limit_default")]
pub cluster_limit: u16,
#[serde(default = "deliver_job_default")]
pub deliver_job_concurrency: u16,
#[serde(default = "inbox_job_default")]
pub inbox_job_concurrency: u16,
#[serde(default = "deliver_job_default")]
pub deliver_job_per_sec: u16,
#[serde(default = "inbox_job_default")]
pub inbox_job_per_sec: u16,
#[serde(default = "deliver_job_attempts_default")]
pub deliver_job_max_attempts: u16,
#[serde(default = "inbox_job_attempts_default")]
pub inbox_job_max_attempts: u16,
// pub outgoing_address_family: IpFamily,
// pub syslog: syslog::SyslogConfig,
// pub proxy: Option<Host>,
// pub proxy_smtp: Option<Host>,
// pub proxy_bypass_hosts: Vec<Host>,
// pub allowed_private_networks: Vec<Host>,
// pub max_file_size: Option<u32>,
// pub media_proxy: Option<String>,
// pub proxy_remote_files: bool,
// pub twa: Option<twa::TWAConfig>,
// pub reserved_usernames: Vec<String>,
// pub max_user_signups: Option<u32>,
// pub is_managed_hosting: bool,
// pub deepl: Option<deepl::DeepLConfig>,
// pub libre_translate: Option<libre_translate::LibreTranslateConfig>,
// pub email: Option<email::Email>,
// pub object_storage: Option<object_storage::ObjectStorageConfig>,
// pub summaly_proxy_url: Option<Host>,
#[serde(skip)]
pub env: env::Environment,
}
#[derive(Debug, PartialEq, Deserialize)]
#[serde(rename = "lowercase")]
pub enum IdGenerator {
AId,
MeId,
ULId,
ObjectID,
}
/// database config
pub mod db {
use super::*;
#[derive(Debug, PartialEq, Deserialize)]
#[serde(rename = "camelCase")]
pub struct DbConfig {
pub host: Host,
pub port: Port,
pub db: String,
pub user: String,
pub pass: String,
#[serde(default = "true_fn")]
pub disable_cache: bool,
#[serde(default)]
pub extra: Extra,
}
#[derive(Debug, PartialEq, Deserialize)]
pub struct Extra {
#[serde(default = "true_fn")]
pub ssl: bool,
}
impl Default for Extra {
fn default() -> Self {
Self { ssl: true }
}
}
}
/// redis config
pub mod redis {
use url::Url;
use super::*;
#[derive(Debug, PartialEq, Deserialize)]
#[serde(rename = "camelCase")]
pub struct RedisConfig {
pub host: Host,
pub port: Port,
#[serde(default)]
pub family: IpFamily,
pub pass: Option<String>,
pub prefix: Option<String>,
#[serde(default)]
pub db: u8,
}
impl From<&RedisConfig> for Url {
fn from(value: &RedisConfig) -> Self {
Url::parse(&format!("redis://{}:{}", value.host.1, value.port))
.expect("Invalid redis host and port")
}
}
}
/// sonic search config
pub mod sonic {
use super::*;
#[derive(Debug, PartialEq, Deserialize)]
pub struct SonicConfig {
pub host: Host,
pub port: Port,
#[serde(default)]
pub auth: Option<String>,
#[serde(default)]
pub collection: Option<String>,
#[serde(default)]
pub bucket: Option<String>,
}
}
/// elasticsearch config
pub mod elasticsearch {
use super::*;
#[derive(Debug, PartialEq, Deserialize)]
pub struct ElasticsearchConfig {
pub host: Host,
pub port: Port,
#[serde(default)]
pub ssl: bool,
pub user: Option<String>,
pub pass: Option<String>,
pub index: Option<String>,
}
}
/// syslog configuration
pub mod syslog {
use super::*;
#[derive(Debug, PartialEq, Deserialize)]
pub struct SyslogConfig {
host: Host,
port: Port,
}
}
/// TWA configuration
pub mod twa {
use super::*;
#[derive(Debug, PartialEq, Deserialize)]
pub struct TWAConfig {}
}
/// Environment variables set when initialized
pub mod env {
use super::*;
#[derive(Debug, PartialEq, Deserialize, Default)]
pub struct Environment {}
}
impl Default for MaxNoteLength {
fn default() -> Self {
Self(3000)
}
}
impl Default for MaxCommentLength {
fn default() -> Self {
Self(1500)
}
}
fn cluster_limit_default() -> u16 {
1
}
fn deliver_job_default() -> u16 {
128
}
fn deliver_job_attempts_default() -> u16 {
12
}
fn inbox_job_default() -> u16 {
16
}
fn inbox_job_attempts_default() -> u16 {
8
}
fn true_fn() -> bool {
true
}

View file

@ -0,0 +1,137 @@
use std::fs::File;
use std::io::{self, Read};
use std::path::Path;
use data::env::Environment;
use once_cell::sync::OnceCell;
mod data;
pub use data::*;
// Config Errors
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("The configuration has not been initialized yet")]
Uninitialized,
#[error("Error when parsing config file: {0}")]
Deserialize(#[from] serde_yaml::Error),
#[error("Error when reading config file: {0}")]
FileError(#[from] io::Error),
}
// Functions
fn fetch_config(path: &Path) -> Result<Config, Error> {
let mut buf = String::new();
File::open(path)?.read_to_string(&mut buf)?;
Ok(serde_yaml::from_str(&buf)?)
}
static CONFIG: OnceCell<Config> = OnceCell::new();
pub fn init_config(cfg_path: &Path) -> Result<(), Error> {
let mut config = fetch_config(cfg_path)?;
config.env = Environment {};
CONFIG.get_or_init(move || config);
Ok(())
}
pub fn get_config() -> Result<&'static Config, Error> {
CONFIG.get().ok_or(Error::Uninitialized)
}
#[cfg(test)]
mod tests {
use std::{
fs::{remove_file, File},
io::Write,
path::PathBuf,
};
use super::*;
#[test]
fn errors_on_invalid_path() {
assert!(init_config(Path::new("./invalid/path/does/not/exist")).is_err());
}
#[test]
fn parses_test_config() {
struct Guard(PathBuf);
impl Drop for Guard {
fn drop(&mut self) {
println!("removing temp file...");
match remove_file(&self.0) {
Ok(_) => println!("Successfully removed file"),
Err(e) => println!("Could not remove file: {}", e),
}
}
}
// setup test temp config
let mut temp_file = std::env::temp_dir();
temp_file.push(Path::new("calckey.test.config"));
let err = File::create(&temp_file).unwrap().write_all(
br"
url: https://example.tld/
port: 3000
db:
host: localhost
port: 5432
db: calckey
user: example-calckey-user
pass: example-calckey-pass
redis:
host: localhost
port: 6379
",
);
let _g = Guard(temp_file.clone());
err.unwrap();
let config = fetch_config(temp_file.as_path()).unwrap();
assert_eq!(
config,
Config {
url: Host(Some("https".into()), "example.tld".into()),
port: 3000,
db: db::DbConfig {
host: Host(None, "localhost".into()),
port: 5432,
db: String::from("calckey"),
user: String::from("example-calckey-user"),
pass: String::from("example-calckey-pass"),
disable_cache: true,
extra: db::Extra { ssl: true }
},
repository_url: None,
feedback_url: None,
redis: redis::RedisConfig {
host: Host(None, "localhost".into()),
port: 6379,
family: IpFamily::Both,
pass: None,
prefix: None,
db: 0,
},
max_note_length: MaxNoteLength(3000),
max_caption_length: MaxCommentLength(1500),
cluster_limit: 1,
env: Environment {},
deliver_job_concurrency: 128,
inbox_job_concurrency: 16,
deliver_job_per_sec: 128,
inbox_job_per_sec: 16,
deliver_job_max_attempts: 12,
inbox_job_max_attempts: 8,
}
);
}
}

View file

@ -0,0 +1,9 @@
[package]
name = "logging"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
config = { path = "../config" }

View file

@ -0,0 +1,7 @@
pub fn idk() {
config::get_config();
}

View file

@ -0,0 +1,9 @@
[package]
name = "macros"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
lazy_static = "1.4.0"

View file

@ -0,0 +1,54 @@
#![macro_use]
use std::env;
use lazy_static::lazy_static;
#[derive(PartialEq)]
pub enum EnvType {
Release,
Debug,
Test,
}
lazy_static! {
pub static ref NODE_ENV: EnvType = init_env_type();
}
#[macro_export]
macro_rules! node_env {
() => {
*macros::environment::NODE_ENV
};
}
#[macro_export]
macro_rules! is_debug {
() => {
macros::node_env!() == macros::environment::EnvType::Debug
};
}
#[macro_export]
macro_rules! is_release {
() => {
macros::node_env!() == macros::environment::EnvType::Release
};
}
#[macro_export]
macro_rules! is_test {
() => {
macros::node_env!() == macros::environment::EnvType::Test
};
}
fn init_env_type() -> EnvType {
use EnvType::*;
match env::var("NODE_ENV") {
Ok(s) if s == *"production" => Release,
Ok(s) if s == *"development" => Debug,
Ok(s) if s == *"test" => Test,
_ => Debug,
}
}

View file

@ -0,0 +1,3 @@
pub mod environment;
//pub use environment::*;

View file

@ -0,0 +1,8 @@
[package]
name = "queue"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View file

@ -0,0 +1,14 @@
pub fn add(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}

View file

@ -0,0 +1,11 @@
[package]
name = "server"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
axum = "0.6.18"
tokio = { version = "1.28.1", features = ["full"] }
config = { path = "../config" }

View file

@ -0,0 +1,5 @@
use axum::{routing::get, Router};
pub fn routes() -> Router {
Router::new().route("/", get(|| async { "Hello world!" }))
}

View file

@ -0,0 +1,37 @@
use std::error;
use axum::Router;
use tokio::runtime;
use config::get_config;
pub mod api {
pub mod routes;
}
pub enum Error {}
pub fn init() -> Result<(), Box<dyn error::Error>> {
// initialize tokio runtime
let mut rt = runtime::Builder::new_multi_thread();
let rt = rt.enable_all();
if let Some(n) = get_config()?.cluster_limit {
rt.worker_threads(n as usize);
}
let rt = rt.build()?;
let app = Router::new().nest("/api", api::routes::routes());
rt.block_on(async {
axum::Server::bind(&format!("127.0.0.1:{}", get_config()?.port).parse()?)
.serve(app.into_make_service())
.await?;
Result::<(), Box<dyn error::Error>>::Ok(())
})?;
Ok(())
}

View file

@ -1,13 +0,0 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"allowSyntheticDefaultImports": true
},
"exclude": [
"node_modules",
"jspm_packages",
"tmp",
"temp"
]
}

View file

@ -1,3 +0,0 @@
[target.aarch64-unknown-linux-musl]
linker = "aarch64-linux-musl-gcc"
rustflags = ["-C", "target-feature=-crt-static"]

View file

@ -1,200 +0,0 @@
# Created by https://www.toptal.com/developers/gitignore/api/node
# Edit at https://www.toptal.com/developers/gitignore?templates=node
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# End of https://www.toptal.com/developers/gitignore/api/node
# Created by https://www.toptal.com/developers/gitignore/api/macos
# Edit at https://www.toptal.com/developers/gitignore?templates=macos
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### macOS Patch ###
# iCloud generated files
*.icloud
# End of https://www.toptal.com/developers/gitignore/api/macos
# Created by https://www.toptal.com/developers/gitignore/api/windows
# Edit at https://www.toptal.com/developers/gitignore?templates=windows
### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# End of https://www.toptal.com/developers/gitignore/api/windows
# napi-rs generated files
built/
#Added by cargo
/target
Cargo.lock
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
*.node

View file

@ -1,13 +0,0 @@
target
Cargo.lock
.cargo
.github
npm
.eslintrc
.prettierignore
rustfmt.toml
yarn.lock
*.node
.yarn
__test__
renovate.json

View file

@ -1,18 +0,0 @@
[package]
edition = "2021"
name = "native-utils"
version = "0.0.0"
[lib]
crate-type = ["cdylib"]
[dependencies]
# Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix
napi = { version = "2.12.0", default-features = false, features = ["napi4"] }
napi-derive = "2.12.0"
[build-dependencies]
napi-build = "2.0.1"
[profile.release]
lto = true

View file

@ -1,7 +0,0 @@
import test from "ava";
import { sum } from "../index.js";
test("sum from native", (t) => {
t.is(sum(1, 2), 3);
});

View file

@ -1,5 +0,0 @@
extern crate napi_build;
fn main() {
napi_build::setup();
}

View file

@ -1,3 +0,0 @@
# `native-utils-android-arm-eabi`
This is the **armv7-linux-androideabi** binary for `native-utils`

View file

@ -1,18 +0,0 @@
{
"name": "native-utils-android-arm-eabi",
"version": "0.0.0",
"os": [
"android"
],
"cpu": [
"arm"
],
"main": "native-utils.android-arm-eabi.node",
"files": [
"native-utils.android-arm-eabi.node"
],
"license": "MIT",
"engines": {
"node": ">= 10"
}
}

View file

@ -1,3 +0,0 @@
# `native-utils-android-arm64`
This is the **aarch64-linux-android** binary for `native-utils`

View file

@ -1,18 +0,0 @@
{
"name": "native-utils-android-arm64",
"version": "0.0.0",
"os": [
"android"
],
"cpu": [
"arm64"
],
"main": "native-utils.android-arm64.node",
"files": [
"native-utils.android-arm64.node"
],
"license": "MIT",
"engines": {
"node": ">= 10"
}
}

View file

@ -1,3 +0,0 @@
# `native-utils-darwin-arm64`
This is the **aarch64-apple-darwin** binary for `native-utils`

View file

@ -1,18 +0,0 @@
{
"name": "native-utils-darwin-arm64",
"version": "0.0.0",
"os": [
"darwin"
],
"cpu": [
"arm64"
],
"main": "native-utils.darwin-arm64.node",
"files": [
"native-utils.darwin-arm64.node"
],
"license": "MIT",
"engines": {
"node": ">= 10"
}
}

View file

@ -1,3 +0,0 @@
# `native-utils-darwin-universal`
This is the **universal-apple-darwin** binary for `native-utils`

View file

@ -1,15 +0,0 @@
{
"name": "native-utils-darwin-universal",
"version": "0.0.0",
"os": [
"darwin"
],
"main": "native-utils.darwin-universal.node",
"files": [
"native-utils.darwin-universal.node"
],
"license": "MIT",
"engines": {
"node": ">= 10"
}
}

View file

@ -1,3 +0,0 @@
# `native-utils-darwin-x64`
This is the **x86_64-apple-darwin** binary for `native-utils`

View file

@ -1,18 +0,0 @@
{
"name": "native-utils-darwin-x64",
"version": "0.0.0",
"os": [
"darwin"
],
"cpu": [
"x64"
],
"main": "native-utils.darwin-x64.node",
"files": [
"native-utils.darwin-x64.node"
],
"license": "MIT",
"engines": {
"node": ">= 10"
}
}

View file

@ -1,3 +0,0 @@
# `native-utils-freebsd-x64`
This is the **x86_64-unknown-freebsd** binary for `native-utils`

View file

@ -1,18 +0,0 @@
{
"name": "native-utils-freebsd-x64",
"version": "0.0.0",
"os": [
"freebsd"
],
"cpu": [
"x64"
],
"main": "native-utils.freebsd-x64.node",
"files": [
"native-utils.freebsd-x64.node"
],
"license": "MIT",
"engines": {
"node": ">= 10"
}
}

View file

@ -1,3 +0,0 @@
# `native-utils-linux-arm-gnueabihf`
This is the **armv7-unknown-linux-gnueabihf** binary for `native-utils`

View file

@ -1,18 +0,0 @@
{
"name": "native-utils-linux-arm-gnueabihf",
"version": "0.0.0",
"os": [
"linux"
],
"cpu": [
"arm"
],
"main": "native-utils.linux-arm-gnueabihf.node",
"files": [
"native-utils.linux-arm-gnueabihf.node"
],
"license": "MIT",
"engines": {
"node": ">= 10"
}
}

View file

@ -1,3 +0,0 @@
# `native-utils-linux-arm64-gnu`
This is the **aarch64-unknown-linux-gnu** binary for `native-utils`

View file

@ -1,21 +0,0 @@
{
"name": "native-utils-linux-arm64-gnu",
"version": "0.0.0",
"os": [
"linux"
],
"cpu": [
"arm64"
],
"main": "native-utils.linux-arm64-gnu.node",
"files": [
"native-utils.linux-arm64-gnu.node"
],
"license": "MIT",
"engines": {
"node": ">= 10"
},
"libc": [
"glibc"
]
}

View file

@ -1,3 +0,0 @@
# `native-utils-linux-arm64-musl`
This is the **aarch64-unknown-linux-musl** binary for `native-utils`

View file

@ -1,21 +0,0 @@
{
"name": "native-utils-linux-arm64-musl",
"version": "0.0.0",
"os": [
"linux"
],
"cpu": [
"arm64"
],
"main": "native-utils.linux-arm64-musl.node",
"files": [
"native-utils.linux-arm64-musl.node"
],
"license": "MIT",
"engines": {
"node": ">= 10"
},
"libc": [
"musl"
]
}

View file

@ -1,3 +0,0 @@
# `native-utils-linux-x64-gnu`
This is the **x86_64-unknown-linux-gnu** binary for `native-utils`

View file

@ -1,21 +0,0 @@
{
"name": "native-utils-linux-x64-gnu",
"version": "0.0.0",
"os": [
"linux"
],
"cpu": [
"x64"
],
"main": "native-utils.linux-x64-gnu.node",
"files": [
"native-utils.linux-x64-gnu.node"
],
"license": "MIT",
"engines": {
"node": ">= 10"
},
"libc": [
"glibc"
]
}

View file

@ -1,3 +0,0 @@
# `native-utils-linux-x64-musl`
This is the **x86_64-unknown-linux-musl** binary for `native-utils`

View file

@ -1,21 +0,0 @@
{
"name": "native-utils-linux-x64-musl",
"version": "0.0.0",
"os": [
"linux"
],
"cpu": [
"x64"
],
"main": "native-utils.linux-x64-musl.node",
"files": [
"native-utils.linux-x64-musl.node"
],
"license": "MIT",
"engines": {
"node": ">= 10"
},
"libc": [
"musl"
]
}

View file

@ -1,3 +0,0 @@
# `native-utils-win32-arm64-msvc`
This is the **aarch64-pc-windows-msvc** binary for `native-utils`

View file

@ -1,18 +0,0 @@
{
"name": "native-utils-win32-arm64-msvc",
"version": "0.0.0",
"os": [
"win32"
],
"cpu": [
"arm64"
],
"main": "native-utils.win32-arm64-msvc.node",
"files": [
"native-utils.win32-arm64-msvc.node"
],
"license": "MIT",
"engines": {
"node": ">= 10"
}
}

View file

@ -1,3 +0,0 @@
# `native-utils-win32-ia32-msvc`
This is the **i686-pc-windows-msvc** binary for `native-utils`

View file

@ -1,18 +0,0 @@
{
"name": "native-utils-win32-ia32-msvc",
"version": "0.0.0",
"os": [
"win32"
],
"cpu": [
"ia32"
],
"main": "native-utils.win32-ia32-msvc.node",
"files": [
"native-utils.win32-ia32-msvc.node"
],
"license": "MIT",
"engines": {
"node": ">= 10"
}
}

View file

@ -1,3 +0,0 @@
# `native-utils-win32-x64-msvc`
This is the **x86_64-pc-windows-msvc** binary for `native-utils`

View file

@ -1,18 +0,0 @@
{
"name": "native-utils-win32-x64-msvc",
"version": "0.0.0",
"os": [
"win32"
],
"cpu": [
"x64"
],
"main": "native-utils.win32-x64-msvc.node",
"files": [
"native-utils.win32-x64-msvc.node"
],
"license": "MIT",
"engines": {
"node": ">= 10"
}
}

View file

@ -1,44 +0,0 @@
{
"name": "native-utils",
"version": "0.0.0",
"main": "built/index.js",
"types": "built/index.d.ts",
"napi": {
"name": "native-utils",
"triples": {
"additional": [
"aarch64-apple-darwin",
"aarch64-linux-android",
"aarch64-unknown-linux-gnu",
"aarch64-unknown-linux-musl",
"aarch64-pc-windows-msvc",
"armv7-unknown-linux-gnueabihf",
"x86_64-unknown-linux-musl",
"x86_64-unknown-freebsd",
"i686-pc-windows-msvc",
"armv7-linux-androideabi",
"universal-apple-darwin"
]
}
},
"license": "MIT",
"devDependencies": {
"@napi-rs/cli": "^2.15.0",
"ava": "^5.1.1"
},
"ava": {
"timeout": "3m"
},
"engines": {
"node": ">= 10"
},
"scripts": {
"artifacts": "napi artifacts",
"build": "napi build --platform --release ./built/",
"build:debug": "napi build --platform",
"prepublishOnly": "napi prepublish -t npm",
"test": "ava",
"universal": "napi universal",
"version": "napi version"
}
}

View file

@ -1,2 +0,0 @@
tab_spaces = 2
edition = "2021"

View file

@ -1,2 +0,0 @@
pub mod mastodon_api;

View file

@ -1,70 +0,0 @@
use napi::{bindgen_prelude::*, Error, Status};
use napi_derive::napi;
static CHAR_COLLECTION: &str = "0123456789abcdefghijklmnopqrstuvwxyz";
// -- NAPI exports --
#[napi]
pub enum IdConvertType {
MastodonId,
CalckeyId,
}
#[napi]
pub fn convert_id(in_id: String, id_convert_type: IdConvertType) -> napi::Result<String> {
use IdConvertType::*;
match id_convert_type {
MastodonId => {
let mut out: i64 = 0;
for (i, c) in in_id.to_lowercase().chars().rev().enumerate() {
out += num_from_char(c)? as i64 * 36_i64.pow(i as u32);
}
Ok(out.to_string())
}
CalckeyId => {
let mut input: i64 = match in_id.parse() {
Ok(s) => s,
Err(_) => {
return Err(Error::new(
Status::InvalidArg,
"Unable to parse ID as MasstodonId",
))
}
};
let mut out = String::new();
while input != 0 {
out.insert(0, char_from_num((input % 36) as u8)?);
input /= 36;
}
Ok(out)
}
}
}
// -- end --
#[inline(always)]
fn num_from_char(character: char) -> napi::Result<u8> {
for (i, c) in CHAR_COLLECTION.chars().enumerate() {
if c == character {
return Ok(i as u8);
}
}
Err(Error::new(
Status::InvalidArg,
"Invalid character in parsed base36 id",
))
}
#[inline(always)]
fn char_from_num(number: u8) -> napi::Result<char> {
CHAR_COLLECTION
.chars()
.nth(number as usize)
.ok_or(Error::from_status(Status::Unknown))
}

View file

@ -1,15 +0,0 @@
import { DataSource } from "typeorm";
import config from "./built/config/index.js";
import { entities } from "./built/db/postgre.js";
export default new DataSource({
type: "postgres",
host: config.db.host,
port: config.db.port,
username: config.db.user,
password: config.db.pass,
database: config.db.db,
extra: config.db.extra,
entities: entities,
migrations: ["migration/*.js"],
});

View file

@ -1,197 +1,15 @@
{ {
"name": "backend", "name": "backend",
"main": "./index.js",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
"start": "pnpm node ./built/index.js", "start": "cargo run --profile ${NODE_ENV:=development}",
"start:test": "NODE_ENV=test pnpm node ./built/index.js", "start:test": "NODE_ENV=test pnpm node ./built/index.js",
"migrate": "typeorm migration:run -d ormconfig.js", "check": "cargo check",
"migrate": "cargo run --bin migrate",
"build": "cargo build --profile ${NODE_ENV:=development}",
"revertmigration": "typeorm migration:revert -d ormconfig.js", "revertmigration": "typeorm migration:revert -d ormconfig.js",
"check:connect": "node ./check_connect.js", "lint": "cargo check",
"build": "napi build --platform --release --cargo-cwd native-utils ./native-utils/built/ && pnpm swc src -d built -D", "test": "cargo test --workspace"
"watch": "pnpm swc src -d built -D -w",
"lint": "pnpm rome check \"src/**/*.ts\"",
"mocha": "cross-env NODE_ENV=test TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha",
"test": "pnpm run mocha"
},
"resolutions": {
"chokidar": "^3.3.1"
},
"optionalDependencies": {
"@swc/core-android-arm64": "1.3.11",
"@tensorflow/tfjs-node": "3.21.1"
},
"dependencies": {
"@bull-board/api": "^4.6.4",
"@bull-board/koa": "^4.6.4",
"@bull-board/ui": "^4.6.4",
"@calckey/megalodon": "5.1.24",
"@discordapp/twemoji": "14.0.2",
"@elastic/elasticsearch": "7.17.0",
"@koa/cors": "3.4.3",
"@koa/multer": "3.0.0",
"@koa/router": "9.0.1",
"@peertube/http-signature": "1.7.0",
"@redocly/openapi-core": "1.0.0-beta.120",
"@sinonjs/fake-timers": "9.1.2",
"@syuilo/aiscript": "0.11.1",
"@tensorflow/tfjs": "^4.2.0",
"ajv": "8.11.2",
"archiver": "5.3.1",
"argon2": "^0.30.3",
"autobind-decorator": "2.4.0",
"autolinker": "4.0.0",
"autwh": "0.1.0",
"aws-sdk": "2.1277.0",
"axios": "^1.3.2",
"bcryptjs": "2.4.3",
"blurhash": "1.1.5",
"bull": "4.10.2",
"cacheable-lookup": "7.0.0",
"calckey-js": "workspace:*",
"cbor": "8.1.0",
"chalk": "5.2.0",
"chalk-template": "0.4.0",
"chokidar": "3.5.3",
"cli-highlight": "2.1.11",
"color-convert": "2.0.1",
"content-disposition": "0.5.4",
"date-fns": "2.29.3",
"deep-email-validator": "0.1.21",
"escape-regexp": "0.0.1",
"feed": "4.2.2",
"file-type": "17.1.6",
"fluent-ffmpeg": "2.1.2",
"got": "12.5.3",
"hpagent": "0.1.2",
"ioredis": "5.2.4",
"ip-cidr": "3.0.11",
"is-svg": "4.3.2",
"js-yaml": "4.1.0",
"jsdom": "20.0.3",
"jsonld": "6.0.0",
"jsrsasign": "10.6.1",
"koa": "2.13.4",
"koa-body": "^6.0.1",
"koa-bodyparser": "4.3.0",
"koa-json-body": "5.3.0",
"koa-logger": "3.2.1",
"koa-mount": "4.0.0",
"koa-send": "5.0.1",
"koa-slow": "2.1.0",
"koa-views": "7.0.2",
"mfm-js": "0.23.2",
"mime-types": "2.1.35",
"multer": "1.4.4-lts.1",
"native-utils": "link:native-utils",
"nested-property": "4.0.0",
"node-fetch": "3.3.0",
"nodemailer": "6.8.0",
"nsfwjs": "2.4.2",
"oauth": "^0.10.0",
"os-utils": "0.0.14",
"parse5": "7.1.2",
"pg": "8.8.0",
"private-ip": "2.3.4",
"probe-image-size": "7.2.3",
"promise-limit": "2.7.0",
"punycode": "2.1.1",
"pureimage": "0.3.15",
"qrcode": "1.5.1",
"qs": "6.9.7",
"random-seed": "0.3.0",
"ratelimiter": "3.4.1",
"re2": "1.18.0",
"redis-lock": "0.1.4",
"reflect-metadata": "0.1.13",
"rename": "1.0.4",
"rndstr": "1.0.0",
"rss-parser": "3.12.0",
"sanitize-html": "2.8.1",
"seedrandom": "^3.0.5",
"semver": "7.3.8",
"sharp": "0.31.3",
"sonic-channel": "^1.3.1",
"speakeasy": "2.0.0",
"stringz": "2.1.0",
"summaly": "github:misskey-dev/summaly",
"syslog-pro": "1.0.0",
"systeminformation": "5.16.9",
"tesseract.js": "^3.0.3",
"tinycolor2": "1.5.2",
"tmp": "0.2.1",
"twemoji-parser": "14.0.0",
"typeorm": "0.3.11",
"ulid": "2.3.0",
"unzipper": "0.10.11",
"uuid": "9.0.0",
"web-push": "3.5.0",
"websocket": "1.0.34",
"xev": "3.0.2"
},
"devDependencies": {
"@swc/cli": "^0.1.62",
"@swc/core": "^1.3.50",
"@types/bcryptjs": "2.4.2",
"@types/bull": "3.15.9",
"@types/cbor": "6.0.0",
"@types/escape-regexp": "0.0.1",
"@types/fluent-ffmpeg": "2.1.20",
"@types/js-yaml": "4.0.5",
"@types/jsdom": "20.0.1",
"@types/jsonld": "1.5.8",
"@types/jsrsasign": "10.5.4",
"@types/koa": "2.13.5",
"@types/koa-bodyparser": "4.3.10",
"@types/koa-cors": "0.0.2",
"@types/koa-favicon": "2.0.21",
"@types/koa-logger": "3.1.2",
"@types/koa-mount": "4.0.2",
"@types/koa-send": "4.1.3",
"@types/koa-views": "7.0.0",
"@types/koa__cors": "3.3.0",
"@types/koa__multer": "2.0.4",
"@types/koa__router": "8.0.11",
"@types/mocha": "9.1.1",
"@types/node": "18.11.18",
"@types/node-fetch": "3.0.3",
"@types/nodemailer": "6.4.7",
"@types/oauth": "0.9.1",
"@types/pug": "2.0.6",
"@types/punycode": "2.1.0",
"@types/qrcode": "1.5.0",
"@types/qs": "6.9.7",
"@types/random-seed": "0.3.3",
"@types/ratelimiter": "3.4.4",
"@types/redis": "4.0.11",
"@types/rename": "1.0.4",
"@types/sanitize-html": "2.8.0",
"@types/semver": "7.3.13",
"@types/sharp": "0.31.1",
"@types/sinonjs__fake-timers": "8.1.2",
"@types/speakeasy": "2.0.7",
"@types/tinycolor2": "1.4.3",
"@types/tmp": "0.2.3",
"@types/uuid": "8.3.4",
"@types/web-push": "3.3.2",
"@types/websocket": "1.0.5",
"@types/ws": "8.5.3",
"autobind-decorator": "2.4.0",
"cross-env": "7.0.3",
"eslint": "^8.31.0",
"execa": "6.1.0",
"json5": "2.2.3",
"json5-loader": "4.0.1",
"mocha": "10.2.0",
"pug": "3.0.2",
"strict-event-emitter-types": "2.0.0",
"swc-loader": "^0.2.3",
"ts-loader": "9.4.2",
"ts-node": "10.9.1",
"tsconfig-paths": "4.1.2",
"typescript": "4.9.4",
"webpack": "^5.75.0",
"ws": "8.11.0"
} }
} }

View file

@ -0,0 +1,2 @@
[toolchain]
channel = "stable"

View file

@ -1,14 +0,0 @@
declare module "hcaptcha" {
interface IVerifyResponse {
success: boolean;
challenge_ts: string;
hostname: string;
credit?: boolean;
"error-codes"?: unknown[];
}
export function verify(
secret: string,
token: string,
): Promise<IVerifyResponse>;
}

View file

@ -1,98 +0,0 @@
declare module "@peertube/http-signature" {
import type { IncomingMessage, ClientRequest } from "node:http";
interface ISignature {
keyId: string;
algorithm: string;
headers: string[];
signature: string;
}
interface IOptions {
headers?: string[];
algorithm?: string;
strict?: boolean;
authorizationHeaderName?: string;
}
interface IParseRequestOptions extends IOptions {
clockSkew?: number;
}
interface IParsedSignature {
scheme: string;
params: ISignature;
signingString: string;
algorithm: string;
keyId: string;
}
type RequestSignerConstructorOptions =
| IRequestSignerConstructorOptionsFromProperties
| IRequestSignerConstructorOptionsFromFunction;
interface IRequestSignerConstructorOptionsFromProperties {
keyId: string;
key: string | Buffer;
algorithm?: string;
}
interface IRequestSignerConstructorOptionsFromFunction {
sign?: (data: string, cb: (err: any, sig: ISignature) => void) => void;
}
class RequestSigner {
constructor(options: RequestSignerConstructorOptions);
public writeHeader(header: string, value: string): string;
public writeDateHeader(): string;
public writeTarget(method: string, path: string): void;
public sign(cb: (err: any, authz: string) => void): void;
}
interface ISignRequestOptions extends IOptions {
keyId: string;
key: string;
httpVersion?: string;
}
export function parse(
request: IncomingMessage,
options?: IParseRequestOptions,
): IParsedSignature;
export function parseRequest(
request: IncomingMessage,
options?: IParseRequestOptions,
): IParsedSignature;
export function sign(
request: ClientRequest,
options: ISignRequestOptions,
): boolean;
export function signRequest(
request: ClientRequest,
options: ISignRequestOptions,
): boolean;
export function createSigner(): RequestSigner;
export function isSigner(obj: any): obj is RequestSigner;
export function sshKeyToPEM(key: string): string;
export function sshKeyFingerprint(key: string): string;
export function pemToRsaSSHKey(pem: string, comment: string): string;
export function verify(
parsedSignature: IParsedSignature,
pubkey: string | Buffer,
): boolean;
export function verifySignature(
parsedSignature: IParsedSignature,
pubkey: string | Buffer,
): boolean;
export function verifyHMAC(
parsedSignature: IParsedSignature,
secret: string,
): boolean;
}

View file

@ -1,15 +0,0 @@
declare module "koa-json-body" {
import type { Middleware } from "koa";
interface IKoaJsonBodyOptions {
strict: boolean;
limit: string;
fallback: boolean;
}
function koaJsonBody(opt?: IKoaJsonBodyOptions): Middleware;
namespace koaJsonBody {} // Hack
export = koaJsonBody;
}

View file

@ -1 +0,0 @@
declare module "koa-remove-trailing-slashes";

View file

@ -1,14 +0,0 @@
declare module "koa-slow" {
import type { Middleware } from "koa";
interface ISlowOptions {
url?: RegExp;
delay?: number;
}
function slow(options?: ISlowOptions): Middleware;
namespace slow {} // Hack
export = slow;
}

View file

@ -1,33 +0,0 @@
declare module "os-utils" {
type FreeCommandCallback = (usedmem: number) => void;
type HarddriveCallback = (total: number, free: number, used: number) => void;
type GetProcessesCallback = (result: string) => void;
type CPUCallback = (perc: number) => void;
export function platform(): NodeJS.Platform;
export function cpuCount(): number;
export function sysUptime(): number;
export function processUptime(): number;
export function freemem(): number;
export function totalmem(): number;
export function freememPercentage(): number;
export function freeCommand(callback: FreeCommandCallback): void;
export function harddrive(callback: HarddriveCallback): void;
export function getProcesses(callback: GetProcessesCallback): void;
export function getProcesses(
nProcess: number,
callback: GetProcessesCallback,
): void;
export function allLoadavg(): string;
export function loadavg(_time?: number): number;
export function cpuFree(callback: CPUCallback): void;
export function cpuUsage(callback: CPUCallback): void;
}

View file

@ -1,10 +0,0 @@
declare module "*/package.json" {
interface IRepository {
type: string;
url: string;
}
export const name: string;
export const version: string;
export const repository: IRepository;
}

View file

@ -1,37 +0,0 @@
declare module "probe-image-size" {
import type { ReadStream } from "node:fs";
type ProbeOptions = {
retries: 1;
timeout: 30000;
};
type ProbeResult = {
width: number;
height: number;
length?: number;
type: string;
mime: string;
wUnits: "in" | "mm" | "cm" | "pt" | "pc" | "px" | "em" | "ex";
hUnits: "in" | "mm" | "cm" | "pt" | "pc" | "px" | "em" | "ex";
url?: string;
};
function probeImageSize(
src: string | ReadStream,
options?: ProbeOptions,
): Promise<ProbeResult>;
function probeImageSize(
src: string | ReadStream,
callback: (err: Error | null, result?: ProbeResult) => void,
): void;
function probeImageSize(
src: string | ReadStream,
options: ProbeOptions,
callback: (err: Error | null, result?: ProbeResult) => void,
): void;
namespace probeImageSize {} // Hack
export = probeImageSize;
}

View file

@ -0,0 +1,3 @@
fn main() {
todo!();
}

View file

@ -1,89 +0,0 @@
import cluster from "node:cluster";
import chalk from "chalk";
import Xev from "xev";
import Logger from "@/services/logger.js";
import { envOption } from "../env.js";
// for typeorm
import "reflect-metadata";
import { masterMain } from "./master.js";
import { workerMain } from "./worker.js";
import os from "node:os";
const logger = new Logger("core", "cyan");
const clusterLogger = logger.createSubLogger("cluster", "orange", false);
const ev = new Xev();
/**
* Init process
*/
export default async function () {
process.title = `Calckey (${cluster.isPrimary ? "master" : "worker"})`;
if (cluster.isPrimary || envOption.disableClustering) {
await masterMain();
if (cluster.isPrimary) {
ev.mount();
}
}
if (cluster.isWorker || envOption.disableClustering) {
await workerMain();
}
if (cluster.isPrimary) {
// Leave the master process with a marginally lower priority but not too low.
os.setPriority(2);
}
if (cluster.isWorker) {
// Set workers to a much lower priority so that the master process will be
// able to respond to api calls even if the workers gank everything.
os.setPriority(10);
}
// For when Calckey is started in a child process during unit testing.
// Otherwise, process.send cannot be used, so start it.
if (process.send) {
process.send("ok");
}
}
//#region Events
// Listen new workers
cluster.on("fork", (worker) => {
clusterLogger.debug(`Process forked: [${worker.id}]`);
});
// Listen online workers
cluster.on("online", (worker) => {
clusterLogger.debug(`Process is now online: [${worker.id}]`);
});
// Listen for dying workers
cluster.on("exit", (worker) => {
// Replace the dead worker,
// we're not sentimental
clusterLogger.error(chalk.red(`[${worker.id}] died :(`));
cluster.fork();
});
// Display detail of unhandled promise rejection
if (!envOption.quiet) {
process.on("unhandledRejection", console.dir);
}
// Display detail of uncaught exception
process.on("uncaughtException", (err) => {
try {
logger.error(err);
} catch {}
});
// Dying away...
process.on("exit", (code) => {
logger.info(`The process is going to exit with code ${code}`);
});
//#endregion

View file

@ -1,189 +0,0 @@
import * as fs from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname } from "node:path";
import * as os from "node:os";
import cluster from "node:cluster";
import chalk from "chalk";
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 { lessThan } from "@/prelude/array.js";
import { envOption } from "../env.js";
import { showMachineInfo } from "@/misc/show-machine-info.js";
import { db, initDb } from "../db/postgre.js";
const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename);
const meta = JSON.parse(
fs.readFileSync(`${_dirname}/../../../../built/meta.json`, "utf-8"),
);
const logger = new Logger("core", "cyan");
const bootLogger = logger.createSubLogger("boot", "magenta", false);
const themeColor = chalk.hex("#31748f");
function greet() {
if (!envOption.quiet) {
//#region Calckey logo
const v = `v${meta.version}`;
console.log(themeColor(" ___ _ _ "));
console.log(themeColor(" / __\\__ _| | ___| | _____ _ _ "));
console.log(themeColor(" / / / _` | |/ __| |/ / _ | | |"));
console.log(themeColor("/ /__| (_| | | (__| < __/ |_| |"));
console.log(themeColor("\\____/\\__,_|_|\\___|_|\\_\\___|\\__, |"));
console.log(themeColor(" (___/ "));
//#endregion
console.log(
" Calckey is an open-source decentralized microblogging platform.",
);
console.log(
chalk.rgb(
255,
136,
0,
)(
" If you like Calckey, please consider starring or contributing to the repo. https://codeberg.org/calckey/calckey",
),
);
console.log("");
console.log(
chalkTemplate`--- ${os.hostname()} {gray (PID: ${process.pid.toString()})} ---`,
);
}
bootLogger.info("Welcome to Calckey!");
bootLogger.info(`Calckey v${meta.version}`, null, true);
}
/**
* 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("Fatal error occurred during initialization", null, true);
process.exit(1);
}
bootLogger.succ("Calckey initialized");
if (!envOption.disableClustering) {
await spawnWorkers(config.clusterLimit);
}
bootLogger.succ(
`Now listening on port ${config.port} on ${config.url}`,
null,
true,
);
if (!envOption.noDaemons) {
import("../daemons/server-stats.js").then((x) => x.default());
import("../daemons/queue-stats.js").then((x) => x.default());
import("../daemons/janitor.js").then((x) => x.default());
}
}
function showEnvironment(): void {
const env = process.env.NODE_ENV;
const logger = bootLogger.createSubLogger("env");
logger.info(
typeof env === "undefined" ? "NODE_ENV is not set" : `NODE_ENV: ${env}`,
);
if (env !== "production") {
logger.warn("The environment is not in production mode.");
logger.warn("DO NOT USE FOR PRODUCTION PURPOSE!", null, true);
}
}
function showNodejsVersion(): void {
const nodejsLogger = bootLogger.createSubLogger("nodejs");
nodejsLogger.info(`Version ${process.version} detected.`);
const minVersion = fs
.readFileSync(`${_dirname}/../../../../.node-version`, "utf-8")
.trim();
if (semver.lt(process.version, minVersion)) {
nodejsLogger.error(`At least Node.js ${minVersion} required!`);
process.exit(1);
}
}
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");
// Try to connect to DB
try {
dbLogger.info("Connecting...");
await initDb();
const v = await db
.query("SHOW server_version")
.then((x) => x[0].server_version);
dbLogger.succ(`Connected: v${v}`);
} catch (e) {
dbLogger.error("Cannot connect", null, true);
dbLogger.error(e);
process.exit(1);
}
}
async function spawnWorkers(limit: number = 1) {
const workers = Math.min(limit, os.cpus().length);
bootLogger.info(`Starting ${workers} worker${workers === 1 ? "" : "s"}...`);
await Promise.all([...Array(workers)].map(spawnWorker));
bootLogger.succ("All workers started");
}
function spawnWorker(): Promise<void> {
return new Promise((res) => {
const worker = cluster.fork();
worker.on("message", (message) => {
if (message === "listenFailed") {
bootLogger.error("The server Listen failed due to the previous error.");
process.exit(1);
}
if (message !== "ready") return;
res();
});
});
}

View file

@ -1,20 +0,0 @@
import cluster from "node:cluster";
import { initDb } from "../db/postgre.js";
/**
* Init worker process
*/
export async function workerMain() {
await initDb();
// start server
await import("../server/index.js").then((x) => x.default());
// start job queue
import("../queue/index.js").then((x) => x.default());
if (cluster.isWorker) {
// Send a 'ready' message to parent process
process.send!("ready");
}
}

View file

@ -1,3 +0,0 @@
import load from "./load.js";
export default load();

View file

@ -1,68 +0,0 @@
/**
* Config loader
*/
import * as fs from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname } from "node:path";
import * as yaml from "js-yaml";
import type { Source, Mixin } from "./types.js";
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 = yaml.load(fs.readFileSync(path, "utf-8")) as Source;
const mixin = {} as Mixin;
const url = tryCreateUrl(config.url);
config.url = url.origin;
config.port = config.port || parseInt(process.env.PORT || "", 10);
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 = `Calckey/${meta.version} (${config.url})`;
mixin.clientEntry = clientManifest["src/init.ts"];
if (!config.redis.prefix) config.redis.prefix = mixin.host;
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.`);
}
}

View file

@ -1,133 +0,0 @@
/**
*
*/
export type Source = {
repository_url?: string;
feedback_url?: string;
url: string;
port: number;
disableHsts?: boolean;
db: {
host: string;
port: number;
db: string;
user: string;
pass: string;
disableCache?: boolean;
extra?: { [x: string]: string };
};
redis: {
host: string;
port: number;
family?: number;
pass: string;
db?: number;
prefix?: string;
};
elasticsearch: {
host: string;
port: number;
ssl?: boolean;
user?: string;
pass?: string;
index?: string;
};
sonic: {
host: string;
port: number;
auth?: string;
collection?: string;
bucket?: string;
};
proxy?: string;
proxySmtp?: string;
proxyBypassHosts?: string[];
allowedPrivateNetworks?: string[];
maxFileSize?: number;
accesslog?: string;
clusterLimit?: number;
id: string;
outgoingAddressFamily?: "ipv4" | "ipv6" | "dual";
deliverJobConcurrency?: number;
inboxJobConcurrency?: number;
deliverJobPerSec?: number;
inboxJobPerSec?: number;
deliverJobMaxAttempts?: number;
inboxJobMaxAttempts?: number;
syslog: {
host: string;
port: number;
};
mediaProxy?: string;
proxyRemoteFiles?: boolean;
twa: {
nameSpace?: string;
packageName?: string;
sha256CertFingerprints?: string[];
};
// Managed hosting stuff
maxUserSignups?: number;
isManagedHosting?: boolean;
maxNoteLength?: number;
maxCaptionLength?: number;
deepl: {
managed?: boolean;
authKey?: string;
isPro?: boolean;
};
email: {
managed?: boolean;
address?: string;
host?: string;
port?: number;
user?: string;
pass?: string;
useImplicitSslTls?: boolean;
};
objectStorage: {
managed?: boolean;
baseUrl?: string;
bucket?: string;
prefix?: string;
endpoint?: string;
region?: string;
accessKey?: string;
secretKey?: string;
useSsl?: boolean;
connnectOverProxy?: boolean;
setPublicReadOnUpload?: boolean;
s3ForcePathStyle?: boolean;
};
summalyProxyUrl?: string;
};
/**
* Misskeyが自動的に()
*/
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 = Source & Mixin;

View file

@ -1,71 +0,0 @@
import config from "@/config/index.js";
import { DB_MAX_IMAGE_COMMENT_LENGTH } from "@/misc/hard-limits.js";
export const MAX_NOTE_TEXT_LENGTH =
config.maxNoteLength != null ? config.maxNoteLength : 3000; // <- should we increase this?
export const MAX_CAPTION_TEXT_LENGTH = Math.min(
config.maxCaptionLength ?? 1500,
DB_MAX_IMAGE_COMMENT_LENGTH,
);
export const SECOND = 1000;
export const SEC = 1000; // why do we need this duplicate here?
export const MINUTE = 60 * SEC;
export const MIN = 60 * SEC; // why do we need this duplicate here?
export const HOUR = 60 * MIN;
export const DAY = 24 * HOUR;
export const USER_ONLINE_THRESHOLD = 10 * MINUTE;
export const USER_ACTIVE_THRESHOLD = 3 * DAY;
// List of file types allowed to be viewed directly in the browser
// Anything not included here will be responded as application/octet-stream
// SVG is not allowed because it generates XSS <- we need to fix this and later allow it to be viewed directly
export const FILE_TYPE_BROWSERSAFE = [
// Images
"image/png",
"image/gif", // TODO: deprecated, but still used by old notes, new gifs should be converted to webp in the future
"image/jpeg",
"image/webp", // TODO: make this the default image format
"image/apng",
"image/bmp",
"image/tiff",
"image/x-icon",
"image/avif", // not as good supported now, but its good to introduce initial support for the future
// OggS
"audio/opus",
"video/ogg",
"audio/ogg",
"application/ogg",
// ISO/IEC base media file format
"video/quicktime",
"video/mp4", // TODO: we need to check for av1 later
"video/vnd.avi", // also av1
"audio/mp4",
"video/x-m4v",
"audio/x-m4a",
"video/3gpp",
"video/3gpp2",
"video/3gp2",
"audio/3gpp",
"audio/3gpp2",
"audio/3gp2",
"video/mpeg",
"audio/mpeg",
"video/webm",
"audio/webm",
"audio/aac",
"audio/x-flac",
"audio/flac",
"audio/vnd.wave",
];
/*
https://github.com/sindresorhus/file-type/blob/main/supported.js
https://github.com/sindresorhus/file-type/blob/main/core.js
https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers
*/

View file

@ -1,20 +0,0 @@
// TODO: 消したい
const interval = 30 * 60 * 1000;
import { AttestationChallenges } from "@/models/index.js";
import { LessThan } from "typeorm";
/**
* Clean up database occasionally
*/
export default function () {
async function tick() {
await AttestationChallenges.delete({
createdAt: LessThan(new Date(new Date().getTime() - 5 * 60 * 1000)),
});
}
tick();
setInterval(tick, interval);
}

View file

@ -1,60 +0,0 @@
import Xev from "xev";
import { deliverQueue, inboxQueue } from "../queue/queues.js";
const ev = new Xev();
const interval = 10000;
/**
* Report queue stats regularly
*/
export default function () {
const log = [] as any[];
ev.on("requestQueueStatsLog", (x) => {
ev.emit(`queueStatsLog:${x.id}`, log.slice(0, x.length || 50));
});
let activeDeliverJobs = 0;
let activeInboxJobs = 0;
deliverQueue.on("global:active", () => {
activeDeliverJobs++;
});
inboxQueue.on("global:active", () => {
activeInboxJobs++;
});
async function tick() {
const deliverJobCounts = await deliverQueue.getJobCounts();
const inboxJobCounts = await inboxQueue.getJobCounts();
const stats = {
deliver: {
activeSincePrevTick: activeDeliverJobs,
active: deliverJobCounts.active,
waiting: deliverJobCounts.waiting,
delayed: deliverJobCounts.delayed,
},
inbox: {
activeSincePrevTick: activeInboxJobs,
active: inboxJobCounts.active,
waiting: inboxJobCounts.waiting,
delayed: inboxJobCounts.delayed,
},
};
ev.emit("queueStats", stats);
log.unshift(stats);
if (log.length > 200) log.pop();
activeDeliverJobs = 0;
activeInboxJobs = 0;
}
tick();
setInterval(tick, interval);
}

View file

@ -1,79 +0,0 @@
import si from "systeminformation";
import Xev from "xev";
import * as osUtils from "os-utils";
const ev = new Xev();
const interval = 2000;
const roundCpu = (num: number) => Math.round(num * 1000) / 1000;
const round = (num: number) => Math.round(num * 10) / 10;
/**
* Report server stats regularly
*/
export default function () {
const log = [] as any[];
ev.on("requestServerStatsLog", (x) => {
ev.emit(`serverStatsLog:${x.id}`, log.slice(0, x.length || 50));
});
async function tick() {
const cpu = await cpuUsage();
const memStats = await mem();
const netStats = await net();
const fsStats = await fs();
const stats = {
cpu: roundCpu(cpu),
mem: {
used: round(memStats.used - memStats.buffers - memStats.cached),
active: round(memStats.active),
},
net: {
rx: round(Math.max(0, netStats.rx_sec)),
tx: round(Math.max(0, netStats.tx_sec)),
},
fs: {
r: round(Math.max(0, fsStats.rIO_sec ?? 0)),
w: round(Math.max(0, fsStats.wIO_sec ?? 0)),
},
};
ev.emit("serverStats", stats);
log.unshift(stats);
if (log.length > 200) log.pop();
}
tick();
setInterval(tick, interval);
}
// CPU STAT
function cpuUsage(): Promise<number> {
return new Promise((res, rej) => {
osUtils.cpuUsage((cpuUsage) => {
res(cpuUsage);
});
});
}
// MEMORY STAT
async function mem() {
const data = await si.mem();
return data;
}
// NETWORK STAT
async function net() {
const iface = await si.networkInterfaceDefault();
const data = await si.networkStats(iface);
return data[0];
}
// FS STAT
async function fs() {
const data = await si.disksIO().catch(() => ({ rIO_sec: 0, wIO_sec: 0 }));
return data || { rIO_sec: 0, wIO_sec: 0 };
}

View file

@ -1,65 +0,0 @@
import * as elasticsearch from "@elastic/elasticsearch";
import config from "@/config/index.js";
const index = {
settings: {
analysis: {
analyzer: {
ngram: {
tokenizer: "ngram",
},
},
},
},
mappings: {
properties: {
text: {
type: "text",
index: true,
analyzer: "ngram",
},
userId: {
type: "keyword",
index: true,
},
userHost: {
type: "keyword",
index: true,
},
},
},
};
// Init ElasticSearch connection
const client = config.elasticsearch
? new elasticsearch.Client({
node: `${config.elasticsearch.ssl ? "https://" : "http://"}${
config.elasticsearch.host
}:${config.elasticsearch.port}`,
auth:
config.elasticsearch.user && config.elasticsearch.pass
? {
username: config.elasticsearch.user,
password: config.elasticsearch.pass,
}
: undefined,
pingTimeout: 30000,
})
: null;
if (client) {
client.indices
.exists({
index: config.elasticsearch.index || "misskey_note",
})
.then((exist) => {
if (!exist.body) {
client.indices.create({
index: config.elasticsearch.index || "misskey_note",
body: index,
});
}
});
}
export default client;

View file

@ -1,3 +0,0 @@
import Logger from "@/services/logger.js";
export const dbLogger = new Logger("db");

View file

@ -1,262 +0,0 @@
// https://github.com/typeorm/typeorm/issues/2400
import pg from "pg";
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 { User } from "@/models/entities/user.js";
import { DriveFile } from "@/models/entities/drive-file.js";
import { DriveFolder } from "@/models/entities/drive-folder.js";
import { AccessToken } from "@/models/entities/access-token.js";
import { App } from "@/models/entities/app.js";
import { PollVote } from "@/models/entities/poll-vote.js";
import { Note } from "@/models/entities/note.js";
import { NoteReaction } from "@/models/entities/note-reaction.js";
import { NoteWatching } from "@/models/entities/note-watching.js";
import { NoteThreadMuting } from "@/models/entities/note-thread-muting.js";
import { NoteUnread } from "@/models/entities/note-unread.js";
import { Notification } from "@/models/entities/notification.js";
import { Meta } from "@/models/entities/meta.js";
import { Following } from "@/models/entities/following.js";
import { Instance } from "@/models/entities/instance.js";
import { Muting } from "@/models/entities/muting.js";
import { RenoteMuting } from "@/models/entities/renote-muting.js";
import { SwSubscription } from "@/models/entities/sw-subscription.js";
import { Blocking } from "@/models/entities/blocking.js";
import { UserList } from "@/models/entities/user-list.js";
import { UserListJoining } from "@/models/entities/user-list-joining.js";
import { UserGroup } from "@/models/entities/user-group.js";
import { UserGroupJoining } from "@/models/entities/user-group-joining.js";
import { UserGroupInvitation } from "@/models/entities/user-group-invitation.js";
import { Hashtag } from "@/models/entities/hashtag.js";
import { NoteFavorite } from "@/models/entities/note-favorite.js";
import { AbuseUserReport } from "@/models/entities/abuse-user-report.js";
import { RegistrationTicket } from "@/models/entities/registration-tickets.js";
import { MessagingMessage } from "@/models/entities/messaging-message.js";
import { Signin } from "@/models/entities/signin.js";
import { AuthSession } from "@/models/entities/auth-session.js";
import { FollowRequest } from "@/models/entities/follow-request.js";
import { Emoji } from "@/models/entities/emoji.js";
import { UserNotePining } from "@/models/entities/user-note-pining.js";
import { Poll } from "@/models/entities/poll.js";
import { UserKeypair } from "@/models/entities/user-keypair.js";
import { UserPublickey } from "@/models/entities/user-publickey.js";
import { UserProfile } from "@/models/entities/user-profile.js";
import { UserSecurityKey } from "@/models/entities/user-security-key.js";
import { AttestationChallenge } from "@/models/entities/attestation-challenge.js";
import { Page } from "@/models/entities/page.js";
import { PageLike } from "@/models/entities/page-like.js";
import { GalleryPost } from "@/models/entities/gallery-post.js";
import { GalleryLike } from "@/models/entities/gallery-like.js";
import { ModerationLog } from "@/models/entities/moderation-log.js";
import { UsedUsername } from "@/models/entities/used-username.js";
import { Announcement } from "@/models/entities/announcement.js";
import { AnnouncementRead } from "@/models/entities/announcement-read.js";
import { Clip } from "@/models/entities/clip.js";
import { ClipNote } from "@/models/entities/clip-note.js";
import { Antenna } from "@/models/entities/antenna.js";
import { AntennaNote } from "@/models/entities/antenna-note.js";
import { PromoNote } from "@/models/entities/promo-note.js";
import { PromoRead } from "@/models/entities/promo-read.js";
import { Relay } from "@/models/entities/relay.js";
import { MutedNote } from "@/models/entities/muted-note.js";
import { Channel } from "@/models/entities/channel.js";
import { ChannelFollowing } from "@/models/entities/channel-following.js";
import { ChannelNotePining } from "@/models/entities/channel-note-pining.js";
import { RegistryItem } from "@/models/entities/registry-item.js";
import { Ad } from "@/models/entities/ad.js";
import { PasswordResetRequest } from "@/models/entities/password-reset-request.js";
import { UserPending } from "@/models/entities/user-pending.js";
import { Webhook } from "@/models/entities/webhook.js";
import { UserIp } from "@/models/entities/user-ip.js";
import { entities as charts } from "@/services/chart/entities.js";
import { envOption } from "../env.js";
import { dbLogger } from "./logger.js";
import { redisClient } from "./redis.js";
const sqlLogger = dbLogger.createSubLogger("sql", "gray", false);
class MyCustomLogger implements Logger {
private highlight(sql: string) {
return highlight.highlight(sql, {
language: "sql",
ignoreIllegals: true,
});
}
public logQuery(query: string, parameters?: any[]) {
sqlLogger.info(this.highlight(query).substring(0, 100));
}
public logQueryError(error: string, query: string, parameters?: any[]) {
sqlLogger.error(this.highlight(query));
}
public logQuerySlow(time: number, query: string, parameters?: any[]) {
sqlLogger.warn(this.highlight(query));
}
public logSchemaBuild(message: string) {
sqlLogger.info(message);
}
public log(message: string) {
sqlLogger.info(message);
}
public logMigration(message: string) {
sqlLogger.info(message);
}
}
export const entities = [
Announcement,
AnnouncementRead,
Meta,
Instance,
App,
AuthSession,
AccessToken,
User,
UserProfile,
UserKeypair,
UserPublickey,
UserList,
UserListJoining,
UserGroup,
UserGroupJoining,
UserGroupInvitation,
UserNotePining,
UserSecurityKey,
UsedUsername,
AttestationChallenge,
Following,
FollowRequest,
Muting,
RenoteMuting,
Blocking,
Note,
NoteFavorite,
NoteReaction,
NoteWatching,
NoteThreadMuting,
NoteUnread,
Page,
PageLike,
GalleryPost,
GalleryLike,
DriveFile,
DriveFolder,
Poll,
PollVote,
Notification,
Emoji,
Hashtag,
SwSubscription,
AbuseUserReport,
RegistrationTicket,
MessagingMessage,
Signin,
ModerationLog,
Clip,
ClipNote,
Antenna,
AntennaNote,
PromoNote,
PromoRead,
Relay,
MutedNote,
Channel,
ChannelFollowing,
ChannelNotePining,
RegistryItem,
Ad,
PasswordResetRequest,
UserPending,
Webhook,
UserIp,
...charts,
];
const log = process.env.NODE_ENV !== "production";
export const db = new DataSource({
type: "postgres",
host: config.db.host,
port: config.db.port,
username: config.db.user,
password: config.db.pass,
database: config.db.db,
extra: {
statement_timeout: 1000 * 10,
...config.db.extra,
},
synchronize: process.env.NODE_ENV === "test",
dropSchema: process.env.NODE_ENV === "test",
cache: !config.db.disableCache
? {
type: "ioredis",
options: {
host: config.redis.host,
port: config.redis.port,
family: config.redis.family == null ? 0 : config.redis.family,
password: config.redis.pass,
keyPrefix: `${config.redis.prefix}:query:`,
db: config.redis.db || 0,
},
}
: false,
logging: log,
logger: log ? new MyCustomLogger() : undefined,
maxQueryExecutionTime: 300,
entities: entities,
migrations: ["../../migration/*.js"],
});
export async function initDb(force = false) {
if (force) {
if (db.isInitialized) {
await db.destroy();
}
await db.initialize();
return;
}
if (db.isInitialized) {
// nop
} else {
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;
}
}

View file

@ -1,18 +0,0 @@
import Redis from "ioredis";
import config from "@/config/index.js";
export function createConnection() {
return new Redis({
port: config.redis.port,
host: config.redis.host,
family: config.redis.family == null ? 0 : config.redis.family,
password: config.redis.pass,
keyPrefix: `${config.redis.prefix}:`,
db: config.redis.db || 0,
});
}
export const subscriber = createConnection();
subscriber.subscribe(config.host);
export const redisClient = createConnection();

View file

@ -1,49 +0,0 @@
import * as SonicChannel from "sonic-channel";
import { dbLogger } from "./logger.js";
import config from "@/config/index.js";
const logger = dbLogger.createSubLogger("sonic", "gray", false);
logger.info("Connecting to Sonic");
const handlers = (type: string): SonicChannel.Handlers => ({
connected: () => {
logger.succ(`Connected to Sonic ${type}`);
},
disconnected: (error) => {
logger.warn(`Disconnected from Sonic ${type}, error: ${error}`);
},
error: (error) => {
logger.warn(`Sonic ${type} error: ${error}`);
},
retrying: () => {
logger.info(`Sonic ${type} retrying`);
},
timeout: () => {
logger.warn(`Sonic ${type} timeout`);
},
});
const hasConfig =
config.sonic && (config.sonic.host || config.sonic.port || config.sonic.auth);
const host = hasConfig ? config.sonic.host ?? "localhost" : "";
const port = hasConfig ? config.sonic.port ?? 1491 : 0;
const auth = hasConfig ? config.sonic.auth ?? "SecretPassword" : "";
const collection = hasConfig ? config.sonic.collection ?? "main" : "";
const bucket = hasConfig ? config.sonic.bucket ?? "default" : "";
export default hasConfig
? {
search: new SonicChannel.Search({ host, port, auth }).connect(
handlers("search"),
),
ingest: new SonicChannel.Ingest({ host, port, auth }).connect(
handlers("ingest"),
),
collection,
bucket,
}
: null;

View file

@ -1,25 +0,0 @@
const envOption = {
onlyQueue: false,
onlyServer: false,
noDaemons: false,
disableClustering: false,
verbose: false,
withLogTime: false,
quiet: 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.quiet = true;
if (process.env.NODE_ENV === "test") envOption.noDaemons = true;
export { envOption };

View file

@ -1,2 +0,0 @@
// rome-ignore lint/suspicious/noExplicitAny: i have no idea
type FIXME = any;

View file

@ -1,13 +0,0 @@
/**
* Misskey Entry Point!
*/
import { EventEmitter } from "node:events";
import boot from "./boot/index.js";
Error.stackTraceLimit = Infinity;
EventEmitter.defaultMaxListeners = 128;
boot().catch((err) => {
console.error(err);
});

View file

@ -0,0 +1,47 @@
use std::{
env, fmt,
path::{Path, PathBuf},
};
use tracing::debug;
#[macro_use]
extern crate macros;
fn main() -> anyhow::Result<()> {
env::set_var(
"CK_REPO_DIR",
PathBuf::from(env!("PWD"))
.parent()
.and_then(|p| p.parent())
.ok_or(fmt::Error)?,
);
// logging
let subscriber = tracing_subscriber::fmt();
if is_release!() {
subscriber.with_max_level(tracing::Level::INFO).init();
} else {
subscriber
.with_max_level(tracing::Level::DEBUG)
.pretty()
.init();
}
// bootstrap
// ENV
// get config
let config_path = &Path::new(&env::var("CK_REPO_DIR")?)
.join(".config")
.join("default.yml");
debug!(target: "config", path = ?config_path, "Loading yaml file");
config::init_config(config_path)?;
eprintln!("{:?}", config::get_config()?);
server::init()?;
Ok(())
}

View file

@ -1,213 +0,0 @@
import { URL } from "node:url";
import * as parse5 from "parse5";
import * as TreeAdapter from "../../node_modules/parse5/dist/tree-adapters/default.js";
const treeAdapter = TreeAdapter.defaultTreeAdapter;
const urlRegex = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+/;
const urlRegexFull = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+$/;
export function fromHtml(html: string, hashtagNames?: string[]): string {
// some AP servers like Pixelfed use br tags as well as newlines
html = html.replace(/<br\s?\/?>\r?\n/gi, "\n");
const dom = parse5.parseFragment(html);
let text = "";
for (const n of dom.childNodes) {
analyze(n);
}
return text.trim();
function getText(node: TreeAdapter.Node): string {
if (treeAdapter.isTextNode(node)) return node.value;
if (!treeAdapter.isElementNode(node)) return "";
if (node.nodeName === "br") return "\n";
if (node.childNodes) {
return node.childNodes.map((n) => getText(n)).join("");
}
return "";
}
function appendChildren(childNodes: TreeAdapter.ChildNode[]): void {
if (childNodes) {
for (const n of childNodes) {
analyze(n);
}
}
}
function analyze(node: TreeAdapter.Node) {
if (treeAdapter.isTextNode(node)) {
text += node.value;
return;
}
// Skip comment or document type node
if (!treeAdapter.isElementNode(node)) return;
switch (node.nodeName) {
case "br": {
text += "\n";
break;
}
case "a": {
const txt = getText(node);
const rel = node.attrs.find((x) => x.name === "rel");
const href = node.attrs.find((x) => x.name === "href");
// ハッシュタグ
if (
hashtagNames &&
href &&
hashtagNames.map((x) => x.toLowerCase()).includes(txt.toLowerCase())
) {
text += txt;
// メンション
} else if (txt.startsWith("@") && !rel?.value.match(/^me /)) {
const part = txt.split("@");
if (part.length === 2 && href) {
//#region ホスト名部分が省略されているので復元する
const acct = `${txt}@${new URL(href.value).hostname}`;
text += acct;
//#endregion
} else if (part.length === 3) {
text += txt;
}
// その他
} else {
const generateLink = () => {
if (!(href || txt)) {
return "";
}
if (!href) {
return txt;
}
if (!txt || txt === href.value) {
// #6383: Missing text node
if (href.value.match(urlRegexFull)) {
return href.value;
} else {
return `<${href.value}>`;
}
}
if (href.value.match(urlRegex) && !href.value.match(urlRegexFull)) {
return `[${txt}](<${href.value}>)`; // #6846
} else {
return `[${txt}](${href.value})`;
}
};
text += generateLink();
}
break;
}
case "h1": {
text += "【";
appendChildren(node.childNodes);
text += "】\n";
break;
}
case "b":
case "strong": {
text += "**";
appendChildren(node.childNodes);
text += "**";
break;
}
case "small": {
text += "<small>";
appendChildren(node.childNodes);
text += "</small>";
break;
}
case "s":
case "del": {
text += "~~";
appendChildren(node.childNodes);
text += "~~";
break;
}
case "i":
case "em": {
text += "<i>";
appendChildren(node.childNodes);
text += "</i>";
break;
}
// block code (<pre><code>)
case "pre": {
if (
node.childNodes.length === 1 &&
node.childNodes[0].nodeName === "code"
) {
text += "\n```\n";
text += getText(node.childNodes[0]);
text += "\n```\n";
} else {
appendChildren(node.childNodes);
}
break;
}
// inline code (<code>)
case "code": {
text += "`";
appendChildren(node.childNodes);
text += "`";
break;
}
case "blockquote": {
const t = getText(node);
if (t) {
text += "\n> ";
text += t.split("\n").join("\n> ");
}
break;
}
case "p":
case "h2":
case "h3":
case "h4":
case "h5":
case "h6": {
text += "\n\n";
appendChildren(node.childNodes);
break;
}
// other block elements
case "div":
case "header":
case "footer":
case "article":
case "li":
case "dt":
case "dd": {
text += "\n";
appendChildren(node.childNodes);
break;
}
default: {
// includes inline elements
appendChildren(node.childNodes);
break;
}
}
}
}

View file

@ -1,174 +0,0 @@
import { JSDOM } from "jsdom";
import type * as mfm from "mfm-js";
import config from "@/config/index.js";
import { intersperse } from "@/prelude/array.js";
import type { IMentionedRemoteUsers } from "@/models/entities/note.js";
export function toHtml(
nodes: mfm.MfmNode[] | null,
mentionedRemoteUsers: IMentionedRemoteUsers = [],
) {
if (nodes == null) {
return null;
}
const { window } = new JSDOM("");
const doc = window.document;
function appendChildren(children: mfm.MfmNode[], targetElement: any): void {
if (children) {
for (const child of children.map((x) => (handlers as any)[x.type](x)))
targetElement.appendChild(child);
}
}
const handlers: {
[K in mfm.MfmNode["type"]]: (node: mfm.NodeType<K>) => any;
} = {
bold(node) {
const el = doc.createElement("b");
appendChildren(node.children, el);
return el;
},
small(node) {
const el = doc.createElement("small");
appendChildren(node.children, el);
return el;
},
strike(node) {
const el = doc.createElement("del");
appendChildren(node.children, el);
return el;
},
italic(node) {
const el = doc.createElement("i");
appendChildren(node.children, el);
return el;
},
fn(node) {
const el = doc.createElement("i");
appendChildren(node.children, el);
return el;
},
blockCode(node) {
const pre = doc.createElement("pre");
const inner = doc.createElement("code");
inner.textContent = node.props.code;
pre.appendChild(inner);
return pre;
},
center(node) {
const el = doc.createElement("div");
appendChildren(node.children, el);
return el;
},
emojiCode(node) {
return doc.createTextNode(`\u200B:${node.props.name}:\u200B`);
},
unicodeEmoji(node) {
return doc.createTextNode(node.props.emoji);
},
hashtag(node) {
const a = doc.createElement("a");
a.href = `${config.url}/tags/${node.props.hashtag}`;
a.textContent = `#${node.props.hashtag}`;
a.setAttribute("rel", "tag");
return a;
},
inlineCode(node) {
const el = doc.createElement("code");
el.textContent = node.props.code;
return el;
},
mathInline(node) {
const el = doc.createElement("code");
el.textContent = node.props.formula;
return el;
},
mathBlock(node) {
const el = doc.createElement("code");
el.textContent = node.props.formula;
return el;
},
link(node) {
const a = doc.createElement("a");
a.href = node.props.url;
appendChildren(node.children, a);
return a;
},
mention(node) {
const a = doc.createElement("a");
const { username, host, acct } = node.props;
const remoteUserInfo = mentionedRemoteUsers.find(
(remoteUser) =>
remoteUser.username === username && remoteUser.host === host,
);
a.href = remoteUserInfo
? remoteUserInfo.url
? remoteUserInfo.url
: remoteUserInfo.uri
: `${config.url}/${acct}`;
a.className = "u-url mention";
a.textContent = acct;
return a;
},
quote(node) {
const el = doc.createElement("blockquote");
appendChildren(node.children, el);
return el;
},
text(node) {
const el = doc.createElement("span");
const nodes = node.props.text
.split(/\r\n|\r|\n/)
.map((x) => doc.createTextNode(x));
for (const x of intersperse<FIXME | "br">("br", nodes)) {
el.appendChild(x === "br" ? doc.createElement("br") : x);
}
return el;
},
url(node) {
const a = doc.createElement("a");
a.href = node.props.url;
a.textContent = node.props.url;
return a;
},
search(node) {
const a = doc.createElement("a");
a.href = `https://search.annoyingorange.xyz/search?q=${node.props.query}`;
a.textContent = node.props.content;
return a;
},
plain(node) {
const el = doc.createElement("span");
appendChildren(node.children, el);
return el;
},
};
appendChildren(nodes, doc.body);
return `<p>${doc.body.innerHTML}</p>`;
}

View file

@ -1,14 +0,0 @@
export type Acct = {
username: string;
host: string | null;
};
export function parse(acct: string): Acct {
if (acct.startsWith("@")) acct = acct.substr(1);
const split = acct.split("@", 2);
return { username: split[0], host: split[1] || null };
}
export function toString(acct: Acct): string {
return acct.host == null ? acct.username : `${acct.username}@${acct.host}`;
}

View file

@ -1,36 +0,0 @@
import { Antennas } from "@/models/index.js";
import type { Antenna } from "@/models/entities/antenna.js";
import { subscriber } from "@/db/redis.js";
let antennasFetched = false;
let antennas: Antenna[] = [];
export async function getAntennas() {
if (!antennasFetched) {
antennas = await Antennas.find();
antennasFetched = true;
}
return antennas;
}
subscriber.on("message", async (_, data) => {
const obj = JSON.parse(data);
if (obj.channel === "internal") {
const { type, body } = obj.message;
switch (type) {
case "antennaCreated":
antennas.push(body);
break;
case "antennaUpdated":
antennas[antennas.findIndex((a) => a.id === body.id)] = body;
break;
case "antennaDeleted":
antennas = antennas.filter((a) => a.id !== body.id);
break;
default:
break;
}
}
});

View file

@ -1,35 +0,0 @@
export const kinds = [
"read:account",
"write:account",
"read:blocks",
"write:blocks",
"read:drive",
"write:drive",
"read:favorites",
"write:favorites",
"read:following",
"write:following",
"read:messaging",
"write:messaging",
"read:mutes",
"write:mutes",
"write:notes",
"read:notifications",
"write:notifications",
"read:reactions",
"write:reactions",
"write:votes",
"read:pages",
"write:pages",
"write:page-likes",
"read:page-likes",
"read:user-groups",
"write:user-groups",
"read:channels",
"write:channels",
"read:gallery",
"write:gallery",
"read:gallery-likes",
"write:gallery-likes",
];
// IF YOU ADD KINDS(PERMISSIONS), YOU MUST ADD TRANSLATIONS (under _permissions).

View file

@ -1,33 +0,0 @@
import { redisClient } from "../db/redis.js";
import { promisify } from "node:util";
import redisLock from "redis-lock";
/**
* Retry delay (ms) for lock acquisition
*/
const retryDelay = 100;
const lock: (key: string, timeout?: number) => Promise<() => void> = redisClient
? promisify(redisLock(redisClient, retryDelay))
: async () => () => {};
/**
* Get AP Object lock
* @param uri AP object ID
* @param timeout Lock timeout (ms), The timeout releases previous lock.
* @returns Unlock function
*/
export function getApLock(uri: string, timeout = 30 * 1000) {
return lock(`ap-object:${uri}`, timeout);
}
export function getFetchInstanceMetadataLock(
host: string,
timeout = 30 * 1000,
) {
return lock(`instance:${host}`, timeout);
}
export function getChartInsertLock(lockKey: string, timeout = 30 * 1000) {
return lock(`chart-insert:${lockKey}`, timeout);
}

View file

@ -1,103 +0,0 @@
// https://gist.github.com/nfantone/1eaa803772025df69d07f4dbf5df7e58
"use strict";
/**
* @callback BeforeShutdownListener
* @param {string} [signalOrEvent] The exit signal or event name received on the process.
*/
/**
* System signals the app will listen to initiate shutdown.
* @const {string[]}
*/
const SHUTDOWN_SIGNALS = ["SIGINT", "SIGTERM"];
/**
* Time in milliseconds to wait before forcing shutdown.
* @const {number}
*/
const SHUTDOWN_TIMEOUT = 15000;
/**
* A queue of listener callbacks to execute before shutting
* down the process.
* @type {BeforeShutdownListener[]}
*/
const shutdownListeners: ((signalOrEvent: string) => void)[] = [];
/**
* Listen for signals and execute given `fn` function once.
* @param {string[]} signals System signals to listen to.
* @param {function(string)} fn Function to execute on shutdown.
*/
const processOnce = (
signals: string[],
fn: (signalOrEvent: string) => void,
) => {
for (const sig of signals) {
process.once(sig, fn);
}
};
/**
* Sets a forced shutdown mechanism that will exit the process after `timeout` milliseconds.
* @param {number} timeout Time to wait before forcing shutdown (milliseconds)
*/
const forceExitAfter = (timeout: number) => () => {
setTimeout(() => {
// Force shutdown after timeout
console.warn(
`Could not close resources gracefully after ${timeout}ms: forcing shutdown`,
);
return process.exit(1);
}, timeout).unref();
};
/**
* Main process shutdown handler. Will invoke every previously registered async shutdown listener
* in the queue and exit with a code of `0`. Any `Promise` rejections from any listener will
* be logged out as a warning, but won't prevent other callbacks from executing.
* @param {string} signalOrEvent The exit signal or event name received on the process.
*/
async function shutdownHandler(signalOrEvent: string) {
if (process.env.NODE_ENV === "test") return process.exit(0);
console.warn(`Shutting down: received [${signalOrEvent}] signal`);
for (const listener of shutdownListeners) {
try {
await listener(signalOrEvent);
} catch (err) {
if (err instanceof Error) {
console.warn(
`A shutdown handler failed before completing with: ${
err.message || err
}`,
);
}
}
}
return process.exit(0);
}
/**
* Registers a new shutdown listener to be invoked before exiting
* the main process. Listener handlers are guaranteed to be called in the order
* they were registered.
* @param {BeforeShutdownListener} listener The shutdown listener to register.
* @returns {BeforeShutdownListener} Echoes back the supplied `listener`.
*/
export function beforeShutdown(listener: () => void) {
shutdownListeners.push(listener);
return listener;
}
// Register shutdown callback that kills the process after `SHUTDOWN_TIMEOUT` milliseconds
// This prevents custom shutdown handlers from hanging the process indefinitely
processOnce(SHUTDOWN_SIGNALS, forceExitAfter(SHUTDOWN_TIMEOUT));
// Register process shutdown callback
// Will listen to incoming signal events and execute all registered handlers in the stack
processOnce(SHUTDOWN_SIGNALS, shutdownHandler);

View file

@ -1,88 +0,0 @@
export class Cache<T> {
public cache: Map<string | null, { date: number; value: T }>;
private lifetime: number;
constructor(lifetime: Cache<never>["lifetime"]) {
this.cache = new Map();
this.lifetime = lifetime;
}
public set(key: string | null, value: T): void {
this.cache.set(key, {
date: Date.now(),
value,
});
}
public get(key: string | null): T | undefined {
const cached = this.cache.get(key);
if (cached == null) return undefined;
if (Date.now() - cached.date > this.lifetime) {
this.cache.delete(key);
return undefined;
}
return cached.value;
}
public delete(key: string | null) {
this.cache.delete(key);
}
/**
* fetcherを呼び出して結果をキャッシュ&
* optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
*/
public async fetch(
key: string | null,
fetcher: () => Promise<T>,
validator?: (cachedValue: T) => boolean,
): Promise<T> {
const cachedValue = this.get(key);
if (cachedValue !== undefined) {
if (validator) {
if (validator(cachedValue)) {
// Cache HIT
return cachedValue;
}
} else {
// Cache HIT
return cachedValue;
}
}
// Cache MISS
const value = await fetcher();
this.set(key, value);
return value;
}
/**
* fetcherを呼び出して結果をキャッシュ&
* optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
*/
public async fetchMaybe(
key: string | null,
fetcher: () => Promise<T | undefined>,
validator?: (cachedValue: T) => boolean,
): Promise<T | undefined> {
const cachedValue = this.get(key);
if (cachedValue !== undefined) {
if (validator) {
if (validator(cachedValue)) {
// Cache HIT
return cachedValue;
}
} else {
// Cache HIT
return cachedValue;
}
}
// Cache MISS
const value = await fetcher();
if (value !== undefined) {
this.set(key, value);
}
return value;
}
}

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