Merge branch 'main' into mk.absturztau.be

This commit is contained in:
Puniko 2022-12-18 14:43:12 +01:00
commit 3900d2f716
326 changed files with 2060 additions and 3384 deletions

View file

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

View file

@ -1,6 +1,6 @@
.autogen
.vscode
.config
.woodpecker
Dockerfile
build/
built/

View file

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

@ -1,7 +1 @@
*.svg -diff -text
*.psd -diff -text
*.ai -diff -text
*.mqo -diff -text
*.glb -diff -text
*.blend -diff -text
*.afdesign -diff -text

2
.gitignore vendored
View file

@ -1,6 +1,6 @@
# Visual Studio Code
/.vscode
!/.vscode/extensions.json
/.vsls.json
# Intelij-IDEA
/.idea

View file

@ -1,8 +0,0 @@
{
"recommendations": [
"editorconfig.editorconfig",
"dbaeumer.vscode-eslint",
"Vue.volar",
"Vue.vscode-typescript-vue-plugin"
]
}

View file

@ -1,4 +0,0 @@
{
"$schema": "http://json.schemastore.org/vsls",
"gitignore": "exclude"
}

22
.woodpecker/lint-sw.yml Normal file
View 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

View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 317 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

42
docs/oauth.md Normal file
View file

