Merge branch 'develop' into fix/post_import_if
This commit is contained in:
commit
f6601aef5e
453 changed files with 8816 additions and 9796 deletions
|
@ -40,14 +40,11 @@ packages/backend/assets/instance.css
|
||||||
.gitattributes
|
.gitattributes
|
||||||
.weblate
|
.weblate
|
||||||
animated.svg
|
animated.svg
|
||||||
cliff.toml
|
|
||||||
docker-compose.yml
|
docker-compose.yml
|
||||||
docker-compose.example.yml
|
docker-compose.example.yml
|
||||||
firefish.apache.conf
|
|
||||||
firefish.nginx.conf
|
|
||||||
title.svg
|
title.svg
|
||||||
/.gitlab
|
/.gitlab
|
||||||
/chart
|
/ci
|
||||||
/dev
|
/dev
|
||||||
/docs
|
/docs
|
||||||
/scripts
|
/scripts
|
||||||
|
@ -56,6 +53,8 @@ biome.json
|
||||||
CODE_OF_CONDUCT.md
|
CODE_OF_CONDUCT.md
|
||||||
CONTRIBUTING.md
|
CONTRIBUTING.md
|
||||||
Dockerfile
|
Dockerfile
|
||||||
Procfile
|
Makefile
|
||||||
README.md
|
README.md
|
||||||
SECURITY.md
|
SECURITY.md
|
||||||
|
patrons.json
|
||||||
|
renovate.json
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -15,6 +15,7 @@ node_modules
|
||||||
report.*.json
|
report.*.json
|
||||||
|
|
||||||
# Cargo
|
# Cargo
|
||||||
|
/.cargo
|
||||||
/target
|
/target
|
||||||
|
|
||||||
# Cypress
|
# Cypress
|
||||||
|
|
|
@ -25,10 +25,11 @@ workflow:
|
||||||
- when: never
|
- when: never
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- dependency
|
|
||||||
- test
|
- test
|
||||||
- doc
|
- doc
|
||||||
- build
|
- build
|
||||||
|
- dependency
|
||||||
|
- clean
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
POSTGRES_DB: 'firefish_db'
|
POSTGRES_DB: 'firefish_db'
|
||||||
|
@ -36,6 +37,7 @@ variables:
|
||||||
POSTGRES_PASSWORD: 'password'
|
POSTGRES_PASSWORD: 'password'
|
||||||
POSTGRES_HOST_AUTH_METHOD: 'trust'
|
POSTGRES_HOST_AUTH_METHOD: 'trust'
|
||||||
DEBIAN_FRONTEND: 'noninteractive'
|
DEBIAN_FRONTEND: 'noninteractive'
|
||||||
|
NODE_OPTIONS: '--max_old_space_size=3072'
|
||||||
CARGO_PROFILE_DEV_OPT_LEVEL: '0'
|
CARGO_PROFILE_DEV_OPT_LEVEL: '0'
|
||||||
CARGO_PROFILE_DEV_LTO: 'off'
|
CARGO_PROFILE_DEV_LTO: 'off'
|
||||||
CARGO_PROFILE_DEV_DEBUG: 'none'
|
CARGO_PROFILE_DEV_DEBUG: 'none'
|
||||||
|
@ -119,6 +121,7 @@ test:build:backend_ts:
|
||||||
- cp packages/backend-rs/index.js packages/backend-rs/built/index.js
|
- cp packages/backend-rs/index.js packages/backend-rs/built/index.js
|
||||||
- cp packages/backend-rs/index.d.ts packages/backend-rs/built/index.d.ts
|
- cp packages/backend-rs/index.d.ts packages/backend-rs/built/index.d.ts
|
||||||
- cp ci/cargo/config.toml /usr/local/cargo/config.toml
|
- cp ci/cargo/config.toml /usr/local/cargo/config.toml
|
||||||
|
- test -f packages/backend-rs/built/backend-rs.linux-x64-gnu.node || pnpm install --frozen-lockfile
|
||||||
- test -f packages/backend-rs/built/backend-rs.linux-x64-gnu.node || pnpm --filter 'backend-rs' run build:debug
|
- test -f packages/backend-rs/built/backend-rs.linux-x64-gnu.node || pnpm --filter 'backend-rs' run build:debug
|
||||||
- cp .config/ci.yml .config/default.yml
|
- cp .config/ci.yml .config/default.yml
|
||||||
- export PGPASSWORD="${POSTGRES_PASSWORD}"
|
- export PGPASSWORD="${POSTGRES_PASSWORD}"
|
||||||
|
@ -199,6 +202,8 @@ build:container:
|
||||||
STORAGE_DRIVER: overlay
|
STORAGE_DRIVER: overlay
|
||||||
before_script:
|
before_script:
|
||||||
- apt-get update && apt-get -y upgrade
|
- apt-get update && apt-get -y upgrade
|
||||||
|
- |-
|
||||||
|
sed -i -r 's/"version": "([-0-9]+)",/"version": "\1-dev",/' package.json
|
||||||
- apt-get install -y --no-install-recommends ca-certificates fuse-overlayfs buildah
|
- apt-get install -y --no-install-recommends ca-certificates fuse-overlayfs buildah
|
||||||
- echo "${CI_REGISTRY_PASSWORD}" | buildah login --username "${CI_REGISTRY_USER}" --password-stdin "${CI_REGISTRY}"
|
- echo "${CI_REGISTRY_PASSWORD}" | buildah login --username "${CI_REGISTRY_USER}" --password-stdin "${CI_REGISTRY}"
|
||||||
- export IMAGE_TAG="${CI_REGISTRY}/${CI_PROJECT_PATH}/develop:not-for-production"
|
- export IMAGE_TAG="${CI_REGISTRY}/${CI_PROJECT_PATH}/develop:not-for-production"
|
||||||
|
@ -221,6 +226,31 @@ build:container:
|
||||||
- buildah inspect "${IMAGE_TAG}"
|
- buildah inspect "${IMAGE_TAG}"
|
||||||
- buildah push "${IMAGE_TAG}"
|
- buildah push "${IMAGE_TAG}"
|
||||||
|
|
||||||
|
cargo:check:msrv:
|
||||||
|
stage: test
|
||||||
|
image: docker.io/rust:1.74-slim-bookworm
|
||||||
|
rules:
|
||||||
|
- if: $TEST == 'true'
|
||||||
|
when: always
|
||||||
|
- if: $TEST == 'false'
|
||||||
|
when: never
|
||||||
|
- if: $CI_COMMIT_BRANCH == 'develop' || $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == 'develop'
|
||||||
|
changes:
|
||||||
|
paths:
|
||||||
|
- packages/backend-rs/**/*
|
||||||
|
- packages/macro-rs/**/*
|
||||||
|
- Cargo.toml
|
||||||
|
- Cargo.lock
|
||||||
|
when: always
|
||||||
|
services: []
|
||||||
|
before_script:
|
||||||
|
- apt-get update && apt-get -y upgrade
|
||||||
|
- apt-get install -y --no-install-recommends build-essential clang mold python3 perl nodejs postgresql-client
|
||||||
|
- cp ci/cargo/config.toml /usr/local/cargo/config.toml
|
||||||
|
script:
|
||||||
|
- cargo fetch --locked --manifest-path Cargo.toml
|
||||||
|
- cargo check --locked --frozen --all-features
|
||||||
|
|
||||||
cargo:test:
|
cargo:test:
|
||||||
stage: test
|
stage: test
|
||||||
rules:
|
rules:
|
||||||
|
@ -235,7 +265,6 @@ cargo:test:
|
||||||
- packages/macro-rs/**/*
|
- packages/macro-rs/**/*
|
||||||
- Cargo.toml
|
- Cargo.toml
|
||||||
- Cargo.lock
|
- Cargo.lock
|
||||||
- package.json
|
|
||||||
when: always
|
when: always
|
||||||
script:
|
script:
|
||||||
- curl -LsSf https://get.nexte.st/latest/linux | tar zxf - -C /usr/local/cargo/bin
|
- curl -LsSf https://get.nexte.st/latest/linux | tar zxf - -C /usr/local/cargo/bin
|
||||||
|
@ -287,7 +316,7 @@ cargo:doc:
|
||||||
- cp ci/cargo/config.toml /usr/local/cargo/config.toml
|
- cp ci/cargo/config.toml /usr/local/cargo/config.toml
|
||||||
script:
|
script:
|
||||||
- cargo doc --document-private-items
|
- cargo doc --document-private-items
|
||||||
- printf "window.ALL_CRATES = ['backend_rs', 'macro_rs'];" > target/doc/crates.js
|
- printf 'window.ALL_CRATES = ["backend_rs", "macros", "macros_impl"];' > target/doc/crates.js
|
||||||
- printf '<meta http-equiv="refresh" content="0; url=%s">' 'backend_rs' > target/doc/index.html
|
- printf '<meta http-equiv="refresh" content="0; url=%s">' 'backend_rs' > target/doc/index.html
|
||||||
- cd target/doc
|
- cd target/doc
|
||||||
- npx --yes netlify-cli deploy --prod --site="${CARGO_DOC_SITE_ID}" --dir=.
|
- npx --yes netlify-cli deploy --prod --site="${CARGO_DOC_SITE_ID}" --dir=.
|
||||||
|
@ -303,3 +332,19 @@ renovate:
|
||||||
before_script: []
|
before_script: []
|
||||||
script:
|
script:
|
||||||
- renovate --platform gitlab --token "${API_TOKEN}" --endpoint "${CI_SERVER_URL}/api/v4" "${CI_PROJECT_PATH}"
|
- renovate --platform gitlab --token "${API_TOKEN}" --endpoint "${CI_SERVER_URL}/api/v4" "${CI_PROJECT_PATH}"
|
||||||
|
|
||||||
|
clean:
|
||||||
|
stage: clean
|
||||||
|
rules:
|
||||||
|
- if: $CLEAN && $CI_PIPELINE_SOURCE == 'schedule'
|
||||||
|
services: []
|
||||||
|
before_script:
|
||||||
|
- apt-get update && apt-get -y upgrade
|
||||||
|
- apt-get -y --no-install-recommends install curl
|
||||||
|
- curl -fsSL 'https://deb.nodesource.com/setup_18.x' | bash -
|
||||||
|
- apt-get install -y --no-install-recommends nodejs
|
||||||
|
- corepack enable
|
||||||
|
- corepack prepare pnpm@latest --activate
|
||||||
|
- pnpm install --frozen-lockfile
|
||||||
|
script:
|
||||||
|
- pnpm run clean-all
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
<!-- 💖 Thanks for taking the time to fill out this bug report!
|
|
||||||
💁 Having trouble with deployment? [Ask the support chat.](https://matrix.to/#/#firefish-community:nitro.chat)
|
|
||||||
🔒 Found a security vulnerability? [Please disclose it responsibly.](https://firefish.dev/firefish/firefish/-/blob/develop/SECURITY.md)
|
|
||||||
🤝 By submitting this issue, you agree to follow our [Contribution Guidelines.](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md) -->
|
|
||||||
|
|
||||||
## What happened? <!-- Please give us a brief description of what happened. -->
|
|
||||||
|
|
||||||
|
|
||||||
## What did you expect to happen? <!-- Please give us a brief description of what you expected to happen. -->
|
|
||||||
|
|
||||||
|
|
||||||
## Version <!-- What version of firefish is your instance running? You can find this by clicking your instance's logo at the bottom left and then clicking instance information. -->
|
|
||||||
|
|
||||||
|
|
||||||
## What type of issue is this? <!-- If this happens on your device and has to do with the user interface, it's client-side. If this happens on either with the API or the backend, or you got a server-side error in the client, it's server-side. -->
|
|
||||||
|
|
||||||
- [ ] server-side
|
|
||||||
- [ ] client-side
|
|
||||||
- [ ] not sure
|
|
||||||
|
|
||||||
<details>
|
|
||||||
|
|
||||||
### Instance <!-- What instance of firefish are you using? -->
|
|
||||||
|
|
||||||
|
|
||||||
### What browser are you using? (client-side issues only)
|
|
||||||
|
|
||||||
|
|
||||||
### What operating system are you using? (client-side issues only)
|
|
||||||
|
|
||||||
|
|
||||||
### How do you deploy Firefish on your server? (server-side issues only)
|
|
||||||
|
|
||||||
|
|
||||||
### What operating system are you using? (Server-side issues only)
|
|
||||||
|
|
||||||
|
|
||||||
### Relevant log output <!-- Please copy and paste any relevant log output. You can find your log by inspecting the page, and going to the "console" tab. -->
|
|
||||||
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Contribution Guidelines
|
|
||||||
By submitting this issue, you agree to follow our [Contribution Guidelines](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md)
|
|
||||||
- [ ] I agree to follow this project's Contribution Guidelines
|
|
||||||
- [ ] I have searched the issue tracker for similar issues, and this is not a duplicate.
|
|
||||||
|
|
||||||
## Are you willing to fix this bug? (optional)
|
|
||||||
- [ ] Yes. I will fix this bug and open a merge request if the change is agreed upon.
|
|
99
.gitlab/issue_templates/default.md
Normal file
99
.gitlab/issue_templates/default.md
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
<!--
|
||||||
|
This issue template is for bug reports.
|
||||||
|
There are other issue templates for feature requests and refactor proposals,
|
||||||
|
so please use them if this is not a bug report.
|
||||||
|
|
||||||
|
Also, you don't need to prefix the issue title with "Bug:", because it's
|
||||||
|
managed by issue labels.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- 💖 Thanks for taking the time to fill out this bug report!
|
||||||
|
💁 Having trouble with deployment? [Ask the support chat.](https://matrix.to/#/#firefish-community:nitro.chat)
|
||||||
|
🔒 Found a security vulnerability? [Please disclose it responsibly.](https://firefish.dev/firefish/firefish/-/blob/develop/SECURITY.md)
|
||||||
|
🤝 By submitting this issue, you agree to follow our [Contribution Guidelines.](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md) -->
|
||||||
|
|
||||||
|
## What type of issue is this?
|
||||||
|
<!-- If this happens on your device and has to do with the user interface, it's client-side. If this happens on either with the API or the backend, or you got a server-side error in the client, it's server-side. -->
|
||||||
|
|
||||||
|
<!-- Uncomment (remove surrounding arrow signs) the following line(s) to specify the category of this issue. -->
|
||||||
|
<!-- /label Server -->
|
||||||
|
<!-- /label Client -->
|
||||||
|
<!-- /label Mobile -->
|
||||||
|
<!-- /label Third-party-client -->
|
||||||
|
<!-- /label Docs -->
|
||||||
|
<!-- /label "Build from source" -->
|
||||||
|
<!-- /label Container -->
|
||||||
|
<!-- /label "Firefish API" -->
|
||||||
|
<!-- /label "Mastodon API" -->
|
||||||
|
|
||||||
|
## What happened?
|
||||||
|
<!-- Please give us a brief description of what happened. -->
|
||||||
|
|
||||||
|
|
||||||
|
## What did you expect to happen?
|
||||||
|
<!-- Please give us a brief description of what you expected to happen. -->
|
||||||
|
|
||||||
|
|
||||||
|
## Steps to reproduce the issue
|
||||||
|
<!-- Please describe how to reproduce this issue (preferably, in a ordered list) -->
|
||||||
|
|
||||||
|
|
||||||
|
## Reproduces how often
|
||||||
|
<!-- Is it always reproducible, or is it conditional/probabilistic ? -->
|
||||||
|
|
||||||
|
|
||||||
|
## What did you try to solve the issue
|
||||||
|
<!-- Not to repeat the same thing, let us share what you have tried so far. -->
|
||||||
|
|
||||||
|
|
||||||
|
## Version
|
||||||
|
<!-- What version of firefish is your instance running? You can find this by the instance information page. -->
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
|
||||||
|
### Instance
|
||||||
|
<!-- What instance of firefish are you using? -->
|
||||||
|
|
||||||
|
|
||||||
|
### What browser are you using? (client-side issues only)
|
||||||
|
|
||||||
|
|
||||||
|
### What operating system are you using? (client-side issues only)
|
||||||
|
|
||||||
|
|
||||||
|
### How do you deploy Firefish on your server? (server-side issues only)
|
||||||
|
|
||||||
|
|
||||||
|
### What operating system are you using? (Server-side issues only)
|
||||||
|
|
||||||
|
|
||||||
|
### Relevant log output
|
||||||
|
<!-- Please copy and paste any relevant log output. -->
|
||||||
|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## Contribution Guidelines
|
||||||
|
By submitting this issue, you agree to follow our [Contribution Guidelines](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md)
|
||||||
|
- [ ] I agree to follow this project's Contribution Guidelines
|
||||||
|
- [ ] I have searched the issue tracker for similar issues, and this is not a duplicate.
|
||||||
|
|
||||||
|
## Are you willing to fix this bug? (optional)
|
||||||
|
<!-- Please uncomment the following line if you want to fix this bug -->
|
||||||
|
<!-- /assign me -->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Please tell us how to fix this bug.
|
||||||
|
As noted in the contribution guidelines, there is a good chance that your
|
||||||
|
merge request will not be merged if there is no agreement with the project maintainers.
|
||||||
|
However, we are currently so understaffed that it is virtually impossible to
|
||||||
|
respond to every single proposal. So, feel free to implement it if there is no response
|
||||||
|
for more than a week or there is a thumbs-up emoji reaction from the project maintainer(s).
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Do not edit the following line -->
|
||||||
|
/label Bug?
|
|
@ -1,18 +1,45 @@
|
||||||
<!-- 💖 Thanks for taking the time to fill out this bug report!
|
<!--
|
||||||
|
This issue template is for feature requests.
|
||||||
|
There are other issue templates for bug reports and refactor proposals,
|
||||||
|
so please use them if this is not a feature request.
|
||||||
|
|
||||||
|
Also, you don't need to prefix the issue title with "Feature:", because it's
|
||||||
|
managed by issue labels.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- 💖 Thanks for taking the time to fill out this feature request!
|
||||||
💁 Having trouble with deployment? [Ask the support chat.](https://matrix.to/#/#firefish-community:nitro.chat)
|
💁 Having trouble with deployment? [Ask the support chat.](https://matrix.to/#/#firefish-community:nitro.chat)
|
||||||
🔒 Found a security vulnerability? [Please disclose it responsibly.](https://firefish.dev/firefish/firefish/-/blob/develop/SECURITY.md)
|
🔒 Found a security vulnerability? [Please disclose it responsibly.](https://firefish.dev/firefish/firefish/-/blob/develop/SECURITY.md)
|
||||||
🤝 By submitting this feature request, you agree to follow our [Contribution Guidelines.](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md) -->
|
🤝 By submitting this refactor proposal, you agree to follow our [Contribution Guidelines.](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md) -->
|
||||||
|
|
||||||
## What feature would you like implemented? <!-- Please give us a brief description of what you'd like. -->
|
## What type of refactoring is this?
|
||||||
|
<!-- If this happens on your device and has to do with the user interface, it's client-side. If this happens on either with the API or the backend, or you got a server-side error in the client, it's server-side. -->
|
||||||
|
|
||||||
|
<!-- Uncomment (remove surrounding arrow signs) the following line(s) to specify the category of this issue. -->
|
||||||
|
<!-- /label Server -->
|
||||||
|
<!-- /label Client -->
|
||||||
|
<!-- /label Mobile -->
|
||||||
|
<!-- /label Third-party-client -->
|
||||||
|
<!-- /label Docs -->
|
||||||
|
<!-- /label "Build from source" -->
|
||||||
|
<!-- /label Container -->
|
||||||
|
<!-- /label "Firefish API" -->
|
||||||
|
<!-- /label "Mastodon API" -->
|
||||||
|
|
||||||
|
## What feature would you like implemented?
|
||||||
|
<!-- Please give us a brief description of what you'd like to be refactored. -->
|
||||||
|
|
||||||
|
|
||||||
## Why should we add this feature? <!-- Please give us a brief description of why your feature is important. -->
|
## Why should we add this feature?
|
||||||
|
<!-- Please give us a brief description of why your feature is important. -->
|
||||||
|
|
||||||
|
|
||||||
## Version <!-- What version of firefish is your instance running? You can find this by clicking your instance's logo at the bottom left and then clicking instance information. -->
|
## Version
|
||||||
|
<!-- What version of firefish is your instance running? You can find this by clicking your instance's logo at the bottom left and then clicking instance information. -->
|
||||||
|
|
||||||
|
|
||||||
## Instance <!-- What instance of firefish are you using? -->
|
## Instance
|
||||||
|
<!-- What instance of Firefish are you using? -->
|
||||||
|
|
||||||
|
|
||||||
## Contribution Guidelines
|
## Contribution Guidelines
|
||||||
|
@ -21,4 +48,20 @@ By submitting this issue, you agree to follow our [Contribution Guidelines](http
|
||||||
- [ ] I have searched the issue tracker for similar requests, and this is not a duplicate.
|
- [ ] I have searched the issue tracker for similar requests, and this is not a duplicate.
|
||||||
|
|
||||||
## Are you willing to implement this feature? (optional)
|
## Are you willing to implement this feature? (optional)
|
||||||
- [ ] Yes. I will implement this feature and open a merge request if the change is agreed upon.
|
<!-- Please uncomment the following line if you want to implement this feature -->
|
||||||
|
<!-- /assign me -->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Please tell us how to implement this feature.
|
||||||
|
As noted in the contribution guidelines, there is a good chance that your
|
||||||
|
merge request will not be merged if there is no agreement with the project maintainers.
|
||||||
|
However, we are currently so understaffed that it is virtually impossible to
|
||||||
|
respond to every single proposal. So, feel free to implement it if there is no response
|
||||||
|
for more than a week or there is a thumbs-up emoji reaction from the project maintainer(s).
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Do not edit the following line -->
|
||||||
|
/label Feature
|
||||||
|
|
67
.gitlab/issue_templates/refactor.md
Normal file
67
.gitlab/issue_templates/refactor.md
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
<!--
|
||||||
|
This issue template is for refactor proposals.
|
||||||
|
There are other issue templates for bug reports and feature requests,
|
||||||
|
so please use them if this is not a refactor proposal.
|
||||||
|
|
||||||
|
Also, you don't need to prefix the issue title with "Refactor:", because it's
|
||||||
|
managed by issue labels.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- 💖 Thanks for taking the time to fill out this report!
|
||||||
|
💁 Having trouble with deployment? [Ask the support chat.](https://matrix.to/#/#firefish-community:nitro.chat)
|
||||||
|
🔒 Found a security vulnerability? [Please disclose it responsibly.](https://firefish.dev/firefish/firefish/-/blob/develop/SECURITY.md)
|
||||||
|
🤝 By submitting this feature request, you agree to follow our [Contribution Guidelines.](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md) -->
|
||||||
|
|
||||||
|
## What type of feature is this?
|
||||||
|
<!-- If this happens on your device and has to do with the user interface, it's client-side. If this happens on either with the API or the backend, or you got a server-side error in the client, it's server-side. -->
|
||||||
|
|
||||||
|
<!-- Uncomment (remove surrounding arrow signs) the following line(s) to specify the category of this issue. -->
|
||||||
|
<!-- /label Server -->
|
||||||
|
<!-- /label Client -->
|
||||||
|
<!-- /label Mobile -->
|
||||||
|
<!-- /label Third-party-client -->
|
||||||
|
<!-- /label Docs -->
|
||||||
|
<!-- /label "Build from source" -->
|
||||||
|
<!-- /label Container -->
|
||||||
|
<!-- /label "Firefish API" -->
|
||||||
|
<!-- /label "Mastodon API" -->
|
||||||
|
|
||||||
|
## What parts of the code do you think should be refactored?
|
||||||
|
<!-- Please give us a brief description of what you'd like. -->
|
||||||
|
|
||||||
|
|
||||||
|
## Why should the code be refactored that way?
|
||||||
|
<!-- Please give us a brief description of the reason of your proposal. -->
|
||||||
|
|
||||||
|
|
||||||
|
## Version
|
||||||
|
<!-- What version of firefish is your instance running? You can find this by clicking your instance's logo at the bottom left and then clicking instance information. -->
|
||||||
|
|
||||||
|
|
||||||
|
## Instance
|
||||||
|
<!-- What instance of Firefish are you using? -->
|
||||||
|
|
||||||
|
|
||||||
|
## Contribution Guidelines
|
||||||
|
By submitting this issue, you agree to follow our [Contribution Guidelines](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md)
|
||||||
|
- [ ] I agree to follow this project's Contribution Guidelines
|
||||||
|
- [ ] I have searched the issue tracker for similar requests, and this is not a duplicate.
|
||||||
|
|
||||||
|
## Are you willing to refactor the code? (optional)
|
||||||
|
<!-- Please uncomment the following line if you want to implement it -->
|
||||||
|
<!-- /assign me -->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Please tell us how to refactor the code.
|
||||||
|
As noted in the contribution guidelines, there is a good chance that your
|
||||||
|
merge request will not be merged if there is no agreement with the project maintainers.
|
||||||
|
However, we are currently so understaffed that it is virtually impossible to
|
||||||
|
respond to every single proposal. So, feel free to implement it if there is no response
|
||||||
|
for more than a week or there is a thumbs-up emoji reaction from the project maintainer(s).
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Do not edit the following line -->
|
||||||
|
/label Refactor
|
|
@ -1,16 +1,19 @@
|
||||||
<!-- Thanks for taking the time to make Firefish better! It's not required, but please consider using [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) when making your commits. If you use VSCode, please use the [Conventional Commits extension](https://marketplace.visualstudio.com/items?itemName=vivaxy.vscode-conventional-commits). -->
|
<!-- Thanks for taking the time to make Firefish better! -->
|
||||||
|
|
||||||
## What does this PR do? <!-- Please give us a brief description of what this PR does. -->
|
## What does this merge request do?
|
||||||
|
<!-- Please give us a brief description of what this merge request does. -->
|
||||||
|
|
||||||
|
|
||||||
## Contribution Guidelines
|
## Contribution Guidelines
|
||||||
By submitting this merge request, you agree to follow our [Contribution Guidelines](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md)
|
By submitting this merge request, you agree to follow our [Contribution Guidelines](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md)
|
||||||
- [ ] This change is reviewed in an issue / This is a minor bug fix
|
- [ ] This closes issue #0000 (please substitute the number)
|
||||||
- [ ] I agree to follow this project's Contribution Guidelines
|
- [ ] This is a minor bug fix or refactoring
|
||||||
- [ ] I have made sure to test this pull request
|
|
||||||
- [ ] I have made sure to run `pnpm run format` before submitting this pull request
|
|
||||||
|
|
||||||
If this merge request makes changes to the Firefish API, please update `docs/api-change.md`
|
- [ ] I agree to follow this project's Contribution Guidelines
|
||||||
|
- [ ] I have made sure to test this merge request
|
||||||
|
- [ ] I have made sure to run `pnpm run format` before submitting this merge request
|
||||||
|
|
||||||
|
If this merge request makes changes to API, please update `docs/api-change.md`
|
||||||
- [ ] I updated the document / This merge request doesn't include API changes
|
- [ ] I updated the document / This merge request doesn't include API changes
|
||||||
|
|
||||||
<!-- Uncomment if your merge request has multiple authors -->
|
<!-- Uncomment if your merge request has multiple authors -->
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<!-- This template is used only when merging the develop branch into the main branch. Please don't use this for other merge requests. -->
|
<!-- This template is used only when merging the develop branch into the main branch. Please don't use this for other merge requests. -->
|
||||||
|
/label Release
|
||||||
|
|
||||||
## Checklist
|
## Checklist
|
||||||
|
|
||||||
|
@ -13,7 +14,5 @@ I have updated...
|
||||||
- [ ] `packages/backend-rs/index.js`
|
- [ ] `packages/backend-rs/index.js`
|
||||||
- [ ] OCI container image
|
- [ ] OCI container image
|
||||||
|
|
||||||
<!-- TODO: Add automated tests (task runners are currently down) -->
|
|
||||||
|
|
||||||
## Remarks
|
## Remarks
|
||||||
|
|
||||||
|
|
565
Cargo.lock
generated
565
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
37
Cargo.toml
37
Cargo.toml
|
@ -1,51 +1,58 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["packages/backend-rs", "packages/macro-rs"]
|
members = ["packages/backend-rs", "packages/macro-rs/macros", "packages/macro-rs/macros-impl"]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
macro-rs = { path = "packages/macro-rs" }
|
macros = { path = "packages/macro-rs/macros" }
|
||||||
|
macros-impl = { path = "packages/macro-rs/macros-impl" }
|
||||||
|
|
||||||
napi = { git = "https://github.com/napi-rs/napi-rs.git", rev = "ca2cd5c35a0c39ec4a94e93c6c5695b681046df2", default-features = false }
|
napi = "2.16.8"
|
||||||
napi-derive = { version = "2.16.5", default-features = false }
|
napi-derive = "2.16.8"
|
||||||
napi-build = { version = "2.1.3", default-features = false }
|
napi-build = "2.1.3"
|
||||||
|
|
||||||
argon2 = { version = "0.5.3", default-features = false }
|
argon2 = { version = "0.5.3", default-features = false }
|
||||||
async-trait = { version = "0.1.80", default-features = false }
|
async-trait = { version = "0.1.80", default-features = false }
|
||||||
basen = { version = "0.1.0", default-features = false }
|
basen = { version = "0.1.0", default-features = false }
|
||||||
bb8 = { version = "0.8.3", default-features = false }
|
bb8 = { version = "0.8.5", default-features = false }
|
||||||
bcrypt = { version = "0.15.1", default-features = false }
|
bcrypt = { version = "0.15.1", default-features = false }
|
||||||
chrono = { version = "0.4.38", default-features = false }
|
chrono = { version = "0.4.38", default-features = false }
|
||||||
convert_case = { version = "0.6.0", default-features = false }
|
convert_case = { version = "0.6.0", default-features = false }
|
||||||
cuid2 = { version = "0.1.2", default-features = false }
|
cuid2 = { version = "0.1.2", default-features = false }
|
||||||
emojis = { version = "0.6.2", default-features = false }
|
emojis = { version = "0.6.2", default-features = false }
|
||||||
idna = { version = "0.5.0", default-features = false }
|
idna = { version = "1.0.2", default-features = false }
|
||||||
image = { version = "0.25.1", default-features = false }
|
image = { version = "0.25.1", default-features = false }
|
||||||
isahc = { version = "1.7.2", default-features = false }
|
isahc = { version = "1.7.2", default-features = false }
|
||||||
nom-exif = { version = "1.2.0", default-features = false }
|
nom-exif = { version = "1.2.0", default-features = false }
|
||||||
once_cell = { version = "1.19.0", default-features = false }
|
once_cell = { version = "1.19.0", default-features = false }
|
||||||
openssl = { version = "0.10.64", default-features = false }
|
|
||||||
pretty_assertions = { version = "1.4.0", default-features = false }
|
pretty_assertions = { version = "1.4.0", default-features = false }
|
||||||
proc-macro2 = { version = "1.0.84", default-features = false }
|
proc-macro2 = { version = "1.0.86", default-features = false }
|
||||||
quote = { version = "1.0.36", default-features = false }
|
quote = { version = "1.0.36", default-features = false }
|
||||||
rand = { version = "0.8.5", default-features = false }
|
rand = { version = "0.8.5", default-features = false }
|
||||||
redis = { version = "0.25.4", default-features = false }
|
redis = { version = "0.25.4", default-features = false }
|
||||||
regex = { version = "1.10.4", default-features = false }
|
regex = { version = "1.10.5", default-features = false }
|
||||||
rmp-serde = { version = "1.3.0", default-features = false }
|
rmp-serde = { version = "1.3.0", default-features = false }
|
||||||
sea-orm = { version = "0.12.15", default-features = false }
|
sea-orm = { version = "0.12.15", default-features = false }
|
||||||
serde = { version = "1.0.203", default-features = false }
|
serde = { version = "1.0.203", default-features = false }
|
||||||
serde_json = { version = "1.0.117", default-features = false }
|
serde_json = { version = "1.0.120", default-features = false }
|
||||||
serde_yaml = { version = "0.9.34", default-features = false }
|
serde_yaml = { version = "0.9.34", default-features = false }
|
||||||
strum = { version = "0.26.2", default-features = false }
|
syn = { version = "2.0.68", default-features = false }
|
||||||
syn = { version = "2.0.66", default-features = false }
|
|
||||||
sysinfo = { version = "0.30.12", default-features = false }
|
sysinfo = { version = "0.30.12", default-features = false }
|
||||||
thiserror = { version = "1.0.61", default-features = false }
|
thiserror = { version = "1.0.61", default-features = false }
|
||||||
tokio = { version = "1.37.0", default-features = false }
|
tokio = { version = "1.38.0", default-features = false }
|
||||||
tokio-test = { version = "0.4.4", default-features = false }
|
tokio-test = { version = "0.4.4", default-features = false }
|
||||||
tracing = { version = "0.1.40", default-features = false }
|
tracing = { version = "0.1.40", default-features = false }
|
||||||
tracing-subscriber = { version = "0.3.18", default-features = false }
|
tracing-subscriber = { version = "0.3.18", default-features = false }
|
||||||
url = { version = "2.5.0", default-features = false }
|
url = { version = "2.5.2", default-features = false }
|
||||||
urlencoding = { version = "2.1.3", default-features = false }
|
urlencoding = { version = "2.1.3", default-features = false }
|
||||||
web-push = { git = "https://github.com/pimeys/rust-web-push.git", rev = "40febe4085e3cef9cdfd539c315e3e945aba0656", default-features = false }
|
web-push = { git = "https://github.com/pimeys/rust-web-push.git", rev = "40febe4085e3cef9cdfd539c315e3e945aba0656", default-features = false }
|
||||||
|
|
||||||
|
# subdependencies
|
||||||
|
## explicitly list OpenSSL to use the vendored version
|
||||||
|
openssl = "0.10.64"
|
||||||
|
|
||||||
|
## some subdependencies require higher Rust version than 1.74 (our MSRV)
|
||||||
|
## cargo update && cargo update ravif --precise 0.11.5 && cargo update bitstream-io --precise 2.3.0
|
||||||
|
## to pin their versions if needed
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
|
23
Dockerfile
23
Dockerfile
|
@ -2,23 +2,20 @@
|
||||||
FROM docker.io/node:20-alpine as build
|
FROM docker.io/node:20-alpine as build
|
||||||
WORKDIR /firefish
|
WORKDIR /firefish
|
||||||
|
|
||||||
|
# Copy only backend-rs pnpm-related files first, to cache efficiently
|
||||||
|
COPY package.json pnpm-workspace.yaml ./
|
||||||
|
COPY packages/backend-rs/package.json packages/backend-rs/package.json
|
||||||
|
|
||||||
# Install compilation dependencies
|
# Install compilation dependencies
|
||||||
RUN apk update && apk add --no-cache build-base linux-headers curl ca-certificates python3 perl
|
RUN apk update && apk add --no-cache build-base linux-headers curl ca-certificates python3 perl
|
||||||
RUN curl --proto '=https' --tlsv1.2 --silent --show-error --fail https://sh.rustup.rs | sh -s -- -y
|
RUN curl --proto '=https' --tlsv1.2 --silent --show-error --fail https://sh.rustup.rs | sh -s -- -y
|
||||||
ENV PATH="/root/.cargo/bin:${PATH}"
|
ENV PATH="/root/.cargo/bin:${PATH}"
|
||||||
|
|
||||||
# Copy only backend-rs dependency-related files first, to cache efficiently
|
# Copy only backend-rs dependency-related files first, to cache efficiently
|
||||||
COPY package.json pnpm-workspace.yaml ./
|
COPY packages/macro-rs packages/macro-rs/
|
||||||
COPY packages/backend-rs/package.json packages/backend-rs/package.json
|
|
||||||
COPY packages/backend-rs/npm/linux-x64-musl/package.json packages/backend-rs/npm/linux-x64-musl/package.json
|
|
||||||
COPY packages/backend-rs/npm/linux-arm64-musl/package.json packages/backend-rs/npm/linux-arm64-musl/package.json
|
|
||||||
|
|
||||||
COPY Cargo.toml Cargo.toml
|
|
||||||
COPY Cargo.lock Cargo.lock
|
|
||||||
COPY packages/backend-rs/Cargo.toml packages/backend-rs/Cargo.toml
|
|
||||||
COPY packages/backend-rs/src/lib.rs packages/backend-rs/src/
|
COPY packages/backend-rs/src/lib.rs packages/backend-rs/src/
|
||||||
COPY packages/macro-rs/Cargo.toml packages/macro-rs/Cargo.toml
|
COPY packages/backend-rs/Cargo.toml packages/backend-rs/Cargo.toml
|
||||||
COPY packages/macro-rs/src/lib.rs packages/macro-rs/src/
|
COPY Cargo.toml Cargo.lock ./
|
||||||
|
|
||||||
# Configure pnpm, and install backend-rs dependencies
|
# Configure pnpm, and install backend-rs dependencies
|
||||||
RUN corepack enable && corepack prepare pnpm@latest --activate && pnpm --filter backend-rs install
|
RUN corepack enable && corepack prepare pnpm@latest --activate && pnpm --filter backend-rs install
|
||||||
|
@ -26,10 +23,10 @@ RUN cargo fetch --locked --manifest-path Cargo.toml
|
||||||
|
|
||||||
# Copy in the rest of the rust files
|
# Copy in the rest of the rust files
|
||||||
COPY packages/backend-rs packages/backend-rs/
|
COPY packages/backend-rs packages/backend-rs/
|
||||||
# COPY packages/macro-rs packages/macro-rs/
|
|
||||||
|
|
||||||
# Compile backend-rs
|
# Compile backend-rs
|
||||||
RUN NODE_ENV='production' pnpm run --filter backend-rs build
|
RUN ln -s $(which gcc) /usr/bin/aarch64-linux-musl-gcc
|
||||||
|
RUN NODE_ENV='production' NODE_OPTIONS='--max_old_space_size=3072' pnpm run --filter backend-rs build
|
||||||
|
|
||||||
# Copy/Overwrite index.js to mitigate the bug in napi-rs codegen
|
# Copy/Overwrite index.js to mitigate the bug in napi-rs codegen
|
||||||
COPY packages/backend-rs/index.js packages/backend-rs/built/index.js
|
COPY packages/backend-rs/index.js packages/backend-rs/built/index.js
|
||||||
|
@ -49,7 +46,7 @@ RUN pnpm install --frozen-lockfile
|
||||||
COPY . ./
|
COPY . ./
|
||||||
|
|
||||||
# Build other workspaces
|
# Build other workspaces
|
||||||
RUN NODE_ENV='production' pnpm run --recursive --filter '!backend-rs' build && pnpm run build:assets
|
RUN NODE_ENV='production' NODE_OPTIONS='--max_old_space_size=3072' pnpm run --recursive --filter '!backend-rs' build && pnpm run build:assets
|
||||||
|
|
||||||
# Trim down the dependencies to only those for production
|
# Trim down the dependencies to only those for production
|
||||||
RUN find . -path '*/node_modules/*' -delete && pnpm install --prod --frozen-lockfile
|
RUN find . -path '*/node_modules/*' -delete && pnpm install --prod --frozen-lockfile
|
||||||
|
|
1
Procfile
1
Procfile
|
@ -1 +0,0 @@
|
||||||
web: NODE_ENV=production npm start
|
|
398
biome.json
398
biome.json
|
@ -1,28 +1,400 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://biomejs.dev/schemas/1.6.4/schema.json",
|
"$schema": "https://biomejs.dev/schemas/1.8.2/schema.json",
|
||||||
"organizeImports": {
|
"organizeImports": { "enabled": false },
|
||||||
"enabled": false
|
|
||||||
},
|
|
||||||
"linter": {
|
"linter": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"rules": {
|
"rules": {
|
||||||
"recommended": true,
|
"recommended": false,
|
||||||
|
"complexity": {
|
||||||
|
"noBannedTypes": "error",
|
||||||
|
"noExtraBooleanCast": "error",
|
||||||
|
"noMultipleSpacesInRegularExpressionLiterals": "error",
|
||||||
|
"noUselessCatch": "error",
|
||||||
|
"noUselessConstructor": "off",
|
||||||
|
"noUselessLoneBlockStatements": "error",
|
||||||
|
"noUselessRename": "error",
|
||||||
|
"noUselessTernary": "error",
|
||||||
|
"noUselessThisAlias": "error",
|
||||||
|
"noUselessTypeConstraint": "error",
|
||||||
|
"noVoid": "error",
|
||||||
|
"noWith": "error",
|
||||||
|
"useLiteralKeys": "error",
|
||||||
|
"useRegexLiterals": "error"
|
||||||
|
},
|
||||||
|
"correctness": {
|
||||||
|
"noConstAssign": "error",
|
||||||
|
"noConstantCondition": "error",
|
||||||
|
"noEmptyCharacterClassInRegex": "error",
|
||||||
|
"noEmptyPattern": "error",
|
||||||
|
"noGlobalObjectCalls": "error",
|
||||||
|
"noInvalidConstructorSuper": "error",
|
||||||
|
"noInvalidUseBeforeDeclaration": "error",
|
||||||
|
"noNewSymbol": "error",
|
||||||
|
"noPrecisionLoss": "error",
|
||||||
|
"noSelfAssign": "error",
|
||||||
|
"noSwitchDeclarations": "error",
|
||||||
|
"noUndeclaredVariables": "error",
|
||||||
|
"noUnreachable": "error",
|
||||||
|
"noUnreachableSuper": "error",
|
||||||
|
"noUnsafeFinally": "error",
|
||||||
|
"noUnusedVariables": "off",
|
||||||
|
"useArrayLiterals": "off",
|
||||||
|
"useIsNan": "error"
|
||||||
|
},
|
||||||
|
"security": { "noGlobalEval": "error" },
|
||||||
"style": {
|
"style": {
|
||||||
"noUselessElse": "off"
|
"noCommaOperator": "error",
|
||||||
|
"noInferrableTypes": "error",
|
||||||
|
"noNamespace": "error",
|
||||||
|
"noNonNullAssertion": "warn",
|
||||||
|
"noUselessElse": "off",
|
||||||
|
"noVar": "error",
|
||||||
|
"useAsConstAssertion": "error",
|
||||||
|
"useBlockStatements": "off",
|
||||||
|
"useConst": "error",
|
||||||
|
"useImportType": "error",
|
||||||
|
"useSingleVarDeclarator": "warn"
|
||||||
|
},
|
||||||
|
"suspicious": {
|
||||||
|
"noAssignInExpressions": "error",
|
||||||
|
"noAsyncPromiseExecutor": "error",
|
||||||
|
"noCatchAssign": "error",
|
||||||
|
"noClassAssign": "error",
|
||||||
|
"noCompareNegZero": "error",
|
||||||
|
"noConfusingLabels": "off",
|
||||||
|
"noConsoleLog": "warn",
|
||||||
|
"noControlCharactersInRegex": "error",
|
||||||
|
"noDebugger": "warn",
|
||||||
|
"noDoubleEquals": "error",
|
||||||
|
"noDuplicateCase": "error",
|
||||||
|
"noDuplicateClassMembers": "error",
|
||||||
|
"noDuplicateObjectKeys": "error",
|
||||||
|
"noDuplicateParameters": "error",
|
||||||
|
"noEmptyBlockStatements": "error",
|
||||||
|
"noEmptyInterface": "error",
|
||||||
|
"noExplicitAny": "warn",
|
||||||
|
"noExtraNonNullAssertion": "error",
|
||||||
|
"noFallthroughSwitchClause": "error",
|
||||||
|
"noFunctionAssign": "error",
|
||||||
|
"noGlobalAssign": "error",
|
||||||
|
"noImportAssign": "error",
|
||||||
|
"noMisleadingCharacterClass": "error",
|
||||||
|
"noMisleadingInstantiator": "error",
|
||||||
|
"noPrototypeBuiltins": "off",
|
||||||
|
"noRedeclare": "error",
|
||||||
|
"noSelfCompare": "error",
|
||||||
|
"noShadowRestrictedNames": "error",
|
||||||
|
"noUnsafeNegation": "error",
|
||||||
|
"useAwait": "off",
|
||||||
|
"useDefaultSwitchClauseLast": "error",
|
||||||
|
"useNamespaceKeyword": "error",
|
||||||
|
"useValidTypeof": "error"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"ignore": [
|
||||||
|
"**/*.json5",
|
||||||
|
"**/*.min.*",
|
||||||
|
"**/dist",
|
||||||
|
"**/LICENSE*",
|
||||||
|
"**/output",
|
||||||
|
"**/coverage",
|
||||||
|
"**/public",
|
||||||
|
"**/temp",
|
||||||
|
"**/packages-lock.json",
|
||||||
|
"**/pnpm-lock.yaml",
|
||||||
|
"**/yarn.lock",
|
||||||
|
"**/__snapshots__"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"javascript": {
|
||||||
|
"globals": [
|
||||||
|
"jest",
|
||||||
|
"withDefaults",
|
||||||
|
"$computed",
|
||||||
|
"$shallowRef",
|
||||||
|
"defineExpose",
|
||||||
|
"$toRef",
|
||||||
|
"h",
|
||||||
|
"$customRef",
|
||||||
|
"navigator",
|
||||||
|
"window",
|
||||||
|
"defineEmits",
|
||||||
|
"$ref",
|
||||||
|
"defineProps",
|
||||||
|
"document"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
"include": ["*.vue", "packages/client/*.ts"],
|
"include": ["**/__tests__/*.{j,t}s?(x)", "**/*.spec.{j,t}s?(x)"],
|
||||||
|
"linter": { "rules": { "suspicious": { "noConsoleLog": "off" } } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": ["*.vue"],
|
||||||
|
"linter": { "rules": { "correctness": { "noUnusedVariables": "off" } } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": ["**/__tests__/*.{j,t}s?(x)", "**/*.spec.{j,t}s?(x)"],
|
||||||
|
"linter": { "rules": { "suspicious": { "noConsoleLog": "off" } } }
|
||||||
|
},
|
||||||
|
{ "include": ["*.vue"], "linter": { "rules": {} } },
|
||||||
|
{ "include": ["*.json", "*.json5"], "linter": { "rules": {} } },
|
||||||
|
{ "include": ["*.yaml", "*.yml"], "linter": { "rules": {} } },
|
||||||
|
{ "include": ["package.json"], "linter": { "rules": {} } },
|
||||||
|
{ "include": ["*.d.ts"], "linter": { "rules": {} } },
|
||||||
|
{ "include": ["*.js"] },
|
||||||
|
{
|
||||||
|
"include": ["scripts/**/*.*", "cli.*"],
|
||||||
|
"linter": { "rules": { "suspicious": { "noConsoleLog": "off" } } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": ["*.test.ts", "*.test.js", "*.spec.ts", "*.spec.js"],
|
||||||
|
"linter": { "rules": {} }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": ["**/*.md/*.*"],
|
||||||
"linter": {
|
"linter": {
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"correctness": {
|
||||||
|
"noUndeclaredVariables": "off",
|
||||||
|
"noUnusedVariables": "off"
|
||||||
|
},
|
||||||
|
"suspicious": { "noConsoleLog": "off" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ "include": ["*.js"], "linter": { "rules": {} } },
|
||||||
|
{
|
||||||
|
"include": ["**/*.md/*.*"],
|
||||||
|
"linter": {
|
||||||
|
"rules": {
|
||||||
|
"correctness": {
|
||||||
|
"noInvalidUseBeforeDeclaration": "off",
|
||||||
|
"noUnusedVariables": "off"
|
||||||
|
},
|
||||||
|
"suspicious": { "noRedeclare": "off" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ "include": ["*.json", "*.json5"], "linter": { "rules": {} } },
|
||||||
|
{ "include": ["*.yaml", "*.yml"], "linter": { "rules": {} } },
|
||||||
|
{ "include": ["package.json"], "linter": { "rules": {} } },
|
||||||
|
{ "include": ["*.d.ts"], "linter": { "rules": {} } },
|
||||||
|
{ "include": ["*.js"] },
|
||||||
|
{
|
||||||
|
"include": ["scripts/**/*.*", "cli.*"],
|
||||||
|
"linter": { "rules": { "suspicious": { "noConsoleLog": "off" } } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": ["*.test.ts", "*.test.js", "*.spec.ts", "*.spec.js"],
|
||||||
|
"linter": { "rules": {} }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": ["**/*.md/*.*"],
|
||||||
|
"linter": {
|
||||||
|
"rules": {
|
||||||
|
"correctness": {
|
||||||
|
"noUndeclaredVariables": "off",
|
||||||
|
"noUnusedVariables": "off"
|
||||||
|
},
|
||||||
|
"suspicious": { "noConsoleLog": "off" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ "include": ["*.js"], "linter": { "rules": {} } },
|
||||||
|
{
|
||||||
|
"include": ["**/*.md/*.*"],
|
||||||
|
"linter": {
|
||||||
|
"rules": {
|
||||||
|
"correctness": {
|
||||||
|
"noInvalidUseBeforeDeclaration": "off",
|
||||||
|
"noUnusedVariables": "off"
|
||||||
|
},
|
||||||
|
"suspicious": { "noRedeclare": "off" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": ["*.ts", "*.tsx", "*.mts", "*.cts"],
|
||||||
|
"linter": {
|
||||||
|
"rules": {
|
||||||
|
"correctness": {
|
||||||
|
"noConstAssign": "off",
|
||||||
|
"noGlobalObjectCalls": "off",
|
||||||
|
"noInvalidConstructorSuper": "off",
|
||||||
|
"noNewSymbol": "off",
|
||||||
|
"noSetterReturn": "off",
|
||||||
|
"noUndeclaredVariables": "off",
|
||||||
|
"noUnreachable": "off",
|
||||||
|
"noUnreachableSuper": "off"
|
||||||
|
},
|
||||||
"style": {
|
"style": {
|
||||||
"useImportType": "warn",
|
"noArguments": "error",
|
||||||
"useShorthandFunctionType": "warn",
|
"noVar": "error",
|
||||||
"useTemplate": "warn",
|
"useConst": "error"
|
||||||
"noNonNullAssertion": "off",
|
},
|
||||||
"useNodejsImportProtocol": "off"
|
"suspicious": {
|
||||||
|
"noDuplicateClassMembers": "off",
|
||||||
|
"noDuplicateObjectKeys": "off",
|
||||||
|
"noDuplicateParameters": "off",
|
||||||
|
"noFunctionAssign": "off",
|
||||||
|
"noImportAssign": "off",
|
||||||
|
"noRedeclare": "off",
|
||||||
|
"noUnsafeNegation": "off",
|
||||||
|
"useGetterReturn": "off",
|
||||||
|
"useValidTypeof": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ "include": ["*.json", "*.json5"], "linter": { "rules": {} } },
|
||||||
|
{ "include": ["*.yaml", "*.yml"], "linter": { "rules": {} } },
|
||||||
|
{ "include": ["package.json"], "linter": { "rules": {} } },
|
||||||
|
{ "include": ["*.d.ts"], "linter": { "rules": {} } },
|
||||||
|
{ "include": ["*.js"], "linter": { "rules": {} } },
|
||||||
|
{
|
||||||
|
"include": ["*.ts", "*.tsx", "*.mts", "*.cts"],
|
||||||
|
"linter": { "rules": { "complexity": { "noVoid": "error" } } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": ["script/**/*.*", "scripts/**/*.*", "cli.*"],
|
||||||
|
"linter": { "rules": { "suspicious": { "noConsoleLog": "off" } } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": ["*.test.ts", "*.test.js", "*.spec.ts", "*.spec.js"],
|
||||||
|
"linter": { "rules": {} }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": ["**/*.md/*.*"],
|
||||||
|
"linter": {
|
||||||
|
"rules": {
|
||||||
|
"correctness": {
|
||||||
|
"noInvalidUseBeforeDeclaration": "off",
|
||||||
|
"noUndeclaredVariables": "off",
|
||||||
|
"noUnusedVariables": "off"
|
||||||
|
},
|
||||||
|
"style": { "useImportType": "off" },
|
||||||
|
"suspicious": { "noConsoleLog": "off", "noRedeclare": "off" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ "include": ["*.json", "*.json5"], "linter": { "rules": {} } },
|
||||||
|
{ "include": ["*.yaml", "*.yml"], "linter": { "rules": {} } },
|
||||||
|
{ "include": ["package.json"], "linter": { "rules": {} } },
|
||||||
|
{ "include": ["*.d.ts"], "linter": { "rules": {} } },
|
||||||
|
{ "include": ["*.js"], "linter": { "rules": {} } },
|
||||||
|
{
|
||||||
|
"include": ["*.ts", "*.tsx", "*.mts", "*.cts"],
|
||||||
|
"linter": { "rules": { "complexity": { "noVoid": "error" } } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": ["script/**/*.*", "scripts/**/*.*", "cli.*"],
|
||||||
|
"linter": { "rules": { "suspicious": { "noConsoleLog": "off" } } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": ["*.test.ts", "*.test.js", "*.spec.ts", "*.spec.js"],
|
||||||
|
"linter": { "rules": {} }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": ["**/*.md/*.*"],
|
||||||
|
"linter": {
|
||||||
|
"rules": {
|
||||||
|
"correctness": {
|
||||||
|
"noInvalidUseBeforeDeclaration": "off",
|
||||||
|
"noUndeclaredVariables": "off",
|
||||||
|
"noUnusedVariables": "off"
|
||||||
|
},
|
||||||
|
"style": { "useImportType": "off" },
|
||||||
|
"suspicious": { "noConsoleLog": "off", "noRedeclare": "off" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ "include": ["*.md"] },
|
||||||
|
{
|
||||||
|
"include": ["**/*.md/**"],
|
||||||
|
"linter": {
|
||||||
|
"rules": {
|
||||||
|
"correctness": {
|
||||||
|
"noUndeclaredVariables": "off",
|
||||||
|
"noUnusedVariables": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": ["*.ts", "*.tsx", "*.mts", "*.cts"],
|
||||||
|
"linter": {
|
||||||
|
"rules": {
|
||||||
|
"correctness": {
|
||||||
|
"noConstAssign": "off",
|
||||||
|
"noGlobalObjectCalls": "off",
|
||||||
|
"noInvalidConstructorSuper": "off",
|
||||||
|
"noNewSymbol": "off",
|
||||||
|
"noSetterReturn": "off",
|
||||||
|
"noUndeclaredVariables": "off",
|
||||||
|
"noUnreachable": "off",
|
||||||
|
"noUnreachableSuper": "off"
|
||||||
|
},
|
||||||
|
"style": {
|
||||||
|
"noArguments": "error",
|
||||||
|
"noVar": "error",
|
||||||
|
"useConst": "error"
|
||||||
|
},
|
||||||
|
"suspicious": {
|
||||||
|
"noDuplicateClassMembers": "off",
|
||||||
|
"noDuplicateObjectKeys": "off",
|
||||||
|
"noDuplicateParameters": "off",
|
||||||
|
"noFunctionAssign": "off",
|
||||||
|
"noImportAssign": "off",
|
||||||
|
"noRedeclare": "off",
|
||||||
|
"noUnsafeNegation": "off",
|
||||||
|
"useGetterReturn": "off",
|
||||||
|
"useValidTypeof": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ "include": ["*.md"] },
|
||||||
|
{
|
||||||
|
"include": ["**/*.md/**"],
|
||||||
|
"linter": {
|
||||||
|
"rules": {
|
||||||
|
"correctness": {
|
||||||
|
"noUndeclaredVariables": "off",
|
||||||
|
"noUnusedVariables": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": ["*.ts", "*.tsx", "*.mts", "*.cts"],
|
||||||
|
"linter": {
|
||||||
|
"rules": {
|
||||||
|
"correctness": {
|
||||||
|
"noConstAssign": "off",
|
||||||
|
"noGlobalObjectCalls": "off",
|
||||||
|
"noInvalidConstructorSuper": "off",
|
||||||
|
"noNewSymbol": "off",
|
||||||
|
"noSetterReturn": "off",
|
||||||
|
"noUndeclaredVariables": "off",
|
||||||
|
"noUnreachable": "off",
|
||||||
|
"noUnreachableSuper": "off"
|
||||||
|
},
|
||||||
|
"style": {
|
||||||
|
"noArguments": "error",
|
||||||
|
"noVar": "error",
|
||||||
|
"useConst": "error"
|
||||||
|
},
|
||||||
|
"suspicious": {
|
||||||
|
"noDuplicateClassMembers": "off",
|
||||||
|
"noDuplicateObjectKeys": "off",
|
||||||
|
"noDuplicateParameters": "off",
|
||||||
|
"noFunctionAssign": "off",
|
||||||
|
"noImportAssign": "off",
|
||||||
|
"noRedeclare": "off",
|
||||||
|
"noUnsafeNegation": "off",
|
||||||
|
"useGetterReturn": "off",
|
||||||
|
"useValidTypeof": "off"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
98
cliff.toml
98
cliff.toml
|
@ -1,98 +0,0 @@
|
||||||
# configuration file for git-cliff (0.1.0)
|
|
||||||
|
|
||||||
[changelog]
|
|
||||||
# changelog header
|
|
||||||
header = """
|
|
||||||
# Changelog\n
|
|
||||||
"""
|
|
||||||
# template for the changelog body
|
|
||||||
# https://tera.netlify.app/docs/#introduction
|
|
||||||
body = """
|
|
||||||
{% if version %}\
|
|
||||||
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
|
||||||
{% else %}\
|
|
||||||
## [unreleased]
|
|
||||||
{% endif %}\
|
|
||||||
{% for group, commits in commits | group_by(attribute="group") %}
|
|
||||||
### {{ group | upper_first }}
|
|
||||||
{% for commit in commits %}
|
|
||||||
- {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}\
|
|
||||||
{% endfor %}
|
|
||||||
{% endfor %}\n
|
|
||||||
"""
|
|
||||||
# remove the leading and trailing whitespace from the template
|
|
||||||
trim = true
|
|
||||||
# changelog footer
|
|
||||||
footer = """
|
|
||||||
<!-- generated by git-cliff -->
|
|
||||||
"""
|
|
||||||
|
|
||||||
[git]
|
|
||||||
# parse the commits based on https://www.conventionalcommits.org
|
|
||||||
conventional_commits = false
|
|
||||||
# filter out the commits that are not conventional
|
|
||||||
filter_unconventional = true
|
|
||||||
# process each line of a commit as an individual commit
|
|
||||||
split_commits = false
|
|
||||||
# regex for parsing and grouping commits
|
|
||||||
commit_parsers = [
|
|
||||||
{ message = "^feat", group = "Features"},
|
|
||||||
{ message = "^add", group = "Features"},
|
|
||||||
{ message = "^fix", group = "Bug Fixes"},
|
|
||||||
{ message = "^prevent", group = "Bug Fixes"},
|
|
||||||
{ message = "^doc", group = "Documentation"},
|
|
||||||
{ message = "^perf", group = "Performance"},
|
|
||||||
{ message = "^🎨", group = "Refactor"},
|
|
||||||
{ message = "^enhance", group = "Refactor"},
|
|
||||||
{ message = "^⚡️", group = "Refactor"},
|
|
||||||
{ message = "^🔥", group = "Features"},
|
|
||||||
{ message = "^🐛", group = "Bug Fixes"},
|
|
||||||
{ message = "^🚑️", group = "Bug Fixes"},
|
|
||||||
{ message = "^block", group = "Bug Fixes"},
|
|
||||||
{ message = "^✨", group = "Features"},
|
|
||||||
{ message = "^📝", group = "Documentation"},
|
|
||||||
{ message = "^🚀", group = "Features"},
|
|
||||||
{ message = "^💄", group = "Styling"},
|
|
||||||
{ message = "^✅", group = "Testing"},
|
|
||||||
{ message = "^🔒️", group = "Security"},
|
|
||||||
{ message = "^🚨", group = "Testing"},
|
|
||||||
{ message = "^💚", group = "CI"},
|
|
||||||
{ message = "^👷", group = "CI"},
|
|
||||||
{ message = "^⬇️", group = "Miscellaneous Tasks"},
|
|
||||||
{ message = "^⬆️", group = "Miscellaneous Tasks"},
|
|
||||||
{ message = "^📌", group = "Miscellaneous Tasks"},
|
|
||||||
{ message = "^➕", group = "Miscellaneous Tasks"},
|
|
||||||
{ message = "^➖", group = "Miscellaneous Tasks"},
|
|
||||||
{ message = "^♻️", group = "Refactor"},
|
|
||||||
{ message = "^🔧", group = "CI"},
|
|
||||||
{ message = "^🔨", group = "CI"},
|
|
||||||
{ message = "^🌐", group = "Localization"},
|
|
||||||
{ message = "^✏️", group = "Localization"},
|
|
||||||
{ message = "^👽️", group = "Bug Fixes"},
|
|
||||||
{ message = "^🍱", group = "Styling"},
|
|
||||||
{ message = "^♿️", group = "Styling"},
|
|
||||||
{ message = "^🩹", group = "Bug Fixes"},
|
|
||||||
{ message = "^refactor", group = "Refactor"},
|
|
||||||
{ message = "^style", group = "Styling"},
|
|
||||||
{ message = "^test", group = "Testing"},
|
|
||||||
{ message = "^chore\\(release\\): prepare for", skip = true},
|
|
||||||
{ message = "^chore", group = "Miscellaneous Tasks"},
|
|
||||||
{ message = "^update", group = "Miscellaneous Tasks"},
|
|
||||||
{ body = ".*security", group = "Security"},
|
|
||||||
]
|
|
||||||
# protect breaking changes from being skipped due to matching a skipping commit_parser
|
|
||||||
protect_breaking_commits = false
|
|
||||||
# filter out the commits that are not matched by commit parsers
|
|
||||||
filter_commits = false
|
|
||||||
# glob pattern for matching git tags
|
|
||||||
tag_pattern = "v[0-9]*"
|
|
||||||
# regex for skipping tags
|
|
||||||
skip_tags = "v0.1.0-beta.1"
|
|
||||||
# regex for ignoring tags
|
|
||||||
ignore_tags = ""
|
|
||||||
# sort the tags chronologically
|
|
||||||
date_order = false
|
|
||||||
# sort the commits inside sections by oldest/newest order
|
|
||||||
sort_commits = "oldest"
|
|
||||||
# limit the number of commits included in the changelog.
|
|
||||||
# limit_commits = 42
|
|
|
@ -27,10 +27,10 @@ services:
|
||||||
interval: 5s
|
interval: 5s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
deploy:
|
# deploy:
|
||||||
resources:
|
# resources:
|
||||||
limits:
|
# limits:
|
||||||
memory: 4096M
|
# memory: 2048M
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
@ -45,11 +45,10 @@ services:
|
||||||
interval: 5s
|
interval: 5s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
# deploy:
|
# deploy:
|
||||||
# resources:
|
# resources:
|
||||||
# limits:
|
# limits:
|
||||||
# memory: 200M
|
# memory: 256M
|
||||||
|
|
||||||
|
|
||||||
db:
|
db:
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
@ -66,10 +65,10 @@ services:
|
||||||
interval: 5s
|
interval: 5s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
# deploy:
|
# deploy:
|
||||||
# resources:
|
# resources:
|
||||||
# limits:
|
# limits:
|
||||||
# memory: 200M
|
# memory: 2048M
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
calcnet:
|
calcnet:
|
||||||
|
|
|
@ -2,6 +2,10 @@
|
||||||
|
|
||||||
Breaking changes are indicated by the :warning: icon.
|
Breaking changes are indicated by the :warning: icon.
|
||||||
|
|
||||||
|
## v20240607
|
||||||
|
|
||||||
|
- `GET` request is now allowed for the `latest-version` endpoint.
|
||||||
|
|
||||||
## v20240523
|
## v20240523
|
||||||
|
|
||||||
- Added `scheduledAt` optional parameter to `notes/create` (!10789)
|
- Added `scheduledAt` optional parameter to `notes/create` (!10789)
|
||||||
|
|
|
@ -2,9 +2,37 @@
|
||||||
|
|
||||||
Critical security updates are indicated by the :warning: icon.
|
Critical security updates are indicated by the :warning: icon.
|
||||||
|
|
||||||
- Server administrators should check [notice-for-admins.md](./notice-for-admins.md) as well.
|
- Server administrators must check [notice-for-admins.md](./notice-for-admins.md) as well.
|
||||||
- Third-party client/bot developers may want to check [api-change.md](./api-change.md) as well.
|
- Third-party client/bot developers may want to check [api-change.md](./api-change.md) as well.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
- Fix bugs
|
||||||
|
|
||||||
|
## [v20240630](https://firefish.dev/firefish/firefish/-/merge_requests/11072/commits)
|
||||||
|
|
||||||
|
- Add ability to automatically append #Alt4Me hashtag when posting a file without an alt text ([What is #Alt4Me?](https://social.growyourown.services/@FediTips/112055775451305236))
|
||||||
|
- Fix a build issue on some environments
|
||||||
|
- Fix bugs
|
||||||
|
|
||||||
|
## [v20240623](https://firefish.dev/firefish/firefish/-/merge_requests/11049/commits)
|
||||||
|
|
||||||
|
- Fix bugs
|
||||||
|
|
||||||
|
## [v20240613](https://firefish.dev/firefish/firefish/-/merge_requests/11003/commits)
|
||||||
|
|
||||||
|
This update contains code refactoring and dependency updates, with no major user-facing changes.
|
||||||
|
|
||||||
|
## [v20240607](https://firefish.dev/firefish/firefish/-/merge_requests/10978/commits)
|
||||||
|
|
||||||
|
- Add the ability to share posts via QR code
|
||||||
|
- Update the API document page (`/api-doc`)
|
||||||
|
- Fix bugs
|
||||||
|
|
||||||
|
## [v20240601](https://firefish.dev/firefish/firefish/-/merge_requests/10943/commits)
|
||||||
|
|
||||||
|
- Fix bugs
|
||||||
|
|
||||||
## [v20240523](https://firefish.dev/firefish/firefish/-/merge_requests/10898/commits)
|
## [v20240523](https://firefish.dev/firefish/firefish/-/merge_requests/10898/commits)
|
||||||
|
|
||||||
- Add scheduled posts
|
- Add scheduled posts
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
BEGIN;
|
BEGIN;
|
||||||
|
|
||||||
DELETE FROM "migrations" WHERE name IN (
|
DELETE FROM "migrations" WHERE name IN (
|
||||||
|
'RefactorScheduledPosts1716804636187',
|
||||||
'RemoveEnumTypenameSuffix1716462794927',
|
'RemoveEnumTypenameSuffix1716462794927',
|
||||||
'CreateScheduledNote1714728200194',
|
'CreateScheduledNote1714728200194',
|
||||||
'AddBackTimezone1715351290096',
|
'AddBackTimezone1715351290096',
|
||||||
|
@ -33,6 +34,38 @@ DELETE FROM "migrations" WHERE name IN (
|
||||||
'RemoveNativeUtilsMigration1705877093218'
|
'RemoveNativeUtilsMigration1705877093218'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- refactor-scheduled-post
|
||||||
|
CREATE TABLE "scheduled_note" (
|
||||||
|
"id" character varying(32) NOT NULL PRIMARY KEY,
|
||||||
|
"noteId" character varying(32) NOT NULL,
|
||||||
|
"userId" character varying(32) NOT NULL,
|
||||||
|
"scheduledAt" TIMESTAMP WITH TIME ZONE NOT NULL
|
||||||
|
);
|
||||||
|
COMMENT ON COLUMN "scheduled_note"."noteId" IS 'The ID of the temporarily created note that corresponds to the schedule.';
|
||||||
|
CREATE EXTENSION pgcrypto;
|
||||||
|
CREATE FUNCTION generate_scheduled_note_id(size int) RETURNS text AS $$ DECLARE
|
||||||
|
characters text := 'abcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
|
bytes bytea := gen_random_bytes(size);
|
||||||
|
l int := length(characters);
|
||||||
|
i int := 0;
|
||||||
|
output text := '';
|
||||||
|
BEGIN
|
||||||
|
WHILE i < size LOOP
|
||||||
|
output := output || substr(characters, get_byte(bytes, i) % l + 1, 1);
|
||||||
|
i := i + 1;
|
||||||
|
END LOOP;
|
||||||
|
RETURN output;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql VOLATILE;
|
||||||
|
INSERT INTO "scheduled_note" ("id", "noteId", "userId", "scheduledAt") (SELECT generate_scheduled_note_id(16), "id", "userId", "scheduledAt" FROM "note" WHERE "note"."scheduledAt" IS NOT NULL);
|
||||||
|
DROP EXTENSION pgcrypto;
|
||||||
|
DROP FUNCTION "generate_scheduled_note_id";
|
||||||
|
CREATE INDEX "IDX_noteId_ScheduledNote" ON "scheduled_note" ("noteId");
|
||||||
|
CREATE INDEX "IDX_userId_ScheduledNote" ON "scheduled_note" ("userId");
|
||||||
|
ALTER TABLE "scheduled_note" ADD FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION;
|
||||||
|
ALTER TABLE "scheduled_note" ADD FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION;
|
||||||
|
ALTER TABLE "note" DROP COLUMN "scheduledAt";
|
||||||
|
|
||||||
-- remove-enum-typename-suffix
|
-- remove-enum-typename-suffix
|
||||||
ALTER TYPE "antenna_src" RENAME TO "antenna_src_enum";
|
ALTER TYPE "antenna_src" RENAME TO "antenna_src_enum";
|
||||||
ALTER TYPE "drive_file_usage_hint" RENAME TO "drive_file_usage_hint_enum";
|
ALTER TYPE "drive_file_usage_hint" RENAME TO "drive_file_usage_hint_enum";
|
||||||
|
|
150
docs/install.md
150
docs/install.md
|
@ -4,9 +4,9 @@ Firefish depends on the following software.
|
||||||
|
|
||||||
## Runtime dependencies
|
## Runtime dependencies
|
||||||
|
|
||||||
- At least [NodeJS](https://nodejs.org/en/) v18.19.0 (v20/v21 recommended)
|
- At least [NodeJS](https://nodejs.org/en/) v18.19.0 (v20/v22 recommended)
|
||||||
- At least [PostgreSQL](https://www.postgresql.org/) v12 (v16 recommended) with [PGroonga](https://pgroonga.github.io/) extension
|
- At least [PostgreSQL](https://www.postgresql.org/) v12 (v16 recommended) with [PGroonga](https://pgroonga.github.io/) extension
|
||||||
- At least [Redis](https://redis.io/) v7
|
- At least [Redis](https://redis.io/) v7 or [Valkey](https://valkey.io/) v7
|
||||||
- Web Proxy (one of the following)
|
- Web Proxy (one of the following)
|
||||||
- Caddy (recommended)
|
- Caddy (recommended)
|
||||||
- Nginx (recommended)
|
- Nginx (recommended)
|
||||||
|
@ -15,27 +15,78 @@ Firefish depends on the following software.
|
||||||
- Caching server (**optional**, one of the following)
|
- Caching server (**optional**, one of the following)
|
||||||
- [DragonflyDB](https://www.dragonflydb.io/)
|
- [DragonflyDB](https://www.dragonflydb.io/)
|
||||||
- [KeyDB](https://keydb.dev/)
|
- [KeyDB](https://keydb.dev/)
|
||||||
- Another [Redis](https://redis.io/) server
|
- Another [Redis](https://redis.io/) / [Valkey](https://valkey.io/) server
|
||||||
|
|
||||||
## Build dependencies
|
## Build dependencies
|
||||||
|
|
||||||
- At least [Rust](https://www.rust-lang.org/) v1.74
|
- At least [Rust](https://www.rust-lang.org/) v1.74
|
||||||
- C/C++ compiler & build tools
|
- C/C++ compiler & build tools (like [GNU Make](https://www.gnu.org/software/make/))
|
||||||
- `build-essential` on Debian/Ubuntu Linux
|
- `build-essential` on Debian/Ubuntu Linux
|
||||||
- `base-devel` on Arch Linux
|
- `base-devel` on Arch Linux
|
||||||
|
- `"Development Tools"` on Fedora/Red Hat Linux
|
||||||
- [Python 3](https://www.python.org/)
|
- [Python 3](https://www.python.org/)
|
||||||
- [Perl](https://www.perl.org/)
|
- [Perl](https://www.perl.org/)
|
||||||
|
|
||||||
This document shows an example procedure for installing these dependencies and Firefish on Debian 12. Note that there is much room for customizing the server setup; this document merely demonstrates a simple installation.
|
This document shows an example procedure for installing these dependencies and Firefish on Debian 12. Note that there is much room for customizing the server setup; this document merely demonstrates a simple installation.
|
||||||
|
|
||||||
|
### Install on non-Linux systems
|
||||||
|
|
||||||
|
We don't test Firefish on non-Linux systems, so please install Firefish on such an environment **only if you can address any problems yourself**. There is absolutely no support. That said, it is possible to install Firefish on some non-Linux systems.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
|
||||||
|
<summary>Possible setup on FreeBSD (as of version `20240630`)</summary>
|
||||||
|
|
||||||
|
You can install Firefish on FreeBSD by adding these extra steps to the standard instructions:
|
||||||
|
|
||||||
|
1. Install `vips` package
|
||||||
|
2. Add the following block to [`package.json`](../package.json)
|
||||||
|
```json
|
||||||
|
"pnpm": {
|
||||||
|
"overrides": {
|
||||||
|
"rollup": "npm:@rollup/wasm-node@4.17.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
3. Create an rc script for Firefish
|
||||||
|
```sh
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# PROVIDE: firefish
|
||||||
|
# REQUIRE: DAEMON redis caddy postgresql
|
||||||
|
# KEYWORD: shutdown
|
||||||
|
|
||||||
|
. /etc/rc.subr
|
||||||
|
|
||||||
|
name=firefish
|
||||||
|
rcvar=firefish_enable
|
||||||
|
|
||||||
|
desc="Firefish daemon"
|
||||||
|
|
||||||
|
load_rc_config ${name}
|
||||||
|
|
||||||
|
: ${firefish_chdir:="/path/to/firefish/local/repository"}
|
||||||
|
: ${firefish_env:="npm_config_cache=/tmp NODE_ENV=production NODE_OPTIONS=--max-old-space-size=3072"}
|
||||||
|
|
||||||
|
pidfile="/var/run/${name}.pid"
|
||||||
|
command=/usr/sbin/daemon
|
||||||
|
command_args="-f -S -u firefish -P ${pidfile} /usr/local/bin/pnpm run start"
|
||||||
|
|
||||||
|
run_rc_command "$1"
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
Please let us know if you deployed Firefish on a curious environment :smile:
|
||||||
|
|
||||||
|
### Use Docker/Podman containers
|
||||||
|
|
||||||
If you want to use the pre-built container image, please refer to [`install-container.md`](./install-container.md).
|
If you want to use the pre-built container image, please refer to [`install-container.md`](./install-container.md).
|
||||||
|
|
||||||
If you do not prepare your environment as document, be sure to meet the minimum dependencies given at the bottom of the page.
|
## 1. Install dependencies
|
||||||
|
|
||||||
Make sure that you can use the `sudo` command before proceeding.
|
Make sure that you can use the `sudo` command before proceeding.
|
||||||
|
|
||||||
## 1. Install dependencies
|
|
||||||
|
|
||||||
### Utilities
|
### Utilities
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
@ -215,7 +266,7 @@ sudo ufw status
|
||||||
|
|
||||||
### 2. Set up a reverse proxy
|
### 2. Set up a reverse proxy
|
||||||
|
|
||||||
In this instruction, we use [Caddy](https://caddyserver.com/) to make the Firefish server accesible from internet. However, you can also use [Nginx](https://nginx.org/en/) if you want ([example Nginx config file](../firefish.nginx.conf)).
|
In this instruction, we use [Caddy](https://caddyserver.com/) to make the Firefish server accesible from internet. However, you can also use [Nginx](https://nginx.org/en/) if you want ([example Nginx config file](./firefish.nginx.conf)).
|
||||||
|
|
||||||
1. Install Caddy
|
1. Install Caddy
|
||||||
```sh
|
```sh
|
||||||
|
@ -310,7 +361,9 @@ In this instruction, we use [Caddy](https://caddyserver.com/) to make the Firefi
|
||||||
sudo systemctl enable --now firefish
|
sudo systemctl enable --now firefish
|
||||||
```
|
```
|
||||||
|
|
||||||
## Upgrading
|
# Maintain the server
|
||||||
|
|
||||||
|
## Upgrade Firefish version
|
||||||
|
|
||||||
Please refer to the [upgrade instruction](./upgrade.md). Be sure to switch to `firefish` user and go to the Firefish directory before executing the `git` command:
|
Please refer to the [upgrade instruction](./upgrade.md). Be sure to switch to `firefish` user and go to the Firefish directory before executing the `git` command:
|
||||||
|
|
||||||
|
@ -319,6 +372,85 @@ sudo su --login firefish
|
||||||
cd ~/firefish
|
cd ~/firefish
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Rotate logs
|
||||||
|
|
||||||
|
As the server runs longer and longer, the size of the log files increases, filling up the disk space. To prevent this, you should set up a log rotation (removing old logs automatically).
|
||||||
|
|
||||||
|
You can edit the `SystemMaxUse` value in the `[journal]` section of `/etc/systemd/journald.conf` to do it:
|
||||||
|
|
||||||
|
```conf
|
||||||
|
[journal]
|
||||||
|
... (omitted)
|
||||||
|
SystemMaxUse=500M
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure to remove the leading `#` to uncomment the line. After editing the config file, you need to restart `systemd-journald` service.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo systemctl restart systemd-journald
|
||||||
|
```
|
||||||
|
|
||||||
|
It is also recommended that you change the [PGroonga log level](https://pgroonga.github.io/reference/parameters/log-level.html). The default level is `notice`, but this is too verbose for daily use.
|
||||||
|
|
||||||
|
To control the log level, add this line to your `postgresql.conf`:
|
||||||
|
|
||||||
|
```conf
|
||||||
|
pgroonga.log_level = error
|
||||||
|
```
|
||||||
|
|
||||||
|
You can check the `postgresql.conf` location by this command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo --user=postgres psql --command='SHOW config_file'
|
||||||
|
```
|
||||||
|
|
||||||
|
The PGroonga log file (`pgroonga.log`) is located under this directory:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo --user=postgres psql --command='SHOW data_directory'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tune database configuration
|
||||||
|
|
||||||
|
The default PostgreSQL configuration not suitable for running a Firefish server. Thus, it is highly recommended that you use [PGTune](https://pgtune.leopard.in.ua/) to tweak the configuration.
|
||||||
|
|
||||||
|
Here is an example set of parameters you can provide to PGTune:
|
||||||
|
|
||||||
|
| Parameter | Value |
|
||||||
|
|----------------------:|---------------------------------------------------------|
|
||||||
|
| DB version | 16 (your PostgreSQL major version) |
|
||||||
|
| OS Type | Linux |
|
||||||
|
| DB Type | Data warehouse |
|
||||||
|
| Total Memory | [total physical memory] minus 700 MB |
|
||||||
|
| Number of CPUs | number of CPU threads (or lower value if you have many) |
|
||||||
|
| Number of connections | 200 |
|
||||||
|
| Data storage | SSD storage |
|
||||||
|
|
||||||
|
Since this is not a dedicated database server, be sure to leave some memory space for other software such as Firefish and Redis.
|
||||||
|
|
||||||
|
Once you have entered the appropriate values for your environment, click the "Generate" button to generate a configuration and replace the values in `postgresql.conf` with the suggested values.
|
||||||
|
|
||||||
|
After that, you need to restart the PostgreSQL service.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo systemctl stop firefish
|
||||||
|
sudo systemctl restart postgresql
|
||||||
|
sudo systemctl start firefish
|
||||||
|
```
|
||||||
|
|
||||||
|
## VACUUM your database
|
||||||
|
|
||||||
|
If the database runs long, accumulated "garbage" can degrade its performance or cause problems. To prevent this, you should `VACUUM` your database regularly.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo systemctl stop firefish
|
||||||
|
sudo --user=postgres psql --dbname=firefish_db --command='VACUUM FULL VERBOSE ANALYZE'
|
||||||
|
sudo systemctl start firefish
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that this operation takes some time.
|
||||||
|
|
||||||
## Customize
|
## Customize
|
||||||
|
|
||||||
- To add custom CSS for all users, edit `./custom/assets/instance.css`.
|
- To add custom CSS for all users, edit `./custom/assets/instance.css`.
|
||||||
|
|
|
@ -2,8 +2,29 @@
|
||||||
|
|
||||||
You can skip intermediate versions when upgrading from an old version, but please read the notices and follow the instructions for each intermediate version before [upgrading](./upgrade.md).
|
You can skip intermediate versions when upgrading from an old version, but please read the notices and follow the instructions for each intermediate version before [upgrading](./upgrade.md).
|
||||||
|
|
||||||
|
## Upcoming breaking change (unreleased)
|
||||||
|
|
||||||
|
Please take a look at #10947.
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### For all users
|
||||||
|
|
||||||
|
This is not related to the recent changes, but we have added a new section called "[Maintain the server](https://firefish.dev/firefish/firefish/-/blob/develop/docs/install.md#maintain-the-server)" in the installation guide. We suggest that you take a look at it. (and we welcome your docs contributions!)
|
||||||
|
|
||||||
|
## v20240607
|
||||||
|
|
||||||
|
The following environment variables are deprecated and no longer have any effect:
|
||||||
|
- `MK_ONLY_QUEUE`
|
||||||
|
- `MK_ONLY_SERVER`
|
||||||
|
- `MK_NO_DAEMONS`
|
||||||
|
- `MK_DISABLE_CLUSTERING`
|
||||||
|
- `MK_VERBOSE`
|
||||||
|
- `MK_WITH_LOG_TIME`
|
||||||
|
- `MK_SLOW`
|
||||||
|
|
||||||
|
## v20240601
|
||||||
|
|
||||||
### For systemd/pm2 users
|
### For systemd/pm2 users
|
||||||
|
|
||||||
Required Node.js version has been bumped from v18.17.0 to v18.19.0. Also, as written in the [v20240430 note](https://firefish.dev/firefish/firefish/-/blob/d3394b97f021dea323ec3ae36e39930680242482/docs/notice-for-admins.md#v20240430), it is highly recommended that you use an even newer version since v18.19.0 has known vulnerabilities.
|
Required Node.js version has been bumped from v18.17.0 to v18.19.0. Also, as written in the [v20240430 note](https://firefish.dev/firefish/firefish/-/blob/d3394b97f021dea323ec3ae36e39930680242482/docs/notice-for-admins.md#v20240430), it is highly recommended that you use an even newer version since v18.19.0 has known vulnerabilities.
|
||||||
|
@ -32,7 +53,7 @@ Therefore, we have contributed to napi-rs to add support for `DateTime<FixedOffs
|
||||||
|
|
||||||
### For systemd/pm2 users
|
### For systemd/pm2 users
|
||||||
|
|
||||||
There is a bug where `pnpm install --frozen-lockfile` may fail on Linux 6.9.x ([GitHub issue](<https://github.com/nodejs/node/issues/53051>)).
|
There is a bug where `pnpm install --frozen-lockfile` may fail on Linux 6.9.0, 6.9.1, and 6.9.2 ([GitHub issue](<https://github.com/nodejs/node/issues/53051>)).
|
||||||
|
|
||||||
To check your Linux kernel version, run:
|
To check your Linux kernel version, run:
|
||||||
|
|
||||||
|
|
|
@ -446,7 +446,7 @@ tooShort: "قصير جدًا"
|
||||||
tooLong: "طويل جدًا"
|
tooLong: "طويل جدًا"
|
||||||
weakPassword: "الكلمة السرية ضعيفة"
|
weakPassword: "الكلمة السرية ضعيفة"
|
||||||
normalPassword: "الكلمة السرية جيدة"
|
normalPassword: "الكلمة السرية جيدة"
|
||||||
strongPassword: "الكلمة السرية قوية"
|
veryStrongPassword: "الكلمة السرية قوية"
|
||||||
passwordMatched: "التطابق صحيح!"
|
passwordMatched: "التطابق صحيح!"
|
||||||
passwordNotMatched: "غير متطابقتان"
|
passwordNotMatched: "غير متطابقتان"
|
||||||
signinWith: "الولوج عبر {x}"
|
signinWith: "الولوج عبر {x}"
|
||||||
|
|
|
@ -586,7 +586,7 @@ unavailable: Не е свободно
|
||||||
tooShort: Твърде кратко
|
tooShort: Твърде кратко
|
||||||
tooLong: Твърде дълго
|
tooLong: Твърде дълго
|
||||||
weakPassword: Слаба парола
|
weakPassword: Слаба парола
|
||||||
strongPassword: Силна парола
|
veryStrongPassword: Силна парола
|
||||||
passwordMatched: Съвпада
|
passwordMatched: Съвпада
|
||||||
passwordNotMatched: Не съвпада
|
passwordNotMatched: Не съвпада
|
||||||
signinWith: Вход с {x}
|
signinWith: Вход с {x}
|
||||||
|
|
|
@ -462,7 +462,7 @@ tooShort: "খুব ছোট"
|
||||||
tooLong: "খুব বড়"
|
tooLong: "খুব বড়"
|
||||||
weakPassword: "দুর্বল পাসওয়ার্ড"
|
weakPassword: "দুর্বল পাসওয়ার্ড"
|
||||||
normalPassword: "সাধারণ পাসওয়ার্ড"
|
normalPassword: "সাধারণ পাসওয়ার্ড"
|
||||||
strongPassword: "শক্তিশালী পাসওয়ার্ড"
|
veryStrongPassword: "শক্তিশালী পাসওয়ার্ড"
|
||||||
passwordMatched: "মিলেছে"
|
passwordMatched: "মিলেছে"
|
||||||
passwordNotMatched: "মিলেনি"
|
passwordNotMatched: "মিলেনি"
|
||||||
signinWith: "{x} এর সাহায্যে সাইন ইন করুন"
|
signinWith: "{x} এর সাহায্যে সাইন ইন করুন"
|
||||||
|
|
|
@ -395,7 +395,7 @@ _profile:
|
||||||
metadataLabel: Etiqueta
|
metadataLabel: Etiqueta
|
||||||
metadataContent: Contingut
|
metadataContent: Contingut
|
||||||
changeAvatar: Canvia l'avatar
|
changeAvatar: Canvia l'avatar
|
||||||
changeBanner: Canvia el banner
|
changeBanner: Canvia el bàner
|
||||||
locationDescription: Si primer introduïu la vostra ciutat, es mostrarà l'hora local
|
locationDescription: Si primer introduïu la vostra ciutat, es mostrarà l'hora local
|
||||||
a altres usuaris.
|
a altres usuaris.
|
||||||
name: Nom
|
name: Nom
|
||||||
|
@ -1022,7 +1022,7 @@ yearsOld: '{age} anys'
|
||||||
copyUrl: Copia l'adreça URL
|
copyUrl: Copia l'adreça URL
|
||||||
rename: Renombra
|
rename: Renombra
|
||||||
unwatch: Deixa de veure
|
unwatch: Deixa de veure
|
||||||
accept: Accepta
|
accept: Acceptar
|
||||||
reject: Rebutja
|
reject: Rebutja
|
||||||
yearX: '{year}'
|
yearX: '{year}'
|
||||||
pages: Pàgines
|
pages: Pàgines
|
||||||
|
@ -1091,7 +1091,7 @@ usernameInvalidFormat: Pots fer servir lletres en majúscules o minúscules, nom
|
||||||
tooShort: Massa curt
|
tooShort: Massa curt
|
||||||
tooLong: Massa llarg
|
tooLong: Massa llarg
|
||||||
weakPassword: Contrasenya amb seguretat feble
|
weakPassword: Contrasenya amb seguretat feble
|
||||||
strongPassword: Contrasenya amb seguretat forta
|
veryStrongPassword: Contrasenya amb seguretat forta
|
||||||
passwordMatched: Coincidències
|
passwordMatched: Coincidències
|
||||||
signinWith: Inicia sessió com {x}
|
signinWith: Inicia sessió com {x}
|
||||||
signinFailed: No es pot iniciar sessió. El nom d'usuari o la contrasenya són incorrectes.
|
signinFailed: No es pot iniciar sessió. El nom d'usuari o la contrasenya són incorrectes.
|
||||||
|
@ -1114,7 +1114,7 @@ createAccount: Crea un compte
|
||||||
fontSize: Mida del text
|
fontSize: Mida del text
|
||||||
noFollowRequests: No tens cap sol·licitud de seguiment per aprovar
|
noFollowRequests: No tens cap sol·licitud de seguiment per aprovar
|
||||||
openImageInNewTab: Obre les imatges a una pestanya nova
|
openImageInNewTab: Obre les imatges a una pestanya nova
|
||||||
dashboard: Tauler
|
dashboard: Taulell
|
||||||
local: Local
|
local: Local
|
||||||
remote: Remot
|
remote: Remot
|
||||||
total: Total
|
total: Total
|
||||||
|
@ -1551,7 +1551,7 @@ itsOn: Activat
|
||||||
itsOff: Desactivat
|
itsOff: Desactivat
|
||||||
emailRequiredForSignup: Requereix una adreça de correu electrònic per registrar-te
|
emailRequiredForSignup: Requereix una adreça de correu electrònic per registrar-te
|
||||||
unread: Sense llegir
|
unread: Sense llegir
|
||||||
controlPanel: Tauler de control
|
controlPanel: Taulell de control
|
||||||
manageAccounts: Gestionar comptes
|
manageAccounts: Gestionar comptes
|
||||||
makeReactionsPublic: Estableix l'historial de reaccions com a públic
|
makeReactionsPublic: Estableix l'historial de reaccions com a públic
|
||||||
classic: Centrat
|
classic: Centrat
|
||||||
|
@ -2233,7 +2233,7 @@ enablePullToRefresh: Activa "Baixa per actualitzar"
|
||||||
pullDownToReload: Baixa per actualitzar
|
pullDownToReload: Baixa per actualitzar
|
||||||
pullToRefreshThreshold: Distancia de baixada per actualitzar
|
pullToRefreshThreshold: Distancia de baixada per actualitzar
|
||||||
searchWords: Paraules / ID o adreça URL que vols cercar
|
searchWords: Paraules / ID o adreça URL que vols cercar
|
||||||
noSentFollowRequests: No tens cap sol·licitud de seguiment enviada
|
noSentFollowRequests: No has enviat cap sol·licitud de seguiment
|
||||||
sentFollowRequests: Enviar sol·licituds de seguiment
|
sentFollowRequests: Enviar sol·licituds de seguiment
|
||||||
replyMute: Silencia les respostes a les línies de temps
|
replyMute: Silencia les respostes a les línies de temps
|
||||||
replyUnmute: Treu el silencia de les respostes a les línies de temps
|
replyUnmute: Treu el silencia de les respostes a les línies de temps
|
||||||
|
@ -2311,7 +2311,7 @@ _later:
|
||||||
future: futur
|
future: futur
|
||||||
justNow: ara mateix
|
justNow: ara mateix
|
||||||
secondsAgo: en {n}s
|
secondsAgo: en {n}s
|
||||||
minutesAgo: en {n}m
|
minutesAgo: en {n}min
|
||||||
daysAgo: en {n}d
|
daysAgo: en {n}d
|
||||||
weeksAgo: en {n}s
|
weeksAgo: en {n}s
|
||||||
monthsAgo: en {n}me
|
monthsAgo: en {n}me
|
||||||
|
@ -2321,3 +2321,6 @@ scheduledDate: Publica el
|
||||||
scheduledPost: Programa aquesta publicació
|
scheduledPost: Programa aquesta publicació
|
||||||
scheduledPostAt: Aquesta publicació s'enviarà {time}
|
scheduledPostAt: Aquesta publicació s'enviarà {time}
|
||||||
cancelScheduledPost: Elimina la planificació
|
cancelScheduledPost: Elimina la planificació
|
||||||
|
addAlt4MeTag: "Afegeix automàticament l'etiqueta #Alt4Me a les teves publicacions
|
||||||
|
que tinguin un fitxer adjunt sense descripció"
|
||||||
|
strongPassword: Bona contrasenya
|
||||||
|
|
|
@ -401,7 +401,7 @@ tooShort: "Příliš krátké"
|
||||||
tooLong: "Příliš dlouhé"
|
tooLong: "Příliš dlouhé"
|
||||||
weakPassword: "Slabé heslo"
|
weakPassword: "Slabé heslo"
|
||||||
normalPassword: "Dobré heslo"
|
normalPassword: "Dobré heslo"
|
||||||
strongPassword: "Silné heslo"
|
veryStrongPassword: "Silné heslo"
|
||||||
passwordMatched: "Hesla se schodují"
|
passwordMatched: "Hesla se schodují"
|
||||||
passwordNotMatched: "Hesla se neschodují"
|
passwordNotMatched: "Hesla se neschodují"
|
||||||
signinWith: "Přihlásit se s {x}"
|
signinWith: "Přihlásit se s {x}"
|
||||||
|
|
|
@ -77,7 +77,7 @@ lists: "Listen"
|
||||||
noLists: "Du hast keine Listen angelegt"
|
noLists: "Du hast keine Listen angelegt"
|
||||||
note: "Beitrag"
|
note: "Beitrag"
|
||||||
notes: "Beiträge"
|
notes: "Beiträge"
|
||||||
following: "Folgen"
|
following: "Folgend"
|
||||||
followers: "Folgen mir"
|
followers: "Folgen mir"
|
||||||
followsYou: "Folgt dir"
|
followsYou: "Folgt dir"
|
||||||
createList: "Liste erstellen"
|
createList: "Liste erstellen"
|
||||||
|
@ -95,7 +95,7 @@ youShouldUpgradeClient: "Bitte aktualisiere diese Seite, um eine neuere Version
|
||||||
Clients zu verwenden."
|
Clients zu verwenden."
|
||||||
enterListName: "Gib einen Namen für die Liste ein"
|
enterListName: "Gib einen Namen für die Liste ein"
|
||||||
privacy: "Privatsphäre"
|
privacy: "Privatsphäre"
|
||||||
makeFollowManuallyApprove: "Folgeanfragen bedürfen der Genehmigung"
|
makeFollowManuallyApprove: "Folgeanfragen müssen akzeptiert werden"
|
||||||
defaultNoteVisibility: "Standard-Sichtbarkeit"
|
defaultNoteVisibility: "Standard-Sichtbarkeit"
|
||||||
follow: "Folgen"
|
follow: "Folgen"
|
||||||
followRequest: "Follow anfragen"
|
followRequest: "Follow anfragen"
|
||||||
|
@ -430,7 +430,7 @@ securityKeyName: "Schlüsselname"
|
||||||
registerSecurityKey: "Sicherheitsschlüssel registrieren"
|
registerSecurityKey: "Sicherheitsschlüssel registrieren"
|
||||||
lastUsed: "Zuletzt benutzt"
|
lastUsed: "Zuletzt benutzt"
|
||||||
unregister: "Deaktivieren"
|
unregister: "Deaktivieren"
|
||||||
passwordLessLogin: "Passwortloses Anmelden einrichten"
|
passwordLessLogin: "Passwortloses Anmelden"
|
||||||
resetPassword: "Passwort zurücksetzen"
|
resetPassword: "Passwort zurücksetzen"
|
||||||
newPasswordIs: "Das neue Passwort ist „{password}“"
|
newPasswordIs: "Das neue Passwort ist „{password}“"
|
||||||
reduceUiAnimation: "Animationen der Benutzeroberfläche reduzieren"
|
reduceUiAnimation: "Animationen der Benutzeroberfläche reduzieren"
|
||||||
|
@ -480,7 +480,7 @@ tooShort: "Zu kurz"
|
||||||
tooLong: "Zu lang"
|
tooLong: "Zu lang"
|
||||||
weakPassword: "Schwaches Passwort"
|
weakPassword: "Schwaches Passwort"
|
||||||
normalPassword: "Durchschnittliches Passwort"
|
normalPassword: "Durchschnittliches Passwort"
|
||||||
strongPassword: "Starkes Passwort"
|
veryStrongPassword: "Starkes Passwort"
|
||||||
passwordMatched: "Stimmt überein"
|
passwordMatched: "Stimmt überein"
|
||||||
passwordNotMatched: "Stimmt nicht überein"
|
passwordNotMatched: "Stimmt nicht überein"
|
||||||
signinWith: "Mit {x} anmelden"
|
signinWith: "Mit {x} anmelden"
|
||||||
|
@ -865,7 +865,7 @@ customCss: "Benutzerdefiniertes CSS"
|
||||||
customCssWarn: "Verwende diese Einstellung nur, wenn du weißt, was sie tut. Ungültige
|
customCssWarn: "Verwende diese Einstellung nur, wenn du weißt, was sie tut. Ungültige
|
||||||
Eingaben können dazu führen, dass der Client nicht mehr normal funktioniert."
|
Eingaben können dazu führen, dass der Client nicht mehr normal funktioniert."
|
||||||
global: "Global"
|
global: "Global"
|
||||||
squareAvatars: "Profilbilder quadratisch anzeigen"
|
squareAvatars: "Profilbilder für Accounts ohne Katzenohren quadratisch anzeigen"
|
||||||
sent: "Gesendet"
|
sent: "Gesendet"
|
||||||
received: "Erhalten"
|
received: "Erhalten"
|
||||||
searchResult: "Suchergebnisse"
|
searchResult: "Suchergebnisse"
|
||||||
|
@ -948,7 +948,7 @@ check: "Überprüfe"
|
||||||
driveCapOverrideLabel: "Die Cloud-Drive-Kapazität dieses Nutzers verändern"
|
driveCapOverrideLabel: "Die Cloud-Drive-Kapazität dieses Nutzers verändern"
|
||||||
driveCapOverrideCaption: "Gib einen Wert von 0 oder weniger ein, um die Kapazität
|
driveCapOverrideCaption: "Gib einen Wert von 0 oder weniger ein, um die Kapazität
|
||||||
auf den Standard zurückzusetzen."
|
auf den Standard zurückzusetzen."
|
||||||
requireAdminForView: "Du musst dich mit einem Administratorkonto anmelden um dies
|
requireAdminForView: "Du musst dich mit einem Administratorkonto anmelden, um dies
|
||||||
zu sehen."
|
zu sehen."
|
||||||
isSystemAccount: "Ein Nutzerkonto, dass durch das System erstellt und automatisch
|
isSystemAccount: "Ein Nutzerkonto, dass durch das System erstellt und automatisch
|
||||||
kontrolliert wird. Jede Anpassung, Veränderung oder Löschung dieses Nutzerkontos,
|
kontrolliert wird. Jede Anpassung, Veränderung oder Löschung dieses Nutzerkontos,
|
||||||
|
@ -1241,6 +1241,7 @@ _wordMute:
|
||||||
muteLangsDescription2: Sprachcode verwenden, z.B. en, fr, ja, zh..
|
muteLangsDescription2: Sprachcode verwenden, z.B. en, fr, ja, zh..
|
||||||
lang: Sprache
|
lang: Sprache
|
||||||
langDescription: Beiträge in der angegebenen Sprache in der Timeline ausblenden.
|
langDescription: Beiträge in der angegebenen Sprache in der Timeline ausblenden.
|
||||||
|
mutePatterns: Gedämpfte Muster
|
||||||
_instanceMute:
|
_instanceMute:
|
||||||
instanceMuteDescription: "Schaltet alle Beiträge/Boosts stumm, die von den gelisteten
|
instanceMuteDescription: "Schaltet alle Beiträge/Boosts stumm, die von den gelisteten
|
||||||
Servern stammen, inklusive Antworten von Nutzern an einen Nutzer eines stummgeschalteten
|
Servern stammen, inklusive Antworten von Nutzern an einen Nutzer eines stummgeschalteten
|
||||||
|
@ -1332,7 +1333,7 @@ _sfx:
|
||||||
channel: "Kanalbenachrichtigung"
|
channel: "Kanalbenachrichtigung"
|
||||||
_ago:
|
_ago:
|
||||||
future: "Zukunft"
|
future: "Zukunft"
|
||||||
justNow: "Gerade eben"
|
justNow: "gerade eben"
|
||||||
secondsAgo: "vor {n} s"
|
secondsAgo: "vor {n} s"
|
||||||
minutesAgo: "vor {n} min"
|
minutesAgo: "vor {n} min"
|
||||||
hoursAgo: "vor {n} h"
|
hoursAgo: "vor {n} h"
|
||||||
|
@ -1931,6 +1932,7 @@ _notification:
|
||||||
voted: haben bei deiner Umfrage abgestimmt
|
voted: haben bei deiner Umfrage abgestimmt
|
||||||
reacted: hat auf deinen Beitrag reagiert
|
reacted: hat auf deinen Beitrag reagiert
|
||||||
renoted: hat deinen Beitrag geteilt
|
renoted: hat deinen Beitrag geteilt
|
||||||
|
andCountUsers: und {count} mehr Nutzer {acted}
|
||||||
_deck:
|
_deck:
|
||||||
alwaysShowMainColumn: "Hauptspalte immer zeigen"
|
alwaysShowMainColumn: "Hauptspalte immer zeigen"
|
||||||
columnAlign: "Spaltenausrichtung"
|
columnAlign: "Spaltenausrichtung"
|
||||||
|
@ -1979,8 +1981,8 @@ flagSpeakAsCatDescription: Deine Beiträge werden im Katzenmodus nyanisiert
|
||||||
hiddenTags: Versteckte Hashtags
|
hiddenTags: Versteckte Hashtags
|
||||||
antennaInstancesDescription: Geben sie einen Server-Namen pro Zeile ein
|
antennaInstancesDescription: Geben sie einen Server-Namen pro Zeile ein
|
||||||
secureModeInfo: Bei Anfragen an andere Server nicht ohne Nachweis zurücksenden.
|
secureModeInfo: Bei Anfragen an andere Server nicht ohne Nachweis zurücksenden.
|
||||||
renoteMute: Boosts stummschalten
|
renoteMute: Boosts in Timelines stummschalten
|
||||||
renoteUnmute: Stummschaltung von Boosts aufheben
|
renoteUnmute: Stummschaltung von Boosts in der Timeline aufheben
|
||||||
noInstances: Keine Server gefunden
|
noInstances: Keine Server gefunden
|
||||||
privateModeInfo: Wenn diese Option aktiviert ist, können nur als vertrauenswürdig
|
privateModeInfo: Wenn diese Option aktiviert ist, können nur als vertrauenswürdig
|
||||||
eingestufte Server mit diesem Server föderieren. Alle Beiträge werden für die Öffentlichkeit
|
eingestufte Server mit diesem Server föderieren. Alle Beiträge werden für die Öffentlichkeit
|
||||||
|
@ -2019,9 +2021,8 @@ moveAccountDescription: 'Dieser Vorgang kann nicht rückgängig gemacht werden!
|
||||||
wie folgt ein: @name@server.xyz'
|
wie folgt ein: @name@server.xyz'
|
||||||
sendPushNotificationReadMessage: Löschung der Push-Benachrichtigungen sobald die entsprechenden
|
sendPushNotificationReadMessage: Löschung der Push-Benachrichtigungen sobald die entsprechenden
|
||||||
Benachrichtigungen oder Nachrichten gelesen wurden
|
Benachrichtigungen oder Nachrichten gelesen wurden
|
||||||
signupsDisabled: Derzeit sind keine Anmeldungen auf diesem Server möglich! Anmeldungen
|
signupsDisabled: Derzeit sind keine Anmeldungen auf diesem Server möglich. Wenn Sie
|
||||||
auf anderen Servern sind jedoch möglich! Wenn Sie einen Einladungscode für diesen
|
einen Einladungscode für diesen Server haben, geben Sie ihn bitte unten ein.
|
||||||
Server haben, geben Sie ihn bitte unten ein.
|
|
||||||
swipeOnDesktop: Am Desktop PC das Wischen wie bei mobilen Geräten zulassen
|
swipeOnDesktop: Am Desktop PC das Wischen wie bei mobilen Geräten zulassen
|
||||||
enterSendsMessage: Drücken sie zum Senden des Beitrages die Eingabetaste (Strg-Taste
|
enterSendsMessage: Drücken sie zum Senden des Beitrages die Eingabetaste (Strg-Taste
|
||||||
ausgeschaltet)
|
ausgeschaltet)
|
||||||
|
@ -2212,3 +2213,131 @@ quotes: Zitate
|
||||||
moreUrlsDescription: "Die Seiten, welche angepinnt werde sollen, im Hilfe-Menü in
|
moreUrlsDescription: "Die Seiten, welche angepinnt werde sollen, im Hilfe-Menü in
|
||||||
der unteren linken Ecke in folgender Notation angeben:\n\"Anzeigename\": https://example.com/"
|
der unteren linken Ecke in folgender Notation angeben:\n\"Anzeigename\": https://example.com/"
|
||||||
toQuote: Zitat
|
toQuote: Zitat
|
||||||
|
releaseToReload: Loslassen, um neu zu laden
|
||||||
|
pullDownToReload: Herunterziehen zum Aktualisieren
|
||||||
|
antennaLimit: Die maximale Anzahl von Antennen, die jeder Nutzer erstellen kann
|
||||||
|
toEdit: Bearbeiten
|
||||||
|
squareCatAvatars: Profilbilder für Accounts mit Katzenohren quadratisch anzeigen
|
||||||
|
moderationNote: Moderationsnotiz
|
||||||
|
ipFirstAcknowledged: Das Datum des ersten Erwerbs der IP Adresse
|
||||||
|
driveCapacityOverride: Benutzerdefinierte Speicherkapazität
|
||||||
|
searchWordsDescription: "Hier den Suchbegriff für Beiträge eingeben. Mit einem Leerzeichen
|
||||||
|
getrennte Begriffe werden in einer UND Suche gesucht, um eine ODER Suche auszuführen
|
||||||
|
'OR' (ohne Anführungszeichen) zwischen die Begriffe schreiben.\nZum Beispiel findet
|
||||||
|
die Suche nach \"Morgen Nacht\" Beiträge, die sowohl \"Morgen, als auch \"Nacht\"\
|
||||||
|
\ enthalten. Die Suchanfrage \"Morgen OR Nacht\" findet Beiträge, die entweder \"\
|
||||||
|
Morgen\" oder \"Nacht\" (oder beides) enthalten.\nDie AND und OR Suche ist zudem
|
||||||
|
kombinierbar, z.B. so: \"(Morgen OR Nacht) Eule)\".\nUm nach einer Sequenz von Wörtern
|
||||||
|
(z.B. einem Satz) zu suchen, muss die gesamte Wortsequenz in Anführungszeichen stehen.
|
||||||
|
Beispiel: \"Nachrichten von heute\"\n\nUm zu einem bestimmten Profil oder Beitrag
|
||||||
|
zu gelangen, muss die ID oder URL (Webadresse) eingegeben und der Suchknopf gedrückt
|
||||||
|
werden. Ansonsten wird nach Beiträgen gesucht, die die ID oder URL wörtlich enthalten."
|
||||||
|
useCdnDescription: Einige statische Ressourcen, wie einen Twemoji, vom JSDelivr CDN
|
||||||
|
anstatt von diesem Firefish server laden.
|
||||||
|
suggested: Vorgeschlagen
|
||||||
|
preventMisclick: Schutz vor versehentlichen Clicks
|
||||||
|
replaceWidgetsButtonWithReloadButton: Widget-Knopf durch Aktualisierungs-Knopf ersetzen
|
||||||
|
hideFollowButtons: Folgen-Knopf in einer versehentlich clickbaren Position verstecken
|
||||||
|
forMobile: Mobil
|
||||||
|
privateDescription: Nur für Sie sichtbar machen
|
||||||
|
makePrivate: Als privat markieren
|
||||||
|
searchUsers: Erstellt von (optional)
|
||||||
|
searchWords: Suchbegriffe / ID oder URL als Suchanfrage
|
||||||
|
searchCwAndAlt: Inhaltswarnungen und Beschreibungen von Dateien einbeziehen
|
||||||
|
searchUsersDescription: "Um nach Beiträgen eines bestimmten Nutzers/ Servers zu suchen,
|
||||||
|
einfach die ID (@Benutzer@beispiel.de, or @Benutzer für einen lokalen Benutzer)
|
||||||
|
oder Webadresse (beispiel.de) eingeben.\n\nDie Suche \"me\" (ohne Anführungszeichen)
|
||||||
|
findet alle Ihre Beiträge (auch nicht-gelistete, direkte, geheime Beiträge und Beiträge,
|
||||||
|
die nur für Follower sichtbar sind).\n\nDie Suche \"local\" (ohne Anführungszeichen)
|
||||||
|
sorgt dafür, dass nur Beiträge von diesem Server angezeigt werden."
|
||||||
|
publishTimelines: Timelines für Besucher veröffentlichen
|
||||||
|
publishTimelinesDescription: Falls konfiguriert, werden die lokale und globale Timeline
|
||||||
|
auf {url} auch ohne Anmeldung angezeigt.
|
||||||
|
showNoAltTextWarning: Eine Warnung beim Hochladen von Dateien ohne Beschreibung anzeigen
|
||||||
|
_emojiModPerm:
|
||||||
|
add: Hinzufügen
|
||||||
|
full: Alles erlauben
|
||||||
|
unauthorized: Kein(e)
|
||||||
|
mod: Hinzufügen und bearbeiten
|
||||||
|
messagingUnencryptedInfo: Unterhaltungen auf Firefish sind nicht Ende-zu-Ende verschlüsselt.
|
||||||
|
Teilen Sie keine sensiblen Informationen über Firefish.
|
||||||
|
autocorrectNoteLanguage: Eine Warnung anzeigen, wenn die Beitragssprache nicht mit
|
||||||
|
der automatisch ermittelten Sprache übereinstimmt
|
||||||
|
emojiModPerm: Berechtigung, personalisierte Emojis zu verwalten
|
||||||
|
emojiModPermDescription: "Hinzufügen: Erlauben Sie diesem Benutzer, neue benutzerdefinierte
|
||||||
|
Emojis hinzuzufügen und Tag/Kategorie/Lizenz für neu hinzugefügte benutzerdefinierte
|
||||||
|
Emojis einzustellen.\nHinzufügen und Bearbeiten: \"Hinzufügen\" Berechtigung + Erlauben
|
||||||
|
Sie diesem Benutzer, den Namen/die Kategorie/Tag/die Lizenz der vorhandenen benutzerdefinierten
|
||||||
|
Emojis zu bearbeiten.\nAlles erlauben: \"Hinzufügen und Bearbeiten\" Berechtigung
|
||||||
|
+ Erlauben Sie diesem Benutzer, bestehende benutzerdefinierte Emojis zu löschen."
|
||||||
|
reloading: Aktualisiert
|
||||||
|
markLocalFilesNsfwByDefault: Standardmäßig alle neuen lokalen Dateien als sensibel
|
||||||
|
markieren
|
||||||
|
markLocalFilesNsfwByDefaultDescription: Unabhäning von dieser Einstellung lässt sich
|
||||||
|
eine NSFW-Markierung entfernen. Bereits existierende Dateien sind nicht betroffen.
|
||||||
|
noLanguage: Keine Sprache
|
||||||
|
showBigPostButton: Anzeigen eines großen Knopfes zum Teilen des Beitrags im Beitragsformular
|
||||||
|
private: Privat
|
||||||
|
searchRange: Veröffentlicht zwischen (optional)
|
||||||
|
searchPostsWithFiles: Nur Beiträge mit Dateien
|
||||||
|
noAltTextWarning: Einige der angehängten Dateien haben keine Beschreibung. Haben Sie
|
||||||
|
vergessen, diese zu schreiben?
|
||||||
|
toReply: Antworten
|
||||||
|
toPost: Teilen
|
||||||
|
sentFollowRequests: Gesendete Follow-Anfragen
|
||||||
|
replyMute: Antworten in Timelines stummschalten
|
||||||
|
replyUnmute: Stummschaltung von Antworten in Timelines aufheben
|
||||||
|
noSentFollowRequests: Keine gesendeten Follow-Anfragen
|
||||||
|
postSearch: Beitragssuche auf diesem Server
|
||||||
|
enablePullToRefresh: '"Herunterziehen um zu aktualisieren" aktivieren'
|
||||||
|
pullToRefreshThreshold: Benötigte heruntergezogene Distanz, um zu Aktualisieren
|
||||||
|
showAddFileDescriptionAtFirstPost: Öffne automatisch ein Eingabefeld, um fehlende
|
||||||
|
Dateibeschreibungen beim Hochladen zu ergänßen
|
||||||
|
searchRangeDescription: "Um eine Zeitspanne zu filtern, geben Sie diese in diesem
|
||||||
|
Format an: 20220615-20231031 (YYYYMMTT)\n\nDas Auslassen der Jahreszahl (z.B.: 0615-1031
|
||||||
|
oder 20220615-1031) wird automatisch wie die aktuelle Jahreszahl interpretiert.\n
|
||||||
|
\nZudem können das Anfangs- oder Enddatum ausgelassen werden. Zum Beispiel gibt
|
||||||
|
-1031 an, nach Beiträgen vor dem 31.10 dieses Jahres zu suchen. Umgekehrt führt
|
||||||
|
20220615- zu einer Suche nach allen Beiträgen nach dem 15.6.2022."
|
||||||
|
incorrectLanguageWarning: "Es sieht so aus, als wäre ihr Beitrag auf {detected}, aber
|
||||||
|
Sie haben {current} ausgewählt.\nMöchten Sie stattdessen die Sprache zu {detected}
|
||||||
|
ändern?"
|
||||||
|
noteEditHistory: Bearbeitungsgeschichte des Beitrags
|
||||||
|
_later:
|
||||||
|
justNow: gerade eben
|
||||||
|
secondsAgo: in {n}s
|
||||||
|
minutesAgo: in {n}min
|
||||||
|
hoursAgo: in {n}h
|
||||||
|
daysAgo: in {n}d
|
||||||
|
weeksAgo: in {n} Woche(n)
|
||||||
|
monthsAgo: in {n} Monat(en)
|
||||||
|
yearsAgo: in {n} Jahr(en)
|
||||||
|
future: zukünftig
|
||||||
|
scheduledPost: Veröffentlichungszeit manuell festlegen
|
||||||
|
scheduledDate: Geplantes Datum
|
||||||
|
mergeRenotesInTimeline: Mehrere Boosts eines Beitrags gruppieren
|
||||||
|
mergeThreadInTimeline: In der Timeline mehrere Beiträge im gleichen Thread zusammenlegen
|
||||||
|
cannotEditVisibility: Die Sichtbarkeit lässt sich nicht einstellen
|
||||||
|
useThisAccountConfirm: Mit diesem Benutzerkonto fortfahren?
|
||||||
|
inputAccountId: Bitte gib dein Benutzerkonto an (z.B. @firefish@info.firefish.dev)
|
||||||
|
remoteFollow: Folgen (fremde Instanz)
|
||||||
|
foldNotification: Ähnliche Benachrichtigungen gruppieren
|
||||||
|
i18nServerInfo: Neue Clients nutzen standardmäßig {language}.
|
||||||
|
i18nServerChange: Stattdessen {language} benutzen.
|
||||||
|
i18nServerSet: Für neue Clients {language} benutzen.
|
||||||
|
getQrCode: QR Code anzeigen
|
||||||
|
useCdn: Ressourcen von einem CDN laden
|
||||||
|
copyRemoteFollowUrl: URL zum Folgen auf einer fremden Instanz kopieren
|
||||||
|
showPreviewByDefault: Standardmäßig Vorschau in Beitragsform anzeigen
|
||||||
|
replaceChatButtonWithAccountButton: Unterhaltungen-Knopf durch Knopf zum Wechseln
|
||||||
|
des Benutzerkontos ersetzen
|
||||||
|
searchEngine: Verwendete Suchmaschine in der Suchleiste MFM
|
||||||
|
makePrivateConfirm: Diese Operation sendet eine Löschungsanfrage an fremde Server
|
||||||
|
und ändert die Sichtbarkeit zu 'privat'. Fortfahren?
|
||||||
|
enableTimelineStreaming: Timelines automatisch aktualisieren
|
||||||
|
scheduledPostAt: Der Beitrag wird {time} gesendet
|
||||||
|
cancelScheduledPost: Zeitplan entfernen
|
||||||
|
media: Medien
|
||||||
|
slashQuote: Kettenzitat
|
||||||
|
addAlt4MeTag: 'Automatisch den Hashtag #Alt4Me am Ende deines Beitrags einfügen, wenn
|
||||||
|
eine angehängte Datei keine Beschreibung hat'
|
||||||
|
|
|
@ -677,7 +677,7 @@ checking: Έλεγχος...
|
||||||
invitationCode: Κωδικός πρόσκλησης
|
invitationCode: Κωδικός πρόσκλησης
|
||||||
normalPassword: Μέτριος κωδικός
|
normalPassword: Μέτριος κωδικός
|
||||||
weakPassword: Αδύναμος κωδικός
|
weakPassword: Αδύναμος κωδικός
|
||||||
strongPassword: Δυνατός κωδικός
|
veryStrongPassword: Δυνατός κωδικός
|
||||||
signinWith: Συνδεθείτε με {x}
|
signinWith: Συνδεθείτε με {x}
|
||||||
tapSecurityKey: Βάλτε το κλειδί ασφάλειας
|
tapSecurityKey: Βάλτε το κλειδί ασφάλειας
|
||||||
signinFailed: Αδυναμία σύνδεσης. Το όνομα μέλους ή ο κωδικός είναι λάθος.
|
signinFailed: Αδυναμία σύνδεσης. Το όνομα μέλους ή ο κωδικός είναι λάθος.
|
||||||
|
|
|
@ -514,8 +514,9 @@ usernameInvalidFormat: "You can use upper- and lowercase letters, numbers, and u
|
||||||
tooShort: "Too short"
|
tooShort: "Too short"
|
||||||
tooLong: "Too long"
|
tooLong: "Too long"
|
||||||
weakPassword: "Weak password"
|
weakPassword: "Weak password"
|
||||||
normalPassword: "Average password"
|
normalPassword: "Medium password"
|
||||||
strongPassword: "Strong password"
|
strongPassword: "Good password"
|
||||||
|
veryStrongPassword: "Great password"
|
||||||
passwordMatched: "Matches"
|
passwordMatched: "Matches"
|
||||||
passwordNotMatched: "Does not match"
|
passwordNotMatched: "Does not match"
|
||||||
signinWith: "Sign in with {x}"
|
signinWith: "Sign in with {x}"
|
||||||
|
@ -542,7 +543,7 @@ existingAccount: "Existing account"
|
||||||
regenerate: "Regenerate"
|
regenerate: "Regenerate"
|
||||||
fontSize: "Font size"
|
fontSize: "Font size"
|
||||||
noFollowRequests: "You don't have any pending follow requests"
|
noFollowRequests: "You don't have any pending follow requests"
|
||||||
noSentFollowRequests: "You don't have any sent follow requests"
|
noSentFollowRequests: "You haven't sent any follow requests"
|
||||||
openImageInNewTab: "Open images in new tab"
|
openImageInNewTab: "Open images in new tab"
|
||||||
dashboard: "Dashboard"
|
dashboard: "Dashboard"
|
||||||
local: "Local"
|
local: "Local"
|
||||||
|
@ -1236,8 +1237,9 @@ publishTimelinesDescription: "If enabled, the Local and Global timelines will be
|
||||||
on {url} even when signed out."
|
on {url} even when signed out."
|
||||||
noAltTextWarning: "Some attached file(s) have no description. Did you forget to write?"
|
noAltTextWarning: "Some attached file(s) have no description. Did you forget to write?"
|
||||||
showNoAltTextWarning: "Show a warning if you attempt to post files without a description"
|
showNoAltTextWarning: "Show a warning if you attempt to post files without a description"
|
||||||
showAddFileDescriptionAtFirstPost: "Automatically open a form to write a description when you
|
showAddFileDescriptionAtFirstPost: "Automatically open a form to write a description
|
||||||
attempt to post files without a description"
|
when you attempt to post files without a description"
|
||||||
|
addAlt4MeTag: "Automatically append #Alt4Me hashtag to your post if attached file has no description"
|
||||||
|
|
||||||
_emojiModPerm:
|
_emojiModPerm:
|
||||||
unauthorized: "None"
|
unauthorized: "None"
|
||||||
|
@ -1590,7 +1592,7 @@ _later:
|
||||||
future: "future"
|
future: "future"
|
||||||
justNow: "right now"
|
justNow: "right now"
|
||||||
secondsAgo: "in {n}s"
|
secondsAgo: "in {n}s"
|
||||||
minutesAgo: "in {n}m"
|
minutesAgo: "in {n}min"
|
||||||
hoursAgo: "in {n}h"
|
hoursAgo: "in {n}h"
|
||||||
daysAgo: "in {n}d"
|
daysAgo: "in {n}d"
|
||||||
weeksAgo: "in {n}w"
|
weeksAgo: "in {n}w"
|
||||||
|
|
|
@ -465,7 +465,7 @@ tooShort: "Demasiado corto"
|
||||||
tooLong: "Demasiado largo"
|
tooLong: "Demasiado largo"
|
||||||
weakPassword: "Contraseña débil"
|
weakPassword: "Contraseña débil"
|
||||||
normalPassword: "Buena contraseña"
|
normalPassword: "Buena contraseña"
|
||||||
strongPassword: "Muy buena contraseña"
|
veryStrongPassword: "Muy buena contraseña"
|
||||||
passwordMatched: "Correcto"
|
passwordMatched: "Correcto"
|
||||||
passwordNotMatched: "Las contraseñas no son las mismas"
|
passwordNotMatched: "Las contraseñas no son las mismas"
|
||||||
signinWith: "Inicie sesión con {x}"
|
signinWith: "Inicie sesión con {x}"
|
||||||
|
@ -1073,7 +1073,7 @@ _aboutFirefish:
|
||||||
source: "Código fuente"
|
source: "Código fuente"
|
||||||
translation: "Traducir Firefish"
|
translation: "Traducir Firefish"
|
||||||
donate: "Donar a Firefish"
|
donate: "Donar a Firefish"
|
||||||
pleaseDonateToFirefish: Por favor considera donar a Firefish para apollar su desarrollo.
|
pleaseDonateToFirefish: Por favor considera donar a Firefish para apoyar su desarrollo.
|
||||||
donateHost: Dona a {host}
|
donateHost: Dona a {host}
|
||||||
donateTitle: ¿Te gusta Firefish?
|
donateTitle: ¿Te gusta Firefish?
|
||||||
pleaseDonateToHost: También considera donar a tu propio servidor , {host}, para
|
pleaseDonateToHost: También considera donar a tu propio servidor , {host}, para
|
||||||
|
|
|
@ -569,7 +569,7 @@ tooShort: Liian lyhyt
|
||||||
tooLong: Liian pitkä
|
tooLong: Liian pitkä
|
||||||
weakPassword: Heikko salasana
|
weakPassword: Heikko salasana
|
||||||
normalPassword: Kohtalainen salasana
|
normalPassword: Kohtalainen salasana
|
||||||
strongPassword: Vahva salasana
|
veryStrongPassword: Vahva salasana
|
||||||
passwordMatched: Vastaa
|
passwordMatched: Vastaa
|
||||||
signinWith: Kirjaudu sisään {x}
|
signinWith: Kirjaudu sisään {x}
|
||||||
signinFailed: Ei voitu kirjautua sisään. Annettu käyttäjänimi tai salasana virheellinen.
|
signinFailed: Ei voitu kirjautua sisään. Annettu käyttäjänimi tai salasana virheellinen.
|
||||||
|
|
|
@ -468,7 +468,7 @@ tooShort: "Trop court"
|
||||||
tooLong: "Trop long"
|
tooLong: "Trop long"
|
||||||
weakPassword: "Mot de passe faible"
|
weakPassword: "Mot de passe faible"
|
||||||
normalPassword: "Mot de passe acceptable"
|
normalPassword: "Mot de passe acceptable"
|
||||||
strongPassword: "Mot de passe fort"
|
veryStrongPassword: "Mot de passe fort"
|
||||||
passwordMatched: "Les mots de passe correspondent"
|
passwordMatched: "Les mots de passe correspondent"
|
||||||
passwordNotMatched: "Les mots de passe ne correspondent pas"
|
passwordNotMatched: "Les mots de passe ne correspondent pas"
|
||||||
signinWith: "Se connecter avec {x}"
|
signinWith: "Se connecter avec {x}"
|
||||||
|
|
|
@ -464,7 +464,7 @@ tooShort: "Terlalu pendek"
|
||||||
tooLong: "Terlalu panjang"
|
tooLong: "Terlalu panjang"
|
||||||
weakPassword: "Kata sandi lemah"
|
weakPassword: "Kata sandi lemah"
|
||||||
normalPassword: "Kata sandi baik"
|
normalPassword: "Kata sandi baik"
|
||||||
strongPassword: "Kata sandi kuat"
|
veryStrongPassword: "Kata sandi kuat"
|
||||||
passwordMatched: "Kata sandi sama"
|
passwordMatched: "Kata sandi sama"
|
||||||
passwordNotMatched: "Kata sandi tidak sama"
|
passwordNotMatched: "Kata sandi tidak sama"
|
||||||
signinWith: "Masuk dengan {x}"
|
signinWith: "Masuk dengan {x}"
|
||||||
|
|
|
@ -454,7 +454,7 @@ tooShort: "Troppo breve"
|
||||||
tooLong: "Troppo lungo"
|
tooLong: "Troppo lungo"
|
||||||
weakPassword: "Password debole"
|
weakPassword: "Password debole"
|
||||||
normalPassword: "Password buona"
|
normalPassword: "Password buona"
|
||||||
strongPassword: "Password forte"
|
veryStrongPassword: "Password forte"
|
||||||
passwordMatched: "Corretta"
|
passwordMatched: "Corretta"
|
||||||
passwordNotMatched: "Le password non corrispondono"
|
passwordNotMatched: "Le password non corrispondono"
|
||||||
signinWith: "Accedi con {x}"
|
signinWith: "Accedi con {x}"
|
||||||
|
|
|
@ -465,7 +465,7 @@ tooShort: "短すぎます"
|
||||||
tooLong: "長すぎます"
|
tooLong: "長すぎます"
|
||||||
weakPassword: "弱いパスワード"
|
weakPassword: "弱いパスワード"
|
||||||
normalPassword: "普通のパスワード"
|
normalPassword: "普通のパスワード"
|
||||||
strongPassword: "強いパスワード"
|
veryStrongPassword: "とても強いパスワード"
|
||||||
passwordMatched: "一致しました"
|
passwordMatched: "一致しました"
|
||||||
passwordNotMatched: "一致していません"
|
passwordNotMatched: "一致していません"
|
||||||
signinWith: "{x}でログイン"
|
signinWith: "{x}でログイン"
|
||||||
|
@ -2087,3 +2087,5 @@ scheduledPost: 予約投稿
|
||||||
scheduledDate: 予定日
|
scheduledDate: 予定日
|
||||||
cancelScheduledPost: 予約を解除する
|
cancelScheduledPost: 予約を解除する
|
||||||
scheduledPostAt: '{time}に投稿されます'
|
scheduledPostAt: '{time}に投稿されます'
|
||||||
|
strongPassword: 強いパスワード
|
||||||
|
addAlt4MeTag: '説明の無いファイルを投稿する際に自動で #Alt4Me のハッシュタグをつける'
|
||||||
|
|
|
@ -435,7 +435,7 @@ tooShort: "短すぎやろ!"
|
||||||
tooLong: "長すぎやろ!"
|
tooLong: "長すぎやろ!"
|
||||||
weakPassword: "へぼいパスワード"
|
weakPassword: "へぼいパスワード"
|
||||||
normalPassword: "普通のパスワード"
|
normalPassword: "普通のパスワード"
|
||||||
strongPassword: "ええ感じのパスワード"
|
veryStrongPassword: "ええ感じのパスワード"
|
||||||
passwordMatched: "よし!一致や!"
|
passwordMatched: "よし!一致や!"
|
||||||
passwordNotMatched: "一致しとらんで?"
|
passwordNotMatched: "一致しとらんで?"
|
||||||
signinWith: "{x}でログイン"
|
signinWith: "{x}でログイン"
|
||||||
|
|
|
@ -442,7 +442,7 @@ tooShort: "너무 짧습니다"
|
||||||
tooLong: "너무 깁니다"
|
tooLong: "너무 깁니다"
|
||||||
weakPassword: "약한 비밀번호"
|
weakPassword: "약한 비밀번호"
|
||||||
normalPassword: "좋은 비밀번호"
|
normalPassword: "좋은 비밀번호"
|
||||||
strongPassword: "강한 비밀번호"
|
veryStrongPassword: "강한 비밀번호"
|
||||||
passwordMatched: "일치합니다"
|
passwordMatched: "일치합니다"
|
||||||
passwordNotMatched: "일치하지 않습니다"
|
passwordNotMatched: "일치하지 않습니다"
|
||||||
signinWith: "{x}로 로그인"
|
signinWith: "{x}로 로그인"
|
||||||
|
|
|
@ -576,7 +576,7 @@ quoteAttached: Quote
|
||||||
noMessagesYet: Nog geen berichten
|
noMessagesYet: Nog geen berichten
|
||||||
weakPassword: Zwak wachtwoord
|
weakPassword: Zwak wachtwoord
|
||||||
normalPassword: Middelmatig wachtwoord
|
normalPassword: Middelmatig wachtwoord
|
||||||
strongPassword: Sterk wachtwoord
|
veryStrongPassword: Sterk wachtwoord
|
||||||
onlyOneFileCanBeAttached: Je kan maar één bestand toevoegen aan je bericht
|
onlyOneFileCanBeAttached: Je kan maar één bestand toevoegen aan je bericht
|
||||||
invitationCode: Uitnodigingscode
|
invitationCode: Uitnodigingscode
|
||||||
checking: Controleren...
|
checking: Controleren...
|
||||||
|
|
|
@ -571,7 +571,7 @@ youHaveNoGroups: Du har ingen grupper
|
||||||
noHistory: Ingen historikk er tilgjengelig
|
noHistory: Ingen historikk er tilgjengelig
|
||||||
aboutX: Om {x}
|
aboutX: Om {x}
|
||||||
signinHistory: Innloggings-historikk
|
signinHistory: Innloggings-historikk
|
||||||
strongPassword: Sterkt passord
|
veryStrongPassword: Sterkt passord
|
||||||
noFollowRequests: Du har ingen utestående følgeforespørsler
|
noFollowRequests: Du har ingen utestående følgeforespørsler
|
||||||
openImageInNewTab: Åpne bilder i ny fane
|
openImageInNewTab: Åpne bilder i ny fane
|
||||||
dashboard: Dashbord
|
dashboard: Dashbord
|
||||||
|
|
|
@ -454,7 +454,7 @@ tooShort: "Zbyt krótka"
|
||||||
tooLong: "Zbyt długa"
|
tooLong: "Zbyt długa"
|
||||||
weakPassword: "Słabe hasło"
|
weakPassword: "Słabe hasło"
|
||||||
normalPassword: "Dobre hasło"
|
normalPassword: "Dobre hasło"
|
||||||
strongPassword: "Silne hasło"
|
veryStrongPassword: "Silne hasło"
|
||||||
passwordMatched: "Pasuje"
|
passwordMatched: "Pasuje"
|
||||||
passwordNotMatched: "Hasła nie pasują do siebie"
|
passwordNotMatched: "Hasła nie pasują do siebie"
|
||||||
signinWith: "Zaloguj się z {x}"
|
signinWith: "Zaloguj się z {x}"
|
||||||
|
|
|
@ -464,7 +464,7 @@ tooShort: "Prea scurt"
|
||||||
tooLong: "Prea lung"
|
tooLong: "Prea lung"
|
||||||
weakPassword: "Parolă slabă"
|
weakPassword: "Parolă slabă"
|
||||||
normalPassword: "Parolă medie"
|
normalPassword: "Parolă medie"
|
||||||
strongPassword: "Parolă puternică"
|
veryStrongPassword: "Parolă puternică"
|
||||||
passwordMatched: "Se potrivește!"
|
passwordMatched: "Se potrivește!"
|
||||||
passwordNotMatched: "Nu se potrivește"
|
passwordNotMatched: "Nu se potrivește"
|
||||||
signinWith: "Autentifică-te cu {x}"
|
signinWith: "Autentifică-te cu {x}"
|
||||||
|
|
|
@ -460,7 +460,7 @@ tooShort: "Слишком короткий"
|
||||||
tooLong: "Слишком длинный"
|
tooLong: "Слишком длинный"
|
||||||
weakPassword: "Слабый пароль"
|
weakPassword: "Слабый пароль"
|
||||||
normalPassword: "Годный пароль"
|
normalPassword: "Годный пароль"
|
||||||
strongPassword: "Надёжный пароль"
|
veryStrongPassword: "Надёжный пароль"
|
||||||
passwordMatched: "Совпали"
|
passwordMatched: "Совпали"
|
||||||
passwordNotMatched: "Не совпадают"
|
passwordNotMatched: "Не совпадают"
|
||||||
signinWith: "Использовать {x} для входа"
|
signinWith: "Использовать {x} для входа"
|
||||||
|
@ -2137,3 +2137,15 @@ replies: Ответы
|
||||||
quotes: Цитаты
|
quotes: Цитаты
|
||||||
clickToShowPatterns: Нажмите, чтобы показать модуль шаблонов
|
clickToShowPatterns: Нажмите, чтобы показать модуль шаблонов
|
||||||
renotes: Репосты
|
renotes: Репосты
|
||||||
|
markLocalFilesNsfwByDefaultDescription: Независимо от данной настройки, пользователи
|
||||||
|
могут самостоятельно удалять метку NSFW. Не применяется на существующие файлы.
|
||||||
|
toEdit: Редактировать
|
||||||
|
attachedToNotes: Посты с этим файлом
|
||||||
|
showAttachedNotes: Показывать посты с этим файлом
|
||||||
|
strongPassword: Хороший пароль
|
||||||
|
toReply: Ответить
|
||||||
|
toPost: Выложить
|
||||||
|
sentFollowRequests: Отправленные запросы на подписку
|
||||||
|
toQuote: Цитировать
|
||||||
|
cannotEditVisibility: Вы не можете изменить видимость
|
||||||
|
noSentFollowRequests: Вы не отправляли никаких запросов на подписку
|
||||||
|
|
|
@ -461,7 +461,7 @@ tooShort: "Príliš krátke"
|
||||||
tooLong: "Príliš dlhé"
|
tooLong: "Príliš dlhé"
|
||||||
weakPassword: "Slabé heslo"
|
weakPassword: "Slabé heslo"
|
||||||
normalPassword: "Dobré heslo"
|
normalPassword: "Dobré heslo"
|
||||||
strongPassword: "Silné heslo"
|
veryStrongPassword: "Silné heslo"
|
||||||
passwordMatched: "Heslá sú rovnaké"
|
passwordMatched: "Heslá sú rovnaké"
|
||||||
passwordNotMatched: "Heslá nie sú rovnaké"
|
passwordNotMatched: "Heslá nie sú rovnaké"
|
||||||
signinWith: "Prihlásiť sa použitím {x}"
|
signinWith: "Prihlásiť sa použitím {x}"
|
||||||
|
|
|
@ -381,7 +381,7 @@ noMessagesYet: Inga meddelande ännu
|
||||||
newMessageExists: Det finns inga nya meddelanden
|
newMessageExists: Det finns inga nya meddelanden
|
||||||
weakPassword: Svagt lösenord
|
weakPassword: Svagt lösenord
|
||||||
normalPassword: Dugligt lösenord
|
normalPassword: Dugligt lösenord
|
||||||
strongPassword: Starkt lösenord
|
veryStrongPassword: Starkt lösenord
|
||||||
passwordMatched: Matchar
|
passwordMatched: Matchar
|
||||||
passwordNotMatched: Matchar inte
|
passwordNotMatched: Matchar inte
|
||||||
signinWith: Logga in med {x}
|
signinWith: Logga in med {x}
|
||||||
|
|
|
@ -452,7 +452,7 @@ tooShort: "สั้นเกินไปนะ"
|
||||||
tooLong: "ยาวเกินไปนะ"
|
tooLong: "ยาวเกินไปนะ"
|
||||||
weakPassword: "รหัสผ่าน แย่มาก"
|
weakPassword: "รหัสผ่าน แย่มาก"
|
||||||
normalPassword: "รหัสผ่านปกติ"
|
normalPassword: "รหัสผ่านปกติ"
|
||||||
strongPassword: "รหัสผ่านรัดกุมมาก"
|
veryStrongPassword: "รหัสผ่านรัดกุมมาก"
|
||||||
passwordMatched: "ถูกต้อง!"
|
passwordMatched: "ถูกต้อง!"
|
||||||
passwordNotMatched: "ไม่ถูกต้อง"
|
passwordNotMatched: "ไม่ถูกต้อง"
|
||||||
signinWith: "ลงชื่อเข้าใช้ด้วย {x}"
|
signinWith: "ลงชื่อเข้าใช้ด้วย {x}"
|
||||||
|
|
|
@ -521,7 +521,7 @@ newMessageExists: Yeni mesaj yok
|
||||||
invitations: Davetler
|
invitations: Davetler
|
||||||
invitationCode: Davet kodu
|
invitationCode: Davet kodu
|
||||||
signinWith: '{x} ile giriş yap'
|
signinWith: '{x} ile giriş yap'
|
||||||
strongPassword: Güçlü şifre
|
veryStrongPassword: Güçlü şifre
|
||||||
passwordNotMatched: Uyuşmuyor
|
passwordNotMatched: Uyuşmuyor
|
||||||
signinFailed: Giriş yapılamadı. Şifre ve ya kullanıcı adı yanlış.
|
signinFailed: Giriş yapılamadı. Şifre ve ya kullanıcı adı yanlış.
|
||||||
tapSecurityKey: Güvenlik anahtarınıza dokunun
|
tapSecurityKey: Güvenlik anahtarınıza dokunun
|
||||||
|
|
|
@ -460,7 +460,7 @@ tooShort: "Занадто короткий"
|
||||||
tooLong: "Занадто довгий"
|
tooLong: "Занадто довгий"
|
||||||
weakPassword: "Слабкий пароль"
|
weakPassword: "Слабкий пароль"
|
||||||
normalPassword: "Достатній пароль"
|
normalPassword: "Достатній пароль"
|
||||||
strongPassword: "Міцний пароль"
|
veryStrongPassword: "Міцний пароль"
|
||||||
passwordMatched: "Все вірно"
|
passwordMatched: "Все вірно"
|
||||||
passwordNotMatched: "Паролі не співпадають"
|
passwordNotMatched: "Паролі не співпадають"
|
||||||
signinWith: "Увійти за допомогою {x}"
|
signinWith: "Увійти за допомогою {x}"
|
||||||
|
|
|
@ -462,7 +462,7 @@ tooShort: "Quá ngắn"
|
||||||
tooLong: "Quá dài"
|
tooLong: "Quá dài"
|
||||||
weakPassword: "Mật khẩu yếu"
|
weakPassword: "Mật khẩu yếu"
|
||||||
normalPassword: "Mật khẩu tạm được"
|
normalPassword: "Mật khẩu tạm được"
|
||||||
strongPassword: "Mật khẩu mạnh"
|
veryStrongPassword: "Mật khẩu mạnh"
|
||||||
passwordMatched: "Trùng khớp"
|
passwordMatched: "Trùng khớp"
|
||||||
passwordNotMatched: "Không trùng khớp"
|
passwordNotMatched: "Không trùng khớp"
|
||||||
signinWith: "Đăng nhập bằng {x}"
|
signinWith: "Đăng nhập bằng {x}"
|
||||||
|
|
|
@ -450,7 +450,7 @@ tooShort: "太短"
|
||||||
tooLong: "太长"
|
tooLong: "太长"
|
||||||
weakPassword: "密码强度:弱"
|
weakPassword: "密码强度:弱"
|
||||||
normalPassword: "密码强度:中等"
|
normalPassword: "密码强度:中等"
|
||||||
strongPassword: "密码强度:强"
|
veryStrongPassword: "密码强度:强"
|
||||||
passwordMatched: "密码一致"
|
passwordMatched: "密码一致"
|
||||||
passwordNotMatched: "密码不一致"
|
passwordNotMatched: "密码不一致"
|
||||||
signinWith: "以 {x} 登录"
|
signinWith: "以 {x} 登录"
|
||||||
|
@ -1950,7 +1950,7 @@ noteId: 帖子 ID
|
||||||
moveFrom: 从旧账号迁移至此账号
|
moveFrom: 从旧账号迁移至此账号
|
||||||
defaultReaction: 发出和收到帖子的默认表情符号反应
|
defaultReaction: 发出和收到帖子的默认表情符号反应
|
||||||
sendModMail: 发送管理通知
|
sendModMail: 发送管理通知
|
||||||
moderationNote: "管理笔记"
|
moderationNote: "管理员备注"
|
||||||
ipFirstAcknowledged: "首次获取此 IP 地址的日期"
|
ipFirstAcknowledged: "首次获取此 IP 地址的日期"
|
||||||
driveCapacityOverride: "网盘容量变更"
|
driveCapacityOverride: "网盘容量变更"
|
||||||
isLocked: 该账号设置了关注请求
|
isLocked: 该账号设置了关注请求
|
||||||
|
|
|
@ -448,7 +448,7 @@ tooShort: "過短"
|
||||||
tooLong: "過長"
|
tooLong: "過長"
|
||||||
weakPassword: "密碼強度過弱"
|
weakPassword: "密碼強度過弱"
|
||||||
normalPassword: "密碼強度普通"
|
normalPassword: "密碼強度普通"
|
||||||
strongPassword: "密碼強度高"
|
veryStrongPassword: "密碼強度高"
|
||||||
passwordMatched: "密碼一致"
|
passwordMatched: "密碼一致"
|
||||||
passwordNotMatched: "密碼不一致"
|
passwordNotMatched: "密碼不一致"
|
||||||
signinWith: "以{x}登錄"
|
signinWith: "以{x}登錄"
|
||||||
|
|
26
package.json
26
package.json
|
@ -1,11 +1,11 @@
|
||||||
{
|
{
|
||||||
"name": "firefish",
|
"name": "firefish",
|
||||||
"version": "20240523",
|
"version": "20240630",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://firefish.dev/firefish/firefish.git"
|
"url": "https://firefish.dev/firefish/firefish.git"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.1.4",
|
"packageManager": "pnpm@9.4.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"rebuild": "pnpm run clean && pnpm run build",
|
"rebuild": "pnpm run clean && pnpm run build",
|
||||||
|
@ -22,13 +22,14 @@
|
||||||
"dev": "pnpm node ./scripts/dev.mjs",
|
"dev": "pnpm node ./scripts/dev.mjs",
|
||||||
"dev:staging": "NODE_OPTIONS=--max_old_space_size=3072 NODE_ENV=development pnpm run build && pnpm run start",
|
"dev:staging": "NODE_OPTIONS=--max_old_space_size=3072 NODE_ENV=development pnpm run build && pnpm run start",
|
||||||
"lint": "pnpm run lint:ts; pnpm run lint:rs",
|
"lint": "pnpm run lint:ts; pnpm run lint:rs",
|
||||||
"lint:ts": "pnpm --filter !firefish-js -r --parallel run lint",
|
"lint:ts": "pnpm --filter !firefish-js --recursive run lint ; pnpm run format:ts",
|
||||||
"lint:rs": "cargo clippy --fix --allow-dirty --allow-staged && cargo fmt --all --",
|
"lint:rs": "cargo clippy --fix --allow-dirty --allow-staged && cargo fmt --all --",
|
||||||
"debug": "pnpm run build:debug && pnpm run start",
|
"debug": "pnpm run build:debug && pnpm run start",
|
||||||
"mocha": "pnpm --filter backend run mocha",
|
"mocha": "pnpm --filter backend run mocha",
|
||||||
"test": "pnpm run test:ts && pnpm run test:rs",
|
"test": "pnpm run test:rs && pnpm run test:rs:miri && pnpm run test:ts",
|
||||||
"test:ts": "pnpm run mocha",
|
"test:ts": "pnpm run mocha",
|
||||||
"test:rs": "cargo test --doc && cargo nextest run",
|
"test:rs": "cargo test --doc && cargo nextest run",
|
||||||
|
"test:rs:miri": "MIRIFLAGS='-Zmiri-disable-isolation' cargo +nightly miri nextest run -j$(nproc --all)",
|
||||||
"format": "pnpm run format:ts; pnpm run format:rs",
|
"format": "pnpm run format:ts; pnpm run format:rs",
|
||||||
"format:ts": "pnpm -r --parallel run format",
|
"format:ts": "pnpm -r --parallel run format",
|
||||||
"format:rs": "cargo fmt --all --",
|
"format:rs": "cargo fmt --all --",
|
||||||
|
@ -41,14 +42,13 @@
|
||||||
"js-yaml": "4.1.0"
|
"js-yaml": "4.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "1.7.3",
|
"@biomejs/biome": "1.8.3",
|
||||||
"@biomejs/cli-darwin-arm64": "1.7.3",
|
"@biomejs/cli-darwin-arm64": "1.8.3",
|
||||||
"@biomejs/cli-darwin-x64": "1.7.3",
|
"@biomejs/cli-darwin-x64": "1.8.3",
|
||||||
"@biomejs/cli-linux-arm64": "1.7.3",
|
"@biomejs/cli-linux-arm64": "1.8.3",
|
||||||
"@biomejs/cli-linux-x64": "1.7.3",
|
"@biomejs/cli-linux-x64": "1.8.3",
|
||||||
"@types/node": "20.12.13",
|
"@types/node": "20.14.9",
|
||||||
"execa": "9.1.0",
|
"execa": "9.3.0",
|
||||||
"pnpm": "9.1.4",
|
"pnpm": "9.4.0"
|
||||||
"typescript": "5.4.5"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,13 @@ rust-version = "1.74"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
napi = ["dep:napi", "dep:napi-derive"]
|
napi = ["dep:napi", "dep:napi-derive", "dep:napi-build"]
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "lib"]
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
macro-rs = { workspace = true }
|
macros = { workspace = true }
|
||||||
|
|
||||||
napi = { workspace = true, optional = true, features = ["chrono_date", "napi4", "serde-json", "tokio_rt"] }
|
napi = { workspace = true, optional = true, features = ["chrono_date", "napi4", "serde-json", "tokio_rt"] }
|
||||||
napi-derive = { workspace = true, optional = true }
|
napi-derive = { workspace = true, optional = true }
|
||||||
|
@ -25,7 +25,7 @@ bcrypt = { workspace = true, features = ["std"] }
|
||||||
chrono = { workspace = true }
|
chrono = { workspace = true }
|
||||||
cuid2 = { workspace = true }
|
cuid2 = { workspace = true }
|
||||||
emojis = { workspace = true }
|
emojis = { workspace = true }
|
||||||
idna = { workspace = true }
|
idna = { workspace = true, features = ["std", "compiled_data"] }
|
||||||
image = { workspace = true, features = ["avif", "bmp", "gif", "ico", "jpeg", "png", "tiff", "webp"] }
|
image = { workspace = true, features = ["avif", "bmp", "gif", "ico", "jpeg", "png", "tiff", "webp"] }
|
||||||
isahc = { workspace = true, features = ["http2", "text-decoding"] }
|
isahc = { workspace = true, features = ["http2", "text-decoding"] }
|
||||||
nom-exif = { workspace = true }
|
nom-exif = { workspace = true }
|
||||||
|
@ -39,7 +39,6 @@ sea-orm = { workspace = true, features = ["macros", "runtime-tokio-rustls", "sql
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
serde_yaml = { workspace = true }
|
serde_yaml = { workspace = true }
|
||||||
strum = { workspace = true, features = ["derive"] }
|
|
||||||
sysinfo = { workspace = true }
|
sysinfo = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
tokio = { workspace = true, features = ["fs", "io-std", "io-util", "macros", "process", "rt-multi-thread", "signal", "sync", "time"] }
|
tokio = { workspace = true, features = ["fs", "io-std", "io-util", "macros", "process", "rt-multi-thread", "signal", "sync", "time"] }
|
||||||
|
@ -54,4 +53,4 @@ pretty_assertions = { workspace = true, features = ["std"] }
|
||||||
tokio-test = { workspace = true }
|
tokio-test = { workspace = true }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
napi-build = { workspace = true }
|
napi-build = { workspace = true, optional = true }
|
||||||
|
|
|
@ -11,25 +11,25 @@ regenerate-entities:
|
||||||
--output-dir='src/model/entity' \
|
--output-dir='src/model/entity' \
|
||||||
--database-url='postgres://$(POSTGRES_USER):$(POSTGRES_PASSWORD)@localhost:25432/$(POSTGRES_DB)' \
|
--database-url='postgres://$(POSTGRES_USER):$(POSTGRES_PASSWORD)@localhost:25432/$(POSTGRES_DB)' \
|
||||||
--date-time-crate='chrono' \
|
--date-time-crate='chrono' \
|
||||||
|
--with-serde='both' \
|
||||||
--model-extra-attributes='NAPI_EXTRA_ATTR_PLACEHOLDER' && \
|
--model-extra-attributes='NAPI_EXTRA_ATTR_PLACEHOLDER' && \
|
||||||
for file in src/model/entity/*; do \
|
for file in src/model/entity/*; do \
|
||||||
base=$$(basename -- "$${file}"); \
|
base=$$(basename -- "$${file}"); \
|
||||||
jsname=$$(printf '%s\n' "$${base%.*}" | perl -pe 's/(^|_)./uc($$&)/ge;s/_//g'); \
|
jsname=$$(printf '%s\n' "$${base%.*}" | perl -pe 's/(^|_)./uc($$&)/ge;s/_//g'); \
|
||||||
attribute=$$(printf 'cfg_attr(feature = "napi", napi_derive::napi(object, js_name = "%s", use_nullable = true))' "$${jsname}"); \
|
attribute=$$(printf 'macros::export(object, js_name = "%s")' "$${jsname}"); \
|
||||||
sed -i "s/NAPI_EXTRA_ATTR_PLACEHOLDER/$${attribute}/" "$${file}"; \
|
sed -i "s/NAPI_EXTRA_ATTR_PLACEHOLDER/$${attribute}/" "$${file}"; \
|
||||||
sed -i 's/#\[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)\]/#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Serialize, serde::Deserialize)]\n#[serde(rename_all = "camelCase")]/' "$${file}"; \
|
sed -i 's/#\[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)\]/#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]\n#[serde(rename_all = "camelCase")]/' "$${file}"; \
|
||||||
done
|
done
|
||||||
sed -i 's/#\[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)\]/#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, serde::Serialize, serde::Deserialize)]\n#[serde(rename_all = "camelCase")]\n#[cfg_attr(not(feature = "napi"), derive(Clone))]\n#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))]/' \
|
sed -i 's/#\[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)\]/#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)]\n#[serde(rename_all = "camelCase")]\n#[macros::derive_clone_and_export(string_enum = "camelCase")]/' \
|
||||||
src/model/entity/sea_orm_active_enums.rs
|
src/model/entity/sea_orm_active_enums.rs
|
||||||
cargo fmt --all --
|
cargo fmt --all --
|
||||||
|
|
||||||
.PHONY: update-index
|
.PHONY: update-index
|
||||||
update-index: index.js index.d.ts
|
update-index: index.js index.d.ts
|
||||||
|
|
||||||
index.js index.d.ts: $(SRC)
|
index.js index.d.ts: $(SRC) package.json
|
||||||
NODE_OPTIONS='--max_old_space_size=3072' pnpm run build:debug
|
NODE_OPTIONS='--max_old_space_size=3072' pnpm run build:debug
|
||||||
[ -f built/index.js ] && [ -f built/index.d.ts ]
|
[ -f built/index.js ] && [ -f built/index.d.ts ]
|
||||||
rm --force index.js index.d.ts
|
rm --force index.js index.d.ts
|
||||||
cp built/index.js index.js
|
cp built/index.js index.js
|
||||||
cp built/index.d.ts index.d.ts
|
cp built/index.d.ts index.d.ts
|
||||||
sed -i 's/^ \*r"/ */g' index.d.ts
|
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
extern crate napi_build;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// watch the version in the project root package.json
|
// watch the version in the project root package.json
|
||||||
println!("cargo:rerun-if-changed=../../package.json");
|
println!("cargo:rerun-if-changed=../../package.json");
|
||||||
|
|
||||||
// napi
|
// napi
|
||||||
|
#[cfg(feature = "napi")]
|
||||||
napi_build::setup();
|
napi_build::setup();
|
||||||
}
|
}
|
||||||
|
|
1626
packages/backend-rs/index.d.ts
vendored
1626
packages/backend-rs/index.d.ts
vendored
File diff suppressed because it is too large
Load diff
|
@ -1,397 +1,453 @@
|
||||||
/* tslint:disable */
|
// prettier-ignore
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
/* prettier-ignore */
|
|
||||||
|
|
||||||
/* auto-generated by NAPI-RS */
|
/* auto-generated by NAPI-RS */
|
||||||
|
|
||||||
const { existsSync, readFileSync } = require('fs')
|
const { readFileSync } = require('fs')
|
||||||
const { join } = require('path')
|
|
||||||
|
|
||||||
const { platform, arch } = process
|
|
||||||
|
|
||||||
let nativeBinding = null
|
let nativeBinding = null
|
||||||
let localFileExisted = false
|
const loadErrors = []
|
||||||
let loadError = null
|
|
||||||
|
|
||||||
function isMusl() {
|
const isMusl = () => {
|
||||||
// For Node 10
|
let musl = false
|
||||||
if (!process.report || typeof process.report.getReport !== 'function') {
|
if (process.platform === 'linux') {
|
||||||
try {
|
musl = isMuslFromFilesystem()
|
||||||
const lddPath = require('child_process').execSync('which ldd').toString().trim()
|
if (musl === null) {
|
||||||
return readFileSync(lddPath, 'utf8').includes('musl')
|
musl = isMuslFromReport()
|
||||||
} catch (e) {
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
} else {
|
if (musl === null) {
|
||||||
const { glibcVersionRuntime } = process.report.getReport().header
|
musl = isMuslFromChildProcess()
|
||||||
return !glibcVersionRuntime
|
}
|
||||||
|
}
|
||||||
|
return musl
|
||||||
|
}
|
||||||
|
|
||||||
|
const isFileMusl = (f) => f.includes('libc.musl-') || f.includes('ld-musl-')
|
||||||
|
|
||||||
|
const isMuslFromFilesystem = () => {
|
||||||
|
try {
|
||||||
|
return readFileSync('/usr/bin/ldd', 'utf-8').includes('musl')
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (platform) {
|
const isMuslFromReport = () => {
|
||||||
case 'android':
|
const report = typeof process.report.getReport === 'function' ? process.report.getReport() : null
|
||||||
switch (arch) {
|
if (!report) {
|
||||||
case 'arm64':
|
return null
|
||||||
localFileExisted = existsSync(join(__dirname, 'backend-rs.android-arm64.node'))
|
}
|
||||||
try {
|
if (report.header && report.header.glibcVersionRuntime) {
|
||||||
if (localFileExisted) {
|
return false
|
||||||
nativeBinding = require('./backend-rs.android-arm64.node')
|
}
|
||||||
} else {
|
if (Array.isArray(report.sharedObjects)) {
|
||||||
nativeBinding = require('backend-rs-android-arm64')
|
if (report.sharedObjects.some(isFileMusl)) {
|
||||||
}
|
return true
|
||||||
} catch (e) {
|
|
||||||
loadError = e
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'arm':
|
|
||||||
localFileExisted = existsSync(join(__dirname, 'backend-rs.android-arm-eabi.node'))
|
|
||||||
try {
|
|
||||||
if (localFileExisted) {
|
|
||||||
nativeBinding = require('./backend-rs.android-arm-eabi.node')
|
|
||||||
} else {
|
|
||||||
nativeBinding = require('backend-rs-android-arm-eabi')
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
loadError = e
|
|
||||||
}
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
throw new Error(`Unsupported architecture on Android ${arch}`)
|
|
||||||
}
|
}
|
||||||
break
|
}
|
||||||
case 'win32':
|
return false
|
||||||
switch (arch) {
|
}
|
||||||
case 'x64':
|
|
||||||
localFileExisted = existsSync(
|
const isMuslFromChildProcess = () => {
|
||||||
join(__dirname, 'backend-rs.win32-x64-msvc.node')
|
try {
|
||||||
)
|
return require('child_process').execSync('ldd --version', { encoding: 'utf8' }).includes('musl')
|
||||||
try {
|
} catch (e) {
|
||||||
if (localFileExisted) {
|
// If we reach this case, we don't know if the system is musl or not, so is better to just fallback to false
|
||||||
nativeBinding = require('./backend-rs.win32-x64-msvc.node')
|
return false
|
||||||
} else {
|
}
|
||||||
nativeBinding = require('backend-rs-win32-x64-msvc')
|
}
|
||||||
}
|
|
||||||
} catch (e) {
|
function requireNative() {
|
||||||
loadError = e
|
if (process.platform === 'android') {
|
||||||
}
|
if (process.arch === 'arm64') {
|
||||||
break
|
try {
|
||||||
case 'ia32':
|
return require('./backend-rs.android-arm64.node')
|
||||||
localFileExisted = existsSync(
|
} catch (e) {
|
||||||
join(__dirname, 'backend-rs.win32-ia32-msvc.node')
|
loadErrors.push(e)
|
||||||
)
|
|
||||||
try {
|
|
||||||
if (localFileExisted) {
|
|
||||||
nativeBinding = require('./backend-rs.win32-ia32-msvc.node')
|
|
||||||
} else {
|
|
||||||
nativeBinding = require('backend-rs-win32-ia32-msvc')
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
loadError = e
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'arm64':
|
|
||||||
localFileExisted = existsSync(
|
|
||||||
join(__dirname, 'backend-rs.win32-arm64-msvc.node')
|
|
||||||
)
|
|
||||||
try {
|
|
||||||
if (localFileExisted) {
|
|
||||||
nativeBinding = require('./backend-rs.win32-arm64-msvc.node')
|
|
||||||
} else {
|
|
||||||
nativeBinding = require('backend-rs-win32-arm64-msvc')
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
loadError = e
|
|
||||||
}
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
throw new Error(`Unsupported architecture on Windows: ${arch}`)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'darwin':
|
|
||||||
localFileExisted = existsSync(join(__dirname, 'backend-rs.darwin-universal.node'))
|
|
||||||
try {
|
|
||||||
if (localFileExisted) {
|
|
||||||
nativeBinding = require('./backend-rs.darwin-universal.node')
|
|
||||||
} else {
|
|
||||||
nativeBinding = require('backend-rs-darwin-universal')
|
|
||||||
}
|
}
|
||||||
break
|
try {
|
||||||
} catch {}
|
return require('backend-rs-android-arm64')
|
||||||
switch (arch) {
|
} catch (e) {
|
||||||
case 'x64':
|
loadErrors.push(e)
|
||||||
localFileExisted = existsSync(join(__dirname, 'backend-rs.darwin-x64.node'))
|
|
||||||
try {
|
|
||||||
if (localFileExisted) {
|
|
||||||
nativeBinding = require('./backend-rs.darwin-x64.node')
|
|
||||||
} else {
|
|
||||||
nativeBinding = require('backend-rs-darwin-x64')
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
loadError = e
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'arm64':
|
|
||||||
localFileExisted = existsSync(
|
|
||||||
join(__dirname, 'backend-rs.darwin-arm64.node')
|
|
||||||
)
|
|
||||||
try {
|
|
||||||
if (localFileExisted) {
|
|
||||||
nativeBinding = require('./backend-rs.darwin-arm64.node')
|
|
||||||
} else {
|
|
||||||
nativeBinding = require('backend-rs-darwin-arm64')
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
loadError = e
|
|
||||||
}
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
throw new Error(`Unsupported architecture on macOS: ${arch}`)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'freebsd':
|
|
||||||
if (arch !== 'x64') {
|
|
||||||
throw new Error(`Unsupported architecture on FreeBSD: ${arch}`)
|
|
||||||
}
|
|
||||||
localFileExisted = existsSync(join(__dirname, 'backend-rs.freebsd-x64.node'))
|
|
||||||
try {
|
|
||||||
if (localFileExisted) {
|
|
||||||
nativeBinding = require('./backend-rs.freebsd-x64.node')
|
|
||||||
} else {
|
|
||||||
nativeBinding = require('backend-rs-freebsd-x64')
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
loadError = e
|
} else if (process.arch === 'arm') {
|
||||||
|
try {
|
||||||
|
return require('./backend-rs.android-arm-eabi.node')
|
||||||
|
} catch (e) {
|
||||||
|
loadErrors.push(e)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return require('backend-rs-android-arm-eabi')
|
||||||
|
} catch (e) {
|
||||||
|
loadErrors.push(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
loadErrors.push(new Error(`Unsupported architecture on Android ${process.arch}`))
|
||||||
}
|
}
|
||||||
break
|
} else if (process.platform === 'win32') {
|
||||||
case 'linux':
|
if (process.arch === 'x64') {
|
||||||
switch (arch) {
|
try {
|
||||||
case 'x64':
|
return require('./backend-rs.win32-x64-msvc.node')
|
||||||
if (isMusl()) {
|
} catch (e) {
|
||||||
localFileExisted = existsSync(
|
loadErrors.push(e)
|
||||||
join(__dirname, 'backend-rs.linux-x64-musl.node')
|
}
|
||||||
)
|
try {
|
||||||
try {
|
return require('backend-rs-win32-x64-msvc')
|
||||||
if (localFileExisted) {
|
} catch (e) {
|
||||||
nativeBinding = require('./backend-rs.linux-x64-musl.node')
|
loadErrors.push(e)
|
||||||
} else {
|
}
|
||||||
nativeBinding = require('backend-rs-linux-x64-musl')
|
|
||||||
}
|
} else if (process.arch === 'ia32') {
|
||||||
} catch (e) {
|
try {
|
||||||
loadError = e
|
return require('./backend-rs.win32-ia32-msvc.node')
|
||||||
}
|
} catch (e) {
|
||||||
} else {
|
loadErrors.push(e)
|
||||||
localFileExisted = existsSync(
|
}
|
||||||
join(__dirname, 'backend-rs.linux-x64-gnu.node')
|
try {
|
||||||
)
|
return require('backend-rs-win32-ia32-msvc')
|
||||||
try {
|
} catch (e) {
|
||||||
if (localFileExisted) {
|
loadErrors.push(e)
|
||||||
nativeBinding = require('./backend-rs.linux-x64-gnu.node')
|
}
|
||||||
} else {
|
|
||||||
nativeBinding = require('backend-rs-linux-x64-gnu')
|
} else if (process.arch === 'arm64') {
|
||||||
}
|
try {
|
||||||
} catch (e) {
|
return require('./backend-rs.win32-arm64-msvc.node')
|
||||||
loadError = e
|
} catch (e) {
|
||||||
}
|
loadErrors.push(e)
|
||||||
}
|
}
|
||||||
break
|
try {
|
||||||
case 'arm64':
|
return require('backend-rs-win32-arm64-msvc')
|
||||||
if (isMusl()) {
|
} catch (e) {
|
||||||
localFileExisted = existsSync(
|
loadErrors.push(e)
|
||||||
join(__dirname, 'backend-rs.linux-arm64-musl.node')
|
}
|
||||||
)
|
|
||||||
try {
|
} else {
|
||||||
if (localFileExisted) {
|
loadErrors.push(new Error(`Unsupported architecture on Windows: ${process.arch}`))
|
||||||
nativeBinding = require('./backend-rs.linux-arm64-musl.node')
|
}
|
||||||
} else {
|
} else if (process.platform === 'darwin') {
|
||||||
nativeBinding = require('backend-rs-linux-arm64-musl')
|
try {
|
||||||
}
|
return require('./backend-rs.darwin-universal.node')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
loadError = e
|
loadErrors.push(e)
|
||||||
}
|
}
|
||||||
} else {
|
try {
|
||||||
localFileExisted = existsSync(
|
return require('backend-rs-darwin-universal')
|
||||||
join(__dirname, 'backend-rs.linux-arm64-gnu.node')
|
} catch (e) {
|
||||||
)
|
loadErrors.push(e)
|
||||||
try {
|
}
|
||||||
if (localFileExisted) {
|
|
||||||
nativeBinding = require('./backend-rs.linux-arm64-gnu.node')
|
if (process.arch === 'x64') {
|
||||||
} else {
|
try {
|
||||||
nativeBinding = require('backend-rs-linux-arm64-gnu')
|
return require('./backend-rs.darwin-x64.node')
|
||||||
}
|
} catch (e) {
|
||||||
} catch (e) {
|
loadErrors.push(e)
|
||||||
loadError = e
|
}
|
||||||
}
|
try {
|
||||||
}
|
return require('backend-rs-darwin-x64')
|
||||||
break
|
} catch (e) {
|
||||||
case 'arm':
|
loadErrors.push(e)
|
||||||
if (isMusl()) {
|
}
|
||||||
localFileExisted = existsSync(
|
|
||||||
join(__dirname, 'backend-rs.linux-arm-musleabihf.node')
|
} else if (process.arch === 'arm64') {
|
||||||
)
|
try {
|
||||||
try {
|
return require('./backend-rs.darwin-arm64.node')
|
||||||
if (localFileExisted) {
|
} catch (e) {
|
||||||
nativeBinding = require('./backend-rs.linux-arm-musleabihf.node')
|
loadErrors.push(e)
|
||||||
} else {
|
}
|
||||||
nativeBinding = require('backend-rs-linux-arm-musleabihf')
|
try {
|
||||||
}
|
return require('backend-rs-darwin-arm64')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
loadError = e
|
loadErrors.push(e)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
localFileExisted = existsSync(
|
} else {
|
||||||
join(__dirname, 'backend-rs.linux-arm-gnueabihf.node')
|
loadErrors.push(new Error(`Unsupported architecture on macOS: ${process.arch}`))
|
||||||
)
|
}
|
||||||
try {
|
} else if (process.platform === 'freebsd') {
|
||||||
if (localFileExisted) {
|
if (process.arch === 'x64') {
|
||||||
nativeBinding = require('./backend-rs.linux-arm-gnueabihf.node')
|
try {
|
||||||
} else {
|
return require('./backend-rs.freebsd-x64.node')
|
||||||
nativeBinding = require('backend-rs-linux-arm-gnueabihf')
|
} catch (e) {
|
||||||
}
|
loadErrors.push(e)
|
||||||
} catch (e) {
|
}
|
||||||
loadError = e
|
try {
|
||||||
}
|
return require('backend-rs-freebsd-x64')
|
||||||
}
|
} catch (e) {
|
||||||
break
|
loadErrors.push(e)
|
||||||
case 'riscv64':
|
}
|
||||||
if (isMusl()) {
|
|
||||||
localFileExisted = existsSync(
|
} else if (process.arch === 'arm64') {
|
||||||
join(__dirname, 'backend-rs.linux-riscv64-musl.node')
|
try {
|
||||||
)
|
return require('./backend-rs.freebsd-arm64.node')
|
||||||
try {
|
} catch (e) {
|
||||||
if (localFileExisted) {
|
loadErrors.push(e)
|
||||||
nativeBinding = require('./backend-rs.linux-riscv64-musl.node')
|
}
|
||||||
} else {
|
try {
|
||||||
nativeBinding = require('backend-rs-linux-riscv64-musl')
|
return require('backend-rs-freebsd-arm64')
|
||||||
}
|
} catch (e) {
|
||||||
} catch (e) {
|
loadErrors.push(e)
|
||||||
loadError = e
|
}
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
localFileExisted = existsSync(
|
loadErrors.push(new Error(`Unsupported architecture on FreeBSD: ${process.arch}`))
|
||||||
join(__dirname, 'backend-rs.linux-riscv64-gnu.node')
|
}
|
||||||
)
|
} else if (process.platform === 'linux') {
|
||||||
try {
|
if (process.arch === 'x64') {
|
||||||
if (localFileExisted) {
|
if (isMusl()) {
|
||||||
nativeBinding = require('./backend-rs.linux-riscv64-gnu.node')
|
|
||||||
} else {
|
|
||||||
nativeBinding = require('backend-rs-linux-riscv64-gnu')
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
loadError = e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 's390x':
|
|
||||||
localFileExisted = existsSync(
|
|
||||||
join(__dirname, 'backend-rs.linux-s390x-gnu.node')
|
|
||||||
)
|
|
||||||
try {
|
try {
|
||||||
if (localFileExisted) {
|
return require('./backend-rs.linux-x64-musl.node')
|
||||||
nativeBinding = require('./backend-rs.linux-s390x-gnu.node')
|
} catch (e) {
|
||||||
} else {
|
loadErrors.push(e)
|
||||||
nativeBinding = require('backend-rs-linux-s390x-gnu')
|
}
|
||||||
}
|
try {
|
||||||
} catch (e) {
|
return require('backend-rs-linux-x64-musl')
|
||||||
loadError = e
|
} catch (e) {
|
||||||
}
|
loadErrors.push(e)
|
||||||
break
|
}
|
||||||
default:
|
|
||||||
throw new Error(`Unsupported architecture on Linux: ${arch}`)
|
} else {
|
||||||
|
try {
|
||||||
|
return require('./backend-rs.linux-x64-gnu.node')
|
||||||
|
} catch (e) {
|
||||||
|
loadErrors.push(e)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return require('backend-rs-linux-x64-gnu')
|
||||||
|
} catch (e) {
|
||||||
|
loadErrors.push(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} else if (process.arch === 'arm64') {
|
||||||
|
if (isMusl()) {
|
||||||
|
try {
|
||||||
|
return require('./backend-rs.linux-arm64-musl.node')
|
||||||
|
} catch (e) {
|
||||||
|
loadErrors.push(e)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return require('backend-rs-linux-arm64-musl')
|
||||||
|
} catch (e) {
|
||||||
|
loadErrors.push(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
return require('./backend-rs.linux-arm64-gnu.node')
|
||||||
|
} catch (e) {
|
||||||
|
loadErrors.push(e)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return require('backend-rs-linux-arm64-gnu')
|
||||||
|
} catch (e) {
|
||||||
|
loadErrors.push(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} else if (process.arch === 'arm') {
|
||||||
|
if (isMusl()) {
|
||||||
|
try {
|
||||||
|
return require('./backend-rs.linux-arm-musleabihf.node')
|
||||||
|
} catch (e) {
|
||||||
|
loadErrors.push(e)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return require('backend-rs-linux-arm-musleabihf')
|
||||||
|
} catch (e) {
|
||||||
|
loadErrors.push(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
return require('./backend-rs.linux-arm-gnueabihf.node')
|
||||||
|
} catch (e) {
|
||||||
|
loadErrors.push(e)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return require('backend-rs-linux-arm-gnueabihf')
|
||||||
|
} catch (e) {
|
||||||
|
loadErrors.push(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} else if (process.arch === 'riscv64') {
|
||||||
|
if (isMusl()) {
|
||||||
|
try {
|
||||||
|
return require('./backend-rs.linux-riscv64-musl.node')
|
||||||
|
} catch (e) {
|
||||||
|
loadErrors.push(e)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return require('backend-rs-linux-riscv64-musl')
|
||||||
|
} catch (e) {
|
||||||
|
loadErrors.push(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
return require('./backend-rs.linux-riscv64-gnu.node')
|
||||||
|
} catch (e) {
|
||||||
|
loadErrors.push(e)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return require('backend-rs-linux-riscv64-gnu')
|
||||||
|
} catch (e) {
|
||||||
|
loadErrors.push(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} else if (process.arch === 'ppc64') {
|
||||||
|
try {
|
||||||
|
return require('./backend-rs.linux-ppc64-gnu.node')
|
||||||
|
} catch (e) {
|
||||||
|
loadErrors.push(e)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return require('backend-rs-linux-ppc64-gnu')
|
||||||
|
} catch (e) {
|
||||||
|
loadErrors.push(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (process.arch === 's390x') {
|
||||||
|
try {
|
||||||
|
return require('./backend-rs.linux-s390x-gnu.node')
|
||||||
|
} catch (e) {
|
||||||
|
loadErrors.push(e)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return require('backend-rs-linux-s390x-gnu')
|
||||||
|
} catch (e) {
|
||||||
|
loadErrors.push(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
loadErrors.push(new Error(`Unsupported architecture on Linux: ${process.arch}`))
|
||||||
}
|
}
|
||||||
break
|
} else {
|
||||||
default:
|
loadErrors.push(new Error(`Unsupported OS: ${process.platform}, architecture: ${process.arch}`))
|
||||||
throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nativeBinding = requireNative()
|
||||||
|
|
||||||
|
if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) {
|
||||||
|
try {
|
||||||
|
nativeBinding = require('./backend-rs.wasi.cjs')
|
||||||
|
} catch (err) {
|
||||||
|
if (process.env.NAPI_RS_FORCE_WASI) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!nativeBinding) {
|
||||||
|
try {
|
||||||
|
nativeBinding = require('backend-rs-wasm32-wasi')
|
||||||
|
} catch (err) {
|
||||||
|
if (process.env.NAPI_RS_FORCE_WASI) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!nativeBinding) {
|
if (!nativeBinding) {
|
||||||
if (loadError) {
|
if (loadErrors.length > 0) {
|
||||||
throw loadError
|
// TODO Link to documentation with potential fixes
|
||||||
|
// - The package owner could build/publish bindings for this arch
|
||||||
|
// - The user may need to bundle the correct files
|
||||||
|
// - The user may need to re-install node_modules to get new packages
|
||||||
|
throw new Error('Failed to load native binding', { cause: loadErrors })
|
||||||
}
|
}
|
||||||
throw new Error(`Failed to load native binding`)
|
throw new Error(`Failed to load native binding`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { SECOND, MINUTE, HOUR, DAY, USER_ONLINE_THRESHOLD, USER_ACTIVE_THRESHOLD, FILE_TYPE_BROWSERSAFE, loadEnv, loadConfig, stringToAcct, acctToString, fetchNodeinfo, nodeinfo_2_1, nodeinfo_2_0, Protocol, Inbound, Outbound, greet, initializeRustLogger, showServerInfo, isBlockedServer, isSilencedServer, isAllowedServer, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, safeForSql, formatMilliseconds, getImageSizeFromUrl, getNoteSummary, isQuote, isSafeUrl, latestVersion, toMastodonId, fromMastodonId, fetchMeta, metaToPugArgs, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, removeOldAttestationChallenges, cpuInfo, cpuUsage, memoryUsage, storageUsage, AntennaSrc, DriveFileUsageHint, MutedNoteReason, NoteVisibility, NotificationType, PageVisibility, PollNoteVisibility, RelayStatus, UserEmojiModPerm, UserProfileFfvisibility, UserProfileMutingNotificationTypes, updateAntennasOnNewNote, watchNote, unwatchNote, PushNotificationKind, sendPushNotification, publishToChannelStream, ChatEvent, publishToChatStream, ChatIndexEvent, publishToChatIndexStream, publishToBroadcastStream, publishToGroupChatStream, publishToModerationStream, getTimestamp, genId, genIdAt, generateSecureRandomString, generateUserToken } = nativeBinding
|
module.exports.acctToString = nativeBinding.acctToString
|
||||||
|
module.exports.AntennaSrc = nativeBinding.AntennaSrc
|
||||||
module.exports.SECOND = SECOND
|
module.exports.ChatEvent = nativeBinding.ChatEvent
|
||||||
module.exports.MINUTE = MINUTE
|
module.exports.ChatIndexEvent = nativeBinding.ChatIndexEvent
|
||||||
module.exports.HOUR = HOUR
|
module.exports.checkWordMute = nativeBinding.checkWordMute
|
||||||
module.exports.DAY = DAY
|
module.exports.countReactions = nativeBinding.countReactions
|
||||||
module.exports.USER_ONLINE_THRESHOLD = USER_ONLINE_THRESHOLD
|
module.exports.cpuInfo = nativeBinding.cpuInfo
|
||||||
module.exports.USER_ACTIVE_THRESHOLD = USER_ACTIVE_THRESHOLD
|
module.exports.cpuUsage = nativeBinding.cpuUsage
|
||||||
module.exports.FILE_TYPE_BROWSERSAFE = FILE_TYPE_BROWSERSAFE
|
module.exports.DAY = nativeBinding.DAY
|
||||||
module.exports.loadEnv = loadEnv
|
module.exports.decodeReaction = nativeBinding.decodeReaction
|
||||||
module.exports.loadConfig = loadConfig
|
module.exports.DriveFileEvent = nativeBinding.DriveFileEvent
|
||||||
module.exports.stringToAcct = stringToAcct
|
module.exports.DriveFileUsageHint = nativeBinding.DriveFileUsageHint
|
||||||
module.exports.acctToString = acctToString
|
module.exports.DriveFolderEvent = nativeBinding.DriveFolderEvent
|
||||||
module.exports.fetchNodeinfo = fetchNodeinfo
|
module.exports.extractHost = nativeBinding.extractHost
|
||||||
module.exports.nodeinfo_2_1 = nodeinfo_2_1
|
module.exports.fetchMeta = nativeBinding.fetchMeta
|
||||||
module.exports.nodeinfo_2_0 = nodeinfo_2_0
|
module.exports.fetchNodeinfo = nativeBinding.fetchNodeinfo
|
||||||
module.exports.Protocol = Protocol
|
module.exports.FILE_TYPE_BROWSERSAFE = nativeBinding.FILE_TYPE_BROWSERSAFE
|
||||||
module.exports.Inbound = Inbound
|
module.exports.formatMilliseconds = nativeBinding.formatMilliseconds
|
||||||
module.exports.Outbound = Outbound
|
module.exports.fromMastodonId = nativeBinding.fromMastodonId
|
||||||
module.exports.greet = greet
|
module.exports.generateSecureRandomString = nativeBinding.generateSecureRandomString
|
||||||
module.exports.initializeRustLogger = initializeRustLogger
|
module.exports.generateUserToken = nativeBinding.generateUserToken
|
||||||
module.exports.showServerInfo = showServerInfo
|
module.exports.genId = nativeBinding.genId
|
||||||
module.exports.isBlockedServer = isBlockedServer
|
module.exports.genIdAt = nativeBinding.genIdAt
|
||||||
module.exports.isSilencedServer = isSilencedServer
|
module.exports.getFullApAccount = nativeBinding.getFullApAccount
|
||||||
module.exports.isAllowedServer = isAllowedServer
|
module.exports.getImageSizeFromUrl = nativeBinding.getImageSizeFromUrl
|
||||||
module.exports.checkWordMute = checkWordMute
|
module.exports.getNoteSummary = nativeBinding.getNoteSummary
|
||||||
module.exports.getFullApAccount = getFullApAccount
|
module.exports.getTimestamp = nativeBinding.getTimestamp
|
||||||
module.exports.isSelfHost = isSelfHost
|
module.exports.greet = nativeBinding.greet
|
||||||
module.exports.isSameOrigin = isSameOrigin
|
module.exports.hashPassword = nativeBinding.hashPassword
|
||||||
module.exports.extractHost = extractHost
|
module.exports.HOUR = nativeBinding.HOUR
|
||||||
module.exports.toPuny = toPuny
|
module.exports.Inbound = nativeBinding.Inbound
|
||||||
module.exports.isUnicodeEmoji = isUnicodeEmoji
|
module.exports.initializeRustLogger = nativeBinding.initializeRustLogger
|
||||||
module.exports.sqlLikeEscape = sqlLikeEscape
|
module.exports.isAllowedServer = nativeBinding.isAllowedServer
|
||||||
module.exports.safeForSql = safeForSql
|
module.exports.isBlockedServer = nativeBinding.isBlockedServer
|
||||||
module.exports.formatMilliseconds = formatMilliseconds
|
module.exports.isOldPasswordAlgorithm = nativeBinding.isOldPasswordAlgorithm
|
||||||
module.exports.getImageSizeFromUrl = getImageSizeFromUrl
|
module.exports.isQuote = nativeBinding.isQuote
|
||||||
module.exports.getNoteSummary = getNoteSummary
|
module.exports.isSafeUrl = nativeBinding.isSafeUrl
|
||||||
module.exports.isQuote = isQuote
|
module.exports.isSameOrigin = nativeBinding.isSameOrigin
|
||||||
module.exports.isSafeUrl = isSafeUrl
|
module.exports.isSelfHost = nativeBinding.isSelfHost
|
||||||
module.exports.latestVersion = latestVersion
|
module.exports.isSilencedServer = nativeBinding.isSilencedServer
|
||||||
module.exports.toMastodonId = toMastodonId
|
module.exports.isUnicodeEmoji = nativeBinding.isUnicodeEmoji
|
||||||
module.exports.fromMastodonId = fromMastodonId
|
module.exports.latestVersion = nativeBinding.latestVersion
|
||||||
module.exports.fetchMeta = fetchMeta
|
module.exports.loadConfig = nativeBinding.loadConfig
|
||||||
module.exports.metaToPugArgs = metaToPugArgs
|
module.exports.memoryUsage = nativeBinding.memoryUsage
|
||||||
module.exports.nyaify = nyaify
|
module.exports.metaToPugArgs = nativeBinding.metaToPugArgs
|
||||||
module.exports.hashPassword = hashPassword
|
module.exports.MINUTE = nativeBinding.MINUTE
|
||||||
module.exports.verifyPassword = verifyPassword
|
module.exports.MutedNoteReason = nativeBinding.MutedNoteReason
|
||||||
module.exports.isOldPasswordAlgorithm = isOldPasswordAlgorithm
|
module.exports.nodeinfo_2_0 = nativeBinding.nodeinfo_2_0
|
||||||
module.exports.decodeReaction = decodeReaction
|
module.exports.nodeinfo_2_1 = nativeBinding.nodeinfo_2_1
|
||||||
module.exports.countReactions = countReactions
|
module.exports.NoteVisibility = nativeBinding.NoteVisibility
|
||||||
module.exports.toDbReaction = toDbReaction
|
module.exports.NotificationType = nativeBinding.NotificationType
|
||||||
module.exports.removeOldAttestationChallenges = removeOldAttestationChallenges
|
module.exports.nyaify = nativeBinding.nyaify
|
||||||
module.exports.cpuInfo = cpuInfo
|
module.exports.Outbound = nativeBinding.Outbound
|
||||||
module.exports.cpuUsage = cpuUsage
|
module.exports.PageVisibility = nativeBinding.PageVisibility
|
||||||
module.exports.memoryUsage = memoryUsage
|
module.exports.PollNoteVisibility = nativeBinding.PollNoteVisibility
|
||||||
module.exports.storageUsage = storageUsage
|
module.exports.Protocol = nativeBinding.Protocol
|
||||||
module.exports.AntennaSrc = AntennaSrc
|
module.exports.publishToBroadcastStream = nativeBinding.publishToBroadcastStream
|
||||||
module.exports.DriveFileUsageHint = DriveFileUsageHint
|
module.exports.publishToChannelStream = nativeBinding.publishToChannelStream
|
||||||
module.exports.MutedNoteReason = MutedNoteReason
|
module.exports.publishToChatIndexStream = nativeBinding.publishToChatIndexStream
|
||||||
module.exports.NoteVisibility = NoteVisibility
|
module.exports.publishToChatStream = nativeBinding.publishToChatStream
|
||||||
module.exports.NotificationType = NotificationType
|
module.exports.publishToDriveFileStream = nativeBinding.publishToDriveFileStream
|
||||||
module.exports.PageVisibility = PageVisibility
|
module.exports.publishToDriveFolderStream = nativeBinding.publishToDriveFolderStream
|
||||||
module.exports.PollNoteVisibility = PollNoteVisibility
|
module.exports.publishToGroupChatStream = nativeBinding.publishToGroupChatStream
|
||||||
module.exports.RelayStatus = RelayStatus
|
module.exports.publishToModerationStream = nativeBinding.publishToModerationStream
|
||||||
module.exports.UserEmojiModPerm = UserEmojiModPerm
|
module.exports.publishToNotesStream = nativeBinding.publishToNotesStream
|
||||||
module.exports.UserProfileFfvisibility = UserProfileFfvisibility
|
module.exports.PushNotificationKind = nativeBinding.PushNotificationKind
|
||||||
module.exports.UserProfileMutingNotificationTypes = UserProfileMutingNotificationTypes
|
module.exports.RelayStatus = nativeBinding.RelayStatus
|
||||||
module.exports.updateAntennasOnNewNote = updateAntennasOnNewNote
|
module.exports.removeOldAttestationChallenges = nativeBinding.removeOldAttestationChallenges
|
||||||
module.exports.watchNote = watchNote
|
module.exports.safeForSql = nativeBinding.safeForSql
|
||||||
module.exports.unwatchNote = unwatchNote
|
module.exports.SECOND = nativeBinding.SECOND
|
||||||
module.exports.PushNotificationKind = PushNotificationKind
|
module.exports.sendPushNotification = nativeBinding.sendPushNotification
|
||||||
module.exports.sendPushNotification = sendPushNotification
|
module.exports.showServerInfo = nativeBinding.showServerInfo
|
||||||
module.exports.publishToChannelStream = publishToChannelStream
|
module.exports.sqlLikeEscape = nativeBinding.sqlLikeEscape
|
||||||
module.exports.ChatEvent = ChatEvent
|
module.exports.storageUsage = nativeBinding.storageUsage
|
||||||
module.exports.publishToChatStream = publishToChatStream
|
module.exports.stringToAcct = nativeBinding.stringToAcct
|
||||||
module.exports.ChatIndexEvent = ChatIndexEvent
|
module.exports.toDbReaction = nativeBinding.toDbReaction
|
||||||
module.exports.publishToChatIndexStream = publishToChatIndexStream
|
module.exports.toMastodonId = nativeBinding.toMastodonId
|
||||||
module.exports.publishToBroadcastStream = publishToBroadcastStream
|
module.exports.toPuny = nativeBinding.toPuny
|
||||||
module.exports.publishToGroupChatStream = publishToGroupChatStream
|
module.exports.unwatchNote = nativeBinding.unwatchNote
|
||||||
module.exports.publishToModerationStream = publishToModerationStream
|
module.exports.updateAntennaCache = nativeBinding.updateAntennaCache
|
||||||
module.exports.getTimestamp = getTimestamp
|
module.exports.updateAntennasOnNewNote = nativeBinding.updateAntennasOnNewNote
|
||||||
module.exports.genId = genId
|
module.exports.updateMetaCache = nativeBinding.updateMetaCache
|
||||||
module.exports.genIdAt = genIdAt
|
module.exports.updateNodeinfoCache = nativeBinding.updateNodeinfoCache
|
||||||
module.exports.generateSecureRandomString = generateSecureRandomString
|
module.exports.USER_ACTIVE_THRESHOLD = nativeBinding.USER_ACTIVE_THRESHOLD
|
||||||
module.exports.generateUserToken = generateUserToken
|
module.exports.USER_ONLINE_THRESHOLD = nativeBinding.USER_ONLINE_THRESHOLD
|
||||||
|
module.exports.UserEmojiModPerm = nativeBinding.UserEmojiModPerm
|
||||||
|
module.exports.UserProfileFfvisibility = nativeBinding.UserProfileFfvisibility
|
||||||
|
module.exports.UserProfileMutingNotificationTypes = nativeBinding.UserProfileMutingNotificationTypes
|
||||||
|
module.exports.verifyPassword = nativeBinding.verifyPassword
|
||||||
|
module.exports.watchNote = nativeBinding.watchNote
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
# `backend-rs-android-arm-eabi`
|
|
||||||
|
|
||||||
This is the **armv7-linux-androideabi** binary for `backend-rs`
|
|
|
@ -1,18 +0,0 @@
|
||||||
{
|
|
||||||
"name": "backend-rs-android-arm-eabi",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"os": [
|
|
||||||
"android"
|
|
||||||
],
|
|
||||||
"cpu": [
|
|
||||||
"arm"
|
|
||||||
],
|
|
||||||
"main": "backend-rs.android-arm-eabi.node",
|
|
||||||
"files": [
|
|
||||||
"backend-rs.android-arm-eabi.node"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
# `backend-rs-android-arm64`
|
|
||||||
|
|
||||||
This is the **aarch64-linux-android** binary for `backend-rs`
|
|
|
@ -1,18 +0,0 @@
|
||||||
{
|
|
||||||
"name": "backend-rs-android-arm64",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"os": [
|
|
||||||
"android"
|
|
||||||
],
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"main": "backend-rs.android-arm64.node",
|
|
||||||
"files": [
|
|
||||||
"backend-rs.android-arm64.node"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
# `backend-rs-darwin-arm64`
|
|
||||||
|
|
||||||
This is the **aarch64-apple-darwin** binary for `backend-rs`
|
|
|
@ -1,18 +0,0 @@
|
||||||
{
|
|
||||||
"name": "backend-rs-darwin-arm64",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
],
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"main": "backend-rs.darwin-arm64.node",
|
|
||||||
"files": [
|
|
||||||
"backend-rs.darwin-arm64.node"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
# `backend-rs-darwin-universal`
|
|
||||||
|
|
||||||
This is the **universal-apple-darwin** binary for `backend-rs`
|
|
|
@ -1,15 +0,0 @@
|
||||||
{
|
|
||||||
"name": "backend-rs-darwin-universal",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
],
|
|
||||||
"main": "backend-rs.darwin-universal.node",
|
|
||||||
"files": [
|
|
||||||
"backend-rs.darwin-universal.node"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
# `backend-rs-darwin-x64`
|
|
||||||
|
|
||||||
This is the **x86_64-apple-darwin** binary for `backend-rs`
|
|
|
@ -1,18 +0,0 @@
|
||||||
{
|
|
||||||
"name": "backend-rs-darwin-x64",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
],
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"main": "backend-rs.darwin-x64.node",
|
|
||||||
"files": [
|
|
||||||
"backend-rs.darwin-x64.node"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
# `backend-rs-freebsd-x64`
|
|
||||||
|
|
||||||
This is the **x86_64-unknown-freebsd** binary for `backend-rs`
|
|
|
@ -1,18 +0,0 @@
|
||||||
{
|
|
||||||
"name": "backend-rs-freebsd-x64",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"os": [
|
|
||||||
"freebsd"
|
|
||||||
],
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"main": "backend-rs.freebsd-x64.node",
|
|
||||||
"files": [
|
|
||||||
"backend-rs.freebsd-x64.node"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
# `backend-rs-linux-arm-gnueabihf`
|
|
||||||
|
|
||||||
This is the **armv7-unknown-linux-gnueabihf** binary for `backend-rs`
|
|
|
@ -1,18 +0,0 @@
|
||||||
{
|
|
||||||
"name": "backend-rs-linux-arm-gnueabihf",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"cpu": [
|
|
||||||
"arm"
|
|
||||||
],
|
|
||||||
"main": "backend-rs.linux-arm-gnueabihf.node",
|
|
||||||
"files": [
|
|
||||||
"backend-rs.linux-arm-gnueabihf.node"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
# `backend-rs-linux-arm64-gnu`
|
|
||||||
|
|
||||||
This is the **aarch64-unknown-linux-gnu** binary for `backend-rs`
|
|
|
@ -1,21 +0,0 @@
|
||||||
{
|
|
||||||
"name": "backend-rs-linux-arm64-gnu",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"main": "backend-rs.linux-arm64-gnu.node",
|
|
||||||
"files": [
|
|
||||||
"backend-rs.linux-arm64-gnu.node"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
},
|
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
# `backend-rs-linux-arm64-musl`
|
|
||||||
|
|
||||||
This is the **aarch64-unknown-linux-musl** binary for `backend-rs`
|
|
|
@ -1,21 +0,0 @@
|
||||||
{
|
|
||||||
"name": "backend-rs-linux-arm64-musl",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"main": "backend-rs.linux-arm64-musl.node",
|
|
||||||
"files": [
|
|
||||||
"backend-rs.linux-arm64-musl.node"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
},
|
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
# `backend-rs-linux-x64-gnu`
|
|
||||||
|
|
||||||
This is the **x86_64-unknown-linux-gnu** binary for `backend-rs`
|
|
|
@ -1,21 +0,0 @@
|
||||||
{
|
|
||||||
"name": "backend-rs-linux-x64-gnu",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"main": "backend-rs.linux-x64-gnu.node",
|
|
||||||
"files": [
|
|
||||||
"backend-rs.linux-x64-gnu.node"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
},
|
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
# `backend-rs-linux-x64-musl`
|
|
||||||
|
|
||||||
This is the **x86_64-unknown-linux-musl** binary for `backend-rs`
|
|
|
@ -1,21 +0,0 @@
|
||||||
{
|
|
||||||
"name": "backend-rs-linux-x64-musl",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"main": "backend-rs.linux-x64-musl.node",
|
|
||||||
"files": [
|
|
||||||
"backend-rs.linux-x64-musl.node"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
},
|
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
# `backend-rs-win32-arm64-msvc`
|
|
||||||
|
|
||||||
This is the **aarch64-pc-windows-msvc** binary for `backend-rs`
|
|
|
@ -1,18 +0,0 @@
|
||||||
{
|
|
||||||
"name": "backend-rs-win32-arm64-msvc",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
],
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"main": "backend-rs.win32-arm64-msvc.node",
|
|
||||||
"files": [
|
|
||||||
"backend-rs.win32-arm64-msvc.node"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
# `backend-rs-win32-ia32-msvc`
|
|
||||||
|
|
||||||
This is the **i686-pc-windows-msvc** binary for `backend-rs`
|
|
|
@ -1,18 +0,0 @@
|
||||||
{
|
|
||||||
"name": "backend-rs-win32-ia32-msvc",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
],
|
|
||||||
"cpu": [
|
|
||||||
"ia32"
|
|
||||||
],
|
|
||||||
"main": "backend-rs.win32-ia32-msvc.node",
|
|
||||||
"files": [
|
|
||||||
"backend-rs.win32-ia32-msvc.node"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
# `backend-rs-win32-x64-msvc`
|
|
||||||
|
|
||||||
This is the **x86_64-pc-windows-msvc** binary for `backend-rs`
|
|
|
@ -1,18 +0,0 @@
|
||||||
{
|
|
||||||
"name": "backend-rs-win32-x64-msvc",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
],
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"main": "backend-rs.win32-x64-msvc.node",
|
|
||||||
"files": [
|
|
||||||
"backend-rs.win32-x64-msvc.node"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,32 +4,13 @@
|
||||||
"main": "built/index.js",
|
"main": "built/index.js",
|
||||||
"types": "built/index.d.ts",
|
"types": "built/index.d.ts",
|
||||||
"napi": {
|
"napi": {
|
||||||
"name": "backend-rs",
|
"binaryName": "backend-rs"
|
||||||
"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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@napi-rs/cli": "2.18.3"
|
"@napi-rs/cli": "3.0.0-alpha.55"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"artifacts": "napi artifacts",
|
"build": "napi build --features napi --no-const-enum --platform --release --output-dir ./built/",
|
||||||
"build": "napi build --features napi --no-const-enum --platform --release ./built/",
|
"build:debug": "napi build --features napi --no-const-enum --platform --output-dir ./built/ --dts-header '/* auto-generated by NAPI-RS */\n/* Do NOT edit this file manually */\n\ntype DateTimeWithTimeZone = Date;\n\ntype Json = any;\n\n'"
|
||||||
"build:debug": "napi build --features napi --no-const-enum --platform ./built/",
|
|
||||||
"prepublishOnly": "napi prepublish -t npm",
|
|
||||||
"universal": "napi universal",
|
|
||||||
"version": "napi version"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
#[crate::ts_export]
|
//! This module is used in the TypeScript backend only.
|
||||||
|
|
||||||
|
#[macros::ts_export]
|
||||||
pub const SECOND: i32 = 1000;
|
pub const SECOND: i32 = 1000;
|
||||||
#[crate::ts_export]
|
#[macros::ts_export]
|
||||||
pub const MINUTE: i32 = 60 * SECOND;
|
pub const MINUTE: i32 = 60 * SECOND;
|
||||||
#[crate::ts_export]
|
#[macros::ts_export]
|
||||||
pub const HOUR: i32 = 60 * MINUTE;
|
pub const HOUR: i32 = 60 * MINUTE;
|
||||||
#[crate::ts_export]
|
#[macros::ts_export]
|
||||||
pub const DAY: i32 = 24 * HOUR;
|
pub const DAY: i32 = 24 * HOUR;
|
||||||
|
|
||||||
#[crate::ts_export]
|
#[macros::ts_export]
|
||||||
pub const USER_ONLINE_THRESHOLD: i32 = 10 * MINUTE;
|
pub const USER_ONLINE_THRESHOLD: i32 = 10 * MINUTE;
|
||||||
#[crate::ts_export]
|
#[macros::ts_export]
|
||||||
pub const USER_ACTIVE_THRESHOLD: i32 = 3 * DAY;
|
pub const USER_ACTIVE_THRESHOLD: i32 = 3 * DAY;
|
||||||
|
|
||||||
/// List of file types allowed to be viewed directly in the browser
|
/// List of file types allowed to be viewed directly in the browser
|
||||||
|
@ -19,7 +21,7 @@ pub const USER_ACTIVE_THRESHOLD: i32 = 3 * DAY;
|
||||||
/// * <https://github.com/sindresorhus/file-type/blob/main/supported.js>
|
/// * <https://github.com/sindresorhus/file-type/blob/main/supported.js>
|
||||||
/// * <https://github.com/sindresorhus/file-type/blob/main/core.js>
|
/// * <https://github.com/sindresorhus/file-type/blob/main/core.js>
|
||||||
/// * <https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers>
|
/// * <https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers>
|
||||||
#[crate::ts_export]
|
#[macros::ts_export]
|
||||||
pub const FILE_TYPE_BROWSERSAFE: [&str; 41] = [
|
pub const FILE_TYPE_BROWSERSAFE: [&str; 41] = [
|
||||||
// Images
|
// Images
|
||||||
"image/png",
|
"image/png",
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
// FIXME: Are these options used?
|
|
||||||
#[crate::export(object)]
|
|
||||||
pub struct EnvConfig {
|
|
||||||
pub only_queue: bool,
|
|
||||||
pub only_server: bool,
|
|
||||||
pub no_daemons: bool,
|
|
||||||
pub disable_clustering: bool,
|
|
||||||
pub verbose: bool,
|
|
||||||
pub with_log_time: bool,
|
|
||||||
pub slow: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[crate::export]
|
|
||||||
pub fn load_env() -> EnvConfig {
|
|
||||||
let node_env = std::env::var("NODE_ENV").unwrap_or_default().to_lowercase();
|
|
||||||
let is_testing = node_env == "test";
|
|
||||||
|
|
||||||
EnvConfig {
|
|
||||||
only_queue: std::env::var("MK_ONLY_QUEUE").is_ok(),
|
|
||||||
only_server: std::env::var("MK_ONLY_SERVER").is_ok(),
|
|
||||||
no_daemons: is_testing || std::env::var("MK_NO_DAEMONS").is_ok(),
|
|
||||||
disable_clustering: is_testing || std::env::var("MK_DISABLE_CLUSTERING").is_ok(),
|
|
||||||
verbose: std::env::var("MK_VERBOSE").is_ok(),
|
|
||||||
with_log_time: std::env::var("MK_WITH_LOG_TIME").is_ok(),
|
|
||||||
slow: std::env::var("MK_SLOW").is_ok(),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +1,28 @@
|
||||||
use crate::database::db_conn;
|
//! Server information
|
||||||
use crate::model::entity::meta;
|
|
||||||
use rand::prelude::*;
|
use crate::{database::db_conn, model::entity::meta};
|
||||||
use sea_orm::{prelude::*, ActiveValue};
|
use sea_orm::{prelude::*, ActiveValue};
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
type Meta = meta::Model;
|
type Meta = meta::Model;
|
||||||
|
|
||||||
static CACHE: Mutex<Option<Meta>> = Mutex::new(None);
|
static CACHE: Mutex<Option<Meta>> = Mutex::new(None);
|
||||||
fn update_cache(meta: &Meta) {
|
fn set_cache(meta: &Meta) {
|
||||||
let _ = CACHE.lock().map(|mut cache| *cache = Some(meta.clone()));
|
let _ = CACHE.lock().map(|mut cache| *cache = Some(meta.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[crate::export]
|
#[macros::export(js_name = "fetchMeta")]
|
||||||
pub async fn fetch_meta(use_cache: bool) -> Result<Meta, DbErr> {
|
pub async fn local_server_info() -> Result<Meta, DbErr> {
|
||||||
|
local_server_info_impl(true).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macros::export(js_name = "updateMetaCache")]
|
||||||
|
pub async fn update() -> Result<(), DbErr> {
|
||||||
|
local_server_info_impl(false).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn local_server_info_impl(use_cache: bool) -> Result<Meta, DbErr> {
|
||||||
// try using cache
|
// try using cache
|
||||||
if use_cache {
|
if use_cache {
|
||||||
if let Some(cache) = CACHE.lock().ok().and_then(|cache| cache.clone()) {
|
if let Some(cache) = CACHE.lock().ok().and_then(|cache| cache.clone()) {
|
||||||
|
@ -24,7 +34,7 @@ pub async fn fetch_meta(use_cache: bool) -> Result<Meta, DbErr> {
|
||||||
let db = db_conn().await?;
|
let db = db_conn().await?;
|
||||||
let meta = meta::Entity::find().one(db).await?;
|
let meta = meta::Entity::find().one(db).await?;
|
||||||
if let Some(meta) = meta {
|
if let Some(meta) = meta {
|
||||||
update_cache(&meta);
|
set_cache(&meta);
|
||||||
return Ok(meta);
|
return Ok(meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,11 +45,11 @@ pub async fn fetch_meta(use_cache: bool) -> Result<Meta, DbErr> {
|
||||||
})
|
})
|
||||||
.exec_with_returning(db)
|
.exec_with_returning(db)
|
||||||
.await?;
|
.await?;
|
||||||
update_cache(&meta);
|
set_cache(&meta);
|
||||||
Ok(meta)
|
Ok(meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[crate::export(object)]
|
#[macros::export(object)]
|
||||||
pub struct PugArgs {
|
pub struct PugArgs {
|
||||||
pub img: Option<String>,
|
pub img: Option<String>,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
|
@ -52,8 +62,9 @@ pub struct PugArgs {
|
||||||
pub private_mode: Option<bool>,
|
pub private_mode: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[crate::export]
|
#[macros::ts_export]
|
||||||
pub fn meta_to_pug_args(meta: Meta) -> PugArgs {
|
pub fn meta_to_pug_args(meta: Meta) -> PugArgs {
|
||||||
|
use rand::prelude::*;
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
let splash_icon = meta
|
let splash_icon = meta
|
|
@ -1,7 +1,8 @@
|
||||||
//! Server configurations and environment variables
|
//! Server configurations and environment variables
|
||||||
|
|
||||||
|
pub use meta::local_server_info;
|
||||||
pub use server::CONFIG;
|
pub use server::CONFIG;
|
||||||
|
|
||||||
pub mod constant;
|
pub mod constant;
|
||||||
pub mod environment;
|
pub mod meta;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
|
//! Server configuration
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::env;
|
use std::{env, fs};
|
||||||
use std::fs;
|
|
||||||
|
|
||||||
pub const VERSION: &str = macro_rs::read_version_from_package_json!();
|
pub const VERSION: &str = macros::read_version_from_package_json!();
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[crate::export(object, use_nullable = false)]
|
#[macros::export(object, use_nullable = false)]
|
||||||
struct ServerConfig {
|
struct ServerConfig {
|
||||||
pub url: String,
|
pub url: String,
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
|
@ -70,9 +71,9 @@ struct ServerConfig {
|
||||||
pub object_storage: Option<ObjectStorageConfig>,
|
pub object_storage: Option<ObjectStorageConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[crate::export(object, use_nullable = false)]
|
#[macros::export(object, use_nullable = false)]
|
||||||
pub struct DbConfig {
|
pub struct DbConfig {
|
||||||
pub host: String,
|
pub host: String,
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
|
@ -83,9 +84,9 @@ pub struct DbConfig {
|
||||||
pub extra: Option<serde_json::Value>,
|
pub extra: Option<serde_json::Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[crate::export(object, use_nullable = false)]
|
#[macros::export(object, use_nullable = false)]
|
||||||
pub struct RedisConfig {
|
pub struct RedisConfig {
|
||||||
pub host: String,
|
pub host: String,
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
|
@ -98,65 +99,65 @@ pub struct RedisConfig {
|
||||||
pub prefix: Option<String>,
|
pub prefix: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[crate::export(object, use_nullable = false)]
|
#[macros::export(object, use_nullable = false)]
|
||||||
pub struct TlsConfig {
|
pub struct TlsConfig {
|
||||||
pub host: String,
|
pub host: String,
|
||||||
pub reject_unauthorized: bool,
|
pub reject_unauthorized: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[crate::export(object, use_nullable = false)]
|
#[macros::export(object, use_nullable = false)]
|
||||||
pub struct WorkerConfig {
|
pub struct WorkerConfig {
|
||||||
pub web: u32,
|
pub web: u32,
|
||||||
pub queue: u32,
|
pub queue: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[crate::export(object, use_nullable = false)]
|
#[macros::export(object, use_nullable = false)]
|
||||||
pub struct WorkerConfigInternal {
|
pub struct WorkerConfigInternal {
|
||||||
pub web: Option<u32>,
|
pub web: Option<u32>,
|
||||||
pub queue: Option<u32>,
|
pub queue: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[crate::export(object, use_nullable = false)]
|
#[macros::export(object, use_nullable = false)]
|
||||||
pub struct IdConfig {
|
pub struct IdConfig {
|
||||||
pub length: Option<u8>,
|
pub length: Option<u8>,
|
||||||
pub fingerprint: Option<String>,
|
pub fingerprint: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[crate::export(object, use_nullable = false)]
|
#[macros::export(object, use_nullable = false)]
|
||||||
pub struct SysLogConfig {
|
pub struct SysLogConfig {
|
||||||
pub host: String,
|
pub host: String,
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[crate::export(object, use_nullable = false)]
|
#[macros::export(object, use_nullable = false)]
|
||||||
pub struct DeepLConfig {
|
pub struct DeepLConfig {
|
||||||
pub managed: Option<bool>,
|
pub managed: Option<bool>,
|
||||||
pub auth_key: Option<String>,
|
pub auth_key: Option<String>,
|
||||||
pub is_pro: Option<bool>,
|
pub is_pro: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[crate::export(object, use_nullable = false)]
|
#[macros::export(object, use_nullable = false)]
|
||||||
pub struct LibreTranslateConfig {
|
pub struct LibreTranslateConfig {
|
||||||
pub managed: Option<bool>,
|
pub managed: Option<bool>,
|
||||||
pub api_url: Option<String>,
|
pub api_url: Option<String>,
|
||||||
pub api_key: Option<String>,
|
pub api_key: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[crate::export(object, use_nullable = false)]
|
#[macros::export(object, use_nullable = false)]
|
||||||
pub struct EmailConfig {
|
pub struct EmailConfig {
|
||||||
pub managed: Option<bool>,
|
pub managed: Option<bool>,
|
||||||
pub address: Option<String>,
|
pub address: Option<String>,
|
||||||
|
@ -167,9 +168,9 @@ pub struct EmailConfig {
|
||||||
pub use_implicit_ssl_tls: Option<bool>,
|
pub use_implicit_ssl_tls: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[crate::export(object, use_nullable = false)]
|
#[macros::export(object, use_nullable = false)]
|
||||||
pub struct ObjectStorageConfig {
|
pub struct ObjectStorageConfig {
|
||||||
pub managed: Option<bool>,
|
pub managed: Option<bool>,
|
||||||
pub base_url: Option<String>,
|
pub base_url: Option<String>,
|
||||||
|
@ -185,7 +186,7 @@ pub struct ObjectStorageConfig {
|
||||||
pub s3_force_path_style: Option<bool>,
|
pub s3_force_path_style: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[crate::export(object, use_nullable = false)]
|
#[macros::export(object, use_nullable = false)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
// ServerConfig (from default.yml)
|
// ServerConfig (from default.yml)
|
||||||
pub url: String,
|
pub url: String,
|
||||||
|
@ -262,7 +263,7 @@ fn read_config_file() -> ServerConfig {
|
||||||
data
|
data
|
||||||
}
|
}
|
||||||
|
|
||||||
#[crate::export]
|
#[macros::export]
|
||||||
pub fn load_config() -> Config {
|
pub fn load_config() -> Config {
|
||||||
let server_config = read_config_file();
|
let server_config = read_config_file();
|
||||||
let version = VERSION.to_owned();
|
let version = VERSION.to_owned();
|
||||||
|
@ -276,7 +277,10 @@ pub fn load_config() -> Config {
|
||||||
None => hostname.clone(),
|
None => hostname.clone(),
|
||||||
};
|
};
|
||||||
let scheme = url.scheme().to_owned();
|
let scheme = url.scheme().to_owned();
|
||||||
let ws_scheme = scheme.replace("http", "ws");
|
let ws_scheme = match scheme.as_str() {
|
||||||
|
"http" => "ws",
|
||||||
|
_ => "wss",
|
||||||
|
};
|
||||||
|
|
||||||
let cluster_limits = match server_config.cluster_limits {
|
let cluster_limits = match server_config.cluster_limits {
|
||||||
Some(cl) => WorkerConfig {
|
Some(cl) => WorkerConfig {
|
||||||
|
@ -291,7 +295,7 @@ pub fn load_config() -> Config {
|
||||||
} else {
|
} else {
|
||||||
server_config.redis.prefix.clone()
|
server_config.redis.prefix.clone()
|
||||||
}
|
}
|
||||||
.unwrap_or(hostname.clone());
|
.unwrap_or_else(|| hostname.clone());
|
||||||
|
|
||||||
Config {
|
Config {
|
||||||
url: server_config.url,
|
url: server_config.url,
|
||||||
|
@ -342,7 +346,7 @@ pub fn load_config() -> Config {
|
||||||
hostname,
|
hostname,
|
||||||
redis_key_prefix,
|
redis_key_prefix,
|
||||||
scheme,
|
scheme,
|
||||||
ws_scheme,
|
ws_scheme: ws_scheme.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,29 +4,23 @@ use crate::database::{redis_conn, redis_key, RedisConnError};
|
||||||
use redis::{AsyncCommands, RedisError};
|
use redis::{AsyncCommands, RedisError};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(strum::Display, Debug)]
|
#[cfg_attr(test, derive(Debug))]
|
||||||
pub enum Category {
|
pub enum Category {
|
||||||
#[strum(serialize = "fetchUrl")]
|
|
||||||
FetchUrl,
|
FetchUrl,
|
||||||
#[strum(serialize = "blocking")]
|
|
||||||
Block,
|
Block,
|
||||||
#[strum(serialize = "following")]
|
|
||||||
Follow,
|
Follow,
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[strum(serialize = "usedOnlyForTesting")]
|
|
||||||
Test,
|
Test,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Redis error: {0}")]
|
#[error("failed to execute Redis command")]
|
||||||
Redis(#[from] RedisError),
|
Redis(#[from] RedisError),
|
||||||
#[error("Redis connection error: {0}")]
|
#[error("bad Redis connection")]
|
||||||
RedisConn(#[from] RedisConnError),
|
RedisConn(#[from] RedisConnError),
|
||||||
#[error("Data serialization error: {0}")]
|
#[error("failed to encode data for Redis")]
|
||||||
Serialize(#[from] rmp_serde::encode::Error),
|
Encode(#[from] rmp_serde::encode::Error),
|
||||||
#[error("Data deserialization error: {0}")]
|
|
||||||
Deserialize(#[from] rmp_serde::decode::Error),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -34,9 +28,15 @@ fn prefix_key(key: &str) -> String {
|
||||||
redis_key(format!("cache:{}", key))
|
redis_key(format!("cache:{}", key))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn categorize(category: Category, key: &str) -> String {
|
fn categorize(category: Category, key: &str) -> String {
|
||||||
format!("{}:{}", category, key)
|
let prefix = match category {
|
||||||
|
Category::FetchUrl => "fetchUrl",
|
||||||
|
Category::Block => "blocking",
|
||||||
|
Category::Follow => "following",
|
||||||
|
#[cfg(test)]
|
||||||
|
Category::Test => "usedOnlyForTesting",
|
||||||
|
};
|
||||||
|
format!("{}:{}", prefix, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -50,9 +50,9 @@ fn wildcard(category: Category) -> String {
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// - `key` : key (prefixed automatically)
|
/// * `key` : key (prefixed automatically)
|
||||||
/// - `value` : (de)serializable value
|
/// * `value` : (de)serializable value
|
||||||
/// - `expire_seconds` : TTL
|
/// * `expire_seconds` : TTL
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
|
@ -96,7 +96,7 @@ pub async fn set<V: for<'a> Deserialize<'a> + Serialize>(
|
||||||
///
|
///
|
||||||
/// # Argument
|
/// # Argument
|
||||||
///
|
///
|
||||||
/// - `key` : key (will be prefixed automatically)
|
/// * `key` : key (will be prefixed automatically)
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
|
@ -123,7 +123,7 @@ pub async fn set<V: for<'a> Deserialize<'a> + Serialize>(
|
||||||
pub async fn get<V: for<'a> Deserialize<'a> + Serialize>(key: &str) -> Result<Option<V>, Error> {
|
pub async fn get<V: for<'a> Deserialize<'a> + Serialize>(key: &str) -> Result<Option<V>, Error> {
|
||||||
let serialized_value: Option<Vec<u8>> = redis_conn().await?.get(prefix_key(key)).await?;
|
let serialized_value: Option<Vec<u8>> = redis_conn().await?.get(prefix_key(key)).await?;
|
||||||
Ok(match serialized_value {
|
Ok(match serialized_value {
|
||||||
Some(v) => Some(rmp_serde::from_slice::<V>(v.as_ref())?),
|
Some(v) => rmp_serde::from_slice::<V>(v.as_ref()).ok(),
|
||||||
None => None,
|
None => None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -135,9 +135,9 @@ pub async fn get<V: for<'a> Deserialize<'a> + Serialize>(key: &str) -> Result<Op
|
||||||
///
|
///
|
||||||
/// # Argument
|
/// # Argument
|
||||||
///
|
///
|
||||||
/// - `key` : key (prefixed automatically)
|
/// * `key` : key (prefixed automatically)
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use backend_rs::database::cache;
|
/// # use backend_rs::database::cache;
|
||||||
|
@ -169,10 +169,10 @@ pub async fn delete(key: &str) -> Result<(), Error> {
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// - `category` : one of [Category]
|
/// * `category` : one of [Category]
|
||||||
/// - `key` : key (prefixed automatically)
|
/// * `key` : key (prefixed automatically)
|
||||||
/// - `value` : (de)serializable value
|
/// * `value` : (de)serializable value
|
||||||
/// - `expire_seconds` : TTL
|
/// * `expire_seconds` : TTL
|
||||||
pub async fn set_one<V: for<'a> Deserialize<'a> + Serialize>(
|
pub async fn set_one<V: for<'a> Deserialize<'a> + Serialize>(
|
||||||
category: Category,
|
category: Category,
|
||||||
key: &str,
|
key: &str,
|
||||||
|
@ -188,8 +188,8 @@ pub async fn set_one<V: for<'a> Deserialize<'a> + Serialize>(
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// - `category` : one of [Category]
|
/// * `category` : one of [Category]
|
||||||
/// - `key` : key (prefixed automatically)
|
/// * `key` : key (prefixed automatically)
|
||||||
pub async fn get_one<V: for<'a> Deserialize<'a> + Serialize>(
|
pub async fn get_one<V: for<'a> Deserialize<'a> + Serialize>(
|
||||||
category: Category,
|
category: Category,
|
||||||
key: &str,
|
key: &str,
|
||||||
|
@ -213,7 +213,7 @@ pub async fn delete_one(category: Category, key: &str) -> Result<(), Error> {
|
||||||
///
|
///
|
||||||
/// # Argument
|
/// # Argument
|
||||||
///
|
///
|
||||||
/// - `category` : one of [Category]
|
/// * `category` : one of [Category]
|
||||||
pub async fn delete_all(category: Category) -> Result<(), Error> {
|
pub async fn delete_all(category: Category) -> Result<(), Error> {
|
||||||
let mut redis = redis_conn().await?;
|
let mut redis = redis_conn().await?;
|
||||||
let keys: Vec<Vec<u8>> = redis.keys(wildcard(category)).await?;
|
let keys: Vec<Vec<u8>> = redis.keys(wildcard(category)).await?;
|
||||||
|
@ -234,6 +234,7 @@ mod unit_test {
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
#[cfg_attr(miri, ignore)] // can't call foreign function `getaddrinfo` on OS `linux`
|
||||||
async fn set_get_expire() {
|
async fn set_get_expire() {
|
||||||
#[derive(serde::Deserialize, serde::Serialize, PartialEq, Debug)]
|
#[derive(serde::Deserialize, serde::Serialize, PartialEq, Debug)]
|
||||||
struct Data {
|
struct Data {
|
||||||
|
@ -278,6 +279,7 @@ mod unit_test {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
#[cfg_attr(miri, ignore)] // can't call foreign function `getaddrinfo` on OS `linux`
|
||||||
async fn use_category() {
|
async fn use_category() {
|
||||||
let key_1 = "fire";
|
let key_1 = "fire";
|
||||||
let key_2 = "fish";
|
let key_2 = "fish";
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
//! Interfaces for accessing PostgreSQL and Redis
|
//! Interfaces for accessing PostgreSQL and Redis
|
||||||
|
|
||||||
pub use postgresql::db_conn;
|
pub use postgresql::get_conn as db_conn;
|
||||||
|
|
||||||
|
pub use redis::get_conn as redis_conn;
|
||||||
pub use redis::key as redis_key;
|
pub use redis::key as redis_key;
|
||||||
pub use redis::redis_conn;
|
|
||||||
pub use redis::RedisConnError;
|
pub use redis::RedisConnError;
|
||||||
|
|
||||||
pub mod cache;
|
pub mod cache;
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
use crate::config::CONFIG;
|
use crate::config::CONFIG;
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use sea_orm::{ConnectOptions, Database, DbConn, DbErr};
|
use sea_orm::{ConnectOptions, Database, DbConn, DbErr};
|
||||||
|
use std::time::Duration;
|
||||||
use tracing::log::LevelFilter;
|
use tracing::log::LevelFilter;
|
||||||
|
|
||||||
static DB_CONN: OnceCell<DbConn> = OnceCell::new();
|
static DB_CONN: OnceCell<DbConn> = OnceCell::new();
|
||||||
|
@ -18,6 +19,7 @@ async fn init_conn() -> Result<&'static DbConn, DbErr> {
|
||||||
);
|
);
|
||||||
let option: ConnectOptions = ConnectOptions::new(database_uri)
|
let option: ConnectOptions = ConnectOptions::new(database_uri)
|
||||||
.sqlx_logging_level(LevelFilter::Trace)
|
.sqlx_logging_level(LevelFilter::Trace)
|
||||||
|
.sqlx_slow_statements_logging_settings(LevelFilter::Warn, Duration::from_secs(3))
|
||||||
.to_owned();
|
.to_owned();
|
||||||
|
|
||||||
tracing::info!("initializing connection");
|
tracing::info!("initializing connection");
|
||||||
|
@ -27,7 +29,7 @@ async fn init_conn() -> Result<&'static DbConn, DbErr> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an async PostgreSQL connection that can be used with [sea_orm] utilities.
|
/// Returns an async PostgreSQL connection that can be used with [sea_orm] utilities.
|
||||||
pub async fn db_conn() -> Result<&'static DbConn, DbErr> {
|
pub async fn get_conn() -> Result<&'static DbConn, DbErr> {
|
||||||
match DB_CONN.get() {
|
match DB_CONN.get() {
|
||||||
Some(conn) => Ok(conn),
|
Some(conn) => Ok(conn),
|
||||||
None => init_conn().await,
|
None => init_conn().await,
|
||||||
|
@ -36,11 +38,52 @@ pub async fn db_conn() -> Result<&'static DbConn, DbErr> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod unit_test {
|
mod unit_test {
|
||||||
use super::db_conn;
|
use super::get_conn;
|
||||||
|
use sea_orm::{prelude::*, DbBackend, Statement};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn connect() {
|
#[cfg_attr(miri, ignore)] // can't call foreign function `geteuid` on OS `linux`
|
||||||
assert!(db_conn().await.is_ok());
|
async fn connect_sequential() {
|
||||||
assert!(db_conn().await.is_ok());
|
get_conn().await.unwrap();
|
||||||
|
get_conn().await.unwrap();
|
||||||
|
get_conn().await.unwrap();
|
||||||
|
get_conn().await.unwrap();
|
||||||
|
get_conn().await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[cfg_attr(miri, ignore)] // can't call foreign function `geteuid` on OS `linux`
|
||||||
|
async fn connect_concurrent() {
|
||||||
|
let [c1, c2, c3, c4, c5] = [get_conn(), get_conn(), get_conn(), get_conn(), get_conn()];
|
||||||
|
let _ = tokio::try_join!(c1, c2, c3, c4, c5).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[cfg_attr(miri, ignore)] // can't call foreign function `geteuid` on OS `linux`
|
||||||
|
async fn connect_spawn() {
|
||||||
|
let mut tasks = Vec::new();
|
||||||
|
|
||||||
|
for _ in 0..5 {
|
||||||
|
tasks.push(tokio::spawn(get_conn()));
|
||||||
|
}
|
||||||
|
for task in tasks {
|
||||||
|
task.await.unwrap().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[cfg_attr(miri, ignore)] // can't call foreign function `geteuid` on OS `linux`
|
||||||
|
async fn access() {
|
||||||
|
// DO NOT write any raw SQL query in the actual program
|
||||||
|
// (with the exception of PGroonga features)
|
||||||
|
get_conn()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.execute(Statement::from_string(
|
||||||
|
DbBackend::Postgres,
|
||||||
|
"SELECT version()",
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,15 +81,15 @@ async fn init_conn_pool() -> Result<(), RedisError> {
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum RedisConnError {
|
pub enum RedisConnError {
|
||||||
#[error("Failed to initialize Redis connection pool: {0}")]
|
#[error("failed to initialize Redis connection pool")]
|
||||||
Redis(RedisError),
|
Redis(RedisError),
|
||||||
#[error("Redis connection pool error: {0}")]
|
#[error("bad Redis connection pool")]
|
||||||
Bb8Pool(RunError<RedisError>),
|
Bb8Pool(RunError<RedisError>),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an async [redis] connection managed by a [bb8] connection pool.
|
/// Returns an async [redis] connection managed by a [bb8] connection pool.
|
||||||
pub async fn redis_conn(
|
pub async fn get_conn() -> Result<PooledConnection<'static, RedisConnectionManager>, RedisConnError>
|
||||||
) -> Result<PooledConnection<'static, RedisConnectionManager>, RedisConnError> {
|
{
|
||||||
if !CONN_POOL.initialized() {
|
if !CONN_POOL.initialized() {
|
||||||
let init_res = init_conn_pool().await;
|
let init_res = init_conn_pool().await;
|
||||||
|
|
||||||
|
@ -114,19 +114,44 @@ pub fn key(key: impl ToString) -> String {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod unit_test {
|
mod unit_test {
|
||||||
use super::redis_conn;
|
use super::get_conn;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use redis::AsyncCommands;
|
use redis::AsyncCommands;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn connect() {
|
#[cfg_attr(miri, ignore)] // can't call foreign function `getaddrinfo` on OS `linux`
|
||||||
assert!(redis_conn().await.is_ok());
|
async fn connect_sequential() {
|
||||||
assert!(redis_conn().await.is_ok());
|
get_conn().await.unwrap();
|
||||||
|
get_conn().await.unwrap();
|
||||||
|
get_conn().await.unwrap();
|
||||||
|
get_conn().await.unwrap();
|
||||||
|
get_conn().await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
#[cfg_attr(miri, ignore)] // can't call foreign function `getaddrinfo` on OS `linux`
|
||||||
|
async fn connect_concurrent() {
|
||||||
|
let [c1, c2, c3, c4, c5] = [get_conn(), get_conn(), get_conn(), get_conn(), get_conn()];
|
||||||
|
let _ = tokio::try_join!(c1, c2, c3, c4, c5).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[cfg_attr(miri, ignore)] // can't call foreign function `getaddrinfo` on OS `linux`
|
||||||
|
async fn connect_spawn() {
|
||||||
|
let mut tasks = Vec::new();
|
||||||
|
|
||||||
|
for _ in 0..5 {
|
||||||
|
tasks.push(tokio::spawn(get_conn()));
|
||||||
|
}
|
||||||
|
for task in tasks {
|
||||||
|
task.await.unwrap().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[cfg_attr(miri, ignore)] // can't call foreign function `getaddrinfo` on OS `linux`
|
||||||
async fn access() {
|
async fn access() {
|
||||||
let mut redis = redis_conn().await.unwrap();
|
let mut redis = get_conn().await.unwrap();
|
||||||
|
|
||||||
let key = "CARGO_UNIT_TEST_KEY";
|
let key = "CARGO_UNIT_TEST_KEY";
|
||||||
let value = "CARGO_UNIT_TEST_VALUE";
|
let value = "CARGO_UNIT_TEST_VALUE";
|
||||||
|
|
|
@ -1,17 +1,20 @@
|
||||||
use std::fmt;
|
use std::{fmt, str::FromStr};
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||||
#[crate::export(object)]
|
#[macros::export(object)]
|
||||||
pub struct Acct {
|
pub struct Acct {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub host: Option<String>,
|
pub host: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for Acct {
|
#[derive(thiserror::Error, Debug)]
|
||||||
type Err = ();
|
#[doc = "Error type to indicate a string-to-[`Acct`] conversion failure"]
|
||||||
|
#[error("failed to convert string '{0}' into acct")]
|
||||||
|
pub struct InvalidAcctString(String);
|
||||||
|
|
||||||
|
impl FromStr for Acct {
|
||||||
|
type Err = InvalidAcctString;
|
||||||
|
|
||||||
/// This never throw errors. Feel free to `.unwrap()` the result.
|
|
||||||
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||||
let split: Vec<&str> = if let Some(stripped) = value.strip_prefix('@') {
|
let split: Vec<&str> = if let Some(stripped) = value.strip_prefix('@') {
|
||||||
stripped
|
stripped
|
||||||
|
@ -48,12 +51,12 @@ impl From<Acct> for String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[crate::ts_export]
|
#[macros::ts_export]
|
||||||
pub fn string_to_acct(acct: &str) -> Acct {
|
pub fn string_to_acct(acct: &str) -> Acct {
|
||||||
Acct::from_str(acct).unwrap()
|
Acct::from_str(acct).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[crate::ts_export]
|
#[macros::ts_export]
|
||||||
pub fn acct_to_string(acct: &Acct) -> String {
|
pub fn acct_to_string(acct: &Acct) -> String {
|
||||||
acct.to_string()
|
acct.to_string()
|
||||||
}
|
}
|
||||||
|
@ -65,7 +68,7 @@ mod unit_test {
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_acct_to_string() {
|
fn acct_to_string() {
|
||||||
let remote_acct = Acct {
|
let remote_acct = Acct {
|
||||||
username: "firefish".to_string(),
|
username: "firefish".to_string(),
|
||||||
host: Some("example.com".to_string()),
|
host: Some("example.com".to_string()),
|
||||||
|
@ -82,7 +85,7 @@ mod unit_test {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_string_to_acct() {
|
fn string_to_acct() {
|
||||||
let remote_acct = Acct {
|
let remote_acct = Acct {
|
||||||
username: "firefish".to_string(),
|
username: "firefish".to_string(),
|
||||||
host: Some("example.com".to_string()),
|
host: Some("example.com".to_string()),
|
||||||
|
|
|
@ -1,45 +1,46 @@
|
||||||
//! NodeInfo fetcher
|
//! NodeInfo fetcher
|
||||||
|
//!
|
||||||
|
//! ref: <https://nodeinfo.diaspora.software/protocol.html>
|
||||||
|
|
||||||
use crate::federation::nodeinfo::schema::*;
|
use crate::{federation::nodeinfo::schema::*, util::http_client};
|
||||||
use crate::util::http_client;
|
|
||||||
use isahc::AsyncReadResponseExt;
|
use isahc::AsyncReadResponseExt;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
/// Errors that can occur while fetching NodeInfo from a remote server
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("HTTP client aquisition error: {0}")]
|
#[error("failed to acquire an HTTP client")]
|
||||||
HttpClient(#[from] http_client::Error),
|
HttpClient(#[from] http_client::Error),
|
||||||
#[error("HTTP error: {0}")]
|
#[error("HTTP request failed")]
|
||||||
Http(#[from] isahc::Error),
|
Http(#[from] isahc::Error),
|
||||||
#[error("Bad status: {0}")]
|
#[doc = "bad HTTP status"]
|
||||||
|
#[error("bad HTTP status ({0})")]
|
||||||
BadStatus(String),
|
BadStatus(String),
|
||||||
#[error("Failed to parse response body as text: {0}")]
|
#[error("failed to parse HTTP response body as text")]
|
||||||
Response(#[from] std::io::Error),
|
Response(#[from] std::io::Error),
|
||||||
#[error("Failed to parse response body as json: {0}")]
|
#[error("failed to parse HTTP response body as json")]
|
||||||
Json(#[from] serde_json::Error),
|
Json(#[from] serde_json::Error),
|
||||||
#[error("No nodeinfo provided")]
|
#[error("nodeinfo is missing")]
|
||||||
MissingNodeinfo,
|
MissingNodeinfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
/// Represents the schema of `/.well-known/nodeinfo`.
|
||||||
|
#[derive(Deserialize)]
|
||||||
pub struct NodeinfoLinks {
|
pub struct NodeinfoLinks {
|
||||||
links: Vec<NodeinfoLink>,
|
links: Vec<NodeinfoLink>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
/// Represents one entry of `/.well-known/nodeinfo`.
|
||||||
|
#[derive(Deserialize)]
|
||||||
pub struct NodeinfoLink {
|
pub struct NodeinfoLink {
|
||||||
rel: String,
|
rel: String,
|
||||||
href: String,
|
href: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
/// Fetches `/.well-known/nodeinfo` and parses the result.
|
||||||
fn wellknown_nodeinfo_url(host: &str) -> String {
|
|
||||||
format!("https://{}/.well-known/nodeinfo", host)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn fetch_nodeinfo_links(host: &str) -> Result<NodeinfoLinks, Error> {
|
async fn fetch_nodeinfo_links(host: &str) -> Result<NodeinfoLinks, Error> {
|
||||||
let client = http_client::client()?;
|
let client = http_client::client()?;
|
||||||
let wellknown_url = wellknown_nodeinfo_url(host);
|
let wellknown_url = format!("https://{}/.well-known/nodeinfo", host);
|
||||||
let mut wellknown_response = client.get_async(&wellknown_url).await?;
|
let mut wellknown_response = client.get_async(&wellknown_url).await?;
|
||||||
|
|
||||||
if !wellknown_response.status().is_success() {
|
if !wellknown_response.status().is_success() {
|
||||||
|
@ -54,6 +55,9 @@ async fn fetch_nodeinfo_links(host: &str) -> Result<NodeinfoLinks, Error> {
|
||||||
Ok(serde_json::from_str(&wellknown_response.text().await?)?)
|
Ok(serde_json::from_str(&wellknown_response.text().await?)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if any of the following relations is present in the given [NodeinfoLinks].
|
||||||
|
/// * <http://nodeinfo.diaspora.software/ns/schema/2.0>
|
||||||
|
/// * <http://nodeinfo.diaspora.software/ns/schema/2.1>
|
||||||
fn check_nodeinfo_link(links: NodeinfoLinks) -> Result<String, Error> {
|
fn check_nodeinfo_link(links: NodeinfoLinks) -> Result<String, Error> {
|
||||||
for link in links.links {
|
for link in links.links {
|
||||||
if link.rel == "http://nodeinfo.diaspora.software/ns/schema/2.1"
|
if link.rel == "http://nodeinfo.diaspora.software/ns/schema/2.1"
|
||||||
|
@ -66,6 +70,7 @@ fn check_nodeinfo_link(links: NodeinfoLinks) -> Result<String, Error> {
|
||||||
Err(Error::MissingNodeinfo)
|
Err(Error::MissingNodeinfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetches the nodeinfo from the given URL and parses the result.
|
||||||
async fn fetch_nodeinfo_impl(nodeinfo_link: &str) -> Result<Nodeinfo20, Error> {
|
async fn fetch_nodeinfo_impl(nodeinfo_link: &str) -> Result<Nodeinfo20, Error> {
|
||||||
let client = http_client::client()?;
|
let client = http_client::client()?;
|
||||||
let mut response = client.get_async(nodeinfo_link).await?;
|
let mut response = client.get_async(nodeinfo_link).await?;
|
||||||
|
@ -85,8 +90,8 @@ async fn fetch_nodeinfo_impl(nodeinfo_link: &str) -> Result<Nodeinfo20, Error> {
|
||||||
// for napi export
|
// for napi export
|
||||||
type Nodeinfo = Nodeinfo20;
|
type Nodeinfo = Nodeinfo20;
|
||||||
|
|
||||||
/// Fetches and returns the NodeInfo of a remote server.
|
/// Fetches and returns the NodeInfo (version 2.0) of a remote server.
|
||||||
#[crate::export]
|
#[macros::export]
|
||||||
pub async fn fetch_nodeinfo(host: &str) -> Result<Nodeinfo, Error> {
|
pub async fn fetch_nodeinfo(host: &str) -> Result<Nodeinfo, Error> {
|
||||||
tracing::info!("fetching from {}", host);
|
tracing::info!("fetching from {}", host);
|
||||||
let links = fetch_nodeinfo_links(host).await?;
|
let links = fetch_nodeinfo_links(host).await?;
|
||||||
|
@ -96,11 +101,11 @@ pub async fn fetch_nodeinfo(host: &str) -> Result<Nodeinfo, Error> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod unit_test {
|
mod unit_test {
|
||||||
use super::{check_nodeinfo_link, fetch_nodeinfo, NodeinfoLink, NodeinfoLinks};
|
use super::{NodeinfoLink, NodeinfoLinks};
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_check_nodeinfo_link() {
|
fn check_nodeinfo_link() {
|
||||||
let links_1 = NodeinfoLinks {
|
let links_1 = NodeinfoLinks {
|
||||||
links: vec![
|
links: vec![
|
||||||
NodeinfoLink {
|
NodeinfoLink {
|
||||||
|
@ -114,7 +119,7 @@ mod unit_test {
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
check_nodeinfo_link(links_1).unwrap(),
|
super::check_nodeinfo_link(links_1).unwrap(),
|
||||||
"https://example.com/real"
|
"https://example.com/real"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -131,7 +136,7 @@ mod unit_test {
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
check_nodeinfo_link(links_2).unwrap(),
|
super::check_nodeinfo_link(links_2).unwrap(),
|
||||||
"https://example.com/real"
|
"https://example.com/real"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -147,13 +152,14 @@ mod unit_test {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
check_nodeinfo_link(links_3).expect_err("No nodeinfo");
|
super::check_nodeinfo_link(links_3).expect_err("No nodeinfo");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_fetch_nodeinfo() {
|
#[cfg_attr(miri, ignore)] // can't call foreign function `curl_global_init` on OS `linux`
|
||||||
|
async fn fetch_nodeinfo() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
fetch_nodeinfo("info.firefish.dev")
|
super::fetch_nodeinfo("info.firefish.dev")
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.software
|
.software
|
||||||
|
|
|
@ -1,24 +1,31 @@
|
||||||
//! NodeInfo generator
|
//! NodeInfo generator
|
||||||
|
|
||||||
use crate::config::CONFIG;
|
use crate::{
|
||||||
use crate::database::{cache, db_conn};
|
config::{local_server_info, CONFIG},
|
||||||
use crate::federation::nodeinfo::schema::*;
|
database::db_conn,
|
||||||
use crate::misc::meta::fetch_meta;
|
federation::nodeinfo::schema::*,
|
||||||
use crate::model::entity::{note, user};
|
model::entity::{note, user},
|
||||||
use sea_orm::{ColumnTrait, DbErr, EntityTrait, PaginatorTrait, QueryFilter};
|
};
|
||||||
|
use sea_orm::prelude::*;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, sync::Mutex};
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
static CACHE: Mutex<Option<Nodeinfo21>> = Mutex::new(None);
|
||||||
pub enum Error {
|
|
||||||
#[error("Database error: {0}")]
|
fn set_cache(nodeinfo: &Nodeinfo21) {
|
||||||
Db(#[from] DbErr),
|
let _ = CACHE
|
||||||
#[error("Cache error: {0}")]
|
.lock()
|
||||||
Cache(#[from] cache::Error),
|
.map(|mut cache| *cache = Some(nodeinfo.to_owned()));
|
||||||
#[error("Failed to serialize nodeinfo to JSON: {0}")]
|
|
||||||
Json(#[from] serde_json::Error),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetches the number of total/active local users and local posts.
|
||||||
|
///
|
||||||
|
/// # Return value
|
||||||
|
/// A tuple containing the following information in this order:
|
||||||
|
/// * the total number of local users
|
||||||
|
/// * the total number of local users active in the last 6 months
|
||||||
|
/// * the total number of local users active in the last month (MAU)
|
||||||
|
/// * the total number of posts from local users
|
||||||
async fn statistics() -> Result<(u64, u64, u64, u64), DbErr> {
|
async fn statistics() -> Result<(u64, u64, u64, u64), DbErr> {
|
||||||
let db = db_conn().await?;
|
let db = db_conn().await?;
|
||||||
|
|
||||||
|
@ -49,14 +56,18 @@ async fn statistics() -> Result<(u64, u64, u64, u64), DbErr> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn generate_nodeinfo_2_1() -> Result<Nodeinfo21, Error> {
|
/// Generates NodeInfo (version 2.1) of the local server.
|
||||||
|
/// This function doesn't use caches and returns the latest information.
|
||||||
|
async fn generate_nodeinfo_2_1() -> Result<Nodeinfo21, DbErr> {
|
||||||
|
tracing::info!("generating NodeInfo");
|
||||||
|
|
||||||
let (local_users, local_active_halfyear, local_active_month, local_posts) =
|
let (local_users, local_active_halfyear, local_active_month, local_posts) =
|
||||||
statistics().await?;
|
statistics().await?;
|
||||||
let meta = fetch_meta(true).await?;
|
let meta = local_server_info().await?;
|
||||||
let metadata = HashMap::from([
|
let mut metadata = HashMap::from([
|
||||||
(
|
(
|
||||||
"nodeName".to_string(),
|
"nodeName".to_string(),
|
||||||
json!(meta.name.unwrap_or(CONFIG.host.clone())),
|
json!(meta.name.unwrap_or_else(|| CONFIG.host.clone())),
|
||||||
),
|
),
|
||||||
("nodeDescription".to_string(), json!(meta.description)),
|
("nodeDescription".to_string(), json!(meta.description)),
|
||||||
("repositoryUrl".to_string(), json!(meta.repository_url)),
|
("repositoryUrl".to_string(), json!(meta.repository_url)),
|
||||||
|
@ -83,12 +94,12 @@ async fn generate_nodeinfo_2_1() -> Result<Nodeinfo21, Error> {
|
||||||
("proxyAccountName".to_string(), json!(meta.proxy_account_id)),
|
("proxyAccountName".to_string(), json!(meta.proxy_account_id)),
|
||||||
(
|
(
|
||||||
"themeColor".to_string(),
|
"themeColor".to_string(),
|
||||||
json!(meta.theme_color.unwrap_or("#31748f".to_string())),
|
json!(meta.theme_color.unwrap_or_else(|| "#31748f".to_string())),
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
metadata.shrink_to_fit();
|
||||||
|
|
||||||
Ok(Nodeinfo21 {
|
Ok(Nodeinfo21 {
|
||||||
version: "2.1".to_string(),
|
|
||||||
software: Software21 {
|
software: Software21 {
|
||||||
name: "firefish".to_string(),
|
name: "firefish".to_string(),
|
||||||
version: CONFIG.version.clone(),
|
version: CONFIG.version.clone(),
|
||||||
|
@ -114,32 +125,53 @@ async fn generate_nodeinfo_2_1() -> Result<Nodeinfo21, Error> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns NodeInfo (version 2.1) of the local server.
|
async fn nodeinfo_2_1_impl(use_cache: bool) -> Result<Nodeinfo21, DbErr> {
|
||||||
pub async fn nodeinfo_2_1() -> Result<Nodeinfo21, Error> {
|
if use_cache {
|
||||||
const NODEINFO_2_1_CACHE_KEY: &str = "nodeinfo_2_1";
|
if let Some(nodeinfo) = CACHE.lock().ok().and_then(|cache| cache.to_owned()) {
|
||||||
|
return Ok(nodeinfo);
|
||||||
let cached = cache::get::<Nodeinfo21>(NODEINFO_2_1_CACHE_KEY).await?;
|
}
|
||||||
|
|
||||||
if let Some(nodeinfo) = cached {
|
|
||||||
Ok(nodeinfo)
|
|
||||||
} else {
|
|
||||||
let nodeinfo = generate_nodeinfo_2_1().await?;
|
|
||||||
cache::set(NODEINFO_2_1_CACHE_KEY, &nodeinfo, 60 * 60).await?;
|
|
||||||
Ok(nodeinfo)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let nodeinfo = generate_nodeinfo_2_1().await?;
|
||||||
|
|
||||||
|
tracing::info!("updating cache");
|
||||||
|
set_cache(&nodeinfo);
|
||||||
|
|
||||||
|
Ok(nodeinfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns NodeInfo (version 2.1) of the local server.
|
||||||
|
pub async fn nodeinfo_2_1() -> Result<Nodeinfo21, DbErr> {
|
||||||
|
nodeinfo_2_1_impl(true).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns NodeInfo (version 2.0) of the local server.
|
/// Returns NodeInfo (version 2.0) of the local server.
|
||||||
pub async fn nodeinfo_2_0() -> Result<Nodeinfo20, Error> {
|
pub async fn nodeinfo_2_0() -> Result<Nodeinfo20, DbErr> {
|
||||||
Ok(nodeinfo_2_1().await?.into())
|
Ok(nodeinfo_2_1().await?.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[crate::ts_export(js_name = "nodeinfo_2_1")]
|
#[cfg(feature = "napi")]
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
#[doc = "database error"]
|
||||||
|
#[error(transparent)]
|
||||||
|
Db(#[from] DbErr),
|
||||||
|
#[error("failed to serialize nodeinfo into JSON")]
|
||||||
|
Json(#[from] serde_json::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macros::ts_export(js_name = "nodeinfo_2_1")]
|
||||||
pub async fn nodeinfo_2_1_as_json() -> Result<serde_json::Value, Error> {
|
pub async fn nodeinfo_2_1_as_json() -> Result<serde_json::Value, Error> {
|
||||||
Ok(serde_json::to_value(nodeinfo_2_1().await?)?)
|
Ok(serde_json::to_value(nodeinfo_2_1().await?)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[crate::ts_export(js_name = "nodeinfo_2_0")]
|
#[macros::ts_export(js_name = "nodeinfo_2_0")]
|
||||||
pub async fn nodeinfo_2_0_as_json() -> Result<serde_json::Value, Error> {
|
pub async fn nodeinfo_2_0_as_json() -> Result<serde_json::Value, Error> {
|
||||||
Ok(serde_json::to_value(nodeinfo_2_0().await?)?)
|
Ok(serde_json::to_value(nodeinfo_2_0().await?)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macros::ts_export(js_name = "updateNodeinfoCache")]
|
||||||
|
pub async fn update_cache() -> Result<(), DbErr> {
|
||||||
|
nodeinfo_2_1_impl(false).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
//! NodeInfo handler
|
//! NodeInfo handler
|
||||||
|
//!
|
||||||
|
//! ref: <https://nodeinfo.diaspora.software/>
|
||||||
|
|
||||||
pub mod fetch;
|
pub mod fetch;
|
||||||
pub mod generate;
|
pub mod generate;
|
||||||
|
|
|
@ -1,18 +1,16 @@
|
||||||
//! Schema definitions of NodeInfo version 2.0 and 2.1
|
//! Schema definitions of NodeInfo version 2.0 and 2.1
|
||||||
|
//!
|
||||||
|
//! ref: <https://nodeinfo.diaspora.software/schema.html>
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
// TODO: I want to use these macros but they don't work with rmp_serde
|
|
||||||
// * #[serde(skip_serializing_if = "Option::is_none")] (https://github.com/3Hren/msgpack-rust/issues/86)
|
|
||||||
// * #[serde(tag = "version", rename = "2.1")] (https://github.com/3Hren/msgpack-rust/issues/318)
|
|
||||||
|
|
||||||
/// NodeInfo schema version 2.1. <https://nodeinfo.diaspora.software/docson/index.html#/ns/schema/2.1>
|
/// NodeInfo schema version 2.1. <https://nodeinfo.diaspora.software/docson/index.html#/ns/schema/2.1>
|
||||||
#[derive(Deserialize, Serialize, Debug, PartialEq)]
|
#[cfg_attr(test, derive(Debug, PartialEq, Deserialize))]
|
||||||
|
#[derive(Clone, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[serde(tag = "version", rename = "2.1")]
|
||||||
pub struct Nodeinfo21 {
|
pub struct Nodeinfo21 {
|
||||||
/// The schema version, must be 2.1.
|
|
||||||
pub version: String,
|
|
||||||
/// Metadata about server software in use.
|
/// Metadata about server software in use.
|
||||||
pub software: Software21,
|
pub software: Software21,
|
||||||
/// The protocols supported on this server.
|
/// The protocols supported on this server.
|
||||||
|
@ -28,12 +26,12 @@ pub struct Nodeinfo21 {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// NodeInfo schema version 2.0. <https://nodeinfo.diaspora.software/docson/index.html#/ns/schema/2.0>
|
/// NodeInfo schema version 2.0. <https://nodeinfo.diaspora.software/docson/index.html#/ns/schema/2.0>
|
||||||
#[derive(Deserialize, Serialize, Debug, PartialEq)]
|
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[crate::export(object, js_name = "Nodeinfo")]
|
#[macros::export(object, js_name = "Nodeinfo")]
|
||||||
|
#[serde(tag = "version", rename = "2.0")]
|
||||||
pub struct Nodeinfo20 {
|
pub struct Nodeinfo20 {
|
||||||
/// The schema version, must be 2.0.
|
|
||||||
pub version: String,
|
|
||||||
/// Metadata about server software in use.
|
/// Metadata about server software in use.
|
||||||
pub software: Software20,
|
pub software: Software20,
|
||||||
/// The protocols supported on this server.
|
/// The protocols supported on this server.
|
||||||
|
@ -49,7 +47,8 @@ pub struct Nodeinfo20 {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Metadata about server software in use (version 2.1).
|
/// Metadata about server software in use (version 2.1).
|
||||||
#[derive(Deserialize, Serialize, Debug, PartialEq)]
|
#[cfg_attr(test, derive(Debug, PartialEq, Deserialize))]
|
||||||
|
#[derive(Clone, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Software21 {
|
pub struct Software21 {
|
||||||
/// The canonical name of this server software.
|
/// The canonical name of this server software.
|
||||||
|
@ -57,15 +56,18 @@ pub struct Software21 {
|
||||||
/// The version of this server software.
|
/// The version of this server software.
|
||||||
pub version: String,
|
pub version: String,
|
||||||
/// The url of the source code repository of this server software.
|
/// The url of the source code repository of this server software.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub repository: Option<String>,
|
pub repository: Option<String>,
|
||||||
/// The url of the homepage of this server software.
|
/// The url of the homepage of this server software.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub homepage: Option<String>,
|
pub homepage: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Metadata about server software in use (version 2.0).
|
/// Metadata about server software in use (version 2.0).
|
||||||
#[derive(Deserialize, Serialize, Debug, PartialEq)]
|
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[crate::export(object)]
|
#[macros::export(object)]
|
||||||
pub struct Software20 {
|
pub struct Software20 {
|
||||||
/// The canonical name of this server software.
|
/// The canonical name of this server software.
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -73,9 +75,10 @@ pub struct Software20 {
|
||||||
pub version: String,
|
pub version: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug, PartialEq)]
|
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
#[crate::export(string_enum = "lowercase")]
|
#[macros::derive_clone_and_export]
|
||||||
pub enum Protocol {
|
pub enum Protocol {
|
||||||
Activitypub,
|
Activitypub,
|
||||||
Buddycloud,
|
Buddycloud,
|
||||||
|
@ -90,9 +93,10 @@ pub enum Protocol {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The third party sites this server can connect to via their application API.
|
/// The third party sites this server can connect to via their application API.
|
||||||
#[derive(Deserialize, Serialize, Debug, PartialEq)]
|
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||||
|
#[derive(Clone, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[crate::export(object)]
|
#[macros::export(object)]
|
||||||
pub struct Services {
|
pub struct Services {
|
||||||
/// The third party sites this server can retrieve messages from for combined display with regular traffic.
|
/// The third party sites this server can retrieve messages from for combined display with regular traffic.
|
||||||
pub inbound: Vec<Inbound>,
|
pub inbound: Vec<Inbound>,
|
||||||
|
@ -101,9 +105,10 @@ pub struct Services {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The third party sites this server can retrieve messages from for combined display with regular traffic.
|
/// The third party sites this server can retrieve messages from for combined display with regular traffic.
|
||||||
#[derive(Deserialize, Serialize, Debug, PartialEq)]
|
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
#[crate::export(string_enum = "lowercase")]
|
#[macros::derive_clone_and_export]
|
||||||
pub enum Inbound {
|
pub enum Inbound {
|
||||||
#[serde(rename = "atom1.0")]
|
#[serde(rename = "atom1.0")]
|
||||||
Atom1,
|
Atom1,
|
||||||
|
@ -119,9 +124,10 @@ pub enum Inbound {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The third party sites this server can publish messages to on the behalf of a user.
|
/// The third party sites this server can publish messages to on the behalf of a user.
|
||||||
#[derive(Deserialize, Serialize, Debug, PartialEq)]
|
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
#[crate::export(string_enum = "lowercase")]
|
#[macros::derive_clone_and_export]
|
||||||
pub enum Outbound {
|
pub enum Outbound {
|
||||||
#[serde(rename = "atom1.0")]
|
#[serde(rename = "atom1.0")]
|
||||||
Atom1,
|
Atom1,
|
||||||
|
@ -156,22 +162,29 @@ pub enum Outbound {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Usage statistics for this server.
|
/// Usage statistics for this server.
|
||||||
#[derive(Deserialize, Serialize, Debug, PartialEq)]
|
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||||
|
#[derive(Clone, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[crate::export(object)]
|
#[macros::export(object)]
|
||||||
pub struct Usage {
|
pub struct Usage {
|
||||||
pub users: Users,
|
pub users: Users,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub local_posts: Option<u32>,
|
pub local_posts: Option<u32>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub local_comments: Option<u32>,
|
pub local_comments: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// statistics about the users of this server.
|
/// statistics about the users of this server.
|
||||||
#[derive(Deserialize, Serialize, Debug, PartialEq)]
|
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||||
|
#[derive(Clone, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[crate::export(object)]
|
#[macros::export(object)]
|
||||||
pub struct Users {
|
pub struct Users {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub total: Option<u32>,
|
pub total: Option<u32>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub active_halfyear: Option<u32>,
|
pub active_halfyear: Option<u32>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub active_month: Option<u32>,
|
pub active_month: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,7 +200,6 @@ impl From<Software21> for Software20 {
|
||||||
impl From<Nodeinfo21> for Nodeinfo20 {
|
impl From<Nodeinfo21> for Nodeinfo20 {
|
||||||
fn from(nodeinfo: Nodeinfo21) -> Self {
|
fn from(nodeinfo: Nodeinfo21) -> Self {
|
||||||
Self {
|
Self {
|
||||||
version: "2.0".to_string(),
|
|
||||||
software: nodeinfo.software.into(),
|
software: nodeinfo.software.into(),
|
||||||
protocols: nodeinfo.protocols,
|
protocols: nodeinfo.protocols,
|
||||||
services: nodeinfo.services,
|
services: nodeinfo.services,
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue