Compare commits
222 commits
docs/insta
...
main
Author | SHA1 | Date | |
---|---|---|---|
Johann150 | 17324e1e94 | ||
Johann150 | 8b98c9f2f4 | ||
Johann150 | be30e70344 | ||
Johann150 | 39fb7e5946 | ||
Johann150 | 75b14124f2 | ||
Johann150 | 7480e27c0c | ||
Johann150 | 953de3e4b2 | ||
Johann150 | 2d32bc33d7 | ||
Chloe Kudryavtsev | bb3ec8bafe | ||
Johann150 | 6fd80816fa | ||
Johann150 | cc83cbe523 | ||
Johann150 | 8abd3ebec7 | ||
Johann150 | 36031c083a | ||
Johann150 | 05f8172ce9 | ||
Johann150 | 151053897d | ||
Johann150 | 95a9027a66 | ||
Johann150 | 57cf6c7163 | ||
Johann150 | 9b76c805ec | ||
Johann150 | 21b20920c2 | ||
Johann150 | e7644eb757 | ||
Johann150 | 66ec875624 | ||
Johann150 | 78f5ca3792 | ||
Johann150 | c792e4199c | ||
Johann150 | afa4094050 | ||
Johann150 | c4b5952788 | ||
Johann150 | e3fd371f4a | ||
Johann150 | 5893a44ff5 | ||
Johann150 | 9bdf24d3a5 | ||
Johann150 | 2bbb85b472 | ||
Johann150 | 70fb1e9a5c | ||
Johann150 | 48163872ed | ||
Johann150 | 7170b86724 | ||
Johann150 | 96e6187e83 | ||
Johann150 | 3d2cfc075a | ||
Norm | 83373e0c51 | ||
Johann150 | 11518d2f26 | ||
Johann150 | f9c360d59f | ||
Johann150 | 2a8792fe07 | ||
Johann150 | b8963a0119 | ||
Johann150 | 1319dc93d9 | ||
Johann150 | 80e2851378 | ||
Johann150 | 14b48fb07c | ||
Johann150 | 5e2a9224f3 | ||
Johann150 | 624628d582 | ||
Johann150 | e68eeba7a6 | ||
Johann150 | ee2fa2e0be | ||
Johann150 | 57d1af1117 | ||
Johann150 | 8c2b7e20b2 | ||
Johann150 | fdf30f60e6 | ||
Johann150 | b245d39b6e | ||
Johann150 | 80f72e21cd | ||
Johann150 | 85e985d13f | ||
Johann150 | 4fe288f17c | ||
Johann150 | cd26e3a35c | ||
Johann150 | c7ab8839dc | ||
Johann150 | 1eda1760d1 | ||
Johann150 | 8772181b6f | ||
Norm | 8f21275dd5 | ||
Norm | 5102d0bc2e | ||
Puniko | cdba5447e6 | ||
Johann150 | 4bb814adfc | ||
Johann150 | 35e9d7f958 | ||
Johann150 | a0c2cf328e | ||
Johann150 | 334368f6e2 | ||
Johann150 | 3efa7046bd | ||
Johann150 | 48f8fb97df | ||
Johann150 | 0230f819e2 | ||
Norm | e58c940d6f | ||
Johann150 | 08af6fda37 | ||
Johann150 | 0c8a3cfeec | ||
Johann150 | 8bc366fde0 | ||
Johann150 | 417d252e9d | ||
Johann150 | b54e07caec | ||
Johann150 | e0560dbe9e | ||
Johann150 | 5b898c6c82 | ||
Johann150 | 6010884e62 | ||
Johann150 | b423d23cf6 | ||
Johann150 | 29714d1ae0 | ||
Johann150 | 7bf4d4426a | ||
Johann150 | d28931bf00 | ||
Johann150 | 2a46719f31 | ||
Johann150 | 7f564431be | ||
Johann150 | 0fbd7fa492 | ||
Johann150 | 3aaa9facc6 | ||
Johann150 | 8f09b05e7c | ||
Johann150 | 8b0b7ff525 | ||
Johann150 | 0b7c9095bf | ||
Johann150 | 338e898f56 | ||
Puniko | 3a9d283630 | ||
Johann150 | ed27f61a4d | ||
Chloe Kudryavtsev | ed9d4023d4 | ||
Johann150 | 76a8e000a3 | ||
Johann150 | a673647fba | ||
Johann150 | eea2eb4919 | ||
Johann150 | 114d416de0 | ||
Johann150 | c2372315f7 | ||
Norm | 09bc3cf95a | ||
Johann150 | de3cdb5833 | ||
Norm | a732cdc1ad | ||
Norm | a8f82050c8 | ||
Norm | 8e12b9a33e | ||
Norm | 6583d0c43d | ||
Johann150 | c02a03168d | ||
Johann150 | 85419326f8 | ||
Johann150 | eaa11647f0 | ||
Johann150 | 61a2db49df | ||
Johann150 | 79ddbafd0d | ||
Norm | 0e1459e5cf | ||
Johann150 | e8e82dac82 | ||
Norm | 9690244848 | ||
Norm | 4db25e4b1f | ||
Norm | 549302e9c0 | ||
Norm | a3354904af | ||
Norm | 28f65bebfc | ||
Norm | 2204adc657 | ||
Norm | b11e4053db | ||
Johann150 | e2ef800708 | ||
Johann150 | a7048f17f7 | ||
Johann150 | ddf3e2c3db | ||
Johann150 | 52afff800a | ||
Johann150 | 33f0b24c56 | ||
Andy | 7685b92511 | ||
Andy | 8276bd3bdc | ||
Andy | aed2752470 | ||
Andy | 4a3b91d658 | ||
Johann150 | 9317d25078 | ||
Johann150 | fc36bb8880 | ||
Johann150 | 711bb8be7d | ||
Johann150 | 275136cf8b | ||
Johann150 | aa33708b90 | ||
Norm | d75e295ee8 | ||
Norm | 766ab1c4c4 | ||
Johann150 | 99c459a21a | ||
Johann150 | bd68096ea9 | ||
Johann150 | c411669133 | ||
Johann150 | 639fa74d43 | ||
Johann150 | 3bf7deb233 | ||
Johann150 | 2520633210 | ||
Johann150 | 4574db523a | ||
Johann150 | 263fb94f3f | ||
Johann150 | 0e3321c106 | ||
Johann150 | 677ee537d1 | ||
Johann150 | 1e539e6af5 | ||
Johann150 | 0e5f744560 | ||
Norm | 8f782f8ce5 | ||
Johann150 | 6c7f1774e3 | ||
Johann150 | af43df15ca | ||
Johann150 | 5f83383ab8 | ||
Johann150 | 8c759dde6c | ||
Johann150 | 84d83d908a | ||
Johann150 | 16d091497a | ||
Johann150 | ef53ec276a | ||
Johann150 | 3582fd8260 | ||
Johann150 | 6256ddbd30 | ||
Johann150 | 00fcc238f7 | ||
Johann150 | 9f1670d5fd | ||
Norm | ff31b8b06d | ||
Johann150 | e317a771b3 | ||
Johann150 | 398ee6435b | ||
Johann150 | ffff2ae5ef | ||
Johann150 | ccc8bf0289 | ||
Johann150 | a231b36d59 | ||
Johann150 | 8e9c65fab0 | ||
Norm | 78a3051313 | ||
Norm | 78717e85d3 | ||
Norm | a9d3cae511 | ||
Norm | bd27b7ca3a | ||
Norm | e28a9eb8e8 | ||
Norm | e5a4c5d2d0 | ||
Norm | 6bba55c196 | ||
Norm | 1d469f3c34 | ||
Norm | 3f0228e14c | ||
Johann150 | 73f81177b4 | ||
Johann150 | 6a26da3516 | ||
Johann150 | 5ea744b1b2 | ||
Johann150 | ae6ba05306 | ||
21069223e3 | |||
Johann150 | d4d1e03479 | ||
Norm | 5513a3eb3a | ||
Johann150 | d5dd7c1ef5 | ||
Johann150 | a80521b6a8 | ||
Norm | 030394b30d | ||
Johann150 | 768d9bbdfb | ||
Johann150 | 3ef1a4b0f9 | ||
Johann150 | ae59ce51b0 | ||
Johann150 | 14a9b9bedd | ||
Johann150 | 985a13f47f | ||
Norm | 3e46433ede | ||
Johann150 | 507b328fdf | ||
Norm | cf7449509f | ||
Norm | f46ba3f700 | ||
Norm | 3cf673960b | ||
Norm | cbfd866122 | ||
Norm | b23a8dbaed | ||
Norm | 80a73a7510 | ||
Norm | 3dec9a47f0 | ||
Norm | b8fb7a38cc | ||
Norm | fdc682e810 | ||
Johann150 | fde751df8f | ||
Johann150 | 1faf1035f9 | ||
Johann150 | e2ce599aca | ||
Johann150 | 73870e85cd | ||
Norm | 350f21d955 | ||
Norm | 873e21f090 | ||
Norm | 2afe54c121 | ||
Johann150 | 501cf834c8 | ||
Norm | b66f7550ab | ||
Johann150 | 18664dbca3 | ||
Johann150 | 0f3f42eb39 | ||
Johann150 | 71b976ec96 | ||
Andy | d3f1ad9a88 | ||
Andy | 1aa3898db5 | ||
Andy | 96c3744555 | ||
Andy | b023741f50 | ||
Andy | 87e1e658f2 | ||
Andy | 7e8d5c3b79 | ||
Andy | c785fbab6e | ||
Andy | 547a1f81d4 | ||
Andy | 95384d0bb2 | ||
Andy | 4cc5b734e7 | ||
Andy | 5d32872999 | ||
Andy | b4b1204f77 |
|
@ -133,3 +133,10 @@ redis:
|
|||
#allowedPrivateNetworks: [
|
||||
# '127.0.0.1/32'
|
||||
#]
|
||||
|
||||
# images used on error screens. You can use absolute or relative URLs.
|
||||
# If you use relative URLs, be aware that the URL may be used on different pages/paths, so the path component should be absolute.
|
||||
#images:
|
||||
# info: /twemoji/1f440.svg
|
||||
# notFound: /twemoji/2049.svg
|
||||
# error: /twemoji/1f480.svg
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.autogen
|
||||
.vscode
|
||||
.config
|
||||
.woodpecker
|
||||
Dockerfile
|
||||
build/
|
||||
built/
|
||||
|
|
|
@ -2,9 +2,10 @@ root = true
|
|||
|
||||
[*]
|
||||
indent_style = tab
|
||||
indent_size = 2
|
||||
indent_size = 4
|
||||
charset = utf-8
|
||||
insert_final_newline = true
|
||||
|
||||
[*.yml]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
|
6
.gitattributes
vendored
|
@ -1,7 +1 @@
|
|||
*.svg -diff -text
|
||||
*.psd -diff -text
|
||||
*.ai -diff -text
|
||||
*.mqo -diff -text
|
||||
*.glb -diff -text
|
||||
*.blend -diff -text
|
||||
*.afdesign -diff -text
|
||||
|
|
5
.gitignore
vendored
|
@ -1,6 +1,6 @@
|
|||
# Visual Studio Code
|
||||
/.vscode
|
||||
!/.vscode/extensions.json
|
||||
/.vsls.json
|
||||
|
||||
# Intelij-IDEA
|
||||
/.idea
|
||||
|
@ -11,6 +11,9 @@
|
|||
# nano
|
||||
.swp
|
||||
|
||||
# vimlocal
|
||||
.vimlocal
|
||||
|
||||
# Node.js
|
||||
node_modules
|
||||
report.*.json
|
||||
|
|
6
.mailmap
|
@ -1,9 +1,9 @@
|
|||
Andreas Nedbal <git@pixelde.su> <andreas.nedbal@in2code.de>
|
||||
Andreas Nedbal <git@pixelde.su> <github-bf215181b5140522137b3d4f6b73544a@desu.email>
|
||||
Balazs Nadasdi <balazs@weave.works> <yitsushi@gmail.com>
|
||||
Chloe Kudryavtsev <code@code.bunkerlabs.net> <code@toast.bunkerlabs.net>
|
||||
Chloe Kudryavtsev <code@code.bunkerlabs.net> <toast+git@toast.cafe>
|
||||
Chloe Kudryavtsev <code@code.bunkerlabs.net> <toast@toast.cafe>
|
||||
Chloe Kudryavtsev <code@toast.bunkerlabs.net> <code@code.bunkerlabs.net>
|
||||
Chloe Kudryavtsev <code@toast.bunkerlabs.net> <toast+git@toast.cafe>
|
||||
Chloe Kudryavtsev <code@toast.bunkerlabs.net> <toast@toast.cafe>
|
||||
Dr. Gutfuck LLC <40531868+gutfuckllc@users.noreply.github.com>
|
||||
Ehsan Javadynia <31900907+ehsanjavadynia@users.noreply.github.com> <ehsan.javadynia@gmail.com>
|
||||
Francis Dinh <normandy@biribiri.dev>
|
||||
|
|
8
.vscode/extensions.json
vendored
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"editorconfig.editorconfig",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"Vue.volar",
|
||||
"Vue.vscode-typescript-vue-plugin"
|
||||
]
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json.schemastore.org/vsls",
|
||||
"gitignore": "exclude"
|
||||
}
|
22
.woodpecker/lint-sw.yml
Normal file
|
@ -0,0 +1,22 @@
|
|||
clone:
|
||||
git:
|
||||
image: woodpeckerci/plugin-git
|
||||
settings:
|
||||
depth: 1 # CI does not need commit history
|
||||
recursive: true
|
||||
|
||||
pipeline:
|
||||
install:
|
||||
when:
|
||||
event:
|
||||
- pull_request
|
||||
image: node:18.6.0
|
||||
commands:
|
||||
- yarn install
|
||||
lint:
|
||||
when:
|
||||
event:
|
||||
- pull_request
|
||||
image: node:18.6.0
|
||||
commands:
|
||||
- yarn workspace sw run lint
|
|
@ -316,8 +316,8 @@ This does not apply when using the Composition API since reactivation is manual.
|
|||
If you import json in TypeScript, the json file will be spit out together with the TypeScript file into the dist directory when compiling with tsc. This behavior may cause unintentional rewriting of files, so when importing json files, be sure to check whether the files are allowed to be rewritten or not. If you do not want the file to be rewritten, you should make sure that the file can be rewritten by importing the json file. If you do not want the file to be rewritten, use functions such as `fs.readFileSync` to read the file instead of importing it.
|
||||
|
||||
### Component style definitions do not have a `margin`
|
||||
Setting the `margin` of a component may be confusing.
|
||||
Instead, it should always be the user of a component that sets a `margin`.
|
||||
~~Setting the `margin` of a component may be confusing. Instead, it should always be the user of a component that sets a `margin`.~~
|
||||
This was a philosophy used previously. Hoever it now seems a better idea to add a default margin to the top level element of a component which can be easily overwritten on the usage of that component with a `style` attribute.
|
||||
|
||||
### Do not use the word "follow" in HTML class names
|
||||
This has caused things to be blocked by an ad blocker in the past.
|
||||
|
|
10
COPYING
|
@ -1,6 +1,6 @@
|
|||
Unless otherwise stated this repository is
|
||||
Copyright © 2014-2022 syuilo and contributors
|
||||
Copyright © 2022 FoundKey contributors
|
||||
Copyright © 2022-2023 FoundKey contributors
|
||||
And is distributed under The GNU Affero General Public License Version 3, you should have received a copy of the license file as LICENSE.
|
||||
(You may be able to run `git shortlog -se` to see a full list of authors.)
|
||||
|
||||
|
@ -13,3 +13,11 @@ https://github.com/muan/emojilib/blob/master/LICENSE
|
|||
RsaSignature2017 implementation by Transmute Industries Inc
|
||||
License: MIT
|
||||
https://github.com/transmute-industries/RsaSignature2017/blob/master/LICENSE
|
||||
|
||||
Chiptune2.js by Simon Gündling
|
||||
License: MIT
|
||||
https://github.com/deskjet/chiptune2.js#license
|
||||
|
||||
libopenmpt (as part of openmpt) by OpenMPT
|
||||
License: BSD 3-Clause
|
||||
https://github.com/OpenMPT/openmpt/blob/master/LICENSE
|
||||
|
|
|
@ -3,9 +3,9 @@ Note: this document is historical.
|
|||
Everything starting with the next section is the original "idea" document that led to the foundation of FoundKey.
|
||||
|
||||
For the current status you should see the following:
|
||||
* The Behavioral Fixes [project](https://akkoma.dev/FoundKeyGang/FoundKey/projects/3)
|
||||
* The Technological Upkeep [project](https://akkoma.dev/FoundKeyGang/FoundKey/projects/4)
|
||||
* The Features [project](https://akkoma.dev/FoundKeyGang/FoundKey/projects/5)
|
||||
* Issues labeled with [behaviour-fix](https://akkoma.dev/FoundKeyGang/FoundKey/issues?labels=44)
|
||||
* Issues labeled with [upkeep](https://akkoma.dev/FoundKeyGang/FoundKey/issues?labels=43)
|
||||
* Issues labeled with [feature](https://akkoma.dev/FoundKeyGang/FoundKey/issues?labels=42)
|
||||
|
||||
## Misskey Goals
|
||||
I’ve been thinking about a community misskey fork for a while now. To some of you, this is not a surprise. Let’s talk about that.
|
||||
|
|
Before Width: | Height: | Size: 94 KiB |
Before Width: | Height: | Size: 317 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 95 KiB |
Before Width: | Height: | Size: 200 KiB |
BIN
assets/ai.png
Before Width: | Height: | Size: 235 KiB |
Before Width: | Height: | Size: 238 KiB |
Before Width: | Height: | Size: 148 KiB |
BIN
assets/title.png
Before Width: | Height: | Size: 3.8 KiB |
|
@ -70,6 +70,8 @@ Build foundkey with the following:
|
|||
|
||||
`NODE_ENV=production yarn build`
|
||||
|
||||
If your system has at least 4GB of RAM, run `NODE_ENV=production yarn build-parallel` to speed up build times.
|
||||
|
||||
If you're still encountering errors about some modules, use node-gyp:
|
||||
|
||||
1. `npx node-gyp configure`
|
||||
|
@ -182,6 +184,7 @@ Use git to pull in the latest changes and rerun the build and migration commands
|
|||
git pull
|
||||
git submodule update --init
|
||||
yarn install
|
||||
# Use build-parallel if your system has 4GB or more RAM and want faster builds
|
||||
NODE_ENV=production yarn build
|
||||
yarn migrate
|
||||
```
|
||||
|
|
103
docs/moderation.md
Normal file
|
@ -0,0 +1,103 @@
|
|||
# User moderation
|
||||
|
||||
A lot of the user moderation activities can be found on the `user-info` page. You can reach this page by going to a users profile page, open the three dot menu, select "About" and navigating to the "Moderation" section of the page that opens.
|
||||
With the necessary privileges, this page will allow you to:
|
||||
- Toggle whether a user is a moderator (administrators on local users only)
|
||||
- Reset the users password (local users only)
|
||||
- Delete a user (administrators only)
|
||||
- Delete all files of a user
|
||||
For remote users, cached files (if any) will be deleted.
|
||||
- Silence a user
|
||||
This disallows a user from making a note with `public` visibility.
|
||||
If necessary the visibility of incoming notes or locally created notes will be lowered.
|
||||
- Suspend a user
|
||||
This will drop any incoming activities of this actor and hide them from public view on this instance.
|
||||
|
||||
# Administrator
|
||||
|
||||
When an instance is first set up, the initial user to be created will be made an administrator by default.
|
||||
This means that typically the instance owner is the administrator.
|
||||
It is also possible to have multiple administrators, however making a user an administrator is not implemented in the client.
|
||||
To make a user an administrator, you will need access to the database.
|
||||
This is intended for security reasons of
|
||||
1. not exposing this very dangerous functionality via the API
|
||||
2. making sure someone that has shell access to the server anyway "approves" this.
|
||||
|
||||
To make a user an administrator, you will first need the user's ID.
|
||||
To get it you can go to the user's profile page, open the three dot menu, select "About" and copy the ID displayed there.
|
||||
Then, go to the database and run the following query, replacing `<ID>` with the ID gotten above.
|
||||
```sql
|
||||
UPDATE "user" SET "isAdmin" = true WHERE "id" = '<ID>';
|
||||
```
|
||||
|
||||
The user that was made administrator may need to reload their client to see the changes take effect.
|
||||
|
||||
To demote a user, you can do a similar operation, but instead with `... SET "isAdmin" = false ...`.
|
||||
|
||||
## Immunity
|
||||
|
||||
- Cannot be reported by local users.
|
||||
- Cannot have their password reset.
|
||||
To see how you can reset an administrator password, see below.
|
||||
- Cannot have their account deleted.
|
||||
- Cannot be suspended.
|
||||
- Cannot be silenced.
|
||||
- Cannot have their account details viewed by moderators.
|
||||
- Cannot be made moderators.
|
||||
|
||||
## Abilities
|
||||
|
||||
- Create or delete user accounts.
|
||||
- Add or remove moderators.
|
||||
- View and change instance configuration (e.g. Translation API keys).
|
||||
- View all followers and followees.
|
||||
|
||||
Administrators also have the same ability as moderators.
|
||||
Note of course that people with access to the server and/or database access can do basically anything without restrictions (including breaking the instance).
|
||||
|
||||
## Resetting an administrators password
|
||||
|
||||
Administrators are blocked from the paths of resetting the password by moderators or administrators.
|
||||
However, if your server has email configured you should be able to use the "Forgot password" link on the normal signin dialog.
|
||||
|
||||
If you did not set up email, you will need to kick of this process instead through modifying the database yourself.
|
||||
You will need the user ID whose password should be reset, indicated in the following as `<USERID>`;
|
||||
as well as a random string (a UUID would be recommended) indicated as `<TOKEN>`.
|
||||
|
||||
Replacing the two terms above, run the following SQL query:
|
||||
```sql
|
||||
INSERT INTO "password_reset_request" VALUES ('0000000000', now(), '<TOKEN>', '<USERID>');
|
||||
```
|
||||
|
||||
After that, navigate to `/reset-password/<TOKEN>` on your instance to finish the password reset process.
|
||||
After that you should be able to sign in with the new password you just set.
|
||||
|
||||
# Moderator
|
||||
|
||||
A moderator has fewer privileges than an administrator.
|
||||
They can also be more easily added or removed by an adminstrator.
|
||||
Having moderators may be a good idea to help with user moderation.
|
||||
|
||||
## Immunity
|
||||
|
||||
- Cannot be reported by local users.
|
||||
- Cannot be suspended.
|
||||
|
||||
## Abilities
|
||||
|
||||
- Suspend users.
|
||||
- Add, list and remove relays.
|
||||
- View queue, database and server information.
|
||||
- Create, edit, delete, export and import local custom emoji.
|
||||
- View global, social and local timelines even if disabled by administrators.
|
||||
- Show, update and delete any users files and file metadata.
|
||||
Managing emoji is described in [a separate file](emoji.md).
|
||||
- Delete any users notes.
|
||||
- Create an invitation.
|
||||
This allows users to register an account even if (public) registrations are closed using an invite code.
|
||||
- View users' account details.
|
||||
- Suspend and unsuspend users.
|
||||
- Silence and unsilence users.
|
||||
- Handle reports.
|
||||
- Create, update and delete announcements.
|
||||
- View the moderation log.
|
|
@ -1,9 +1,9 @@
|
|||
# 3rd party access
|
||||
Foundkey supports:
|
||||
- OAuth 2.0 Authorization Code grant per RFC 6749.
|
||||
- OAuth Bearer Token Usage per RFC 6750.
|
||||
- Proof Key for Code Exchange (PKCE) per RFC 7636.
|
||||
- OAuth 2.0 Authorization Server Metadata per RFC 8414.
|
||||
- OAuth 2.0 Authorization Code grant per [RFC 6749](https://www.rfc-editor.org/rfc/rfc6749).
|
||||
- OAuth Bearer Token Usage per [RFC 6750](https://www.rfc-editor.org/rfc/rfc6750).
|
||||
- Proof Key for Code Exchange (PKCE) per [RFC 7636](https://www.rfc-editor.org/rfc/rfc7636).
|
||||
- OAuth 2.0 Authorization Server Metadata per [RFC 8414](https://www.rfc-editor.org/rfc/rfc8414.html).
|
||||
|
||||
# Discovery
|
||||
Because the implementation may change in the future, it is recommended that you use OAuth 2.0 Authorization Server Metadata a.k.a. OpenID Connect Discovery.
|
||||
|
|
|
@ -36,7 +36,7 @@ gulp.task('copy:client:locales', cb => {
|
|||
});
|
||||
|
||||
gulp.task('build:backend:script', () => {
|
||||
return gulp.src(['./packages/backend/src/server/web/boot.js', './packages/backend/src/server/web/bios.js', './packages/backend/src/server/web/cli.js'])
|
||||
return gulp.src(['./packages/backend/src/server/web/boot.js'])
|
||||
.pipe(replace('LANGS', JSON.stringify(Object.keys(locales))))
|
||||
.pipe(terser({
|
||||
toplevel: true
|
||||
|
@ -45,7 +45,7 @@ gulp.task('build:backend:script', () => {
|
|||
});
|
||||
|
||||
gulp.task('build:backend:style', () => {
|
||||
return gulp.src(['./packages/backend/src/server/web/style.css', './packages/backend/src/server/web/bios.css', './packages/backend/src/server/web/cli.css'])
|
||||
return gulp.src(['./packages/backend/src/server/web/style.css'])
|
||||
.pipe(cssnano({
|
||||
zindex: false
|
||||
}))
|
||||
|
|
|
@ -293,9 +293,6 @@ dayX: "{day}"
|
|||
monthX: "{month}"
|
||||
yearX: "{year}"
|
||||
pages: "الصفحات"
|
||||
integration: "التكامل"
|
||||
connectService: "اتصل"
|
||||
disconnectService: "اقطع الاتصال"
|
||||
enableLocalTimeline: "تفعيل الخيط المحلي"
|
||||
enableGlobalTimeline: "تفعيل الخيط الزمني الشامل"
|
||||
disablingTimelinesInfo: "سيتمكن المديرون والمشرفون من الوصول إلى كل الخيوط الزمنية\
|
||||
|
@ -402,7 +399,6 @@ normalPassword: "الكلمة السرية جيدة"
|
|||
strongPassword: "الكلمة السرية قوية"
|
||||
passwordMatched: "التطابق صحيح!"
|
||||
passwordNotMatched: "غير متطابقتان"
|
||||
signinWith: "الولوج عبر {x}"
|
||||
signinFailed: "فشل الولوج، خطأ في اسم المستخدم أو كلمة المرور."
|
||||
tapSecurityKey: "أنقر مفتاح الأمان"
|
||||
or: "أو"
|
||||
|
|
|
@ -308,9 +308,6 @@ dayX: "{day}"
|
|||
monthX: "{month}"
|
||||
yearX: "{year}"
|
||||
pages: "পৃষ্ঠা"
|
||||
integration: "ইন্টিগ্রেশন"
|
||||
connectService: "সংযুক্ত করুন"
|
||||
disconnectService: "সংযোগ বিচ্ছিন্ন করুন"
|
||||
enableLocalTimeline: "স্থানীয় টাইমলাইন চালু করুন"
|
||||
enableGlobalTimeline: "গ্লোবাল টাইমলাইন চালু করুন"
|
||||
disablingTimelinesInfo: "আপনি এই টাইমলাইনগুলি বন্ধ করলেও প্রশাসক এবং মডারেটররা এই\
|
||||
|
@ -417,7 +414,6 @@ normalPassword: "সাধারণ পাসওয়ার্ড"
|
|||
strongPassword: "শক্তিশালী পাসওয়ার্ড"
|
||||
passwordMatched: "মিলেছে"
|
||||
passwordNotMatched: "মিলেনি"
|
||||
signinWith: "{x} এর সাহায্যে সাইন ইন করুন"
|
||||
signinFailed: "লগ ইন করা যায়নি। আপনার ব্যবহারকারীর নাম এবং পাসওয়ার্ড চেক করুন."
|
||||
tapSecurityKey: "সিকিউরিটি কী স্পর্শ করুন"
|
||||
or: "অথবা"
|
||||
|
|
|
@ -277,9 +277,6 @@ dayX: "{day}"
|
|||
monthX: "{month}"
|
||||
yearX: "{year}"
|
||||
pages: "Stránky"
|
||||
integration: "Integrace"
|
||||
connectService: "Připojit"
|
||||
disconnectService: "Odpojit"
|
||||
enableLocalTimeline: "Povolit lokální čas"
|
||||
enableGlobalTimeline: "Povolit globální čas"
|
||||
enableRegistration: "Povolit registraci novým uživatelům"
|
||||
|
@ -353,7 +350,6 @@ normalPassword: "Dobré heslo"
|
|||
strongPassword: "Silné heslo"
|
||||
passwordMatched: "Hesla se schodují"
|
||||
passwordNotMatched: "Hesla se neschodují"
|
||||
signinWith: "Přihlásit se s {x}"
|
||||
signinFailed: "Nelze se přihlásit. Zkontrolujte prosím své uživatelské jméno a heslo."
|
||||
or: "Nebo"
|
||||
language: "Jazyk"
|
||||
|
|
|
@ -321,9 +321,6 @@ dayX: "{day}"
|
|||
monthX: "{month}"
|
||||
yearX: "{year}"
|
||||
pages: "Seiten"
|
||||
integration: "Integration"
|
||||
connectService: "Verbinden"
|
||||
disconnectService: "Trennen"
|
||||
enableLocalTimeline: "Lokale Chronik aktivieren"
|
||||
enableGlobalTimeline: "Globale Chronik aktivieren"
|
||||
disablingTimelinesInfo: "Administratoren und Moderatoren haben immer Zugriff auf alle\
|
||||
|
@ -433,7 +430,6 @@ normalPassword: "Durchschnittliches Passwort"
|
|||
strongPassword: "Starkes Passwort"
|
||||
passwordMatched: "Stimmt überein"
|
||||
passwordNotMatched: "Stimmt nicht überein"
|
||||
signinWith: "Mit {x} anmelden"
|
||||
signinFailed: "Anmeldung fehlgeschlagen. Überprüfe Benutzername und Passswort."
|
||||
tapSecurityKey: "Tippe deinen Sicherheitsschlüssel an"
|
||||
or: "Oder"
|
||||
|
@ -1373,18 +1369,6 @@ recommended: "Empfehlung"
|
|||
check: "Check"
|
||||
maxCustomEmojiPicker: Maximale Anzahl vorgeschlagener benutzerdefinierter Emoji
|
||||
maxUnicodeEmojiPicker: Maximale Anzahl vorgeschlagener Unicode-Emoji
|
||||
_services:
|
||||
_discord:
|
||||
connected: 'Discord: @{username}#{discriminator} wurde mit Foundkey-Account @{mkUsername}
|
||||
verknüpft!'
|
||||
disconnected: Discord-Verknüpfung wurde entfernt.
|
||||
_twitter:
|
||||
connected: Twitter-Account @{twitterUserName} wurde mit Foundkey-Account @{userName}
|
||||
verknüpft!
|
||||
disconnected: Twitter-Verknüpfung wurde entfernt.
|
||||
_github:
|
||||
connected: GitHub-Account @{login} wurde mit Foundkey-Account @{userName} verknüpft!
|
||||
disconnected: GitHub-Verknüpfung wurde entfernt.
|
||||
documentation: Dokumentation
|
||||
signinHistoryExpires: Frühere Login-Versuche werden aus Datenschutzgründen nach 60
|
||||
Tagen automatisch gelöscht.
|
||||
|
|
|
@ -96,6 +96,8 @@ unfollow: "Unfollow"
|
|||
followRequestPending: "Follow request pending"
|
||||
renote: "Renote"
|
||||
unrenote: "Take back renote"
|
||||
unrenoteAll: "Take back all renotes"
|
||||
unrenoteAllConfirm: "Are you sure that you want to take back all renotes of this note?"
|
||||
quote: "Quote"
|
||||
pinnedNote: "Pinned note"
|
||||
you: "You"
|
||||
|
@ -311,9 +313,6 @@ dayX: "{day}"
|
|||
monthX: "{month}"
|
||||
yearX: "{year}"
|
||||
pages: "Pages"
|
||||
integration: "Integration"
|
||||
connectService: "Connect"
|
||||
disconnectService: "Disconnect"
|
||||
enableLocalTimeline: "Enable local timeline"
|
||||
enableGlobalTimeline: "Enable global timeline"
|
||||
disablingTimelinesInfo: "Adminstrators and Moderators will always have access to all\
|
||||
|
@ -420,7 +419,6 @@ normalPassword: "Average password"
|
|||
strongPassword: "Strong password"
|
||||
passwordMatched: "Matches"
|
||||
passwordNotMatched: "Does not match"
|
||||
signinWith: "Sign in with {x}"
|
||||
signinFailed: "Unable to sign in. The entered username or password is incorrect."
|
||||
tapSecurityKey: "Tap your security key"
|
||||
or: "Or"
|
||||
|
@ -503,6 +501,7 @@ scratchpadDescription: "The Scratchpad provides an environment for AiScript expe
|
|||
\ in it."
|
||||
output: "Output"
|
||||
updateRemoteUser: "Update remote user information"
|
||||
deleteAllFiles: "Delete all files"
|
||||
deleteAllFilesConfirm: "Are you sure that you want to delete all files?"
|
||||
removeAllFollowing: "Unfollow all followed users"
|
||||
removeAllFollowingDescription: "Executing this unfollows all accounts from {host}.\
|
||||
|
@ -680,7 +679,6 @@ editCode: "Edit code"
|
|||
apply: "Apply"
|
||||
receiveAnnouncementFromInstance: "Receive notifications from this instance"
|
||||
emailNotification: "Email notifications"
|
||||
publish: "Publish"
|
||||
useReactionPickerForContextMenu: "Open reaction picker on right-click"
|
||||
typingUsers: "{users} is/are typing..."
|
||||
jumpToSpecifiedDate: "Jump to specific date"
|
||||
|
@ -721,11 +719,7 @@ switch: "Switch"
|
|||
noMaintainerInformationWarning: "Maintainer information is not configured."
|
||||
noBotProtectionWarning: "Bot protection is not configured."
|
||||
configure: "Configure"
|
||||
postToGallery: "Create new gallery post"
|
||||
attachmentRequired: "At least 1 attachment is required."
|
||||
gallery: "Gallery"
|
||||
recentPosts: "Recent posts"
|
||||
popularPosts: "Popular posts"
|
||||
shareWithNote: "Share with note"
|
||||
emailNotConfiguredWarning: "Email address not set."
|
||||
ratio: "Ratio"
|
||||
|
@ -864,11 +858,6 @@ _forgotPassword:
|
|||
\ instance administrator instead."
|
||||
contactAdmin: "This instance does not support using email addresses, please contact\
|
||||
\ the instance administrator to reset your password instead."
|
||||
_gallery:
|
||||
my: "My Gallery"
|
||||
liked: "Liked Posts"
|
||||
like: "Like"
|
||||
unlike: "Remove like"
|
||||
_email:
|
||||
_follow:
|
||||
title: "You've got a new follower"
|
||||
|
@ -1099,6 +1088,7 @@ _permissions:
|
|||
"write:notes": "Create and delete notes"
|
||||
"read:notifications": "Read notifications"
|
||||
"write:notifications": "Mark notifications as read and create custom notifications"
|
||||
"read:reactions": "View reactions"
|
||||
"write:reactions": "Create and delete reactions"
|
||||
"write:votes": "Vote in polls"
|
||||
"read:pages": "List and read pages"
|
||||
|
@ -1109,10 +1099,6 @@ _permissions:
|
|||
"write:user-groups": "Create, modify, delete, transfer, join and leave groups. Invite and ban others from groups. Accept and reject group invitations."
|
||||
"read:channels": "List and read followed and joined channels"
|
||||
"write:channels": "Create, modify, follow and unfollow channels"
|
||||
"read:gallery": "List and read gallery posts"
|
||||
"write:gallery": "Create, modify and delete gallery posts"
|
||||
"read:gallery-likes": "List and read gallery post likes"
|
||||
"write:gallery-likes": "Like and unlike gallery posts"
|
||||
_auth:
|
||||
shareAccess: "Would you like to authorize \"{name}\" to access this account?"
|
||||
shareAccessAsk: "Are you sure you want to authorize this application to access your\
|
||||
|
@ -1344,16 +1330,6 @@ _deck:
|
|||
list: "List"
|
||||
mentions: "Mentions"
|
||||
direct: "Direct notes"
|
||||
_services:
|
||||
_discord:
|
||||
connected: "Discord: @{username}#{discriminator} connected to FoundKey: @{mkUsername}!"
|
||||
disconnected: "Discord linkage has been removed."
|
||||
_twitter:
|
||||
connected: "Twitter: @{twitterUserName} connected to FoundKey: @{userName}!"
|
||||
disconnected: "Twitter linkage has been removed."
|
||||
_github:
|
||||
connected: "GitHub: @{login} connected to FoundKey: @{userName}!"
|
||||
disconnected: "GitHub linkage has been removed."
|
||||
_translationService:
|
||||
_deepl:
|
||||
authKey: "DeepL Auth Key"
|
||||
|
|
|
@ -311,9 +311,6 @@ dayX: "Día {day}"
|
|||
monthX: "Mes {month}"
|
||||
yearX: "Año {year}"
|
||||
pages: "Páginas"
|
||||
integration: "Integración"
|
||||
connectService: "Conectar"
|
||||
disconnectService: "Desconectar"
|
||||
enableLocalTimeline: "Habilitar linea de tiempo local"
|
||||
enableGlobalTimeline: "Habilitar linea de tiempo global"
|
||||
disablingTimelinesInfo: "Aunque se desactiven estas lineas de tiempo, por conveniencia\
|
||||
|
@ -420,7 +417,6 @@ normalPassword: "Buena contraseña"
|
|||
strongPassword: "Muy buena contraseña"
|
||||
passwordMatched: "Correcto"
|
||||
passwordNotMatched: "Las contraseñas no son las mismas"
|
||||
signinWith: "Inicie sesión con {x}"
|
||||
signinFailed: "Autenticación fallida. Asegúrate de haber usado el nombre de usuario\
|
||||
\ y contraseña correctos."
|
||||
tapSecurityKey: "Toque la clave de seguridad"
|
||||
|
|
|
@ -311,9 +311,6 @@ dayX: "{day}"
|
|||
monthX: "{month}"
|
||||
yearX: "{year}"
|
||||
pages: "Pages"
|
||||
integration: "Intégrations"
|
||||
connectService: "Connexion"
|
||||
disconnectService: "Déconnexion"
|
||||
enableLocalTimeline: "Activer le fil local"
|
||||
enableGlobalTimeline: "Activer le fil global"
|
||||
disablingTimelinesInfo: "Même si vous désactivez ces fils, les administrateur·rice·s\
|
||||
|
@ -423,7 +420,6 @@ normalPassword: "Mot de passe acceptable"
|
|||
strongPassword: "Mot de passe fort"
|
||||
passwordMatched: "Les mots de passe correspondent"
|
||||
passwordNotMatched: "Les mots de passe ne correspondent pas"
|
||||
signinWith: "Se connecter avec {x}"
|
||||
signinFailed: "Échec d’authentification. Veuillez vérifier que votre nom d’utilisateur\
|
||||
\ et mot de passe sont corrects."
|
||||
tapSecurityKey: "Appuyez sur votre clé de sécurité"
|
||||
|
@ -1331,18 +1327,6 @@ _deck:
|
|||
list: "Listes"
|
||||
mentions: "Mentions"
|
||||
direct: "Direct"
|
||||
_services:
|
||||
_discord:
|
||||
connected: '@{username}#{discriminator} sur Discord est connecté à @{mkUsername}
|
||||
sur FoundKey !'
|
||||
disconnected: La liaison avec Discord à été supprimée.
|
||||
_twitter:
|
||||
connected: '@{twitterUserName} sur Twitter est connecté à @{userName} sur FoundKey
|
||||
!'
|
||||
disconnected: La liaison avec Twitter à été supprimée.
|
||||
_github:
|
||||
disconnected: La liaison avec Github à été supprimée.
|
||||
connected: '@{login} sur Github est connecté à @{userName} sur FoundKey !'
|
||||
exportAll: Tout exporter
|
||||
stopActivityDeliveryDescription: L'activité locale ne sera pas envoyé à cette instance.
|
||||
La réception de l'activité continuera de fonctionner comme avant.
|
||||
|
|
|
@ -310,9 +310,6 @@ dayX: "{day}"
|
|||
monthX: "{month}"
|
||||
yearX: "{year}"
|
||||
pages: "Halaman"
|
||||
integration: "Integrasi"
|
||||
connectService: "Sambungkan"
|
||||
disconnectService: "Putuskan"
|
||||
enableLocalTimeline: "Nyalakan linimasa lokal"
|
||||
enableGlobalTimeline: "Nyalakan linimasa global"
|
||||
disablingTimelinesInfo: "Admin dan Moderator akan selalu memiliki akses ke semua linimasa\
|
||||
|
@ -419,7 +416,6 @@ normalPassword: "Kata sandi baik"
|
|||
strongPassword: "Kata sandi kuat"
|
||||
passwordMatched: "Kata sandi sama"
|
||||
passwordNotMatched: "Kata sandi tidak sama"
|
||||
signinWith: "Masuk dengan {x}"
|
||||
signinFailed: "Tidak dapat masuk. Nama pengguna atau kata sandi yang kamu masukkan\
|
||||
\ salah."
|
||||
tapSecurityKey: "Ketuk kunci keamanan kamu"
|
||||
|
|
|
@ -304,9 +304,6 @@ dayX: "{day}"
|
|||
monthX: "{month}"
|
||||
yearX: "{year}"
|
||||
pages: "Pagine"
|
||||
integration: "App collegate"
|
||||
connectService: "Connessione"
|
||||
disconnectService: "Disconnessione "
|
||||
enableLocalTimeline: "Abilita Timeline locale"
|
||||
enableGlobalTimeline: "Abilita Timeline federata"
|
||||
disablingTimelinesInfo: "Anche se disabiliti queste timeline, gli amministratori e\
|
||||
|
@ -413,7 +410,6 @@ normalPassword: "Password buona"
|
|||
strongPassword: "Password forte"
|
||||
passwordMatched: "Corretta"
|
||||
passwordNotMatched: "Le password non corrispondono."
|
||||
signinWith: "Accedi con {x}"
|
||||
signinFailed: "Autenticazione non riuscita. Controlla la tua password e nome utente."
|
||||
tapSecurityKey: "Premi la chiave di sicurezza"
|
||||
or: "oppure"
|
||||
|
|
|
@ -286,9 +286,6 @@ dayX: "{day}日"
|
|||
monthX: "{month}月"
|
||||
yearX: "{year}年"
|
||||
pages: "ページ"
|
||||
integration: "連携"
|
||||
connectService: "接続する"
|
||||
disconnectService: "切断する"
|
||||
enableLocalTimeline: "ローカルタイムラインを有効にする"
|
||||
enableGlobalTimeline: "グローバルタイムラインを有効にする"
|
||||
disablingTimelinesInfo: "これらのタイムラインを無効化しても、利便性のため管理者およびモデレーターは引き続き利用することができます。"
|
||||
|
@ -392,7 +389,6 @@ normalPassword: "普通のパスワード"
|
|||
strongPassword: "強いパスワード"
|
||||
passwordMatched: "一致しました"
|
||||
passwordNotMatched: "一致していません"
|
||||
signinWith: "{x}でログイン"
|
||||
signinFailed: "ログインできませんでした。ユーザー名とパスワードを確認してください。"
|
||||
tapSecurityKey: "セキュリティキーにタッチ"
|
||||
or: "もしくは"
|
||||
|
@ -1273,13 +1269,3 @@ _deck:
|
|||
list: "リスト"
|
||||
mentions: "あなた宛て"
|
||||
direct: "ダイレクト"
|
||||
_services:
|
||||
_discord:
|
||||
connected: "Discord: @{username}#{discriminator} を、FoundKey: @{mkUsername} に接続しました!"
|
||||
disconnected: "Discordの連携を解除しました :v:"
|
||||
_twitter:
|
||||
connected: "Twitter: @{twitterUserName} を、FoundKey: @{userName} に接続しました!"
|
||||
disconnected: "Twitterの連携を解除しました :v:"
|
||||
_github:
|
||||
connected: "GitHub: @{login} を、FoundKey: @{userName} に接続しました!"
|
||||
disconnected: "GitHubの連携を解除しました :v:"
|
||||
|
|
|
@ -288,7 +288,6 @@ dayX: "{day}日"
|
|||
monthX: "{month}月"
|
||||
yearX: "{year}年"
|
||||
pages: "ページ"
|
||||
integration: "連携"
|
||||
enableLocalTimeline: "ローカルタイムラインを使えるようにする"
|
||||
enableGlobalTimeline: "グローバルタイムラインを使えるようにする"
|
||||
disablingTimelinesInfo: "ここらへんのタイムラインを使えんようにしてしもても、管理者とモデレーターは使えるままになってるで、そうやなかったら不便やからな。"
|
||||
|
@ -391,7 +390,6 @@ normalPassword: "普通のパスワード"
|
|||
strongPassword: "ええ感じのパスワード"
|
||||
passwordMatched: "よし!一致や!"
|
||||
passwordNotMatched: "一致しとらんで?"
|
||||
signinWith: "{x}でログイン"
|
||||
or: "それか"
|
||||
language: "言語"
|
||||
uiLanguage: "UIの表示言語"
|
||||
|
|
|
@ -39,7 +39,6 @@ userList: "Tibdarin"
|
|||
securityKey: "Tasarutt n tɣellist"
|
||||
securityKeyName: "Isem n tsarutt"
|
||||
signinRequired: "Ttxil jerred"
|
||||
signinWith: "Tuqqna s {x}"
|
||||
tapSecurityKey: "Sekcem tasarutt-ik·im n tɣellist"
|
||||
uiLanguage: "Tutlayt n wegrudem"
|
||||
plugins: "Izegrar"
|
||||
|
|
|
@ -315,9 +315,6 @@ dayX: "{day}일"
|
|||
monthX: "{month}월"
|
||||
yearX: "{year}년"
|
||||
pages: "페이지"
|
||||
integration: "연동"
|
||||
connectService: "계정 연동"
|
||||
disconnectService: "계정 연동 해제"
|
||||
enableLocalTimeline: "로컬 타임라인 활성화"
|
||||
enableGlobalTimeline: "글로벌 타임라인 활성화"
|
||||
disablingTimelinesInfo: "특정 타임라인을 비활성화하더라도 관리자 및 모더레이터는 계속 사용할 수 있습니다."
|
||||
|
@ -438,7 +435,6 @@ normalPassword: "좋은 비밀번호"
|
|||
strongPassword: "강한 비밀번호"
|
||||
passwordMatched: "일치합니다"
|
||||
passwordNotMatched: "일치하지 않습니다"
|
||||
signinWith: "{x}로 로그인"
|
||||
signinFailed: "로그인할 수 없습니다. 사용자명과 비밀번호를 확인하여 주십시오."
|
||||
tapSecurityKey: "보안 키를 터치"
|
||||
or: "혹은"
|
||||
|
|
|
@ -298,9 +298,6 @@ dayX: "{day}"
|
|||
monthX: "{month}"
|
||||
yearX: "{year}"
|
||||
pages: "Strony"
|
||||
integration: "Integracja"
|
||||
connectService: "Połącz"
|
||||
disconnectService: "Rozłącz"
|
||||
enableLocalTimeline: "Włącz lokalną oś czasu"
|
||||
enableGlobalTimeline: "Włącz globalną oś czasu"
|
||||
disablingTimelinesInfo: "Administratorzy i moderatorzy będą zawsze mieć dostęp do\
|
||||
|
@ -407,7 +404,6 @@ normalPassword: "Dobre hasło"
|
|||
strongPassword: "Silne hasło"
|
||||
passwordMatched: "Pasuje"
|
||||
passwordNotMatched: "Hasła nie pasują do siebie"
|
||||
signinWith: "Zaloguj się z {x}"
|
||||
signinFailed: "Nie udało się zalogować. Wprowadzona nazwa użytkownika lub hasło są\
|
||||
\ nieprawidłowe."
|
||||
tapSecurityKey: "Wybierz swój klucz bezpieczeństwa"
|
||||
|
|
|
@ -311,9 +311,6 @@ dayX: "{day}"
|
|||
monthX: "{month}"
|
||||
yearX: "{year}"
|
||||
pages: "Pagini"
|
||||
integration: "Integrare"
|
||||
connectService: "Conectează"
|
||||
disconnectService: "Deconectează"
|
||||
enableLocalTimeline: "Activează cronologia locală"
|
||||
enableGlobalTimeline: "Activeaza cronologia globală"
|
||||
disablingTimelinesInfo: "Administratorii și Moderatorii vor avea mereu access la toate\
|
||||
|
@ -420,7 +417,6 @@ normalPassword: "Parolă medie"
|
|||
strongPassword: "Parolă puternică"
|
||||
passwordMatched: "Se potrivește!"
|
||||
passwordNotMatched: "Nu se potrivește"
|
||||
signinWith: "Autentifică-te cu {x}"
|
||||
signinFailed: "Nu se poate autentifica. Numele de utilizator sau parola introduse\
|
||||
\ sunt incorecte."
|
||||
tapSecurityKey: "Apasă pe cheia ta de securitate."
|
||||
|
|
|
@ -304,9 +304,6 @@ dayX: "{day} день"
|
|||
monthX: "{month} месяц"
|
||||
yearX: "{year} год"
|
||||
pages: "Страницы"
|
||||
integration: "Интеграция"
|
||||
connectService: "Подключиться"
|
||||
disconnectService: "Отключиться"
|
||||
enableLocalTimeline: "Включить локальную ленту"
|
||||
enableGlobalTimeline: "Включить глобальную ленту"
|
||||
disablingTimelinesInfo: "У администраторов и модераторов есть доступ ко всем лентам,\
|
||||
|
@ -415,7 +412,6 @@ normalPassword: "Годный пароль"
|
|||
strongPassword: "Надёжный пароль"
|
||||
passwordMatched: "Совпали"
|
||||
passwordNotMatched: "Не совпадают"
|
||||
signinWith: "Использовать {x} для входа"
|
||||
signinFailed: "Невозможно войти в систему. Введенное вами имя пользователя или пароль\
|
||||
\ неверны."
|
||||
tapSecurityKey: "Нажмите на свой электронный ключ"
|
||||
|
|
|
@ -305,9 +305,6 @@ dayX: "{day}"
|
|||
monthX: "{month}"
|
||||
yearX: "{year}"
|
||||
pages: "Stránky"
|
||||
integration: "Integrácia"
|
||||
connectService: "Pripojiť"
|
||||
disconnectService: "Odpojiť"
|
||||
enableLocalTimeline: "Povoliť lokálnu časovú os"
|
||||
enableGlobalTimeline: "Povoliť globálnu časovú os"
|
||||
disablingTimelinesInfo: "Administrátori a moderátori majú vždy prístup ku všetkým\
|
||||
|
@ -414,7 +411,6 @@ normalPassword: "Dobré heslo"
|
|||
strongPassword: "Silné heslo"
|
||||
passwordMatched: "Heslá sú rovnaké"
|
||||
passwordNotMatched: "Heslá nie sú rovnaké"
|
||||
signinWith: "Prihlásiť sa použitím {x}"
|
||||
signinFailed: "Nedá sa prihlásiť. Skontrolujte prosím meno používateľa a heslo."
|
||||
tapSecurityKey: "Ťuknite na bezpečnostný kľúč"
|
||||
or: "Alebo"
|
||||
|
|
|
@ -305,9 +305,6 @@ dayX: "{day}"
|
|||
monthX: "{month}"
|
||||
yearX: "{year}"
|
||||
pages: "Сторінки"
|
||||
integration: "Інтеграція"
|
||||
connectService: "Під’єднати"
|
||||
disconnectService: "Відключитися"
|
||||
enableLocalTimeline: "Увімкнути локальну стрічку"
|
||||
enableGlobalTimeline: "Увімкнути глобальну стрічку"
|
||||
disablingTimelinesInfo: "Адміністратори та модератори завжди мають доступ до всіх\
|
||||
|
@ -414,7 +411,6 @@ normalPassword: "Достатній пароль"
|
|||
strongPassword: "Міцний пароль"
|
||||
passwordMatched: "Все вірно"
|
||||
passwordNotMatched: "Паролі не співпадають"
|
||||
signinWith: "Увійти за допомогою {x}"
|
||||
signinFailed: "Не вдалося увійти. Введені ім’я користувача або пароль неправильнi."
|
||||
tapSecurityKey: "Торкніться ключа безпеки"
|
||||
or: "або"
|
||||
|
|
|
@ -305,9 +305,6 @@ dayX: "{day}"
|
|||
monthX: "{month}"
|
||||
yearX: "{year}"
|
||||
pages: "Trang"
|
||||
integration: "Tương tác"
|
||||
connectService: "Kết nối"
|
||||
disconnectService: "Ngắt kết nối"
|
||||
enableLocalTimeline: "Bật bảng tin máy chủ"
|
||||
enableGlobalTimeline: "Bật bảng tin liên hợp"
|
||||
disablingTimelinesInfo: "Quản trị viên và Kiểm duyệt viên luôn có quyền truy cập mọi\
|
||||
|
@ -415,7 +412,6 @@ normalPassword: "Mật khẩu tạm được"
|
|||
strongPassword: "Mật khẩu mạnh"
|
||||
passwordMatched: "Trùng khớp"
|
||||
passwordNotMatched: "Không trùng khớp"
|
||||
signinWith: "Đăng nhập bằng {x}"
|
||||
signinFailed: "Không thể đăng nhập. Vui lòng kiểm tra tên người dùng và mật khẩu của\
|
||||
\ bạn."
|
||||
tapSecurityKey: "Nhấn mã bảo mật của bạn"
|
||||
|
|
|
@ -284,9 +284,6 @@ dayX: "{day}日"
|
|||
monthX: "{month}月"
|
||||
yearX: "{year}年"
|
||||
pages: "页面"
|
||||
integration: "关联"
|
||||
connectService: "连接"
|
||||
disconnectService: "断开连接"
|
||||
enableLocalTimeline: "启用本地时间线功能"
|
||||
enableGlobalTimeline: "启用全局时间线"
|
||||
disablingTimelinesInfo: "即使时间线功能被禁用,出于方便,管理员和数据图表也可以继续使用。"
|
||||
|
@ -390,7 +387,6 @@ normalPassword: "密码强度:中等"
|
|||
strongPassword: "密码强度:强"
|
||||
passwordMatched: "密码一致"
|
||||
passwordNotMatched: "密码不一致"
|
||||
signinWith: "以{x}登录"
|
||||
signinFailed: "无法登录,请检查您的用户名和密码是否正确。"
|
||||
tapSecurityKey: "轻触硬件安全密钥"
|
||||
or: "或者"
|
||||
|
|
|
@ -284,9 +284,6 @@ dayX: "{day}日"
|
|||
monthX: "{month}月"
|
||||
yearX: "{year}年"
|
||||
pages: "頁面"
|
||||
integration: "整合"
|
||||
connectService: "己連結"
|
||||
disconnectService: "己斷開 "
|
||||
enableLocalTimeline: "開啟本地時間軸"
|
||||
enableGlobalTimeline: "啟用公開時間軸"
|
||||
disablingTimelinesInfo: "即使您關閉了時間線功能,管理員和協調人仍可以繼續使用,以方便您。"
|
||||
|
@ -390,7 +387,6 @@ normalPassword: "密碼強度普通"
|
|||
strongPassword: "密碼強度高"
|
||||
passwordMatched: "密碼一致"
|
||||
passwordNotMatched: "密碼不一致"
|
||||
signinWith: "以{x}登錄"
|
||||
signinFailed: "登入失敗。 請檢查使用者名稱和密碼。"
|
||||
tapSecurityKey: "點擊安全密鑰"
|
||||
or: "或者"
|
||||
|
|
14
package.json
|
@ -10,7 +10,8 @@
|
|||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "yarn workspaces foreach --parallel --topological run build && yarn run gulp",
|
||||
"build": "yarn workspaces foreach --topological run build && yarn run gulp",
|
||||
"build-parallel": "yarn workspaces foreach --parallel --topological run build && yarn run gulp",
|
||||
"start": "yarn workspace backend run start",
|
||||
"start:test": "yarn workspace backend run start:test",
|
||||
"init": "yarn migrate",
|
||||
|
@ -18,7 +19,7 @@
|
|||
"migrateandstart": "yarn migrate && yarn start",
|
||||
"gulp": "gulp build",
|
||||
"watch": "yarn dev",
|
||||
"dev": "node ./scripts/dev.js",
|
||||
"dev": "node ./scripts/dev.mjs",
|
||||
"lint": "yarn workspaces foreach run lint",
|
||||
"cy:open": "cypress open --browser --e2e --config-file=cypress.config.ts",
|
||||
"cy:run": "cypress run",
|
||||
|
@ -26,8 +27,8 @@
|
|||
"mocha": "yarn workspace backend run mocha",
|
||||
"test": "yarn mocha",
|
||||
"format": "gulp format",
|
||||
"clean": "node ./scripts/clean.js",
|
||||
"clean-all": "node ./scripts/clean-all.js",
|
||||
"clean": "node ./scripts/clean.mjs",
|
||||
"clean-all": "node ./scripts/clean-all.mjs",
|
||||
"cleanall": "yarn clean-all"
|
||||
},
|
||||
"resolutions": {
|
||||
|
@ -35,6 +36,7 @@
|
|||
"lodash": "^4.17.21"
|
||||
},
|
||||
"dependencies": {
|
||||
"argon2": "^0.30.2",
|
||||
"execa": "5.1.1",
|
||||
"gulp": "4.0.2",
|
||||
"gulp-cssnano": "2.1.3",
|
||||
|
@ -46,11 +48,11 @@
|
|||
"devDependencies": {
|
||||
"@types/gulp": "4.0.9",
|
||||
"@types/gulp-rename": "2.0.1",
|
||||
"@typescript-eslint/parser": "^5.44.0",
|
||||
"@typescript-eslint/parser": "^5.46.1",
|
||||
"cross-env": "7.0.3",
|
||||
"cypress": "10.3.0",
|
||||
"start-server-and-test": "1.14.0",
|
||||
"typescript": "^4.9.3"
|
||||
"typescript": "^4.9.4"
|
||||
},
|
||||
"packageManager": "yarn@3.3.0"
|
||||
}
|
||||
|
|
|
@ -6,7 +6,11 @@ module.exports = {
|
|||
extends: [
|
||||
'../shared/.eslintrc.js',
|
||||
],
|
||||
plugins: [
|
||||
'foundkey-custom-rules',
|
||||
],
|
||||
rules: {
|
||||
'foundkey-custom-rules/typeorm-prefer-count': 'error',
|
||||
'import/order': ['warn', {
|
||||
'groups': ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
|
||||
'pathGroups': [
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
export class removeIntegrations1670359028055 {
|
||||
name = 'removeIntegrations1670359028055'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableTwitterIntegration"`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "twitterConsumerKey"`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "twitterConsumerSecret"`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableGithubIntegration"`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "githubClientId"`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "githubClientSecret"`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableDiscordIntegration"`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "discordClientId"`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "discordClientSecret"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "integrations"`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" ADD "integrations" jsonb NOT NULL DEFAULT '{}'`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "discordClientSecret" character varying(128)`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "discordClientId" character varying(128)`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "enableDiscordIntegration" boolean NOT NULL DEFAULT false`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "githubClientSecret" character varying(128)`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "githubClientId" character varying(128)`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "enableGithubIntegration" boolean NOT NULL DEFAULT false`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "twitterConsumerSecret" character varying(128)`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "twitterConsumerKey" character varying(128)`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "enableTwitterIntegration" boolean NOT NULL DEFAULT false`);
|
||||
}
|
||||
}
|
21
packages/backend/migration/1672607891750-remove-reversi.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
export class removeReversi1672607891750 {
|
||||
name = 'removeReversi1672607891750';
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`DROP TABLE "reversi_matching"`);
|
||||
await queryRunner.query(`DROP TABLE "reversi_game"`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`CREATE TABLE "reversi_game" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "startedAt" TIMESTAMP WITH TIME ZONE, "user1Id" character varying(32) NOT NULL, "user2Id" character varying(32) NOT NULL, "user1Accepted" boolean NOT NULL DEFAULT false, "user2Accepted" boolean NOT NULL DEFAULT false, "black" integer, "isStarted" boolean NOT NULL DEFAULT false, "isEnded" boolean NOT NULL DEFAULT false, "winnerId" character varying(32), "surrendered" character varying(32), "logs" jsonb NOT NULL DEFAULT '[]', "map" character varying(64) array NOT NULL, "bw" character varying(32) NOT NULL, "isLlotheo" boolean NOT NULL DEFAULT false, "canPutEverywhere" boolean NOT NULL DEFAULT false, "loopedBoard" boolean NOT NULL DEFAULT false, "form1" jsonb DEFAULT null, "form2" jsonb DEFAULT null, "crc32" character varying(32), CONSTRAINT "PK_76b30eeba71b1193ad7c5311c3f" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_b46ec40746efceac604142be1c" ON "reversi_game" ("createdAt")`);
|
||||
await queryRunner.query(`CREATE TABLE "reversi_matching" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "parentId" character varying(32) NOT NULL, "childId" character varying(32) NOT NULL, CONSTRAINT "PK_880bd0afbab232f21c8b9d146cf" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_b604d92d6c7aec38627f6eaf16" ON "reversi_matching" ("createdAt")`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_3b25402709dd9882048c2bbade" ON "reversi_matching" ("parentId")`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_e247b23a3c9b45f89ec1299d06" ON "reversi_matching" ("childId")`);
|
||||
await queryRunner.query(`ALTER TABLE "reversi_game" ADD CONSTRAINT "FK_f7467510c60a45ce5aca6292743" FOREIGN KEY ("user1Id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "reversi_game" ADD CONSTRAINT "FK_6649a4e8c5d5cf32fb03b5da9f6" FOREIGN KEY ("user2Id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "reversi_matching" ADD CONSTRAINT "FK_3b25402709dd9882048c2bbade0" FOREIGN KEY ("parentId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "reversi_matching" ADD CONSTRAINT "FK_e247b23a3c9b45f89ec1299d066" FOREIGN KEY ("childId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
export class removeUserGroupInvite1672991292018 {
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "user_group_invite" DROP CONSTRAINT "FK_e10924607d058004304611a436a"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_group_invite" DROP CONSTRAINT "FK_1039988afa3bf991185b277fe03"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_d9ecaed8c6dc43f3592c229282"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_78787741f9010886796f2320a4"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_e10924607d058004304611a436"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_1039988afa3bf991185b277fe0"`);
|
||||
await queryRunner.query(`DROP TABLE "user_group_invite"`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`CREATE TABLE "user_group_invite" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "userGroupId" character varying(32) NOT NULL, CONSTRAINT "PK_3893884af0d3a5f4d01e7921a97" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_1039988afa3bf991185b277fe0" ON "user_group_invite" ("userId") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_e10924607d058004304611a436" ON "user_group_invite" ("userGroupId") `);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_78787741f9010886796f2320a4" ON "user_group_invite" ("userId", "userGroupId") `);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_d9ecaed8c6dc43f3592c229282" ON "user_group_joining" ("userId", "userGroupId") `);
|
||||
await queryRunner.query(`ALTER TABLE "user_group_invite" ADD CONSTRAINT "FK_1039988afa3bf991185b277fe03" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "user_group_invite" ADD CONSTRAINT "FK_e10924607d058004304611a436a" FOREIGN KEY ("userGroupId") REFERENCES "user_group"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
}
|
65
packages/backend/migration/1673892262930-remove-groups.js
Normal file
|
@ -0,0 +1,65 @@
|
|||
import { genId } from '../built/misc/gen-id.js';
|
||||
|
||||
export class removeGroups1673892262930 {
|
||||
name = 'removeGroups1673892262930';
|
||||
|
||||
async up(queryRunner) {
|
||||
// migrate gallery posts into notes, keeping the ids
|
||||
await queryRunner.query(`
|
||||
INSERT INTO "note" (
|
||||
"id", "createdAt", "text", "cw", "userId", "visibility", "fileIds", "attachedFileTypes", "tags"
|
||||
)
|
||||
WITH "file_types" ("id", "types") AS (
|
||||
SELECT "gallery_post"."id", ARRAY_AGG("drive_file"."type")
|
||||
FROM "gallery_post"
|
||||
JOIN "drive_file" ON "drive_file"."id" = ANY("gallery_post"."fileIds")
|
||||
GROUP BY "gallery_post"."id"
|
||||
)
|
||||
SELECT "gallery_post"."id", "gallery_post"."createdAt",
|
||||
CASE
|
||||
WHEN "gallery_post"."title" IS NULL THEN "gallery_post"."description"
|
||||
ELSE '<b>' || "gallery_post"."title" || E'</b>\\n\\n' || "gallery_post"."description"
|
||||
END,
|
||||
CASE
|
||||
WHEN "gallery_post"."isSensitive" THEN 'NSFW'
|
||||
ELSE NULL
|
||||
END,
|
||||
"gallery_post"."userId", 'home', "gallery_post"."fileIds", "file_types"."types", "gallery_post"."tags"
|
||||
FROM "gallery_post"
|
||||
JOIN "file_types" ON "gallery_post"."id" = "file_types"."id"
|
||||
`);
|
||||
// make a clip for each users gallery
|
||||
await queryRunner.query(`SELECT DISTINCT "userId" FROM "gallery_post"`).then(userIds =>
|
||||
Promise.all(userIds.map(({ userId }) => {
|
||||
const clipId = genId();
|
||||
|
||||
// generate the clip itself
|
||||
return queryRunner.query(`INSERT INTO "clip" ("id", "createdAt", "userId", "name", "isPublic") VALUES ($1, now(), $2, 'Gallery', true)`, [clipId, userId])
|
||||
// and add all the previous gallery posts to it
|
||||
// to not have to use genId for each gallery post, we just prepend a zero, something that could never be generated by genId
|
||||
.then(() => queryRunner.query(`INSERT INTO "clip_note" ("id", "noteId", "clipId") SELECT '0' || "id", "id", $1 FROM "gallery_post" WHERE "userId" = $2`, [clipId, userId]));
|
||||
}))
|
||||
);
|
||||
|
||||
await queryRunner.query(`DROP TABLE "gallery_like"`);
|
||||
await queryRunner.query(`DROP TABLE "gallery_post"`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
// can only restore the table structure
|
||||
await queryRunner.query(`CREATE TABLE "gallery_post" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, "title" character varying(256) NOT NULL, "description" character varying(2048), "userId" character varying(32) NOT NULL, "fileIds" character varying(32) array NOT NULL DEFAULT '{}'::varchar[], "isSensitive" boolean NOT NULL DEFAULT false, "likedCount" integer NOT NULL DEFAULT '0', "tags" character varying(128) array NOT NULL DEFAULT '{}'::varchar[], CONSTRAINT "PK_8e90d7b6015f2c4518881b14753" PRIMARY KEY ("id")); COMMENT ON COLUMN "gallery_post"."createdAt" IS 'The created date of the GalleryPost.'; COMMENT ON COLUMN "gallery_post"."updatedAt" IS 'The updated date of the GalleryPost.'; COMMENT ON COLUMN "gallery_post"."userId" IS 'The ID of author.'; COMMENT ON COLUMN "gallery_post"."isSensitive" IS 'Whether the post is sensitive.'`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_8f1a239bd077c8864a20c62c2c" ON "gallery_post" ("createdAt") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_f631d37835adb04792e361807c" ON "gallery_post" ("updatedAt") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_985b836dddd8615e432d7043dd" ON "gallery_post" ("userId") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_3ca50563facd913c425e7a89ee" ON "gallery_post" ("fileIds") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_f2d744d9a14d0dfb8b96cb7fc5" ON "gallery_post" ("isSensitive") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_1a165c68a49d08f11caffbd206" ON "gallery_post" ("likedCount") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_05cca34b985d1b8edc1d1e28df" ON "gallery_post" ("tags") `);
|
||||
await queryRunner.query(`CREATE TABLE "gallery_like" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "postId" character varying(32) NOT NULL, CONSTRAINT "PK_853ab02be39b8de45cd720cc15f" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_8fd5215095473061855ceb948c" ON "gallery_like" ("userId") `);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_df1b5f4099e99fb0bc5eae53b6" ON "gallery_like" ("userId", "postId") `);
|
||||
await queryRunner.query(`ALTER TABLE "gallery_post" ADD CONSTRAINT "FK_985b836dddd8615e432d7043ddb" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "gallery_like" ADD CONSTRAINT "FK_8fd5215095473061855ceb948cf" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "gallery_like" ADD CONSTRAINT "FK_b1cb568bfe569e47b7051699fc8" FOREIGN KEY ("postId") REFERENCES "gallery_post"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
}
|
||||
}
|
17
packages/backend/migration/1674499888924-sync-orm.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
export class syncOrm1674499888924 {
|
||||
name = 'syncOrm1674499888924'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."token" IS 'The native access token of local users, or null.'`);
|
||||
await queryRunner.query(`ALTER TABLE "auth_session" DROP CONSTRAINT "UQ_8e001e5a101c6dca37df1a76d66"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_71d35fceee0d0fa62b2fa8f3b2" ON "note" ("url") `);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_d9ecaed8c6dc43f3592c229282" ON "user_group_joining" ("userId", "userGroupId") `);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_d9ecaed8c6dc43f3592c229282"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_71d35fceee0d0fa62b2fa8f3b2"`);
|
||||
await queryRunner.query(`ALTER TABLE "auth_session" ADD CONSTRAINT "UQ_8e001e5a101c6dca37df1a76d66" UNIQUE ("accessTokenId")`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."token" IS 'The native access token of the User. It will be null if the origin of the user is local.'`);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
export class registryRemoveDomain1675375940759 {
|
||||
name = 'registryRemoveDomain1675375940759'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_0a72bdfcdb97c0eca11fe7ecad"`);
|
||||
await queryRunner.query(`ALTER TABLE "registry_item" DROP COLUMN "domain"`);
|
||||
await queryRunner.query(`ALTER TABLE "registry_item" ALTER COLUMN "key" TYPE text USING "key"::text`);
|
||||
// delete existing duplicated entries, keeping the latest updated one
|
||||
await queryRunner.query(`DELETE FROM "registry_item" AS "a" WHERE "updatedAt" != (SELECT MAX("updatedAt") OVER (PARTITION BY "userId", "key", "scope") FROM "registry_item" AS "b" WHERE "a"."userId" = "b"."userId" AND "a"."key" = "b"."key" AND "a"."scope" = "b"."scope")`);
|
||||
await queryRunner.query(`ALTER TABLE "registry_item" ADD CONSTRAINT "UQ_b8d6509f847331273ab99daccc7" UNIQUE ("userId", "key", "scope")`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "registry_item" DROP CONSTRAINT "UQ_b8d6509f847331273ab99daccc7"`);
|
||||
await queryRunner.query(`ALTER TABLE "registry_item" ALTER COLUMN "key" TYPE character varying(1024) USING "key"::varchar(1024)`);
|
||||
await queryRunner.query(`ALTER TABLE "registry_item" ADD "domain" character varying(512)`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_0a72bdfcdb97c0eca11fe7ecad" ON "registry_item" ("domain") `);
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@
|
|||
"scripts": {
|
||||
"build": "tsc -p tsconfig.json || echo done. && tsc-alias -p tsconfig.json",
|
||||
"watch": "node watch.mjs",
|
||||
"lint": "eslint src --ext .ts",
|
||||
"lint": "tsc --noEmit --skipLibCheck && eslint src --ext .ts",
|
||||
"mocha": "cross-env NODE_ENV=test TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha",
|
||||
"migrate": "npx typeorm migration:run -d ormconfig.js",
|
||||
"start": "node --experimental-json-modules ./built/index.js",
|
||||
|
@ -29,7 +29,6 @@
|
|||
"ajv": "8.11.0",
|
||||
"archiver": "5.3.1",
|
||||
"autobind-decorator": "2.4.0",
|
||||
"autwh": "0.1.0",
|
||||
"aws-sdk": "2.1165.0",
|
||||
"bcryptjs": "2.4.3",
|
||||
"blurhash": "1.1.5",
|
||||
|
@ -92,7 +91,6 @@
|
|||
"reflect-metadata": "0.1.13",
|
||||
"rename": "1.0.4",
|
||||
"require-all": "3.0.0",
|
||||
"rndstr": "1.0.0",
|
||||
"rss-parser": "3.12.0",
|
||||
"sanitize-html": "2.7.0",
|
||||
"semver": "7.3.7",
|
||||
|
@ -115,7 +113,6 @@
|
|||
"unzipper": "0.10.11",
|
||||
"uuid": "8.3.2",
|
||||
"web-push": "3.5.0",
|
||||
"websocket": "1.0.34",
|
||||
"ws": "8.8.0",
|
||||
"xev": "3.0.2"
|
||||
},
|
||||
|
@ -124,6 +121,7 @@
|
|||
"@types/bcryptjs": "2.4.2",
|
||||
"@types/bull": "3.15.8",
|
||||
"@types/cbor": "6.0.0",
|
||||
"@types/color-convert": "^2.0.0",
|
||||
"@types/escape-regexp": "0.0.1",
|
||||
"@types/fluent-ffmpeg": "2.1.20",
|
||||
"@types/is-url": "1.2.30",
|
||||
|
@ -146,7 +144,6 @@
|
|||
"@types/node": "18.7.16",
|
||||
"@types/node-fetch": "3.0.3",
|
||||
"@types/nodemailer": "6.4.5",
|
||||
"@types/oauth": "^0.9.1",
|
||||
"@types/pg": "^8.6.5",
|
||||
"@types/pug": "2.0.6",
|
||||
"@types/punycode": "2.1.0",
|
||||
|
@ -161,20 +158,21 @@
|
|||
"@types/sinon": "^10.0.13",
|
||||
"@types/sinonjs__fake-timers": "8.1.2",
|
||||
"@types/speakeasy": "2.0.7",
|
||||
"@types/syslog-pro": "^1.0.0",
|
||||
"@types/tinycolor2": "1.4.3",
|
||||
"@types/tmp": "0.2.3",
|
||||
"@types/uuid": "8.3.4",
|
||||
"@types/web-push": "3.3.2",
|
||||
"@types/websocket": "1.0.5",
|
||||
"@types/ws": "8.5.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.44.0",
|
||||
"@typescript-eslint/parser": "^5.44.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.46.1",
|
||||
"@typescript-eslint/parser": "^5.46.1",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint": "^8.28.0",
|
||||
"eslint": "^8.29.0",
|
||||
"eslint-plugin-foundkey-custom-rules": "file:../shared/custom-rules",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"execa": "6.1.0",
|
||||
"form-data": "^4.0.0",
|
||||
"sinon": "^14.0.2",
|
||||
"typescript": "^4.9.3"
|
||||
"typescript": "^4.9.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ const ev = new Xev();
|
|||
/**
|
||||
* Init process
|
||||
*/
|
||||
export default async function(): Promise<void> {
|
||||
export async function boot(): Promise<void> {
|
||||
process.title = `FoundKey (${cluster.isPrimary ? 'master' : 'worker'})`;
|
||||
|
||||
if (cluster.isPrimary || envOption.disableClustering) {
|
||||
|
|
|
@ -140,7 +140,7 @@ async function connectDb(): Promise<void> {
|
|||
}
|
||||
|
||||
async function spawnWorkers(clusterLimits: Required<Config['clusterLimits']>): Promise<void> {
|
||||
const modes = ['web', 'queue'];
|
||||
const modes = ['web' as const, 'queue' as const];
|
||||
const cpus = os.cpus().length;
|
||||
for (const mode of modes.filter(mode => clusterLimits[mode] > cpus)) {
|
||||
bootLogger.warn(`configuration warning: cluster limit for ${mode} exceeds number of cores (${cpus})`);
|
||||
|
@ -153,7 +153,7 @@ async function spawnWorkers(clusterLimits: Required<Config['clusterLimits']>): P
|
|||
|
||||
bootLogger.info(`Starting ${total} workers...`);
|
||||
await Promise.all(workers.map(mode => spawnWorker(mode)));
|
||||
bootLogger.succ(`All workers started`);
|
||||
bootLogger.succ('All workers started');
|
||||
}
|
||||
|
||||
function spawnWorker(mode: 'web' | 'queue'): Promise<void> {
|
||||
|
|
|
@ -38,6 +38,12 @@ export default function load(): Config {
|
|||
|
||||
config.port = config.port || parseInt(process.env.PORT || '', 10);
|
||||
|
||||
config.images = Object.assign({
|
||||
info: '/twemoji/1f440.svg',
|
||||
notFound: '/twemoji/2049.svg',
|
||||
error: '/twemoji/1f480.svg',
|
||||
}, config.images ?? {});
|
||||
|
||||
if (!config.maxNoteTextLength) config.maxNoteTextLength = 3000;
|
||||
|
||||
mixin.version = meta.version;
|
||||
|
|
|
@ -59,7 +59,7 @@ export type Source = {
|
|||
deliverJobMaxAttempts?: number;
|
||||
inboxJobMaxAttempts?: number;
|
||||
|
||||
syslog: {
|
||||
syslog?: {
|
||||
host: string;
|
||||
port: number;
|
||||
};
|
||||
|
@ -67,6 +67,12 @@ export type Source = {
|
|||
mediaProxy?: string;
|
||||
proxyRemoteFiles?: boolean;
|
||||
internalStoragePath?: string;
|
||||
|
||||
images?: {
|
||||
info?: string;
|
||||
notFound?: string;
|
||||
error?: string;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
// https://github.com/typeorm/typeorm/issues/2400
|
||||
import pg from 'pg';
|
||||
import { SECOND } from '@/const.js';
|
||||
|
||||
pg.types.setTypeParser(20, Number);
|
||||
|
||||
|
@ -8,6 +7,7 @@ import { Logger, DataSource } from 'typeorm';
|
|||
import * as highlight from 'cli-highlight';
|
||||
import config from '@/config/index.js';
|
||||
|
||||
import { SECOND } from '@/const.js';
|
||||
import { User } from '@/models/entities/user.js';
|
||||
import { DriveFile } from '@/models/entities/drive-file.js';
|
||||
import { DriveFolder } from '@/models/entities/drive-folder.js';
|
||||
|
@ -50,8 +50,6 @@ import { UserSecurityKey } from '@/models/entities/user-security-key.js';
|
|||
import { AttestationChallenge } from '@/models/entities/attestation-challenge.js';
|
||||
import { Page } from '@/models/entities/page.js';
|
||||
import { PageLike } from '@/models/entities/page-like.js';
|
||||
import { GalleryPost } from '@/models/entities/gallery-post.js';
|
||||
import { GalleryLike } from '@/models/entities/gallery-like.js';
|
||||
import { ModerationLog } from '@/models/entities/moderation-log.js';
|
||||
import { UsedUsername } from '@/models/entities/used-username.js';
|
||||
import { Announcement } from '@/models/entities/announcement.js';
|
||||
|
@ -78,33 +76,33 @@ import { redisClient } from './redis.js';
|
|||
const sqlLogger = dbLogger.createSubLogger('sql', 'gray', false);
|
||||
|
||||
class MyCustomLogger implements Logger {
|
||||
private highlight(sql: string) {
|
||||
private highlight(sql: string): string {
|
||||
return highlight.highlight(sql, {
|
||||
language: 'sql', ignoreIllegals: true,
|
||||
});
|
||||
}
|
||||
|
||||
public logQuery(query: string, parameters?: any[]) {
|
||||
public logQuery(query: string): void {
|
||||
sqlLogger.info(this.highlight(query).substring(0, 100));
|
||||
}
|
||||
|
||||
public logQueryError(error: string, query: string, parameters?: any[]) {
|
||||
public logQueryError(error: string, query: string): void {
|
||||
sqlLogger.error(this.highlight(query));
|
||||
}
|
||||
|
||||
public logQuerySlow(time: number, query: string, parameters?: any[]) {
|
||||
public logQuerySlow(time: number, query: string): void {
|
||||
sqlLogger.warn(this.highlight(query));
|
||||
}
|
||||
|
||||
public logSchemaBuild(message: string) {
|
||||
public logSchemaBuild(message: string): void {
|
||||
sqlLogger.info(message);
|
||||
}
|
||||
|
||||
public log(message: string) {
|
||||
public log(message: string): void {
|
||||
sqlLogger.info(message);
|
||||
}
|
||||
|
||||
public logMigration(message: string) {
|
||||
public logMigration(message: string): void {
|
||||
sqlLogger.info(message);
|
||||
}
|
||||
}
|
||||
|
@ -143,8 +141,6 @@ export const entities = [
|
|||
NoteUnread,
|
||||
Page,
|
||||
PageLike,
|
||||
GalleryPost,
|
||||
GalleryLike,
|
||||
DriveFile,
|
||||
DriveFolder,
|
||||
Poll,
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*/
|
||||
|
||||
import { EventEmitter } from 'node:events';
|
||||
import boot from '@/boot/index.js';
|
||||
import { boot } from '@/boot/index.js';
|
||||
|
||||
Error.stackTraceLimit = Infinity;
|
||||
EventEmitter.defaultMaxListeners = 128;
|
||||
|
|
|
@ -72,7 +72,7 @@ export function fromHtml(html: string, quoteUri?: string | null): string {
|
|||
const href = getAttr(node, 'href');
|
||||
|
||||
// hashtags
|
||||
if (txt.startsWith('#') && href && (attrHas(node, 'rel', 'tag') || attrHas(node, 'class', 'hashtag')) {
|
||||
if (txt.startsWith('#') && href && (attrHas(node, 'rel', 'tag') || attrHas(node, 'class', 'hashtag'))) {
|
||||
text += txt;
|
||||
// mentions
|
||||
} else if (txt.startsWith('@') && !attrHas(node, 'rel', 'me')) {
|
||||
|
|
|
@ -27,9 +27,5 @@ export const kinds = [
|
|||
'write:user-groups',
|
||||
'read:channels',
|
||||
'write:channels',
|
||||
'read:gallery',
|
||||
'write:gallery',
|
||||
'read:gallery-likes',
|
||||
'write:gallery-likes',
|
||||
];
|
||||
// IF YOU ADD KINDS(PERMISSIONS), YOU MUST ADD TRANSLATIONS (under _permissions).
|
||||
|
|
|
@ -8,10 +8,7 @@ import { SECOND } from '@/const.js';
|
|||
*/
|
||||
const retryDelay = 100;
|
||||
|
||||
const lock: (key: string, timeout?: number) => Promise<() => void>
|
||||
= redisClient
|
||||
? promisify(redisLock(redisClient, retryDelay))
|
||||
: async () => () => { };
|
||||
const lock: (key: string, timeout?: number) => Promise<() => void> = promisify(redisLock(redisClient, retryDelay));
|
||||
|
||||
/**
|
||||
* Get AP Object lock
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
export class Cache<T> {
|
||||
public cache: Map<string | null, { date: number; value: T; }>;
|
||||
public cache: Map<string, { date: number; value: T; }>;
|
||||
private lifetime: number;
|
||||
public fetcher: (key: string | null) => Promise<T | undefined>;
|
||||
public fetcher: (key: string) => Promise<T | undefined>;
|
||||
|
||||
constructor(lifetime: number, fetcher: Cache<T>['fetcher']) {
|
||||
this.cache = new Map();
|
||||
|
@ -9,14 +9,14 @@ export class Cache<T> {
|
|||
this.fetcher = fetcher;
|
||||
}
|
||||
|
||||
public set(key: string | null, value: T): void {
|
||||
public set(key: string, value: T): void {
|
||||
this.cache.set(key, {
|
||||
date: Date.now(),
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
public get(key: string | null): T | undefined {
|
||||
public get(key: string): T | undefined {
|
||||
const cached = this.cache.get(key);
|
||||
if (cached == null) return undefined;
|
||||
|
||||
|
@ -29,7 +29,7 @@ export class Cache<T> {
|
|||
return cached.value;
|
||||
}
|
||||
|
||||
public delete(key: string | null): void {
|
||||
public delete(key: string): void {
|
||||
this.cache.delete(key);
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,7 @@ export class Cache<T> {
|
|||
* run to get the value. If the fetcher returns undefined, it is
|
||||
* returned but not cached.
|
||||
*/
|
||||
public async fetch(key: string | null): Promise<T | undefined> {
|
||||
public async fetch(key: string): Promise<T | undefined> {
|
||||
const cached = this.get(key);
|
||||
if (cached !== undefined) {
|
||||
return cached;
|
||||
|
|
|
@ -22,7 +22,7 @@ export async function checkHitAntenna(antenna: Antenna, note: (Note | Packed<'No
|
|||
if (note.visibility === 'specified') return false;
|
||||
|
||||
// skip if the antenna creator is blocked by the note author
|
||||
const blockings = await blockingCache.fetch(noteUser.id);
|
||||
const blockings = (await blockingCache.fetch(noteUser.id)) ?? [];
|
||||
if (blockings.some(blocking => blocking === antenna.userId)) return false;
|
||||
|
||||
if (note.visibility === 'followers') {
|
||||
|
|
|
@ -3,7 +3,7 @@ import { db } from '@/db/postgre.js';
|
|||
import { Meta } from '@/models/entities/meta.js';
|
||||
import { getFetchInstanceMetadataLock } from '@/misc/app-lock.js';
|
||||
|
||||
let cache: Meta;
|
||||
let cache: Meta | undefined;
|
||||
|
||||
/**
|
||||
* Performs the primitive database operation to set the server configuration
|
||||
|
@ -57,5 +57,5 @@ export async function fetchMeta(noCache = false): Promise<Meta> {
|
|||
|
||||
await getMeta();
|
||||
|
||||
return cache;
|
||||
return cache!;
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ export async function getHtml(url: string, accept = 'text/html, */*', timeout =
|
|||
return await res.text();
|
||||
}
|
||||
|
||||
export async function getResponse(args: { url: string, method: string, body?: string, headers: Record<string, string>, timeout?: number, size?: number }) {
|
||||
export async function getResponse(args: { url: string, method: string, body?: string, headers: Record<string, string>, timeout?: number, size?: number, redirect: 'follow' | 'manual' | 'error' = 'follow' }) {
|
||||
const timeout = args.timeout || 10 * SECOND;
|
||||
|
||||
const controller = new AbortController();
|
||||
|
@ -47,13 +47,18 @@ export async function getResponse(args: { url: string, method: string, body?: st
|
|||
method: args.method,
|
||||
headers: args.headers,
|
||||
body: args.body,
|
||||
redirect: args.redirect,
|
||||
timeout,
|
||||
size: args.size || 10 * 1024 * 1024,
|
||||
size: args.size || 10 * 1024 * 1024, // 10 MiB
|
||||
agent: getAgentByUrl,
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
if (
|
||||
!res.ok
|
||||
&&
|
||||
// intended redirect is not an error
|
||||
!(args.redirect != 'follow' && res.status >= 300 && res.status < 400)) {
|
||||
throw new StatusError(`${res.status} ${res.statusText}`, res.status, res.statusText);
|
||||
}
|
||||
|
||||
|
|
17
packages/backend/src/misc/password.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import bcrypt from 'bcryptjs';
|
||||
import * as argon2 from 'argon2';
|
||||
|
||||
export async function hashPassword(password: string): Promise<string> {
|
||||
return argon2.hash(password);
|
||||
}
|
||||
|
||||
export async function comparePassword(password: string, hash: string): Promise<boolean> {
|
||||
if (isOldAlgorithm(hash)) return bcrypt.compare(password, hash);
|
||||
|
||||
return argon2.verify(hash, password);
|
||||
}
|
||||
|
||||
export function isOldAlgorithm(hash: string): boolean {
|
||||
// bcrypt hashes start with $2[ab]$
|
||||
return hash.startsWith('$2');
|
||||
}
|
|
@ -75,7 +75,7 @@ export async function toDbReaction(reaction?: string | null, idnReacterHost?: st
|
|||
const custom = reaction.match(/^:([\w+-]+)(?:@\.)?:$/);
|
||||
if (custom) {
|
||||
const name = custom[1];
|
||||
const emoji = await Emojis.findOneBy({
|
||||
const emoji = await Emojis.countBy({
|
||||
host: reacterHost ?? IsNull(),
|
||||
name,
|
||||
});
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
import { Note } from '@/models/entities/note.js';
|
||||
|
||||
export function isPureRenote(note: Note): note is Note & { renoteId: string, text: null, fileIds: null | never[], hasPoll: false } {
|
||||
return note.renoteId != null && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && !note.hasPoll;
|
||||
return note.renoteId != null
|
||||
&& note.text == null
|
||||
&& (
|
||||
note.fileIds == null
|
||||
|| note.fileIds.length === 0
|
||||
)
|
||||
&& !note.hasPoll;
|
||||
}
|
||||
|
|
|
@ -28,7 +28,6 @@ import { packedAntennaSchema } from '@/models/schema/antenna.js';
|
|||
import { packedClipSchema } from '@/models/schema/clip.js';
|
||||
import { packedFederationInstanceSchema } from '@/models/schema/federation-instance.js';
|
||||
import { packedQueueCountSchema } from '@/models/schema/queue.js';
|
||||
import { packedGalleryPostSchema } from '@/models/schema/gallery-post.js';
|
||||
import { packedEmojiSchema } from '@/models/schema/emoji.js';
|
||||
|
||||
export const refs = {
|
||||
|
@ -61,7 +60,6 @@ export const refs = {
|
|||
Antenna: packedAntennaSchema,
|
||||
Clip: packedClipSchema,
|
||||
FederationInstance: packedFederationInstanceSchema,
|
||||
GalleryPost: packedGalleryPostSchema,
|
||||
Emoji: packedEmojiSchema,
|
||||
};
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import * as crypto from 'node:crypto';
|
||||
|
||||
const L_CHARS = '0123456789abcdefghijklmnopqrstuvwxyz';
|
||||
const LU_CHARS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
const LU_CHARS = L_CHARS + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
|
||||
export function secureRndstr(length = 32, useLU = true): string {
|
||||
const chars = useLU ? LU_CHARS : L_CHARS;
|
||||
export function secureRndstrCustom(length = 32, chars: string): string {
|
||||
const chars_len = chars.length;
|
||||
|
||||
let str = '';
|
||||
|
@ -19,3 +18,8 @@ export function secureRndstr(length = 32, useLU = true): string {
|
|||
|
||||
return str;
|
||||
}
|
||||
|
||||
export function secureRndstr(length = 32, useLU = true): string {
|
||||
const chars = useLU ? LU_CHARS : L_CHARS;
|
||||
return secureRndstrCustom(length, chars);
|
||||
}
|
||||
|
|
|
@ -6,11 +6,10 @@ import { Meta } from '@/models/entities/meta.js';
|
|||
* Returns whether a specific host (punycoded) should be blocked.
|
||||
*
|
||||
* @param host punycoded instance host
|
||||
* @param meta a Promise contatining the information from the meta table (optional)
|
||||
* @param meta a resolved Meta table
|
||||
* @returns whether the given host should be blocked
|
||||
*/
|
||||
|
||||
export async function shouldBlockInstance(host: Instance['host'], meta: Promise<Meta> = fetchMeta()): Promise<boolean> {
|
||||
const { blockedHosts } = await meta;
|
||||
export async function shouldBlockInstance(host: Instance['host'], meta?: Meta): Promise<boolean> {
|
||||
const { blockedHosts } = meta ?? await fetchMeta();
|
||||
return blockedHosts.some(blockedHost => host === blockedHost || host.endsWith('.' + blockedHost));
|
||||
}
|
||||
|
|
|
@ -15,8 +15,8 @@ const deadThreshold = 7 * DAY;
|
|||
* @returns array of punycoded instance hosts that should be skipped (subset of hosts parameter)
|
||||
*/
|
||||
export async function skippedInstances(hosts: Array<Instance['host']>): Promise<Array<Instance['host']>> {
|
||||
// Resolve the boolean promises before filtering
|
||||
const meta = fetchMeta();
|
||||
// first check for blocked instances since that info may already be in memory
|
||||
const meta = await fetchMeta();
|
||||
const shouldSkip = await Promise.all(hosts.map(host => shouldBlockInstance(host, meta)));
|
||||
const skipped = hosts.filter((_, i) => shouldSkip[i]);
|
||||
|
||||
|
@ -35,7 +35,7 @@ export async function skippedInstances(hosts: Array<Instance['host']>): Promise<
|
|||
hosts.filter(host => !skipped.includes(host) && !host.includes(',')).join(','),
|
||||
],
|
||||
)
|
||||
.then(res => res.map(row => row.host)),
|
||||
.then((res: Instance[]) => res.map(row => row.host)),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ export class AbuseUserReport {
|
|||
@Column(id())
|
||||
public targetUserId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -27,7 +27,7 @@ export class AbuseUserReport {
|
|||
@Column(id())
|
||||
public reporterId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -39,7 +39,7 @@ export class AbuseUserReport {
|
|||
})
|
||||
public assigneeId: User['id'] | null;
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'SET NULL',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -41,7 +41,7 @@ export class AccessToken {
|
|||
@Column(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -53,7 +53,7 @@ export class AccessToken {
|
|||
})
|
||||
public appId: App['id'] | null;
|
||||
|
||||
@ManyToOne(type => App, {
|
||||
@ManyToOne(() => App, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -79,7 +79,6 @@ export class AccessToken {
|
|||
|
||||
@Column('varchar', {
|
||||
length: 64, array: true,
|
||||
default: '{}',
|
||||
})
|
||||
public permission: string[];
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ export class AnnouncementRead {
|
|||
@Column(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -28,7 +28,7 @@ export class AnnouncementRead {
|
|||
@Column(id())
|
||||
public announcementId: Announcement['id'];
|
||||
|
||||
@ManyToOne(type => Announcement, {
|
||||
@ManyToOne(() => Announcement, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -32,12 +32,4 @@ export class Announcement {
|
|||
length: 1024, nullable: true,
|
||||
})
|
||||
public imageUrl: string | null;
|
||||
|
||||
constructor(data: Partial<Announcement>) {
|
||||
if (data == null) return;
|
||||
|
||||
for (const [k, v] of Object.entries(data)) {
|
||||
(this as any)[k] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ export class AntennaNote {
|
|||
})
|
||||
public noteId: Note['id'];
|
||||
|
||||
@ManyToOne(type => Note, {
|
||||
@ManyToOne(() => Note, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -29,7 +29,7 @@ export class AntennaNote {
|
|||
})
|
||||
public antennaId: Antenna['id'];
|
||||
|
||||
@ManyToOne(type => Antenna, {
|
||||
@ManyToOne(() => Antenna, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -21,7 +21,7 @@ export class Antenna {
|
|||
})
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -42,7 +42,7 @@ export class Antenna {
|
|||
})
|
||||
public userListId: UserList['id'] | null;
|
||||
|
||||
@ManyToOne(type => UserList, {
|
||||
@ManyToOne(() => UserList, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -54,7 +54,7 @@ export class Antenna {
|
|||
})
|
||||
public userGroupJoiningId: UserGroupJoining['id'] | null;
|
||||
|
||||
@ManyToOne(type => UserGroupJoining, {
|
||||
@ManyToOne(() => UserGroupJoining, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -21,7 +21,7 @@ export class App {
|
|||
})
|
||||
public userId: User['id'] | null;
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'SET NULL',
|
||||
nullable: true,
|
||||
})
|
||||
|
|
|
@ -11,7 +11,7 @@ export class AttestationChallenge {
|
|||
@PrimaryColumn(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -35,12 +35,4 @@ export class AttestationChallenge {
|
|||
default: false,
|
||||
})
|
||||
public registrationChallenge: boolean;
|
||||
|
||||
constructor(data: Partial<AttestationChallenge>) {
|
||||
if (data == null) return;
|
||||
|
||||
for (const [k, v] of Object.entries(data)) {
|
||||
(this as any)[k] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Entity, PrimaryColumn, Index, Column, ManyToOne, OneToOne, JoinColumn } from 'typeorm';
|
||||
import { Entity, PrimaryColumn, Index, Column, ManyToOne, JoinColumn } from 'typeorm';
|
||||
import { id } from '../id.js';
|
||||
import { AccessToken } from './access-token.js';
|
||||
import { App } from './app.js';
|
||||
|
@ -45,5 +45,5 @@ export class AuthSession {
|
|||
nullable: true,
|
||||
comment: 'PKCE code_challenge value, if provided (OAuth only)',
|
||||
})
|
||||
pkceChallenge: string | null;
|
||||
pkceChallenge: string | null;
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ export class Blocking {
|
|||
})
|
||||
public blockeeId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -34,7 +34,7 @@ export class Blocking {
|
|||
})
|
||||
public blockerId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -22,7 +22,7 @@ export class ChannelFollowing {
|
|||
})
|
||||
public followeeId: Channel['id'];
|
||||
|
||||
@ManyToOne(type => Channel, {
|
||||
@ManyToOne(() => Channel, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -35,7 +35,7 @@ export class ChannelFollowing {
|
|||
})
|
||||
public followerId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -18,7 +18,7 @@ export class ChannelNotePining {
|
|||
@Column(id())
|
||||
public channelId: Channel['id'];
|
||||
|
||||
@ManyToOne(type => Channel, {
|
||||
@ManyToOne(() => Channel, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -27,7 +27,7 @@ export class ChannelNotePining {
|
|||
@Column(id())
|
||||
public noteId: Note['id'];
|
||||
|
||||
@ManyToOne(type => Note, {
|
||||
@ManyToOne(() => Note, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -28,7 +28,7 @@ export class Channel {
|
|||
})
|
||||
public userId: User['id'] | null;
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'SET NULL',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -53,7 +53,7 @@ export class Channel {
|
|||
})
|
||||
public bannerId: DriveFile['id'] | null;
|
||||
|
||||
@ManyToOne(type => DriveFile, {
|
||||
@ManyToOne(() => DriveFile, {
|
||||
onDelete: 'SET NULL',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -16,7 +16,7 @@ export class ClipNote {
|
|||
})
|
||||
public noteId: Note['id'];
|
||||
|
||||
@ManyToOne(type => Note, {
|
||||
@ManyToOne(() => Note, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -29,7 +29,7 @@ export class ClipNote {
|
|||
})
|
||||
public clipId: Clip['id'];
|
||||
|
||||
@ManyToOne(type => Clip, {
|
||||
@ManyToOne(() => Clip, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -19,7 +19,7 @@ export class Clip {
|
|||
})
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -23,7 +23,7 @@ export class DriveFile {
|
|||
})
|
||||
public userId: User['id'] | null;
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -144,7 +144,7 @@ export class DriveFile {
|
|||
})
|
||||
public folderId: DriveFolder['id'] | null;
|
||||
|
||||
@ManyToOne(type => DriveFolder, {
|
||||
@ManyToOne(() => DriveFolder, {
|
||||
onDelete: 'SET NULL',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -27,7 +27,7 @@ export class DriveFolder {
|
|||
})
|
||||
public userId: User['id'] | null;
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -41,7 +41,7 @@ export class DriveFolder {
|
|||
})
|
||||
public parentId: DriveFolder['id'] | null;
|
||||
|
||||
@ManyToOne(type => DriveFolder, {
|
||||
@ManyToOne(() => DriveFolder, {
|
||||
onDelete: 'SET NULL',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -20,7 +20,7 @@ export class FollowRequest {
|
|||
})
|
||||
public followeeId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -33,7 +33,7 @@ export class FollowRequest {
|
|||
})
|
||||
public followerId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -21,7 +21,7 @@ export class Following {
|
|||
})
|
||||
public followeeId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -34,7 +34,7 @@ export class Following {
|
|||
})
|
||||
public followerId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { id } from '../id.js';
|
||||
import { User } from './user.js';
|
||||
import { GalleryPost } from './gallery-post.js';
|
||||
|
||||
@Entity()
|
||||
@Index(['userId', 'postId'], { unique: true })
|
||||
export class GalleryLike {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Column('timestamp with time zone')
|
||||
public createdAt: Date;
|
||||
|
||||
@Index()
|
||||
@Column(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
|
||||
@Column(id())
|
||||
public postId: GalleryPost['id'];
|
||||
|
||||
@ManyToOne(type => GalleryPost, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public post: GalleryPost | null;
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from 'typeorm';
|
||||
import { id } from '../id.js';
|
||||
import { User } from './user.js';
|
||||
import { DriveFile } from './drive-file.js';
|
||||
|
||||
@Entity()
|
||||
export class GalleryPost {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index()
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The created date of the GalleryPost.',
|
||||
})
|
||||
public createdAt: Date;
|
||||
|
||||
@Index()
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The updated date of the GalleryPost.',
|
||||
})
|
||||
public updatedAt: Date;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256,
|
||||
})
|
||||
public title: string;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 2048, nullable: true,
|
||||
})
|
||||
public description: string | null;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
comment: 'The ID of author.',
|
||||
})
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
array: true, default: '{}',
|
||||
})
|
||||
public fileIds: DriveFile['id'][];
|
||||
|
||||
@Index()
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
comment: 'Whether the post is sensitive.',
|
||||
})
|
||||
public isSensitive: boolean;
|
||||
|
||||
@Index()
|
||||
@Column('integer', {
|
||||
default: 0,
|
||||
})
|
||||
public likedCount: number;
|
||||
|
||||
@Index()
|
||||
@Column('varchar', {
|
||||
length: 128, array: true, default: '{}',
|
||||
})
|
||||
public tags: string[];
|
||||
|
||||
constructor(data: Partial<GalleryPost>) {
|
||||
if (data == null) return;
|
||||
|
||||
for (const [k, v] of Object.entries(data)) {
|
||||
(this as any)[k] = v;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,7 +22,7 @@ export class MessagingMessage {
|
|||
})
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -35,7 +35,7 @@ export class MessagingMessage {
|
|||
})
|
||||
public recipientId: User['id'] | null;
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -48,7 +48,7 @@ export class MessagingMessage {
|
|||
})
|
||||
public groupId: UserGroup['id'] | null;
|
||||
|
||||
@ManyToOne(type => UserGroup, {
|
||||
@ManyToOne(() => UserGroup, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -81,7 +81,7 @@ export class MessagingMessage {
|
|||
})
|
||||
public fileId: DriveFile['id'] | null;
|
||||
|
||||
@ManyToOne(type => DriveFile, {
|
||||
@ManyToOne(() => DriveFile, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|