@ -0,0 +1,42 @@
# 3rd party access
Foundkey supports:
- 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.
In short, this means that to discover the URLs for the grant endpoints you should request `/.well-known/oauth-authorization-server`, which is a JSON object.
From there, `authorization_endpoint` and `token_endpoint` will probably be most interesting to you.
The definitions of all data fields are to be found in [RFC 8414, section 2](https://www.rfc-editor.org/rfc/rfc8414#section-2).
# App registration
Before using the OAuth grant you need to register your application.
Currently you will need to use the pre-existing Misskey API to register, though Dynamic Client Registration may be implemented at a later point.
(You'd be able to tell from the Authorization Server Metadata, see above.)
The data you will need to know before registering is the following:
- a name for your app,
- a short description to be shown to users,
- which API permissions you need, and
- the callback URL you want to use.
There can only be 1 callback URL per registration.
Note that you can specify permissions a 2nd time in the OAuth flow.
If you do not provide permissions again in the grant flow, the default is to use all permissions you gave when registering the app.
If you do provide permissions in the grant flow, permissions that were not registered will never be granted.
A list of available permissions can be viewed on any Foundkey instance by going to the API documentation at `/api-doc`.
To register your app you need to `POST` to `/api/app/create`.
The body of the request must be a JSON object with the following keys:
- `name` (string): a name for your app,
- `description` (string): a short description to be shown to users,
- `permission` (array of permission names) which API permissions you need, and
- `callbackUrl` (string): the callback URL you want to use.
If successful (HTTP response code 200) you will receive back a JSON object containing among other things:
- `id` (string): the client ID
- `secret` (string): the client secret
With these credentials you should be able to use the Authorization Code grant to obtain authorization.

View file

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

View file

@ -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: "أو"

View file

@ -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: "অথবা"

View file

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

View file

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

View file

@ -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"
@ -187,7 +189,7 @@ clearCachedFiles: "Clear cache"
clearCachedFilesConfirm: "Are you sure that you want to delete all cached remote files?"
blockedInstances: "Blocked Instances"
blockedInstancesDescription: "List the hostnames of the instances that you want to\
\ block. Listed instances will no longer be able to communicate with this instance. Non-ASCII domain names must be encoded in punycode. You can use an asterisk (*) as a placeholder for zero or more character(s)."
\ block. Listed instances will no longer be able to communicate with this instance. Non-ASCII domain names must be encoded in punycode. Subdomains of the listed instances will also be blocked."
muteAndBlock: "Mutes and Blocks"
mutedUsers: "Muted users"
blockedUsers: "Blocked users"
@ -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"
@ -829,6 +827,10 @@ setTag: "Set tag"
addTag: "Add tag"
removeTag: "Remove tag"
externalCssSnippets: "Some CSS snippets for your inspiration (not managed by FoundKey)"
oauthErrorGoBack: "An error happened while trying to authenticate a 3rd party app.\
\ Please go back and try again."
appAuthorization: "App authorization"
noPermissionsRequested: "(No permissions requested.)"
_emailUnavailable:
used: "This email address is already being used"
format: "The format of this email address is invalid"
@ -1079,38 +1081,37 @@ _2fa:
\ authentication via hardware security keys that support FIDO2 to further secure\
\ your account."
_permissions:
"read:account": "View your account information"
"write:account": "Edit your account information"
"read:blocks": "View your list of blocked users"
"write:blocks": "Edit your list of blocked users"
"read:drive": "Access your Drive files and folders"
"write:drive": "Edit or delete your Drive files and folders"
"read:favorites": "View your list of favorites"
"write:favorites": "Edit your list of favorites"
"read:following": "View information on who you follow"
"write:following": "Follow or unfollow other accounts"
"read:messaging": "View your chats"
"write:messaging": "Compose or delete chat messages"
"read:mutes": "View your list of muted users"
"write:mutes": "Edit your list of muted users"
"write:notes": "Compose or delete notes"
"read:notifications": "View your notifications"
"write:notifications": "Manage your notifications"
"read:reactions": "View your reactions"
"write:reactions": "Edit your reactions"
"write:votes": "Vote on a poll"
"read:pages": "View your pages"
"write:pages": "Edit or delete your pages"
"read:page-likes": "View your likes on pages"
"write:page-likes": "Edit your likes on pages"
"read:user-groups": "View your user groups"
"write:user-groups": "Edit or delete your user groups"
"read:channels": "View your channels"
"write:channels": "Edit your channels"
"read:gallery": "View your gallery"
"write:gallery": "Edit your gallery"
"read:gallery-likes": "View your list of liked gallery posts"
"write:gallery-likes": "Edit your list of liked gallery posts"
"read:account": "Read account information"
"write:account": "Edit account information"
"read:blocks": "Read which users are blocked"
"write:blocks": "Block and unblock users"
"read:drive": "List files and folders in the drive"
"write:drive": "Create, change and delete files in the drive"
"read:favorites": "List favourited notes"
"write:favorites": "Favorite and unfavorite notes"
"read:following": "List followed and following users"
"write:following": "Follow and unfollow other users"
"read:messaging": "View chat messages and history"
"write:messaging": "Create and delete chat messages"
"read:mutes": "List users which are muted or whose renotes are muted"
"write:mutes": "Mute and unmute users or their renotes"
"write:notes": "Create and delete notes"
"read:notifications": "Read notifications"
"write:notifications": "Mark notifications as read and create custom notifications"
"write:reactions": "Create and delete reactions"
"write:votes": "Vote in polls"
"read:pages": "List and read pages"
"write:pages": "Create, change and delete pages"
"read:page-likes": "List and read page likes"
"write:page-likes": "Like and unlike pages"
"read:user-groups": "List and view joined, owned and invited to groups"
"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\
@ -1342,16 +1343,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"

View file

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

View file

@ -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 dauthentification. Veuillez vérifier que votre nom dutilisateur\
\ 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.

View file

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

View file

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

View file

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

View file

@ -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の表示言語"

View file

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

View file

@ -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: "혹은"

View file

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

View file

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

View file

@ -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: "Нажмите на свой электронный ключ"

View file

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

View file

@ -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: "або"

View file

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

View file

@ -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: "或者"

View file

@ -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: "或者"

View file

@ -18,7 +18,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 +26,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": {
@ -46,11 +46,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"
}

View file

@ -0,0 +1,33 @@
export class tokenPermissions1667653936442 {
name = 'tokenPermissions1667653936442'
async up(queryRunner) {
// Carry over the permissions from the app for tokens that have an associated app.
await queryRunner.query(`UPDATE "access_token" SET permission = (SELECT permission FROM "app" WHERE "app"."id" = "access_token"."appId") WHERE "appId" IS NOT NULL AND CARDINALITY("permission") = 0`);
// The permission column should now always be set explicitly, so the default is not needed any more.
await queryRunner.query(`ALTER TABLE "access_token" ALTER COLUMN "permission" DROP DEFAULT`);
// Drop all currently running authorization sessions. Already created tokens remain untouched.
// If you were registering an app just before upgrade started, try again later. ¯\_(ツ)_/¯
await queryRunner.query(`TRUNCATE TABLE "auth_session"`);
// Refactor scheme to allow multiple access tokens per app.
await queryRunner.query(`ALTER TABLE "auth_session" DROP CONSTRAINT "FK_c072b729d71697f959bde66ade0"`);
await queryRunner.query(`ALTER TABLE "auth_session" RENAME COLUMN "userId" TO "accessTokenId"`);
await queryRunner.query(`ALTER TABLE "auth_session" ADD CONSTRAINT "UQ_8e001e5a101c6dca37df1a76d66" UNIQUE ("accessTokenId")`);
await queryRunner.query(`ALTER TABLE "auth_session" ADD CONSTRAINT "FK_8e001e5a101c6dca37df1a76d66" FOREIGN KEY ("accessTokenId") REFERENCES "access_token"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
}
async down(queryRunner) {
// Drop all currently running authorization sessions. Already created tokens remain untouched.
// If you were registering an app just before downgrade started, try again later. ¯\_(ツ)_/¯
await queryRunner.query(`TRUNCATE TABLE "auth_session"`);
await queryRunner.query(`ALTER TABLE "auth_session" DROP CONSTRAINT "FK_8e001e5a101c6dca37df1a76d66"`);
await queryRunner.query(`ALTER TABLE "auth_session" DROP CONSTRAINT "UQ_8e001e5a101c6dca37df1a76d66"`);
await queryRunner.query(`ALTER TABLE "access_token" ALTER COLUMN "permission" DROP DEFAULT`);
await queryRunner.query(`ALTER TABLE "auth_session" RENAME COLUMN "accessTokenId" TO "userId"`);
await queryRunner.query(`ALTER TABLE "auth_session" ADD CONSTRAINT "FK_c072b729d71697f959bde66ade0" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "access_token" ALTER COLUMN "permission" SET DEFAULT '{}'::varchar[]`);
await queryRunner.query(`UPDATE "access_token" SET permission = '{}'::varchar[] WHERE "appId" IS NOT NULL`);
}
}

View file

@ -0,0 +1,12 @@
export class pkce1667738304733 {
name = 'pkce1667738304733'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "auth_session" ADD "pkceChallenge" text`);
await queryRunner.query(`COMMENT ON COLUMN "auth_session"."pkceChallenge" IS 'PKCE code_challenge value, if provided (OAuth only)'`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "auth_session" DROP COLUMN "pkceChallenge"`);
}
}

View file

@ -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`);
}
}

View file

@ -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 && 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",
@ -124,6 +122,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 +145,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 +159,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-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"
}
}

View file

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

View file

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

View file

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

View file

@ -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;
};
};
/**

View file

@ -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';
@ -78,33 +78,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);
}
}

View file

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

View file

@ -7,6 +7,16 @@ const treeAdapter = parse5.defaultTreeAdapter;
const urlRegex = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+/;
const urlRegexFull = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+$/;
function getAttr(node: TreeAdapter.Node, attr: string): string {
return node.attrs.find(({ name }) => name === attr)?.value;
}
function attrHas(node: TreeAdapter.Node, attr: string, value: string): boolean {
const attrValue = getAttr(node, attr);
if (!attrValue) return false;
return new RegExp('\\b' + value + '\\b').test(attrValue);
}
export function fromHtml(html: string, quoteUri?: string | null): string {
const dom = parse5.parseFragment(
// some AP servers like Pixelfed use br tags as well as newlines
@ -59,19 +69,18 @@ export function fromHtml(html: string, quoteUri?: string | null): string {
case 'a':
{
const txt = getText(node);
const rel = node.attrs.find(x => x.name === 'rel');
const href = node.attrs.find(x => x.name === 'href');
const href = getAttr(node, 'href');
// hashtags
if (txt.startsWith('#') && href && /\btag\b/.test(rel?.value)) {
if (txt.startsWith('#') && href && (attrHas(node, 'rel', 'tag') || attrHas(node, 'class', 'hashtag'))) {
text += txt;
// mentions
} else if (txt.startsWith('@') && !(rel && rel.value.match(/^me /))) {
} else if (txt.startsWith('@') && !attrHas(node, 'rel', 'me')) {
const part = txt.split('@');
if (part.length === 2 && href) {
// restore the host name part
const acct = `${txt}@${(new URL(href.value)).hostname}`;
const acct = `${txt}@${(new URL(href)).hostname}`;
text += acct;
} else if (part.length === 3) {
text += txt;
@ -85,17 +94,17 @@ export function fromHtml(html: string, quoteUri?: string | null): string {
if (!href) {
return txt;
}
if (!txt || txt === href.value) { // #6383: Missing text node
if (href.value.match(urlRegexFull)) {
return href.value;
if (!txt || txt === href) { // #6383: Missing text node
if (href.match(urlRegexFull)) {
return href;
} else {
return `<${href.value}>`;
return `<${href}>`;
}
}
if (href.value.match(urlRegex) && !href.value.match(urlRegexFull)) {
return `[${txt}](<${href.value}>)`; // #6846
if (href.match(urlRegex) && !href.match(urlRegexFull)) {
return `[${txt}](<${href}>)`; // #6846
} else {
return `[${txt}](${href.value})`;
return `[${txt}](${href})`;
}
};
@ -204,8 +213,7 @@ export function fromHtml(html: string, quoteUri?: string | null): string {
case 'span':
{
const nodeClass = node.attrs.find(({ name }) => name === 'class')?.value;
if (/\bquote-inline\b/.test(nodeClass) && quoteUri && getText(node).trim() === `RE: ${quoteUri}`) {
if (attrHas(node, 'class', 'quote-inline') && quoteUri && getText(node).trim() === `RE: ${quoteUri}`) {
// embedded quote thingy for backwards compatibility, don't show it
} else {
appendChildren(node.childNodes);

View file

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

View file

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

View file

@ -0,0 +1,16 @@
import { fetchMeta } from '@/misc/fetch-meta.js';
import { Instance } from '@/models/entities/instance.js';
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)
* @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;
return blockedHosts.some(blockedHost => host === blockedHost || host.endsWith('.' + blockedHost));
}

View file

@ -2,39 +2,12 @@ import { db } from '@/db/postgre.js';
import { fetchMeta } from '@/misc/fetch-meta.js';
import { Instance } from '@/models/entities/instance.js';
import { DAY } from '@/const.js';
import { Meta } from '@/models/entities/meta.js';
import { shouldBlockInstance } from '@/misc/should-block-instance.js';
// Threshold from last contact after which an instance will be considered
// "dead" and should no longer get activities delivered to it.
const deadThreshold = 7 * DAY;
/**
* Returns whether a given host matches a wildcard pattern.
* @param host punycoded instance host
* @param pattern wildcard pattern containing a punycoded instance host
* @returns whether the post matches the pattern
*/
function matchHost(host: Instance['host'], pattern: string): boolean {
// Escape all of the regex special characters. Pattern from:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
const escape = (str: string): string => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const re = new RegExp('^' + pattern.split('*').map(escape).join('.*') + '$');
return re.test(host);
}
/**
* 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 (oprional)
* @returns whether the given host should be blocked
*/
export async function shouldBlockInstance(host: string, meta: Promise<Meta> = fetchMeta()): Promise<boolean> {
const { blockedHosts } = await meta;
return blockedHosts.some(blockedHost => matchHost(host, blockedHost));
}
/**
* Returns the subset of hosts which should be skipped.
*
@ -62,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)),
);
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -21,7 +21,7 @@ export class App {
})
public userId: User['id'] | null;
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'SET NULL',
nullable: true,
})

View file

@ -11,7 +11,7 @@ export class AttestationChallenge {
@PrimaryColumn(id())
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -1,6 +1,6 @@
import { Entity, PrimaryColumn, Index, Column, ManyToOne, JoinColumn } from 'typeorm';
import { id } from '../id.js';
import { User } from './user.js';
import { AccessToken } from './access-token.js';
import { App } from './app.js';
@Entity()
@ -23,21 +23,27 @@ export class AuthSession {
...id(),
nullable: true,
})
public userId: User['id'] | null;
public accessTokenId: AccessToken['id'] | null;
@ManyToOne(type => User, {
@ManyToOne(() => AccessToken, {
onDelete: 'CASCADE',
nullable: true,
})
@JoinColumn()
public user: User | null;
public accessToken: AccessToken | null;
@Column(id())
public appId: App['id'];
@ManyToOne(type => App, {
@ManyToOne(() => App, {
onDelete: 'CASCADE',
})
@JoinColumn()
public app: App | null;
@Column('text', {
nullable: true,
comment: 'PKCE code_challenge value, if provided (OAuth only)',
})
pkceChallenge: string | null;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -19,7 +19,7 @@ export class Clip {
})
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

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

View file

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

View file

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

View file

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

View file

@ -16,7 +16,7 @@ export class GalleryLike {
@Column(id())
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -25,7 +25,7 @@ export class GalleryLike {
@Column(id())
public postId: GalleryPost['id'];
@ManyToOne(type => GalleryPost, {
@ManyToOne(() => GalleryPost, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -37,7 +37,7 @@ export class GalleryPost {
})
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

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

View file

@ -134,7 +134,7 @@ export class Meta {
})
public proxyAccountId: User['id'] | null;
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'SET NULL',
})
@JoinColumn()
@ -246,57 +246,6 @@ export class Meta {
})
public swPrivateKey: string;
@Column('boolean', {
default: false,
})
public enableTwitterIntegration: boolean;
@Column('varchar', {
length: 128,
nullable: true,
})
public twitterConsumerKey: string | null;
@Column('varchar', {
length: 128,
nullable: true,
})
public twitterConsumerSecret: string | null;
@Column('boolean', {
default: false,
})
public enableGithubIntegration: boolean;
@Column('varchar', {
length: 128,
nullable: true,
})
public githubClientId: string | null;
@Column('varchar', {
length: 128,
nullable: true,
})
public githubClientSecret: string | null;
@Column('boolean', {
default: false,
})
public enableDiscordIntegration: boolean;
@Column('varchar', {
length: 128,
nullable: true,
})
public discordClientId: string | null;
@Column('varchar', {
length: 128,
nullable: true,
})
public discordClientSecret: string | null;
@Column('enum', {
enum: TranslationService,
nullable: true,

View file

@ -16,7 +16,7 @@ export class ModerationLog {
@Column(id())
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -17,7 +17,7 @@ export class MutedNote {
})
public noteId: Note['id'];
@ManyToOne(type => Note, {
@ManyToOne(() => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -30,7 +30,7 @@ export class MutedNote {
})
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -27,7 +27,7 @@ export class Muting {
})
public muteeId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -40,7 +40,7 @@ export class Muting {
})
public muterId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -18,7 +18,7 @@ export class NoteFavorite {
@Column(id())
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -27,7 +27,7 @@ export class NoteFavorite {
@Column(id())
public noteId: Note['id'];
@ManyToOne(type => Note, {
@ManyToOne(() => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -19,7 +19,7 @@ export class NoteReaction {
@Column(id())
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -29,7 +29,7 @@ export class NoteReaction {
@Column(id())
public noteId: Note['id'];
@ManyToOne(type => Note, {
@ManyToOne(() => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -20,7 +20,7 @@ export class NoteThreadMuting {
})
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -14,7 +14,7 @@ export class NoteUnread {
@Column(id())
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -24,7 +24,7 @@ export class NoteUnread {
@Column(id())
public noteId: Note['id'];
@ManyToOne(type => Note, {
@ManyToOne(() => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -22,7 +22,7 @@ export class NoteWatching {
})
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -35,7 +35,7 @@ export class NoteWatching {
})
public noteId: Note['id'];
@ManyToOne(type => Note, {
@ManyToOne(() => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -27,7 +27,7 @@ export class Note {
})
public replyId: Note['id'] | null;
@ManyToOne(type => Note, {
@ManyToOne(() => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -41,7 +41,7 @@ export class Note {
})
public renoteId: Note['id'] | null;
@ManyToOne(type => Note, {
@ManyToOne(() => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -75,7 +75,7 @@ export class Note {
})
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -179,7 +179,7 @@ export class Note {
})
public channelId: Channel['id'] | null;
@ManyToOne(type => Channel, {
@ManyToOne(() => Channel, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -28,7 +28,7 @@ export class Notification {
})
public notifieeId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -45,7 +45,7 @@ export class Notification {
})
public notifierId: User['id'] | null;
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -89,7 +89,7 @@ export class Notification {
})
public noteId: Note['id'] | null;
@ManyToOne(type => Note, {
@ManyToOne(() => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -101,7 +101,7 @@ export class Notification {
})
public followRequestId: FollowRequest['id'] | null;
@ManyToOne(type => FollowRequest, {
@ManyToOne(() => FollowRequest, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -113,7 +113,7 @@ export class Notification {
})
public userGroupInvitationId: UserGroupInvitation['id'] | null;
@ManyToOne(type => UserGroupInvitation, {
@ManyToOne(() => UserGroupInvitation, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -165,7 +165,7 @@ export class Notification {
})
public appAccessTokenId: AccessToken['id'] | null;
@ManyToOne(type => AccessToken, {
@ManyToOne(() => AccessToken, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -16,7 +16,7 @@ export class PageLike {
@Column(id())
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -25,7 +25,7 @@ export class PageLike {
@Column(id())
public pageId: Page['id'];
@ManyToOne(type => Page, {
@ManyToOne(() => Page, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -57,7 +57,7 @@ export class Page {
})
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -69,7 +69,7 @@ export class Page {
})
public eyeCatchingImageId: DriveFile['id'] | null;
@ManyToOne(type => DriveFile, {
@ManyToOne(() => DriveFile, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -22,7 +22,7 @@ export class PasswordResetRequest {
})
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -19,7 +19,7 @@ export class PollVote {
@Column(id())
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -29,7 +29,7 @@ export class PollVote {
@Column(id())
public noteId: Note['id'];
@ManyToOne(type => Note, {
@ManyToOne(() => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -9,7 +9,7 @@ export class Poll {
@PrimaryColumn(id())
public noteId: Note['id'];
@OneToOne(type => Note, {
@OneToOne(() => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -25,7 +25,7 @@ export class RegistryItem {
})
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -21,7 +21,7 @@ export class RenoteMuting {
})
public muteeId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -34,7 +34,7 @@ export class RenoteMuting {
})
public muterId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -16,7 +16,7 @@ export class Signin {
@Column(id())
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

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