diff --git a/.config/example.yml b/.config/example.yml index 8534d3e1ea..ca47c31415 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -8,6 +8,9 @@ # Final accessible URL seen by a user. url: https://example.tld/ +# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE +# URL SETTINGS AFTER THAT! + # ┌───────────────────────┐ #───┘ Port and TLS settings └─────────────────────────────────── diff --git a/.eslintrc b/.eslintrc index 9db60222ac..3a220319e5 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,23 +1,23 @@ { "parserOptions": { - "parser": "typescript-eslint-parser" + "parser": "@typescript-eslint/parser" }, "extends": [ "eslint:recommended", "plugin:vue/recommended" ], "rules": { - "vue/require-v-for-key": false, - "vue/max-attributes-per-line": false, - "vue/html-indent": false, - "vue/html-self-closing": false, - "vue/no-unused-vars": false, - "vue/attributes-order": false, - "vue/require-prop-types": false, - "vue/require-default-prop": false, - "vue/html-closing-bracket-spacing": false, - "vue/singleline-html-element-content-newline": false, - "vue/no-v-html": false, + "vue/require-v-for-key": 0, + "vue/max-attributes-per-line": 0, + "vue/html-indent": 0, + "vue/html-self-closing": 0, + "vue/no-unused-vars": 0, + "vue/attributes-order": 0, + "vue/require-prop-types": 0, + "vue/require-default-prop": 0, + "vue/html-closing-bracket-spacing": 0, + "vue/singleline-html-element-content-newline": 0, + "vue/no-v-html": 0, "no-console": 0, "no-unused-vars": 0, "no-empty": 0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a0193fa18..12861d31ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,29 @@ npm i -g ts-node npm run migrate ``` +11.24.0 (2019/07/05) +-------------------- +注意: このアップデート後に、`node built/tools/accept-migration Init 1000000000000`してください。 + +### ✨Improvements +* WebAuthnサポート +* APNGサポート +* アバターファイル選択でimage以外は表示しないように +* データベース手動バキューム機能 +* メールサーバー設定が正しいか確認できるように +* ピン止めの上限に達したときエラーを表示するように +* AP: attributedTo, to, cc が Array や Object のパターンに対応 +* AP: object type が Audio, Document, Image, Page, Video のパターンに対応 +* フォロー解除時に確認ダイアログを表示するように +* ユーザー入力ダイアログで、入力時にユーザーの存在を確認するように + +### 🐛Fixes +* 自分のインスタンスのルートURIへのリンクが無になる問題を修正 +* リモートファイルの削除が重い問題を修正 +* 投稿などの連続したスペースがひとつに纏まるのを修正 +* MFMのパース修正 +* マイグレーション時にエラーが出る問題を修正 + 11.23.1 (2019/06/25) -------------------- ### 🐛Fixes @@ -588,10 +611,9 @@ mongodb: 3. migration ブランチに切り替え 4. `npm i` 5. `npm run build` -6. `npm run init` -7. `npm run migrate` -8. master ブランチに戻す -9. enjoy +6. `npm run migrate` +7. master ブランチに戻す +8. enjoy 10.100.0 ---------- diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 179cf5304e..894122b04f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -219,8 +219,6 @@ const user = await Users.findOne(userId).then(ensure); ``` ### Migration作成方法 -コードの変更をした後、`ormconfig.json`(`npm run ormconfig`で生成)を用意し、 - ``` npm i -g ts-node ts-node ./node_modules/typeorm/cli.js migration:generate -n 変更の名前 diff --git a/Dockerfile b/Dockerfile index 940a22d0f7..40ad068a49 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:12.1-alpine AS base +FROM node:12.5-alpine AS base ENV NODE_ENV=production diff --git a/README.md b/README.md index a2d90a3e1a..2941074c2e 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,6 @@ Please see the [Contribution Guide](./CONTRIBUTING.md). - @@ -156,7 +155,6 @@ Please see the [Contribution Guide](./CONTRIBUTING.md). - @@ -177,7 +175,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
Satsuki Yanagi takimuraDamillora Atsuko Tominaga natalie Hiratake
Satsuki Yanagi takimuraDamillora Atsuko Tominaga natalie HiratakeTakashi Shibuya
-**Last updated:** Tue, 11 Jun 2019 01:46:06 UTC +**Last updated:** Mon, 01 Jul 2019 21:44:06 UTC :four_leaf_clover: Copyright diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml index ff7b730c20..ac30e9512f 100644 --- a/locales/cs-CZ.yml +++ b/locales/cs-CZ.yml @@ -260,6 +260,7 @@ common: disable-via-mobile: "Neoznačovat příspěvky jako „z mobilu“" load-raw-images: "Zobrazovat obrázky v původní kvalitě" load-remote-media: "Zobrazovat média ze vzdáleného serveru" + sync: "Synchronizace" search: "Hledání" delete: "Odstranit" loading: "Načítám..." @@ -449,6 +450,7 @@ common/views/components/messaging.vue: you: "Vy" no-history: "Žádná historie" user: "Uživatel" + group: "Skupina" common/views/components/messaging-room.vue: new-message: "Máte novou zprávu" common/views/components/messaging-room.form.vue: @@ -466,6 +468,7 @@ common/views/components/nav.vue: repository: "Úložiště" develop: "Vývojáři" feedback: "Zpětná vazba" + tos: "Podmínky užívání" common/views/components/note-menu.vue: mention: "Zmínění" detail: "Více" @@ -984,6 +987,7 @@ admin/views/instance.vue: smtp-auth: "Provést SMTP autentikaci" smtp-user: "SMTP uživatel" smtp-pass: "SMTP heslo" + test-email: "Test" serviceworker-config: "ServiceWorker" enable-serviceworker: "Povolit ServiceWorker" vapid-publickey: "VAPID veřejný klíč" diff --git a/locales/da-DK.yml b/locales/da-DK.yml index 3b667258f7..f1f3751c05 100644 --- a/locales/da-DK.yml +++ b/locales/da-DK.yml @@ -1195,6 +1195,7 @@ admin/views/instance.vue: smtp-auth: "Udfør autentifikation af SMTP" smtp-user: "SMTP bruger" smtp-pass: "SMTP adgangskode" + test-email: "Test" serviceworker-config: "ServiceWorker" enable-serviceworker: "Aktiver ServiceWorker" serviceworker-info: "Skal være aktiveret for at give push notifikationer." diff --git a/locales/de-DE.yml b/locales/de-DE.yml index 2541e1ca63..e6ba5fa9d7 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -775,6 +775,7 @@ admin/views/instance.vue: invite: "Einladen" save: "Speichern" saved: "Gespeichert" + test-email: "Test" admin/views/charts.vue: drive: "Drive" admin/views/drive.vue: diff --git a/locales/en-US.yml b/locales/en-US.yml index fd8e965887..d4311d09a0 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -523,6 +523,7 @@ common/views/components/note-menu.vue: delete: "Delete" delete-confirm: "Are you sure you want to delete this post?" remote: "Show original note" + pin-limit-exceeded: "You can't pin any more posts." common/views/components/user-menu.vue: mention: "Mention" mute: "Mute" @@ -601,6 +602,8 @@ common/views/components/signin.vue: signin-with-github: "Sign in with GitHub" signin-with-discord: "Sign in with Discord" login-failed: "Logging in has failed. Make sure you have entered the correct username and password." + tap-key: "Click on the Security Key to log in" + enter-2fa-code: "Enter your verification code" common/views/components/signup.vue: invitation-code: "Invitation code" invitation-info: "If you do not have an invitation code, please contact an administrator." @@ -984,7 +987,7 @@ desktop/views/components/settings.2fa.vue: url: "https://www.google.com/landing/2step/" caution: "If you lose access to your registered device, you won't be able to connect to Misskey anymore!" register: "Register a device" - already-registered: "This device is already registered" + already-registered: "Your account is currently registered to an authenticator application" unregister: "Unregister" unregistered: "Two-factor authentication has been disabled." enter-password: "Enter the password" @@ -997,6 +1000,15 @@ desktop/views/components/settings.2fa.vue: success: "Settings saved!" failed: "Failed to setup. Please ensure that the token is correct." info: "From the next time you sign in to Misskey, the token displayed on your device will be necessary too, as well as the password." + totp-header: "Authenticator App" + security-key-header: "Security Key" + security-key: "For additional security, you can log in to your account using a hardware Security Key that supports FIDO2. When you then sign in, you'll need the registered Security Key, or an authenticator app with you." + last-used: "Last used:" + activate-key: "Click to activate the Security Key" + security-key-name: "Name the Key" + register-security-key: "Complete Key registration" + something-went-wrong: "Wow! There was a problem registering the Key:" + key-unregistered: "The Key has been deleted" common/views/components/media-image.vue: sensitive: "NSFW" click-to-show: "Click to show" @@ -1110,6 +1122,9 @@ admin/views/index.vue: back-to-misskey: "Back to Misskey" admin/views/db.vue: tables: "Tables" + vacuum: "Vacuum" + vacuum-info: "Tidies up the database. Keeps the data intact and reduces disk usage. This is usually done automatically and periodically." + vacuum-exclamation: "Vacuuming can overload the database for a while, and cause users not to be able to participate in interactions." admin/views/dashboard.vue: dashboard: "Dashboard" accounts: "Accounts" @@ -1244,6 +1259,7 @@ admin/views/instance.vue: smtp-auth: "Perform SMTP authentication" smtp-user: "SMTP User" smtp-pass: "SMTP Password" + test-email: "Test" serviceworker-config: "ServiceWorker" enable-serviceworker: "Enable ServiceWorker" serviceworker-info: "Must be enabled for push notifications." @@ -1947,6 +1963,7 @@ pages: _pick: arg1: "Lists" arg2: "Position" + listLen: "Get length of list" _listLen: arg1: "Lists" number: "Number" diff --git a/locales/es-ES.yml b/locales/es-ES.yml index 174b69110e..991377fcc0 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -939,6 +939,7 @@ admin/views/instance.vue: smtp-port: "Puerto SMTP" smtp-user: "Usuario SMTP" smtp-pass: "Contraseña SMTP" + test-email: "Prueba" admin/views/charts.vue: title: "Gráficos" per-day: "Por día" diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index fe593c24f5..d0e06544d7 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -1217,6 +1217,7 @@ admin/views/instance.vue: smtp-auth: "Effectuer une authentification SMTP" smtp-user: "Utilisateur SMTP" smtp-pass: "Mot de passe SMTP" + test-email: "Test" serviceworker-config: "ServiceWorker" enable-serviceworker: "Activer ServiceWorker" serviceworker-info: "Devrait être activé pour les notifications push." diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 88673e804f..15cb30997b 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -36,6 +36,7 @@ common: signout: "ログアウト" reload-to-apply-the-setting: "この設定を反映するにはページをリロードする必要があります。今すぐリロードしますか?" fetching-as-ap-object: "連合に照会中" + unfollow-confirm: "{name}さんをフォロー解除しますか?" got-it: "わかった" customization-tips: @@ -561,6 +562,7 @@ common/views/components/note-menu.vue: delete: "削除" delete-confirm: "この投稿を削除しますか?" remote: "投稿元で見る" + pin-limit-exceeded: "これ以上ピン留めできません。" common/views/components/user-menu.vue: mention: "メンション" @@ -645,6 +647,8 @@ common/views/components/signin.vue: signin-with-github: "GitHubでログイン" signin-with-discord: "Discordでログイン" login-failed: "ログインできませんでした。ユーザー名とパスワードを確認してください。" + tap-key: "セキュリティキーをクリックしてログイン" + enter-2fa-code: "認証コードを入力してください" common/views/components/signup.vue: invitation-code: "招待コード" @@ -1099,6 +1103,15 @@ desktop/views/components/settings.2fa.vue: success: "設定が完了しました!" failed: "設定に失敗しました。トークンに誤りがないかご確認ください。" info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。" + totp-header: "認証アプリ" + security-key-header: "セキュリティキー" + security-key: "セキュリティを強化するために、FIDO2をサポートするハードウェアセキュリティキーを使用してアカウントにログインできます。 サインインの際は、登録されたセキュリティキーまたは認証アプリが必要になります。" + last-used: "最後の使用:" + activate-key: "クリックしてセキュリティキーをアクティベートしてください" + security-key-name: "キー名" + register-security-key: "キーの登録を完了" + something-went-wrong: "わー! キーを登録する際に問題が発生しました:" + key-unregistered: "キーが削除されました" common/views/components/media-image.vue: sensitive: "閲覧注意" @@ -1234,6 +1247,9 @@ admin/views/index.vue: admin/views/db.vue: tables: "テーブル" + vacuum: "バキューム" + vacuum-info: "データベースの掃除を行います。データはそのままで、ディスク使用量を減らします。通常この操作は自動で定期的に行われます。" + vacuum-exclamation: "バキュームを行うと、しばらくの間データベースの負荷が高くなり、ユーザーの操作を受け付けなくなる場合があります。" admin/views/dashboard.vue: dashboard: "ダッシュボード" @@ -1373,6 +1389,7 @@ admin/views/instance.vue: smtp-auth: "SMTP認証を行う" smtp-user: "SMTPユーザー" smtp-pass: "SMTPパスワード" + test-email: "テスト" serviceworker-config: "ServiceWorker" enable-serviceworker: "ServiceWorkerを有効にする" serviceworker-info: "プッシュ通知を行うには有効する必要があります。" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 5272973ecc..2938dd6e67 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -35,6 +35,7 @@ common: signout: "로그아웃" reload-to-apply-the-setting: "이 설정을 적용하려면 페이지를 새로고침해야 합니다. 바로 새로고침하시겠습니까?" fetching-as-ap-object: "연합에서 조회 중" + unfollow-confirm: "{name} 님을 팔로우 해제하시겠습니까?" got-it: "알겠습니다" customization-tips: title: "커스터마이징 도움말" @@ -523,6 +524,7 @@ common/views/components/note-menu.vue: delete: "삭제" delete-confirm: "이 글을 삭제하시겠습니까?" remote: "글 원본 보기" + pin-limit-exceeded: "더 이상 고정할 수 없습니다." common/views/components/user-menu.vue: mention: "멘션" mute: "뮤트" @@ -601,6 +603,8 @@ common/views/components/signin.vue: signin-with-github: "GitHub으로 로그인" signin-with-discord: "Discord로 로그인" login-failed: "로그인할 수 없습니다. 사용자명과 비밀번호를 확인하여 주십시오." + tap-key: "보안 키를 클릭하여 로그인" + enter-2fa-code: "인증 코드를 입력하여 주십시오" common/views/components/signup.vue: invitation-code: "초대 코드" invitation-info: "초대 코드가 없으신 분은 관리자에게 연락하시기 바랍니다." @@ -997,6 +1001,15 @@ desktop/views/components/settings.2fa.vue: success: "설정이 완료되었습니다!" failed: "설정에 실패했습니다. 토큰이 잘못되었는지 확인해주십시오." info: "다음 로그인부터는 이와 동일하게 비밀번호에 더해 장치에 표시된 토큰을 입력합니다." + totp-header: "인증 앱" + security-key-header: "보안 키" + security-key: "보안을 강화하려면 FIDO2를 지원하는 하드웨어 보안 키를 사용하여 계정에 로그인할 수 있습니다. 로그인 시 등록하였던 보안 키 또는 인증 앱이 필요하게 됩니다." + last-used: "마지막 사용:" + activate-key: "클릭하여 보안 키를 활성화하여 주십시오" + security-key-name: "키 이름" + register-security-key: "키 등록 완료" + something-went-wrong: "으악! 키를 등록하는 도중 문제가 발생하였습니다:" + key-unregistered: "키가 등록되어 있지 않습니다" common/views/components/media-image.vue: sensitive: "열람주의" click-to-show: "클릭하여 보기" @@ -1110,6 +1123,9 @@ admin/views/index.vue: back-to-misskey: "Misskey로 돌아가기" admin/views/db.vue: tables: "테이블" + vacuum: "청소" + vacuum-info: "데이터베이스를 청소합니다. 데이터는 그대로인 채로 데이터의 사용량을 줄입니다. 일반적으로 이 작업은 자동으로 정기적으로 수행됩니다." + vacuum-exclamation: "청소를 수행하면 한동안 데이터베이스의 부하가 높아져 사용자 조작을 처리하지 못 할 수 있습니다." admin/views/dashboard.vue: dashboard: "대시보드" accounts: "계정" @@ -1244,6 +1260,7 @@ admin/views/instance.vue: smtp-auth: "SMTP 인증 수행" smtp-user: "SMTP 사용자" smtp-pass: "SMTP 비밀번호" + test-email: "테스트" serviceworker-config: "ServiceWorker" enable-serviceworker: "ServiceWorker 사용" serviceworker-info: "푸시알림을 수행하려면 사용해야 합니다." @@ -1947,6 +1964,7 @@ pages: _pick: arg1: "리스트" arg2: "위치" + listLen: "리스트의 길이 가져오기" _listLen: arg1: "리스트" number: "수치" diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml index e7a34c4acc..891dd07274 100644 --- a/locales/pl-PL.yml +++ b/locales/pl-PL.yml @@ -65,6 +65,7 @@ common: favorites: "Moje ulubione" permissions: "read:drive": "Wyświetl dysk" + "read:messaging": "Zobacz konwersację" "write:votes": "Zagłosuj" empty-timeline-info: explore: "Poznaj" @@ -72,6 +73,7 @@ common: hide-contents: "Ukryj zawartość" reply-placeholder: "Odpowiedź na ten wpis…" quote-placeholder: "Zacytuj ten wpis…" + quote-attached: "Z cytatem" submit: "Wpis" reply: "Odpowiedz" renote: "Udostępnij" @@ -880,6 +882,7 @@ admin/views/instance.vue: save: "Zapisz" saved: "Zapisano" email: "Adres e-mail" + test-email: "Test" admin/views/charts.vue: notes: "Wpisy" users: "Użytkownicy" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index 6ff353f20e..b8c073200e 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -1244,6 +1244,7 @@ admin/views/instance.vue: smtp-auth: "SMTP身份验证" smtp-user: "SMTP 用户名" smtp-pass: "SMTP 密码" + test-email: "测试" serviceworker-config: "ServiceWorker" enable-serviceworker: "启用ServiceWorker" serviceworker-info: "您需要启用推送通知" diff --git a/migration/1000000000000-Init.ts b/migration/1000000000000-Init.ts new file mode 100644 index 0000000000..ce54d8a7ef --- /dev/null +++ b/migration/1000000000000-Init.ts @@ -0,0 +1,485 @@ +import {MigrationInterface, QueryRunner} from "typeorm"; + +export class Init1000000000000 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TYPE "log_level_enum" AS ENUM('error', 'warning', 'info', 'success', 'debug')`); + await queryRunner.query(`CREATE TABLE "log" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "domain" character varying(64) array NOT NULL DEFAULT '{}'::varchar[], "level" "log_level_enum" NOT NULL, "worker" character varying(8) NOT NULL, "machine" character varying(128) NOT NULL, "message" character varying(1024) NOT NULL, "data" jsonb NOT NULL DEFAULT '{}', CONSTRAINT "PK_350604cbdf991d5930d9e618fbd" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_8e4eb51a35d81b64dda28eed0a" ON "log" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_8cb40cfc8f3c28261e6f887b03" ON "log" ("domain") `); + await queryRunner.query(`CREATE INDEX "IDX_584b536b49e53ac81beb39a177" ON "log" ("level") `); + await queryRunner.query(`CREATE TABLE "drive_folder" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "name" character varying(128) NOT NULL, "userId" character varying(32), "parentId" character varying(32), CONSTRAINT "PK_7a0c089191f5ebdc214e0af808a" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_02878d441ceae15ce060b73daf" ON "drive_folder" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_f4fc06e49c0171c85f1c48060d" ON "drive_folder" ("userId") `); + await queryRunner.query(`CREATE INDEX "IDX_00ceffb0cdc238b3233294f08f" ON "drive_folder" ("parentId") `); + await queryRunner.query(`CREATE TABLE "drive_file" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32), "userHost" character varying(128), "md5" character varying(32) NOT NULL, "name" character varying(256) NOT NULL, "type" character varying(128) NOT NULL, "size" integer NOT NULL, "comment" character varying(512), "properties" jsonb NOT NULL DEFAULT '{}', "storedInternal" boolean NOT NULL, "url" character varying(512) NOT NULL, "thumbnailUrl" character varying(512), "webpublicUrl" character varying(512), "accessKey" character varying(256), "thumbnailAccessKey" character varying(256), "webpublicAccessKey" character varying(256), "uri" character varying(512), "src" character varying(512), "folderId" character varying(32), "isSensitive" boolean NOT NULL DEFAULT false, "isLink" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_43ddaaaf18c9e68029b7cbb032e" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_c8dfad3b72196dd1d6b5db168a" ON "drive_file" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_860fa6f6c7df5bb887249fba22" ON "drive_file" ("userId") `); + await queryRunner.query(`CREATE INDEX "IDX_92779627994ac79277f070c91e" ON "drive_file" ("userHost") `); + await queryRunner.query(`CREATE INDEX "IDX_37bb9a1b4585f8a3beb24c62d6" ON "drive_file" ("md5") `); + await queryRunner.query(`CREATE INDEX "IDX_a40b8df8c989d7db937ea27cf6" ON "drive_file" ("type") `); + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_d85a184c2540d2deba33daf642" ON "drive_file" ("accessKey") `); + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_e74022ce9a074b3866f70e0d27" ON "drive_file" ("thumbnailAccessKey") `); + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_c55b2b7c284d9fef98026fc88e" ON "drive_file" ("webpublicAccessKey") `); + await queryRunner.query(`CREATE INDEX "IDX_e5848eac4940934e23dbc17581" ON "drive_file" ("uri") `); + await queryRunner.query(`CREATE INDEX "IDX_bb90d1956dafc4068c28aa7560" ON "drive_file" ("folderId") `); + await queryRunner.query(`CREATE TABLE "user" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE, "lastFetchedAt" TIMESTAMP WITH TIME ZONE, "username" character varying(128) NOT NULL, "usernameLower" character varying(128) NOT NULL, "name" character varying(128), "followersCount" integer NOT NULL DEFAULT 0, "followingCount" integer NOT NULL DEFAULT 0, "notesCount" integer NOT NULL DEFAULT 0, "avatarId" character varying(32), "bannerId" character varying(32), "tags" character varying(128) array NOT NULL DEFAULT '{}'::varchar[], "avatarUrl" character varying(512), "bannerUrl" character varying(512), "avatarColor" character varying(32), "bannerColor" character varying(32), "isSuspended" boolean NOT NULL DEFAULT false, "isSilenced" boolean NOT NULL DEFAULT false, "isLocked" boolean NOT NULL DEFAULT false, "isBot" boolean NOT NULL DEFAULT false, "isCat" boolean NOT NULL DEFAULT false, "isAdmin" boolean NOT NULL DEFAULT false, "isModerator" boolean NOT NULL DEFAULT false, "isVerified" boolean NOT NULL DEFAULT false, "emojis" character varying(128) array NOT NULL DEFAULT '{}'::varchar[], "host" character varying(128), "inbox" character varying(512), "sharedInbox" character varying(512), "featured" character varying(512), "uri" character varying(512), "token" character(16), CONSTRAINT "UQ_a854e557b1b14814750c7c7b0c9" UNIQUE ("token"), CONSTRAINT "REL_58f5c71eaab331645112cf8cfa" UNIQUE ("avatarId"), CONSTRAINT "REL_afc64b53f8db3707ceb34eb28e" UNIQUE ("bannerId"), CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_e11e649824a45d8ed01d597fd9" ON "user" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_80ca6e6ef65fb9ef34ea8c90f4" ON "user" ("updatedAt") `); + await queryRunner.query(`CREATE INDEX "IDX_a27b942a0d6dcff90e3ee9b5e8" ON "user" ("usernameLower") `); + await queryRunner.query(`CREATE INDEX "IDX_fa99d777623947a5b05f394cae" ON "user" ("tags") `); + await queryRunner.query(`CREATE INDEX "IDX_3252a5df8d5bbd16b281f7799e" ON "user" ("host") `); + await queryRunner.query(`CREATE INDEX "IDX_be623adaa4c566baf5d29ce0c8" ON "user" ("uri") `); + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a854e557b1b14814750c7c7b0c" ON "user" ("token") `); + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_5deb01ae162d1d70b80d064c27" ON "user" ("usernameLower", "host") `); + await queryRunner.query(`CREATE TABLE "app" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32), "secret" character varying(64) NOT NULL, "name" character varying(128) NOT NULL, "description" character varying(512) NOT NULL, "permission" character varying(64) array NOT NULL, "callbackUrl" character varying(512), CONSTRAINT "PK_9478629fc093d229df09e560aea" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_048a757923ed8b157e9895da53" ON "app" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_3f5b0899ef90527a3462d7c2cb" ON "app" ("userId") `); + await queryRunner.query(`CREATE INDEX "IDX_f49922d511d666848f250663c4" ON "app" ("secret") `); + await queryRunner.query(`CREATE TABLE "access_token" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "token" character varying(128) NOT NULL, "hash" character varying(128) NOT NULL, "userId" character varying(32) NOT NULL, "appId" character varying(32) NOT NULL, CONSTRAINT "PK_f20f028607b2603deabd8182d12" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_70ba8f6af34bc924fc9e12adb8" ON "access_token" ("token") `); + await queryRunner.query(`CREATE INDEX "IDX_64c327441248bae40f7d92f34f" ON "access_token" ("hash") `); + await queryRunner.query(`CREATE INDEX "IDX_9949557d0e1b2c19e5344c171e" ON "access_token" ("userId") `); + await queryRunner.query(`CREATE TYPE "note_visibility_enum" AS ENUM('public', 'home', 'followers', 'specified')`); + await queryRunner.query(`CREATE TABLE "note" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "replyId" character varying(32), "renoteId" character varying(32), "text" text, "name" character varying(256), "cw" character varying(512), "appId" character varying(32), "userId" character varying(32) NOT NULL, "viaMobile" boolean NOT NULL DEFAULT false, "localOnly" boolean NOT NULL DEFAULT false, "renoteCount" smallint NOT NULL DEFAULT 0, "repliesCount" smallint NOT NULL DEFAULT 0, "reactions" jsonb NOT NULL DEFAULT '{}', "visibility" "note_visibility_enum" NOT NULL, "uri" character varying(512), "score" integer NOT NULL DEFAULT 0, "fileIds" character varying(32) array NOT NULL DEFAULT '{}'::varchar[], "attachedFileTypes" character varying(256) array NOT NULL DEFAULT '{}'::varchar[], "visibleUserIds" character varying(32) array NOT NULL DEFAULT '{}'::varchar[], "mentions" character varying(32) array NOT NULL DEFAULT '{}'::varchar[], "mentionedRemoteUsers" text NOT NULL DEFAULT '[]', "emojis" character varying(128) array NOT NULL DEFAULT '{}'::varchar[], "tags" character varying(128) array NOT NULL DEFAULT '{}'::varchar[], "hasPoll" boolean NOT NULL DEFAULT false, "geo" jsonb DEFAULT null, "userHost" character varying(128), "replyUserId" character varying(32), "replyUserHost" character varying(128), "renoteUserId" character varying(32), "renoteUserHost" character varying(128), CONSTRAINT "PK_96d0c172a4fba276b1bbed43058" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_e7c0567f5261063592f022e9b5" ON "note" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_17cb3553c700a4985dff5a30ff" ON "note" ("replyId") `); + await queryRunner.query(`CREATE INDEX "IDX_52ccc804d7c69037d558bac4c9" ON "note" ("renoteId") `); + await queryRunner.query(`CREATE INDEX "IDX_5b87d9d19127bd5d92026017a7" ON "note" ("userId") `); + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_153536c67d05e9adb24e99fc2b" ON "note" ("uri") `); + await queryRunner.query(`CREATE INDEX "IDX_51c063b6a133a9cb87145450f5" ON "note" ("fileIds") `); + await queryRunner.query(`CREATE INDEX "IDX_25dfc71b0369b003a4cd434d0b" ON "note" ("attachedFileTypes") `); + await queryRunner.query(`CREATE INDEX "IDX_796a8c03959361f97dc2be1d5c" ON "note" ("visibleUserIds") `); + await queryRunner.query(`CREATE INDEX "IDX_54ebcb6d27222913b908d56fd8" ON "note" ("mentions") `); + await queryRunner.query(`CREATE INDEX "IDX_88937d94d7443d9a99a76fa5c0" ON "note" ("tags") `); + await queryRunner.query(`CREATE INDEX "IDX_7125a826ab192eb27e11d358a5" ON "note" ("userHost") `); + await queryRunner.query(`CREATE TABLE "poll_vote" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "noteId" character varying(32) NOT NULL, "choice" integer NOT NULL, CONSTRAINT "PK_fd002d371201c472490ba89c6a0" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_0fb627e1c2f753262a74f0562d" ON "poll_vote" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_66d2bd2ee31d14bcc23069a89f" ON "poll_vote" ("userId") `); + await queryRunner.query(`CREATE INDEX "IDX_aecfbd5ef60374918e63ee95fa" ON "poll_vote" ("noteId") `); + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_50bd7164c5b78f1f4a42c4d21f" ON "poll_vote" ("userId", "noteId", "choice") `); + await queryRunner.query(`CREATE TABLE "note_reaction" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "noteId" character varying(32) NOT NULL, "reaction" character varying(128) NOT NULL, CONSTRAINT "PK_767ec729b108799b587a3fcc9cf" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_01f4581f114e0ebd2bbb876f0b" ON "note_reaction" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_13761f64257f40c5636d0ff95e" ON "note_reaction" ("userId") `); + await queryRunner.query(`CREATE INDEX "IDX_45145e4953780f3cd5656f0ea6" ON "note_reaction" ("noteId") `); + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_ad0c221b25672daf2df320a817" ON "note_reaction" ("userId", "noteId") `); + await queryRunner.query(`CREATE TABLE "note_watching" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "noteId" character varying(32) NOT NULL, "noteUserId" character varying(32) NOT NULL, CONSTRAINT "PK_49286fdb23725945a74aa27d757" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_318cdf42a9cfc11f479bd802bb" ON "note_watching" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_b0134ec406e8d09a540f818288" ON "note_watching" ("userId") `); + await queryRunner.query(`CREATE INDEX "IDX_03e7028ab8388a3f5e3ce2a861" ON "note_watching" ("noteId") `); + await queryRunner.query(`CREATE INDEX "IDX_44499765eec6b5489d72c4253b" ON "note_watching" ("noteUserId") `); + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a42c93c69989ce1d09959df4cf" ON "note_watching" ("userId", "noteId") `); + await queryRunner.query(`CREATE TABLE "note_unread" ("id" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "noteId" character varying(32) NOT NULL, "noteUserId" character varying(32) NOT NULL, "isSpecified" boolean NOT NULL, CONSTRAINT "PK_1904eda61a784f57e6e51fa9c1f" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_56b0166d34ddae49d8ef7610bb" ON "note_unread" ("userId") `); + await queryRunner.query(`CREATE INDEX "IDX_e637cba4dc4410218c4251260e" ON "note_unread" ("noteId") `); + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_d908433a4953cc13216cd9c274" ON "note_unread" ("userId", "noteId") `); + await queryRunner.query(`CREATE TABLE "notification" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "notifieeId" character varying(32) NOT NULL, "notifierId" character varying(32) NOT NULL, "type" character varying(32) NOT NULL, "isRead" boolean NOT NULL DEFAULT false, "noteId" character varying(32), "reaction" character varying(128), "choice" integer, CONSTRAINT "PK_705b6c7cdf9b2c2ff7ac7872cb7" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_b11a5e627c41d4dc3170f1d370" ON "notification" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_3c601b70a1066d2c8b517094cb" ON "notification" ("notifieeId") `); + await queryRunner.query(`CREATE TABLE "meta" ("id" character varying(32) NOT NULL, "name" character varying(128), "description" character varying(1024), "maintainerName" character varying(128), "maintainerEmail" character varying(128), "announcements" jsonb NOT NULL DEFAULT '[]', "disableRegistration" boolean NOT NULL DEFAULT false, "disableLocalTimeline" boolean NOT NULL DEFAULT false, "disableGlobalTimeline" boolean NOT NULL DEFAULT false, "enableEmojiReaction" boolean NOT NULL DEFAULT true, "useStarForReactionFallback" boolean NOT NULL DEFAULT false, "langs" character varying(64) array NOT NULL DEFAULT '{}'::varchar[], "hiddenTags" character varying(256) array NOT NULL DEFAULT '{}'::varchar[], "blockedHosts" character varying(256) array NOT NULL DEFAULT '{}'::varchar[], "mascotImageUrl" character varying(512) DEFAULT '/assets/ai.png', "bannerUrl" character varying(512), "errorImageUrl" character varying(512) DEFAULT 'https://xn--931a.moe/aiart/yubitun.png', "iconUrl" character varying(512), "cacheRemoteFiles" boolean NOT NULL DEFAULT true, "proxyAccount" character varying(128), "enableRecaptcha" boolean NOT NULL DEFAULT false, "recaptchaSiteKey" character varying(64), "recaptchaSecretKey" character varying(64), "localDriveCapacityMb" integer NOT NULL DEFAULT 1024, "remoteDriveCapacityMb" integer NOT NULL DEFAULT 32, "maxNoteTextLength" integer NOT NULL DEFAULT 500, "summalyProxy" character varying(128), "enableEmail" boolean NOT NULL DEFAULT false, "email" character varying(128), "smtpSecure" boolean NOT NULL DEFAULT false, "smtpHost" character varying(128), "smtpPort" integer, "smtpUser" character varying(128), "smtpPass" character varying(128), "enableServiceWorker" boolean NOT NULL DEFAULT false, "swPublicKey" character varying(128), "swPrivateKey" character varying(128), "enableTwitterIntegration" boolean NOT NULL DEFAULT false, "twitterConsumerKey" character varying(128), "twitterConsumerSecret" character varying(128), "enableGithubIntegration" boolean NOT NULL DEFAULT false, "githubClientId" character varying(128), "githubClientSecret" character varying(128), "enableDiscordIntegration" boolean NOT NULL DEFAULT false, "discordClientId" character varying(128), "discordClientSecret" character varying(128), CONSTRAINT "PK_c4c17a6c2bd7651338b60fc590b" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE TABLE "following" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "followeeId" character varying(32) NOT NULL, "followerId" character varying(32) NOT NULL, "followerHost" character varying(128), "followerInbox" character varying(512), "followerSharedInbox" character varying(512), "followeeHost" character varying(128), "followeeInbox" character varying(512), "followeeSharedInbox" character varying(512), CONSTRAINT "PK_c76c6e044bdf76ecf8bfb82a645" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_582f8fab771a9040a12961f3e7" ON "following" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_24e0042143a18157b234df186c" ON "following" ("followeeId") `); + await queryRunner.query(`CREATE INDEX "IDX_6516c5a6f3c015b4eed39978be" ON "following" ("followerId") `); + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_307be5f1d1252e0388662acb96" ON "following" ("followerId", "followeeId") `); + await queryRunner.query(`CREATE TABLE "instance" ("id" character varying(32) NOT NULL, "caughtAt" TIMESTAMP WITH TIME ZONE NOT NULL, "host" character varying(128) NOT NULL, "system" character varying(64), "usersCount" integer NOT NULL DEFAULT 0, "notesCount" integer NOT NULL DEFAULT 0, "followingCount" integer NOT NULL DEFAULT 0, "followersCount" integer NOT NULL DEFAULT 0, "driveUsage" integer NOT NULL DEFAULT 0, "driveFiles" integer NOT NULL DEFAULT 0, "latestRequestSentAt" TIMESTAMP WITH TIME ZONE, "latestStatus" integer, "latestRequestReceivedAt" TIMESTAMP WITH TIME ZONE, "lastCommunicatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, "isNotResponding" boolean NOT NULL DEFAULT false, "isMarkedAsClosed" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_eaf60e4a0c399c9935413e06474" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_2cd3b2a6b4cf0b910b260afe08" ON "instance" ("caughtAt") `); + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_8d5afc98982185799b160e10eb" ON "instance" ("host") `); + await queryRunner.query(`CREATE TABLE "muting" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "muteeId" character varying(32) NOT NULL, "muterId" character varying(32) NOT NULL, CONSTRAINT "PK_2e92d06c8b5c602eeb27ca9ba48" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_f86d57fbca33c7a4e6897490cc" ON "muting" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_ec96b4fed9dae517e0dbbe0675" ON "muting" ("muteeId") `); + await queryRunner.query(`CREATE INDEX "IDX_93060675b4a79a577f31d260c6" ON "muting" ("muterId") `); + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_1eb9d9824a630321a29fd3b290" ON "muting" ("muterId", "muteeId") `); + await queryRunner.query(`CREATE TABLE "sw_subscription" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "endpoint" character varying(512) NOT NULL, "auth" character varying(256) NOT NULL, "publickey" character varying(128) NOT NULL, CONSTRAINT "PK_e8f763631530051b95eb6279b91" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_97754ca6f2baff9b4abb7f853d" ON "sw_subscription" ("userId") `); + await queryRunner.query(`CREATE TABLE "blocking" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "blockeeId" character varying(32) NOT NULL, "blockerId" character varying(32) NOT NULL, CONSTRAINT "PK_e5d9a541cc1965ee7e048ea09dd" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_b9a354f7941c1e779f3b33aea6" ON "blocking" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_2cd4a2743a99671308f5417759" ON "blocking" ("blockeeId") `); + await queryRunner.query(`CREATE INDEX "IDX_0627125f1a8a42c9a1929edb55" ON "blocking" ("blockerId") `); + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_98a1bc5cb30dfd159de056549f" ON "blocking" ("blockerId", "blockeeId") `); + await queryRunner.query(`CREATE TABLE "user_list" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "name" character varying(128) NOT NULL, CONSTRAINT "PK_87bab75775fd9b1ff822b656402" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_b7fcefbdd1c18dce86687531f9" ON "user_list" ("userId") `); + await queryRunner.query(`CREATE TABLE "user_list_joining" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "userListId" character varying(32) NOT NULL, CONSTRAINT "PK_11abb3768da1c5f8de101c9df45" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_d844bfc6f3f523a05189076efa" ON "user_list_joining" ("userId") `); + await queryRunner.query(`CREATE INDEX "IDX_605472305f26818cc93d1baaa7" ON "user_list_joining" ("userListId") `); + await queryRunner.query(`CREATE TABLE "hashtag" ("id" character varying(32) NOT NULL, "name" character varying(128) NOT NULL, "mentionedUserIds" character varying(32) array NOT NULL, "mentionedUsersCount" integer NOT NULL DEFAULT 0, "mentionedLocalUserIds" character varying(32) array NOT NULL, "mentionedLocalUsersCount" integer NOT NULL DEFAULT 0, "mentionedRemoteUserIds" character varying(32) array NOT NULL, "mentionedRemoteUsersCount" integer NOT NULL DEFAULT 0, "attachedUserIds" character varying(32) array NOT NULL, "attachedUsersCount" integer NOT NULL DEFAULT 0, "attachedLocalUserIds" character varying(32) array NOT NULL, "attachedLocalUsersCount" integer NOT NULL DEFAULT 0, "attachedRemoteUserIds" character varying(32) array NOT NULL, "attachedRemoteUsersCount" integer NOT NULL DEFAULT 0, CONSTRAINT "PK_cb36eb8af8412bfa978f1165d78" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_347fec870eafea7b26c8a73bac" ON "hashtag" ("name") `); + await queryRunner.query(`CREATE INDEX "IDX_2710a55f826ee236ea1a62698f" ON "hashtag" ("mentionedUsersCount") `); + await queryRunner.query(`CREATE INDEX "IDX_0e206cec573f1edff4a3062923" ON "hashtag" ("mentionedLocalUsersCount") `); + await queryRunner.query(`CREATE INDEX "IDX_4c02d38a976c3ae132228c6fce" ON "hashtag" ("mentionedRemoteUsersCount") `); + await queryRunner.query(`CREATE INDEX "IDX_d57f9030cd3af7f63ffb1c267c" ON "hashtag" ("attachedUsersCount") `); + await queryRunner.query(`CREATE INDEX "IDX_0c44bf4f680964145f2a68a341" ON "hashtag" ("attachedLocalUsersCount") `); + await queryRunner.query(`CREATE INDEX "IDX_0b03cbcd7e6a7ce068efa8ecc2" ON "hashtag" ("attachedRemoteUsersCount") `); + await queryRunner.query(`CREATE TABLE "note_favorite" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "noteId" character varying(32) NOT NULL, CONSTRAINT "PK_af0da35a60b9fa4463a62082b36" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_47f4b1892f5d6ba8efb3057d81" ON "note_favorite" ("userId") `); + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_0f4fb9ad355f3effff221ef245" ON "note_favorite" ("userId", "noteId") `); + await queryRunner.query(`CREATE TABLE "abuse_user_report" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "reporterId" character varying(32) NOT NULL, "comment" character varying(512) NOT NULL, CONSTRAINT "PK_87873f5f5cc5c321a1306b2d18c" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_db2098070b2b5a523c58181f74" ON "abuse_user_report" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_d049123c413e68ca52abe73420" ON "abuse_user_report" ("userId") `); + await queryRunner.query(`CREATE INDEX "IDX_04cc96756f89d0b7f9473e8cdf" ON "abuse_user_report" ("reporterId") `); + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_5cd442c3b2e74fdd99dae20243" ON "abuse_user_report" ("userId", "reporterId") `); + await queryRunner.query(`CREATE TABLE "registration_ticket" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "code" character varying(64) NOT NULL, CONSTRAINT "PK_f11696b6fafcf3662d4292734f8" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_0ff69e8dfa9fe31bb4a4660f59" ON "registration_ticket" ("code") `); + await queryRunner.query(`CREATE TABLE "messaging_message" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "recipientId" character varying(32) NOT NULL, "text" character varying(4096), "isRead" boolean NOT NULL DEFAULT false, "fileId" character varying(32), CONSTRAINT "PK_db398fd79dc95d0eb8c30456eaa" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_e21cd3646e52ef9c94aaf17c2e" ON "messaging_message" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_5377c307783fce2b6d352e1203" ON "messaging_message" ("userId") `); + await queryRunner.query(`CREATE INDEX "IDX_cac14a4e3944454a5ce7daa514" ON "messaging_message" ("recipientId") `); + await queryRunner.query(`CREATE TABLE "signin" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "ip" character varying(128) NOT NULL, "headers" jsonb NOT NULL, "success" boolean NOT NULL, CONSTRAINT "PK_9e96ddc025712616fc492b3b588" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_2c308dbdc50d94dc625670055f" ON "signin" ("userId") `); + await queryRunner.query(`CREATE TABLE "auth_session" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "token" character varying(128) NOT NULL, "userId" character varying(32), "appId" character varying(32) NOT NULL, CONSTRAINT "PK_19354ed146424a728c1112a8cbf" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_62cb09e1129f6ec024ef66e183" ON "auth_session" ("token") `); + await queryRunner.query(`CREATE TABLE "follow_request" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "followeeId" character varying(32) NOT NULL, "followerId" character varying(32) NOT NULL, "requestId" character varying(128), "followerHost" character varying(128), "followerInbox" character varying(512), "followerSharedInbox" character varying(512), "followeeHost" character varying(128), "followeeInbox" character varying(512), "followeeSharedInbox" character varying(512), CONSTRAINT "PK_53a9aa3725f7a3deb150b39dbfc" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_12c01c0d1a79f77d9f6c15fadd" ON "follow_request" ("followeeId") `); + await queryRunner.query(`CREATE INDEX "IDX_a7fd92dd6dc519e6fb435dd108" ON "follow_request" ("followerId") `); + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_d54a512b822fac7ed52800f6b4" ON "follow_request" ("followerId", "followeeId") `); + await queryRunner.query(`CREATE TABLE "emoji" ("id" character varying(32) NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE, "name" character varying(128) NOT NULL, "host" character varying(128), "url" character varying(512) NOT NULL, "uri" character varying(512), "type" character varying(64), "aliases" character varying(128) array NOT NULL DEFAULT '{}'::varchar[], CONSTRAINT "PK_df74ce05e24999ee01ea0bc50a3" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_b37dafc86e9af007e3295c2781" ON "emoji" ("name") `); + await queryRunner.query(`CREATE INDEX "IDX_5900e907bb46516ddf2871327c" ON "emoji" ("host") `); + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_4f4d35e1256c84ae3d1f0eab10" ON "emoji" ("name", "host") `); + 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(`CREATE TABLE "user_note_pining" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "noteId" character varying(32) NOT NULL, CONSTRAINT "PK_a6a2dad4ae000abce2ea9d9b103" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_bfbc6f79ba4007b4ce5097f08d" ON "user_note_pining" ("userId") `); + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_410cd649884b501c02d6e72738" ON "user_note_pining" ("userId", "noteId") `); + await queryRunner.query(`CREATE TYPE "poll_notevisibility_enum" AS ENUM('public', 'home', 'followers', 'specified')`); + await queryRunner.query(`CREATE TABLE "poll" ("noteId" character varying(32) NOT NULL, "expiresAt" TIMESTAMP WITH TIME ZONE, "multiple" boolean NOT NULL, "choices" character varying(128) array NOT NULL DEFAULT '{}'::varchar[], "votes" integer array NOT NULL, "noteVisibility" "poll_notevisibility_enum" NOT NULL, "userId" character varying(32) NOT NULL, "userHost" character varying(128), CONSTRAINT "REL_da851e06d0dfe2ef397d8b1bf1" UNIQUE ("noteId"), CONSTRAINT "PK_da851e06d0dfe2ef397d8b1bf1b" PRIMARY KEY ("noteId"))`); + await queryRunner.query(`CREATE INDEX "IDX_0610ebcfcfb4a18441a9bcdab2" ON "poll" ("userId") `); + await queryRunner.query(`CREATE INDEX "IDX_7fa20a12319c7f6dc3aed98c0a" ON "poll" ("userHost") `); + await queryRunner.query(`CREATE TABLE "user_keypair" ("userId" character varying(32) NOT NULL, "publicKey" character varying(4096) NOT NULL, "privateKey" character varying(4096) NOT NULL, CONSTRAINT "REL_f4853eb41ab722fe05f81cedeb" UNIQUE ("userId"), CONSTRAINT "PK_f4853eb41ab722fe05f81cedeb6" PRIMARY KEY ("userId"))`); + await queryRunner.query(`CREATE TABLE "user_publickey" ("userId" character varying(32) NOT NULL, "keyId" character varying(256) NOT NULL, "keyPem" character varying(4096) NOT NULL, CONSTRAINT "REL_10c146e4b39b443ede016f6736" UNIQUE ("userId"), CONSTRAINT "PK_10c146e4b39b443ede016f6736d" PRIMARY KEY ("userId"))`); + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_171e64971c780ebd23fae140bb" ON "user_publickey" ("keyId") `); + await queryRunner.query(`CREATE TABLE "user_profile" ("userId" character varying(32) NOT NULL, "location" character varying(128), "birthday" character(10), "description" character varying(1024), "fields" jsonb NOT NULL DEFAULT '[]', "url" character varying(512), "email" character varying(128), "emailVerifyCode" character varying(128), "emailVerified" boolean NOT NULL DEFAULT false, "twoFactorTempSecret" character varying(128), "twoFactorSecret" character varying(128), "twoFactorEnabled" boolean NOT NULL DEFAULT false, "password" character varying(128), "clientData" jsonb NOT NULL DEFAULT '{}', "autoWatch" boolean NOT NULL DEFAULT false, "autoAcceptFollowed" boolean NOT NULL DEFAULT false, "alwaysMarkNsfw" boolean NOT NULL DEFAULT false, "carefulBot" boolean NOT NULL DEFAULT false, "twitter" boolean NOT NULL DEFAULT false, "twitterAccessToken" character varying(64) DEFAULT null, "twitterAccessTokenSecret" character varying(64) DEFAULT null, "twitterUserId" character varying(64) DEFAULT null, "twitterScreenName" character varying(64) DEFAULT null, "github" boolean NOT NULL DEFAULT false, "githubAccessToken" character varying(64) DEFAULT null, "githubId" integer DEFAULT null, "githubLogin" character varying(64) DEFAULT null, "discord" boolean NOT NULL DEFAULT false, "discordAccessToken" character varying(64) DEFAULT null, "discordRefreshToken" character varying(64) DEFAULT null, "discordExpiresDate" integer DEFAULT null, "discordId" character varying(64) DEFAULT null, "discordUsername" character varying(64) DEFAULT null, "discordDiscriminator" character varying(64) DEFAULT null, "userHost" character varying(128), CONSTRAINT "REL_51cb79b5555effaf7d69ba1cff" UNIQUE ("userId"), CONSTRAINT "PK_51cb79b5555effaf7d69ba1cff9" PRIMARY KEY ("userId"))`); + await queryRunner.query(`CREATE INDEX "IDX_dce530b98e454793dac5ec2f5a" ON "user_profile" ("userHost") `); + await queryRunner.query(`CREATE TYPE "__chart__active_users_span_enum" AS ENUM('hour', 'day')`); + await queryRunner.query(`CREATE TABLE "__chart__active_users" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128), "span" "__chart__active_users_span_enum" NOT NULL, "unique" jsonb NOT NULL DEFAULT '{}', "___local_count" bigint NOT NULL, "___remote_count" bigint NOT NULL, CONSTRAINT "PK_317237a9f733b970604a11e314f" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE TYPE "__chart__drive_span_enum" AS ENUM('hour', 'day')`); + await queryRunner.query(`CREATE TABLE "__chart__drive" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128), "span" "__chart__drive_span_enum" NOT NULL, "unique" jsonb NOT NULL DEFAULT '{}', "___local_totalCount" bigint NOT NULL, "___local_totalSize" bigint NOT NULL, "___local_incCount" bigint NOT NULL, "___local_incSize" bigint NOT NULL, "___local_decCount" bigint NOT NULL, "___local_decSize" bigint NOT NULL, "___remote_totalCount" bigint NOT NULL, "___remote_totalSize" bigint NOT NULL, "___remote_incCount" bigint NOT NULL, "___remote_incSize" bigint NOT NULL, "___remote_decCount" bigint NOT NULL, "___remote_decSize" bigint NOT NULL, CONSTRAINT "PK_f96bc548a765cd4b3b354221ce7" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE TYPE "__chart__federation_span_enum" AS ENUM('hour', 'day')`); + await queryRunner.query(`CREATE TABLE "__chart__federation" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128), "span" "__chart__federation_span_enum" NOT NULL, "unique" jsonb NOT NULL DEFAULT '{}', "___instance_total" bigint NOT NULL, "___instance_inc" bigint NOT NULL, "___instance_dec" bigint NOT NULL, CONSTRAINT "PK_b39dcd31a0fe1a7757e348e85fd" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE TYPE "__chart__hashtag_span_enum" AS ENUM('hour', 'day')`); + await queryRunner.query(`CREATE TABLE "__chart__hashtag" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128), "span" "__chart__hashtag_span_enum" NOT NULL, "unique" jsonb NOT NULL DEFAULT '{}', "___local_count" bigint NOT NULL, "___remote_count" bigint NOT NULL, CONSTRAINT "PK_c32f1ea2b44a5d2f7881e37f8f9" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE TYPE "__chart__instance_span_enum" AS ENUM('hour', 'day')`); + await queryRunner.query(`CREATE TABLE "__chart__instance" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128), "span" "__chart__instance_span_enum" NOT NULL, "unique" jsonb NOT NULL DEFAULT '{}', "___requests_failed" bigint NOT NULL, "___requests_succeeded" bigint NOT NULL, "___requests_received" bigint NOT NULL, "___notes_total" bigint NOT NULL, "___notes_inc" bigint NOT NULL, "___notes_dec" bigint NOT NULL, "___notes_diffs_normal" bigint NOT NULL, "___notes_diffs_reply" bigint NOT NULL, "___notes_diffs_renote" bigint NOT NULL, "___users_total" bigint NOT NULL, "___users_inc" bigint NOT NULL, "___users_dec" bigint NOT NULL, "___following_total" bigint NOT NULL, "___following_inc" bigint NOT NULL, "___following_dec" bigint NOT NULL, "___followers_total" bigint NOT NULL, "___followers_inc" bigint NOT NULL, "___followers_dec" bigint NOT NULL, "___drive_totalFiles" bigint NOT NULL, "___drive_totalUsage" bigint NOT NULL, "___drive_incFiles" bigint NOT NULL, "___drive_incUsage" bigint NOT NULL, "___drive_decFiles" bigint NOT NULL, "___drive_decUsage" bigint NOT NULL, CONSTRAINT "PK_1267c67c7c2d47b4903975f2c00" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE TYPE "__chart__network_span_enum" AS ENUM('hour', 'day')`); + await queryRunner.query(`CREATE TABLE "__chart__network" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128), "span" "__chart__network_span_enum" NOT NULL, "unique" jsonb NOT NULL DEFAULT '{}', "___incomingRequests" bigint NOT NULL, "___outgoingRequests" bigint NOT NULL, "___totalTime" bigint NOT NULL, "___incomingBytes" bigint NOT NULL, "___outgoingBytes" bigint NOT NULL, CONSTRAINT "PK_bc4290c2e27fad14ef0c1ca93f3" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE TYPE "__chart__notes_span_enum" AS ENUM('hour', 'day')`); + await queryRunner.query(`CREATE TABLE "__chart__notes" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128), "span" "__chart__notes_span_enum" NOT NULL, "unique" jsonb NOT NULL DEFAULT '{}', "___local_total" bigint NOT NULL, "___local_inc" bigint NOT NULL, "___local_dec" bigint NOT NULL, "___local_diffs_normal" bigint NOT NULL, "___local_diffs_reply" bigint NOT NULL, "___local_diffs_renote" bigint NOT NULL, "___remote_total" bigint NOT NULL, "___remote_inc" bigint NOT NULL, "___remote_dec" bigint NOT NULL, "___remote_diffs_normal" bigint NOT NULL, "___remote_diffs_reply" bigint NOT NULL, "___remote_diffs_renote" bigint NOT NULL, CONSTRAINT "PK_0aec823fa85c7f901bdb3863b14" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE TYPE "__chart__per_user_drive_span_enum" AS ENUM('hour', 'day')`); + await queryRunner.query(`CREATE TABLE "__chart__per_user_drive" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128), "span" "__chart__per_user_drive_span_enum" NOT NULL, "unique" jsonb NOT NULL DEFAULT '{}', "___totalCount" bigint NOT NULL, "___totalSize" bigint NOT NULL, "___incCount" bigint NOT NULL, "___incSize" bigint NOT NULL, "___decCount" bigint NOT NULL, "___decSize" bigint NOT NULL, CONSTRAINT "PK_d0ef23d24d666e1a44a0cd3d208" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE TYPE "__chart__per_user_following_span_enum" AS ENUM('hour', 'day')`); + await queryRunner.query(`CREATE TABLE "__chart__per_user_following" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128), "span" "__chart__per_user_following_span_enum" NOT NULL, "unique" jsonb NOT NULL DEFAULT '{}', "___local_followings_total" bigint NOT NULL, "___local_followings_inc" bigint NOT NULL, "___local_followings_dec" bigint NOT NULL, "___local_followers_total" bigint NOT NULL, "___local_followers_inc" bigint NOT NULL, "___local_followers_dec" bigint NOT NULL, "___remote_followings_total" bigint NOT NULL, "___remote_followings_inc" bigint NOT NULL, "___remote_followings_dec" bigint NOT NULL, "___remote_followers_total" bigint NOT NULL, "___remote_followers_inc" bigint NOT NULL, "___remote_followers_dec" bigint NOT NULL, CONSTRAINT "PK_85bb1b540363a29c2fec83bd907" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE TYPE "__chart__per_user_notes_span_enum" AS ENUM('hour', 'day')`); + await queryRunner.query(`CREATE TABLE "__chart__per_user_notes" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128), "span" "__chart__per_user_notes_span_enum" NOT NULL, "unique" jsonb NOT NULL DEFAULT '{}', "___total" bigint NOT NULL, "___inc" bigint NOT NULL, "___dec" bigint NOT NULL, "___diffs_normal" bigint NOT NULL, "___diffs_reply" bigint NOT NULL, "___diffs_renote" bigint NOT NULL, CONSTRAINT "PK_334acf6e915af2f29edc11b8e50" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE TYPE "__chart__per_user_reaction_span_enum" AS ENUM('hour', 'day')`); + await queryRunner.query(`CREATE TABLE "__chart__per_user_reaction" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128), "span" "__chart__per_user_reaction_span_enum" NOT NULL, "unique" jsonb NOT NULL DEFAULT '{}', "___local_count" bigint NOT NULL, "___remote_count" bigint NOT NULL, CONSTRAINT "PK_984f54dae441e65b633e8d27a7f" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE TYPE "__chart__test_grouped_span_enum" AS ENUM('hour', 'day')`); + await queryRunner.query(`CREATE TABLE "__chart__test_grouped" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128), "span" "__chart__test_grouped_span_enum" NOT NULL, "unique" jsonb NOT NULL DEFAULT '{}', "___foo_total" bigint NOT NULL, "___foo_inc" bigint NOT NULL, "___foo_dec" bigint NOT NULL, CONSTRAINT "PK_f4a2b175d308695af30d4293272" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE TYPE "__chart__test_unique_span_enum" AS ENUM('hour', 'day')`); + await queryRunner.query(`CREATE TABLE "__chart__test_unique" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128), "span" "__chart__test_unique_span_enum" NOT NULL, "unique" jsonb NOT NULL DEFAULT '{}', "___foo" bigint NOT NULL, CONSTRAINT "PK_409bac9c97cc612d8500012319d" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE TYPE "__chart__test_span_enum" AS ENUM('hour', 'day')`); + await queryRunner.query(`CREATE TABLE "__chart__test" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128), "span" "__chart__test_span_enum" NOT NULL, "unique" jsonb NOT NULL DEFAULT '{}', "___foo_total" bigint NOT NULL, "___foo_inc" bigint NOT NULL, "___foo_dec" bigint NOT NULL, CONSTRAINT "PK_b4bc31dffbd1b785276a3ecfc1e" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE TYPE "__chart__users_span_enum" AS ENUM('hour', 'day')`); + await queryRunner.query(`CREATE TABLE "__chart__users" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128), "span" "__chart__users_span_enum" NOT NULL, "unique" jsonb NOT NULL DEFAULT '{}', "___local_total" bigint NOT NULL, "___local_inc" bigint NOT NULL, "___local_dec" bigint NOT NULL, "___remote_total" bigint NOT NULL, "___remote_inc" bigint NOT NULL, "___remote_dec" bigint NOT NULL, CONSTRAINT "PK_4dfcf2c78d03524b9eb2c99d328" PRIMARY KEY ("id"))`); + await queryRunner.query(`ALTER TABLE "drive_folder" ADD CONSTRAINT "FK_f4fc06e49c0171c85f1c48060d2" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "drive_folder" ADD CONSTRAINT "FK_00ceffb0cdc238b3233294f08f2" FOREIGN KEY ("parentId") REFERENCES "drive_folder"("id") ON DELETE SET NULL ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "drive_file" ADD CONSTRAINT "FK_860fa6f6c7df5bb887249fba22e" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE SET NULL ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "drive_file" ADD CONSTRAINT "FK_bb90d1956dafc4068c28aa7560a" FOREIGN KEY ("folderId") REFERENCES "drive_folder"("id") ON DELETE SET NULL ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "user" ADD CONSTRAINT "FK_58f5c71eaab331645112cf8cfa5" FOREIGN KEY ("avatarId") REFERENCES "drive_file"("id") ON DELETE SET NULL ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "user" ADD CONSTRAINT "FK_afc64b53f8db3707ceb34eb28e2" FOREIGN KEY ("bannerId") REFERENCES "drive_file"("id") ON DELETE SET NULL ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "app" ADD CONSTRAINT "FK_3f5b0899ef90527a3462d7c2cb3" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE SET NULL ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "access_token" ADD CONSTRAINT "FK_9949557d0e1b2c19e5344c171e9" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "access_token" ADD CONSTRAINT "FK_a3ff16c90cc87a82a0b5959e560" FOREIGN KEY ("appId") REFERENCES "app"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "note" ADD CONSTRAINT "FK_17cb3553c700a4985dff5a30ff5" FOREIGN KEY ("replyId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "note" ADD CONSTRAINT "FK_52ccc804d7c69037d558bac4c96" FOREIGN KEY ("renoteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "note" ADD CONSTRAINT "FK_ec5c201576192ba8904c345c5cc" FOREIGN KEY ("appId") REFERENCES "app"("id") ON DELETE SET NULL ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "note" ADD CONSTRAINT "FK_5b87d9d19127bd5d92026017a7b" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "poll_vote" ADD CONSTRAINT "FK_66d2bd2ee31d14bcc23069a89f8" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "poll_vote" ADD CONSTRAINT "FK_aecfbd5ef60374918e63ee95fa7" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "note_reaction" ADD CONSTRAINT "FK_13761f64257f40c5636d0ff95ee" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "note_reaction" ADD CONSTRAINT "FK_45145e4953780f3cd5656f0ea6a" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "note_watching" ADD CONSTRAINT "FK_b0134ec406e8d09a540f8182888" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "note_watching" ADD CONSTRAINT "FK_03e7028ab8388a3f5e3ce2a8619" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "note_unread" ADD CONSTRAINT "FK_56b0166d34ddae49d8ef7610bb9" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "note_unread" ADD CONSTRAINT "FK_e637cba4dc4410218c4251260e4" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "notification" ADD CONSTRAINT "FK_3c601b70a1066d2c8b517094cb9" FOREIGN KEY ("notifieeId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "notification" ADD CONSTRAINT "FK_3b4e96eec8d36a8bbb9d02aa710" FOREIGN KEY ("notifierId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "notification" ADD CONSTRAINT "FK_769cb6b73a1efe22ddf733ac453" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "following" ADD CONSTRAINT "FK_24e0042143a18157b234df186c3" FOREIGN KEY ("followeeId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "following" ADD CONSTRAINT "FK_6516c5a6f3c015b4eed39978be5" FOREIGN KEY ("followerId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "muting" ADD CONSTRAINT "FK_ec96b4fed9dae517e0dbbe0675c" FOREIGN KEY ("muteeId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "muting" ADD CONSTRAINT "FK_93060675b4a79a577f31d260c67" FOREIGN KEY ("muterId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "sw_subscription" ADD CONSTRAINT "FK_97754ca6f2baff9b4abb7f853dd" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "blocking" ADD CONSTRAINT "FK_2cd4a2743a99671308f5417759e" FOREIGN KEY ("blockeeId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "blocking" ADD CONSTRAINT "FK_0627125f1a8a42c9a1929edb552" FOREIGN KEY ("blockerId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "user_list" ADD CONSTRAINT "FK_b7fcefbdd1c18dce86687531f99" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "user_list_joining" ADD CONSTRAINT "FK_d844bfc6f3f523a05189076efaa" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "user_list_joining" ADD CONSTRAINT "FK_605472305f26818cc93d1baaa74" FOREIGN KEY ("userListId") REFERENCES "user_list"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "note_favorite" ADD CONSTRAINT "FK_47f4b1892f5d6ba8efb3057d81a" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "note_favorite" ADD CONSTRAINT "FK_0e00498f180193423c992bc4370" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD CONSTRAINT "FK_d049123c413e68ca52abe734203" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD CONSTRAINT "FK_04cc96756f89d0b7f9473e8cdf3" FOREIGN KEY ("reporterId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "messaging_message" ADD CONSTRAINT "FK_5377c307783fce2b6d352e1203b" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "messaging_message" ADD CONSTRAINT "FK_cac14a4e3944454a5ce7daa5142" FOREIGN KEY ("recipientId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "messaging_message" ADD CONSTRAINT "FK_535def119223ac05ad3fa9ef64b" FOREIGN KEY ("fileId") REFERENCES "drive_file"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "signin" ADD CONSTRAINT "FK_2c308dbdc50d94dc625670055f7" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + 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 "auth_session" ADD CONSTRAINT "FK_dbe037d4bddd17b03a1dc778dee" FOREIGN KEY ("appId") REFERENCES "app"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "follow_request" ADD CONSTRAINT "FK_12c01c0d1a79f77d9f6c15fadd2" FOREIGN KEY ("followeeId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "follow_request" ADD CONSTRAINT "FK_a7fd92dd6dc519e6fb435dd108f" FOREIGN KEY ("followerId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + 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`); + await queryRunner.query(`ALTER TABLE "user_note_pining" ADD CONSTRAINT "FK_bfbc6f79ba4007b4ce5097f08d6" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "user_note_pining" ADD CONSTRAINT "FK_68881008f7c3588ad7ecae471cf" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "poll" ADD CONSTRAINT "FK_da851e06d0dfe2ef397d8b1bf1b" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "user_keypair" ADD CONSTRAINT "FK_f4853eb41ab722fe05f81cedeb6" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "user_publickey" ADD CONSTRAINT "FK_10c146e4b39b443ede016f6736d" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "user_profile" ADD CONSTRAINT "FK_51cb79b5555effaf7d69ba1cff9" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "user_profile" DROP CONSTRAINT "FK_51cb79b5555effaf7d69ba1cff9"`); + await queryRunner.query(`ALTER TABLE "user_publickey" DROP CONSTRAINT "FK_10c146e4b39b443ede016f6736d"`); + await queryRunner.query(`ALTER TABLE "user_keypair" DROP CONSTRAINT "FK_f4853eb41ab722fe05f81cedeb6"`); + await queryRunner.query(`ALTER TABLE "poll" DROP CONSTRAINT "FK_da851e06d0dfe2ef397d8b1bf1b"`); + await queryRunner.query(`ALTER TABLE "user_note_pining" DROP CONSTRAINT "FK_68881008f7c3588ad7ecae471cf"`); + await queryRunner.query(`ALTER TABLE "user_note_pining" DROP CONSTRAINT "FK_bfbc6f79ba4007b4ce5097f08d6"`); + await queryRunner.query(`ALTER TABLE "reversi_matching" DROP CONSTRAINT "FK_e247b23a3c9b45f89ec1299d066"`); + await queryRunner.query(`ALTER TABLE "reversi_matching" DROP CONSTRAINT "FK_3b25402709dd9882048c2bbade0"`); + await queryRunner.query(`ALTER TABLE "reversi_game" DROP CONSTRAINT "FK_6649a4e8c5d5cf32fb03b5da9f6"`); + await queryRunner.query(`ALTER TABLE "reversi_game" DROP CONSTRAINT "FK_f7467510c60a45ce5aca6292743"`); + await queryRunner.query(`ALTER TABLE "follow_request" DROP CONSTRAINT "FK_a7fd92dd6dc519e6fb435dd108f"`); + await queryRunner.query(`ALTER TABLE "follow_request" DROP CONSTRAINT "FK_12c01c0d1a79f77d9f6c15fadd2"`); + await queryRunner.query(`ALTER TABLE "auth_session" DROP CONSTRAINT "FK_dbe037d4bddd17b03a1dc778dee"`); + await queryRunner.query(`ALTER TABLE "auth_session" DROP CONSTRAINT "FK_c072b729d71697f959bde66ade0"`); + await queryRunner.query(`ALTER TABLE "signin" DROP CONSTRAINT "FK_2c308dbdc50d94dc625670055f7"`); + await queryRunner.query(`ALTER TABLE "messaging_message" DROP CONSTRAINT "FK_535def119223ac05ad3fa9ef64b"`); + await queryRunner.query(`ALTER TABLE "messaging_message" DROP CONSTRAINT "FK_cac14a4e3944454a5ce7daa5142"`); + await queryRunner.query(`ALTER TABLE "messaging_message" DROP CONSTRAINT "FK_5377c307783fce2b6d352e1203b"`); + await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP CONSTRAINT "FK_04cc96756f89d0b7f9473e8cdf3"`); + await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP CONSTRAINT "FK_d049123c413e68ca52abe734203"`); + await queryRunner.query(`ALTER TABLE "note_favorite" DROP CONSTRAINT "FK_0e00498f180193423c992bc4370"`); + await queryRunner.query(`ALTER TABLE "note_favorite" DROP CONSTRAINT "FK_47f4b1892f5d6ba8efb3057d81a"`); + await queryRunner.query(`ALTER TABLE "user_list_joining" DROP CONSTRAINT "FK_605472305f26818cc93d1baaa74"`); + await queryRunner.query(`ALTER TABLE "user_list_joining" DROP CONSTRAINT "FK_d844bfc6f3f523a05189076efaa"`); + await queryRunner.query(`ALTER TABLE "user_list" DROP CONSTRAINT "FK_b7fcefbdd1c18dce86687531f99"`); + await queryRunner.query(`ALTER TABLE "blocking" DROP CONSTRAINT "FK_0627125f1a8a42c9a1929edb552"`); + await queryRunner.query(`ALTER TABLE "blocking" DROP CONSTRAINT "FK_2cd4a2743a99671308f5417759e"`); + await queryRunner.query(`ALTER TABLE "sw_subscription" DROP CONSTRAINT "FK_97754ca6f2baff9b4abb7f853dd"`); + await queryRunner.query(`ALTER TABLE "muting" DROP CONSTRAINT "FK_93060675b4a79a577f31d260c67"`); + await queryRunner.query(`ALTER TABLE "muting" DROP CONSTRAINT "FK_ec96b4fed9dae517e0dbbe0675c"`); + await queryRunner.query(`ALTER TABLE "following" DROP CONSTRAINT "FK_6516c5a6f3c015b4eed39978be5"`); + await queryRunner.query(`ALTER TABLE "following" DROP CONSTRAINT "FK_24e0042143a18157b234df186c3"`); + await queryRunner.query(`ALTER TABLE "notification" DROP CONSTRAINT "FK_769cb6b73a1efe22ddf733ac453"`); + await queryRunner.query(`ALTER TABLE "notification" DROP CONSTRAINT "FK_3b4e96eec8d36a8bbb9d02aa710"`); + await queryRunner.query(`ALTER TABLE "notification" DROP CONSTRAINT "FK_3c601b70a1066d2c8b517094cb9"`); + await queryRunner.query(`ALTER TABLE "note_unread" DROP CONSTRAINT "FK_e637cba4dc4410218c4251260e4"`); + await queryRunner.query(`ALTER TABLE "note_unread" DROP CONSTRAINT "FK_56b0166d34ddae49d8ef7610bb9"`); + await queryRunner.query(`ALTER TABLE "note_watching" DROP CONSTRAINT "FK_03e7028ab8388a3f5e3ce2a8619"`); + await queryRunner.query(`ALTER TABLE "note_watching" DROP CONSTRAINT "FK_b0134ec406e8d09a540f8182888"`); + await queryRunner.query(`ALTER TABLE "note_reaction" DROP CONSTRAINT "FK_45145e4953780f3cd5656f0ea6a"`); + await queryRunner.query(`ALTER TABLE "note_reaction" DROP CONSTRAINT "FK_13761f64257f40c5636d0ff95ee"`); + await queryRunner.query(`ALTER TABLE "poll_vote" DROP CONSTRAINT "FK_aecfbd5ef60374918e63ee95fa7"`); + await queryRunner.query(`ALTER TABLE "poll_vote" DROP CONSTRAINT "FK_66d2bd2ee31d14bcc23069a89f8"`); + await queryRunner.query(`ALTER TABLE "note" DROP CONSTRAINT "FK_5b87d9d19127bd5d92026017a7b"`); + await queryRunner.query(`ALTER TABLE "note" DROP CONSTRAINT "FK_ec5c201576192ba8904c345c5cc"`); + await queryRunner.query(`ALTER TABLE "note" DROP CONSTRAINT "FK_52ccc804d7c69037d558bac4c96"`); + await queryRunner.query(`ALTER TABLE "note" DROP CONSTRAINT "FK_17cb3553c700a4985dff5a30ff5"`); + await queryRunner.query(`ALTER TABLE "access_token" DROP CONSTRAINT "FK_a3ff16c90cc87a82a0b5959e560"`); + await queryRunner.query(`ALTER TABLE "access_token" DROP CONSTRAINT "FK_9949557d0e1b2c19e5344c171e9"`); + await queryRunner.query(`ALTER TABLE "app" DROP CONSTRAINT "FK_3f5b0899ef90527a3462d7c2cb3"`); + await queryRunner.query(`ALTER TABLE "user" DROP CONSTRAINT "FK_afc64b53f8db3707ceb34eb28e2"`); + await queryRunner.query(`ALTER TABLE "user" DROP CONSTRAINT "FK_58f5c71eaab331645112cf8cfa5"`); + await queryRunner.query(`ALTER TABLE "drive_file" DROP CONSTRAINT "FK_bb90d1956dafc4068c28aa7560a"`); + await queryRunner.query(`ALTER TABLE "drive_file" DROP CONSTRAINT "FK_860fa6f6c7df5bb887249fba22e"`); + await queryRunner.query(`ALTER TABLE "drive_folder" DROP CONSTRAINT "FK_00ceffb0cdc238b3233294f08f2"`); + await queryRunner.query(`ALTER TABLE "drive_folder" DROP CONSTRAINT "FK_f4fc06e49c0171c85f1c48060d2"`); + await queryRunner.query(`DROP TABLE "__chart__users"`); + await queryRunner.query(`DROP TYPE "__chart__users_span_enum"`); + await queryRunner.query(`DROP TABLE "__chart__test"`); + await queryRunner.query(`DROP TYPE "__chart__test_span_enum"`); + await queryRunner.query(`DROP TABLE "__chart__test_unique"`); + await queryRunner.query(`DROP TYPE "__chart__test_unique_span_enum"`); + await queryRunner.query(`DROP TABLE "__chart__test_grouped"`); + await queryRunner.query(`DROP TYPE "__chart__test_grouped_span_enum"`); + await queryRunner.query(`DROP TABLE "__chart__per_user_reaction"`); + await queryRunner.query(`DROP TYPE "__chart__per_user_reaction_span_enum"`); + await queryRunner.query(`DROP TABLE "__chart__per_user_notes"`); + await queryRunner.query(`DROP TYPE "__chart__per_user_notes_span_enum"`); + await queryRunner.query(`DROP TABLE "__chart__per_user_following"`); + await queryRunner.query(`DROP TYPE "__chart__per_user_following_span_enum"`); + await queryRunner.query(`DROP TABLE "__chart__per_user_drive"`); + await queryRunner.query(`DROP TYPE "__chart__per_user_drive_span_enum"`); + await queryRunner.query(`DROP TABLE "__chart__notes"`); + await queryRunner.query(`DROP TYPE "__chart__notes_span_enum"`); + await queryRunner.query(`DROP TABLE "__chart__network"`); + await queryRunner.query(`DROP TYPE "__chart__network_span_enum"`); + await queryRunner.query(`DROP TABLE "__chart__instance"`); + await queryRunner.query(`DROP TYPE "__chart__instance_span_enum"`); + await queryRunner.query(`DROP TABLE "__chart__hashtag"`); + await queryRunner.query(`DROP TYPE "__chart__hashtag_span_enum"`); + await queryRunner.query(`DROP TABLE "__chart__federation"`); + await queryRunner.query(`DROP TYPE "__chart__federation_span_enum"`); + await queryRunner.query(`DROP TABLE "__chart__drive"`); + await queryRunner.query(`DROP TYPE "__chart__drive_span_enum"`); + await queryRunner.query(`DROP TABLE "__chart__active_users"`); + await queryRunner.query(`DROP TYPE "__chart__active_users_span_enum"`); + await queryRunner.query(`DROP INDEX "IDX_dce530b98e454793dac5ec2f5a"`); + await queryRunner.query(`DROP TABLE "user_profile"`); + await queryRunner.query(`DROP INDEX "IDX_171e64971c780ebd23fae140bb"`); + await queryRunner.query(`DROP TABLE "user_publickey"`); + await queryRunner.query(`DROP TABLE "user_keypair"`); + await queryRunner.query(`DROP INDEX "IDX_7fa20a12319c7f6dc3aed98c0a"`); + await queryRunner.query(`DROP INDEX "IDX_0610ebcfcfb4a18441a9bcdab2"`); + await queryRunner.query(`DROP TABLE "poll"`); + await queryRunner.query(`DROP TYPE "poll_notevisibility_enum"`); + await queryRunner.query(`DROP INDEX "IDX_410cd649884b501c02d6e72738"`); + await queryRunner.query(`DROP INDEX "IDX_bfbc6f79ba4007b4ce5097f08d"`); + await queryRunner.query(`DROP TABLE "user_note_pining"`); + await queryRunner.query(`DROP INDEX "IDX_e247b23a3c9b45f89ec1299d06"`); + await queryRunner.query(`DROP INDEX "IDX_3b25402709dd9882048c2bbade"`); + await queryRunner.query(`DROP INDEX "IDX_b604d92d6c7aec38627f6eaf16"`); + await queryRunner.query(`DROP TABLE "reversi_matching"`); + await queryRunner.query(`DROP INDEX "IDX_b46ec40746efceac604142be1c"`); + await queryRunner.query(`DROP TABLE "reversi_game"`); + await queryRunner.query(`DROP INDEX "IDX_4f4d35e1256c84ae3d1f0eab10"`); + await queryRunner.query(`DROP INDEX "IDX_5900e907bb46516ddf2871327c"`); + await queryRunner.query(`DROP INDEX "IDX_b37dafc86e9af007e3295c2781"`); + await queryRunner.query(`DROP TABLE "emoji"`); + await queryRunner.query(`DROP INDEX "IDX_d54a512b822fac7ed52800f6b4"`); + await queryRunner.query(`DROP INDEX "IDX_a7fd92dd6dc519e6fb435dd108"`); + await queryRunner.query(`DROP INDEX "IDX_12c01c0d1a79f77d9f6c15fadd"`); + await queryRunner.query(`DROP TABLE "follow_request"`); + await queryRunner.query(`DROP INDEX "IDX_62cb09e1129f6ec024ef66e183"`); + await queryRunner.query(`DROP TABLE "auth_session"`); + await queryRunner.query(`DROP INDEX "IDX_2c308dbdc50d94dc625670055f"`); + await queryRunner.query(`DROP TABLE "signin"`); + await queryRunner.query(`DROP INDEX "IDX_cac14a4e3944454a5ce7daa514"`); + await queryRunner.query(`DROP INDEX "IDX_5377c307783fce2b6d352e1203"`); + await queryRunner.query(`DROP INDEX "IDX_e21cd3646e52ef9c94aaf17c2e"`); + await queryRunner.query(`DROP TABLE "messaging_message"`); + await queryRunner.query(`DROP INDEX "IDX_0ff69e8dfa9fe31bb4a4660f59"`); + await queryRunner.query(`DROP TABLE "registration_ticket"`); + await queryRunner.query(`DROP INDEX "IDX_5cd442c3b2e74fdd99dae20243"`); + await queryRunner.query(`DROP INDEX "IDX_04cc96756f89d0b7f9473e8cdf"`); + await queryRunner.query(`DROP INDEX "IDX_d049123c413e68ca52abe73420"`); + await queryRunner.query(`DROP INDEX "IDX_db2098070b2b5a523c58181f74"`); + await queryRunner.query(`DROP TABLE "abuse_user_report"`); + await queryRunner.query(`DROP INDEX "IDX_0f4fb9ad355f3effff221ef245"`); + await queryRunner.query(`DROP INDEX "IDX_47f4b1892f5d6ba8efb3057d81"`); + await queryRunner.query(`DROP TABLE "note_favorite"`); + await queryRunner.query(`DROP INDEX "IDX_0b03cbcd7e6a7ce068efa8ecc2"`); + await queryRunner.query(`DROP INDEX "IDX_0c44bf4f680964145f2a68a341"`); + await queryRunner.query(`DROP INDEX "IDX_d57f9030cd3af7f63ffb1c267c"`); + await queryRunner.query(`DROP INDEX "IDX_4c02d38a976c3ae132228c6fce"`); + await queryRunner.query(`DROP INDEX "IDX_0e206cec573f1edff4a3062923"`); + await queryRunner.query(`DROP INDEX "IDX_2710a55f826ee236ea1a62698f"`); + await queryRunner.query(`DROP INDEX "IDX_347fec870eafea7b26c8a73bac"`); + await queryRunner.query(`DROP TABLE "hashtag"`); + await queryRunner.query(`DROP INDEX "IDX_605472305f26818cc93d1baaa7"`); + await queryRunner.query(`DROP INDEX "IDX_d844bfc6f3f523a05189076efa"`); + await queryRunner.query(`DROP TABLE "user_list_joining"`); + await queryRunner.query(`DROP INDEX "IDX_b7fcefbdd1c18dce86687531f9"`); + await queryRunner.query(`DROP TABLE "user_list"`); + await queryRunner.query(`DROP INDEX "IDX_98a1bc5cb30dfd159de056549f"`); + await queryRunner.query(`DROP INDEX "IDX_0627125f1a8a42c9a1929edb55"`); + await queryRunner.query(`DROP INDEX "IDX_2cd4a2743a99671308f5417759"`); + await queryRunner.query(`DROP INDEX "IDX_b9a354f7941c1e779f3b33aea6"`); + await queryRunner.query(`DROP TABLE "blocking"`); + await queryRunner.query(`DROP INDEX "IDX_97754ca6f2baff9b4abb7f853d"`); + await queryRunner.query(`DROP TABLE "sw_subscription"`); + await queryRunner.query(`DROP INDEX "IDX_1eb9d9824a630321a29fd3b290"`); + await queryRunner.query(`DROP INDEX "IDX_93060675b4a79a577f31d260c6"`); + await queryRunner.query(`DROP INDEX "IDX_ec96b4fed9dae517e0dbbe0675"`); + await queryRunner.query(`DROP INDEX "IDX_f86d57fbca33c7a4e6897490cc"`); + await queryRunner.query(`DROP TABLE "muting"`); + await queryRunner.query(`DROP INDEX "IDX_8d5afc98982185799b160e10eb"`); + await queryRunner.query(`DROP INDEX "IDX_2cd3b2a6b4cf0b910b260afe08"`); + await queryRunner.query(`DROP TABLE "instance"`); + await queryRunner.query(`DROP INDEX "IDX_307be5f1d1252e0388662acb96"`); + await queryRunner.query(`DROP INDEX "IDX_6516c5a6f3c015b4eed39978be"`); + await queryRunner.query(`DROP INDEX "IDX_24e0042143a18157b234df186c"`); + await queryRunner.query(`DROP INDEX "IDX_582f8fab771a9040a12961f3e7"`); + await queryRunner.query(`DROP TABLE "following"`); + await queryRunner.query(`DROP TABLE "meta"`); + await queryRunner.query(`DROP INDEX "IDX_3c601b70a1066d2c8b517094cb"`); + await queryRunner.query(`DROP INDEX "IDX_b11a5e627c41d4dc3170f1d370"`); + await queryRunner.query(`DROP TABLE "notification"`); + await queryRunner.query(`DROP INDEX "IDX_d908433a4953cc13216cd9c274"`); + await queryRunner.query(`DROP INDEX "IDX_e637cba4dc4410218c4251260e"`); + await queryRunner.query(`DROP INDEX "IDX_56b0166d34ddae49d8ef7610bb"`); + await queryRunner.query(`DROP TABLE "note_unread"`); + await queryRunner.query(`DROP INDEX "IDX_a42c93c69989ce1d09959df4cf"`); + await queryRunner.query(`DROP INDEX "IDX_44499765eec6b5489d72c4253b"`); + await queryRunner.query(`DROP INDEX "IDX_03e7028ab8388a3f5e3ce2a861"`); + await queryRunner.query(`DROP INDEX "IDX_b0134ec406e8d09a540f818288"`); + await queryRunner.query(`DROP INDEX "IDX_318cdf42a9cfc11f479bd802bb"`); + await queryRunner.query(`DROP TABLE "note_watching"`); + await queryRunner.query(`DROP INDEX "IDX_ad0c221b25672daf2df320a817"`); + await queryRunner.query(`DROP INDEX "IDX_45145e4953780f3cd5656f0ea6"`); + await queryRunner.query(`DROP INDEX "IDX_13761f64257f40c5636d0ff95e"`); + await queryRunner.query(`DROP INDEX "IDX_01f4581f114e0ebd2bbb876f0b"`); + await queryRunner.query(`DROP TABLE "note_reaction"`); + await queryRunner.query(`DROP INDEX "IDX_50bd7164c5b78f1f4a42c4d21f"`); + await queryRunner.query(`DROP INDEX "IDX_aecfbd5ef60374918e63ee95fa"`); + await queryRunner.query(`DROP INDEX "IDX_66d2bd2ee31d14bcc23069a89f"`); + await queryRunner.query(`DROP INDEX "IDX_0fb627e1c2f753262a74f0562d"`); + await queryRunner.query(`DROP TABLE "poll_vote"`); + await queryRunner.query(`DROP INDEX "IDX_7125a826ab192eb27e11d358a5"`); + await queryRunner.query(`DROP INDEX "IDX_88937d94d7443d9a99a76fa5c0"`); + await queryRunner.query(`DROP INDEX "IDX_54ebcb6d27222913b908d56fd8"`); + await queryRunner.query(`DROP INDEX "IDX_796a8c03959361f97dc2be1d5c"`); + await queryRunner.query(`DROP INDEX "IDX_25dfc71b0369b003a4cd434d0b"`); + await queryRunner.query(`DROP INDEX "IDX_51c063b6a133a9cb87145450f5"`); + await queryRunner.query(`DROP INDEX "IDX_153536c67d05e9adb24e99fc2b"`); + await queryRunner.query(`DROP INDEX "IDX_5b87d9d19127bd5d92026017a7"`); + await queryRunner.query(`DROP INDEX "IDX_52ccc804d7c69037d558bac4c9"`); + await queryRunner.query(`DROP INDEX "IDX_17cb3553c700a4985dff5a30ff"`); + await queryRunner.query(`DROP INDEX "IDX_e7c0567f5261063592f022e9b5"`); + await queryRunner.query(`DROP TABLE "note"`); + await queryRunner.query(`DROP TYPE "note_visibility_enum"`); + await queryRunner.query(`DROP INDEX "IDX_9949557d0e1b2c19e5344c171e"`); + await queryRunner.query(`DROP INDEX "IDX_64c327441248bae40f7d92f34f"`); + await queryRunner.query(`DROP INDEX "IDX_70ba8f6af34bc924fc9e12adb8"`); + await queryRunner.query(`DROP TABLE "access_token"`); + await queryRunner.query(`DROP INDEX "IDX_f49922d511d666848f250663c4"`); + await queryRunner.query(`DROP INDEX "IDX_3f5b0899ef90527a3462d7c2cb"`); + await queryRunner.query(`DROP INDEX "IDX_048a757923ed8b157e9895da53"`); + await queryRunner.query(`DROP TABLE "app"`); + await queryRunner.query(`DROP INDEX "IDX_5deb01ae162d1d70b80d064c27"`); + await queryRunner.query(`DROP INDEX "IDX_a854e557b1b14814750c7c7b0c"`); + await queryRunner.query(`DROP INDEX "IDX_be623adaa4c566baf5d29ce0c8"`); + await queryRunner.query(`DROP INDEX "IDX_3252a5df8d5bbd16b281f7799e"`); + await queryRunner.query(`DROP INDEX "IDX_fa99d777623947a5b05f394cae"`); + await queryRunner.query(`DROP INDEX "IDX_a27b942a0d6dcff90e3ee9b5e8"`); + await queryRunner.query(`DROP INDEX "IDX_80ca6e6ef65fb9ef34ea8c90f4"`); + await queryRunner.query(`DROP INDEX "IDX_e11e649824a45d8ed01d597fd9"`); + await queryRunner.query(`DROP TABLE "user"`); + await queryRunner.query(`DROP INDEX "IDX_bb90d1956dafc4068c28aa7560"`); + await queryRunner.query(`DROP INDEX "IDX_e5848eac4940934e23dbc17581"`); + await queryRunner.query(`DROP INDEX "IDX_c55b2b7c284d9fef98026fc88e"`); + await queryRunner.query(`DROP INDEX "IDX_e74022ce9a074b3866f70e0d27"`); + await queryRunner.query(`DROP INDEX "IDX_d85a184c2540d2deba33daf642"`); + await queryRunner.query(`DROP INDEX "IDX_a40b8df8c989d7db937ea27cf6"`); + await queryRunner.query(`DROP INDEX "IDX_37bb9a1b4585f8a3beb24c62d6"`); + await queryRunner.query(`DROP INDEX "IDX_92779627994ac79277f070c91e"`); + await queryRunner.query(`DROP INDEX "IDX_860fa6f6c7df5bb887249fba22"`); + await queryRunner.query(`DROP INDEX "IDX_c8dfad3b72196dd1d6b5db168a"`); + await queryRunner.query(`DROP TABLE "drive_file"`); + await queryRunner.query(`DROP INDEX "IDX_00ceffb0cdc238b3233294f08f"`); + await queryRunner.query(`DROP INDEX "IDX_f4fc06e49c0171c85f1c48060d"`); + await queryRunner.query(`DROP INDEX "IDX_02878d441ceae15ce060b73daf"`); + await queryRunner.query(`DROP TABLE "drive_folder"`); + await queryRunner.query(`DROP INDEX "IDX_584b536b49e53ac81beb39a177"`); + await queryRunner.query(`DROP INDEX "IDX_8cb40cfc8f3c28261e6f887b03"`); + await queryRunner.query(`DROP INDEX "IDX_8e4eb51a35d81b64dda28eed0a"`); + await queryRunner.query(`DROP TABLE "log"`); + await queryRunner.query(`DROP TYPE "log_level_enum"`); + } + +} diff --git a/migration/1561706992953-webauthn.ts b/migration/1561706992953-webauthn.ts new file mode 100644 index 0000000000..fc1f0c042f --- /dev/null +++ b/migration/1561706992953-webauthn.ts @@ -0,0 +1,29 @@ +import {MigrationInterface, QueryRunner} from "typeorm"; + +export class webauthn1561706992953 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "attestation_challenge" ("id" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "challenge" character varying(64) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "registrationChallenge" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_d0ba6786e093f1bcb497572a6b5" PRIMARY KEY ("id", "userId"))`); + await queryRunner.query(`CREATE INDEX "IDX_f1a461a618fa1755692d0e0d59" ON "attestation_challenge" ("userId") `); + await queryRunner.query(`CREATE INDEX "IDX_47efb914aed1f72dd39a306c7b" ON "attestation_challenge" ("challenge") `); + await queryRunner.query(`CREATE TABLE "user_security_key" ("id" character varying NOT NULL, "userId" character varying(32) NOT NULL, "publicKey" character varying NOT NULL, "lastUsed" TIMESTAMP WITH TIME ZONE NOT NULL, "name" character varying(30) NOT NULL, CONSTRAINT "PK_3e508571121ab39c5f85d10c166" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_ff9ca3b5f3ee3d0681367a9b44" ON "user_security_key" ("userId") `); + await queryRunner.query(`CREATE INDEX "IDX_0d7718e562dcedd0aa5cf2c9f7" ON "user_security_key" ("publicKey") `); + await queryRunner.query(`ALTER TABLE "user_profile" ADD "securityKeysAvailable" boolean NOT NULL DEFAULT false`); + await queryRunner.query(`ALTER TABLE "attestation_challenge" ADD CONSTRAINT "FK_f1a461a618fa1755692d0e0d592" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "user_security_key" ADD CONSTRAINT "FK_ff9ca3b5f3ee3d0681367a9b447" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "user_security_key" DROP CONSTRAINT "FK_ff9ca3b5f3ee3d0681367a9b447"`); + await queryRunner.query(`ALTER TABLE "attestation_challenge" DROP CONSTRAINT "FK_f1a461a618fa1755692d0e0d592"`); + await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "securityKeysAvailable"`); + await queryRunner.query(`DROP INDEX "IDX_0d7718e562dcedd0aa5cf2c9f7"`); + await queryRunner.query(`DROP INDEX "IDX_ff9ca3b5f3ee3d0681367a9b44"`); + await queryRunner.query(`DROP TABLE "user_security_key"`); + await queryRunner.query(`DROP INDEX "IDX_47efb914aed1f72dd39a306c7b"`); + await queryRunner.query(`DROP INDEX "IDX_f1a461a618fa1755692d0e0d59"`); + await queryRunner.query(`DROP TABLE "attestation_challenge"`); + } + +} diff --git a/migration/1561873850023-ChartIndexes.ts b/migration/1561873850023-ChartIndexes.ts new file mode 100644 index 0000000000..b870ad3b7d --- /dev/null +++ b/migration/1561873850023-ChartIndexes.ts @@ -0,0 +1,201 @@ +import {MigrationInterface, QueryRunner} from "typeorm"; + +export class ChartIndexes1561873850023 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE INDEX "IDX_0ad37b7ef50f4ddc84363d7ccc" ON "__chart__active_users" ("date") `); + await queryRunner.query(`CREATE INDEX "IDX_15e91a03aeeac9dbccdf43fc06" ON "__chart__active_users" ("span") `); + await queryRunner.query(`CREATE INDEX "IDX_00ed5f86db1f7efafb1978bf21" ON "__chart__active_users" ("group") `); + await queryRunner.query(`CREATE INDEX "IDX_20f57cc8f142c131340ee16742" ON "__chart__active_users" ("span", "date") `); + await queryRunner.query(`CREATE INDEX "IDX_9a3ed15a30ab7e3a37702e6e08" ON "__chart__active_users" ("date", "group") `); + await queryRunner.query(`CREATE INDEX "IDX_c26e2c1cbb6e911e0554b27416" ON "__chart__active_users" ("span", "date", "group") `); + await queryRunner.query(`CREATE INDEX "IDX_13565815f618a1ff53886c5b28" ON "__chart__drive" ("date") `); + await queryRunner.query(`CREATE INDEX "IDX_3fa0d0f17ca72e3dc80999a032" ON "__chart__drive" ("span") `); + await queryRunner.query(`CREATE INDEX "IDX_7a170f67425e62a8fabb76c872" ON "__chart__drive" ("group") `); + await queryRunner.query(`CREATE INDEX "IDX_6e1df243476e20cbf86572ecc0" ON "__chart__drive" ("span", "date") `); + await queryRunner.query(`CREATE INDEX "IDX_3313d7288855ec105b5bbf6c21" ON "__chart__drive" ("date", "group") `); + await queryRunner.query(`CREATE INDEX "IDX_06690fc959f1c9fdaf21928222" ON "__chart__drive" ("span", "date", "group") `); + await queryRunner.query(`CREATE INDEX "IDX_36cb699c49580d4e6c2e6159f9" ON "__chart__federation" ("date") `); + await queryRunner.query(`CREATE INDEX "IDX_e447064455928cf627590ef527" ON "__chart__federation" ("span") `); + await queryRunner.query(`CREATE INDEX "IDX_76e87c7bfc5d925fcbba405d84" ON "__chart__federation" ("group") `); + await queryRunner.query(`CREATE INDEX "IDX_2d416e6af791a82e338c79d480" ON "__chart__federation" ("span", "date") `); + await queryRunner.query(`CREATE INDEX "IDX_dd907becf76104e4b656659e6b" ON "__chart__federation" ("date", "group") `); + await queryRunner.query(`CREATE INDEX "IDX_e9cd07672b37d8966cf3709283" ON "__chart__federation" ("span", "date", "group") `); + await queryRunner.query(`CREATE INDEX "IDX_07747a1038c05f532a718fe1de" ON "__chart__hashtag" ("date") `); + await queryRunner.query(`CREATE INDEX "IDX_fcc181fb8283009c61cc4083ef" ON "__chart__hashtag" ("span") `); + await queryRunner.query(`CREATE INDEX "IDX_99a7d2faaef84a6f728d714ad6" ON "__chart__hashtag" ("group") `); + await queryRunner.query(`CREATE INDEX "IDX_49975586f50ed7b800fdd88fbd" ON "__chart__hashtag" ("span", "date") `); + await queryRunner.query(`CREATE INDEX "IDX_25a97c02003338124b2b75fdbc" ON "__chart__hashtag" ("date", "group") `); + await queryRunner.query(`CREATE INDEX "IDX_6d6f156ceefc6bc5f273a0e370" ON "__chart__hashtag" ("span", "date", "group") `); + await queryRunner.query(`CREATE INDEX "IDX_6b8f34a1a64b06014b6fb66824" ON "__chart__instance" ("date") `); + await queryRunner.query(`CREATE INDEX "IDX_c12f0af4a66cdd30c2287ce8aa" ON "__chart__instance" ("span") `); + await queryRunner.query(`CREATE INDEX "IDX_da8a46ba84ca1d8bb5a29bfb63" ON "__chart__instance" ("group") `); + await queryRunner.query(`CREATE INDEX "IDX_d0a4f79af5a97b08f37b547197" ON "__chart__instance" ("span", "date") `); + await queryRunner.query(`CREATE INDEX "IDX_39ee857ab2f23493037c6b6631" ON "__chart__instance" ("date", "group") `); + await queryRunner.query(`CREATE INDEX "IDX_f5448d9633cff74208d850aabe" ON "__chart__instance" ("span", "date", "group") `); + await queryRunner.query(`CREATE INDEX "IDX_a1efd3e0048a5f2793a47360dc" ON "__chart__network" ("date") `); + await queryRunner.query(`CREATE INDEX "IDX_f8dd01baeded2ffa833e0a610a" ON "__chart__network" ("span") `); + await queryRunner.query(`CREATE INDEX "IDX_7b5da130992ec9df96712d4290" ON "__chart__network" ("group") `); + await queryRunner.query(`CREATE INDEX "IDX_08fac0eb3b11f04c200c0b40dd" ON "__chart__network" ("span", "date") `); + await queryRunner.query(`CREATE INDEX "IDX_0a905b992fecd2b5c3fb98759e" ON "__chart__network" ("date", "group") `); + await queryRunner.query(`CREATE INDEX "IDX_9ff6944f01acb756fdc92d7563" ON "__chart__network" ("span", "date", "group") `); + await queryRunner.query(`CREATE INDEX "IDX_42eb716a37d381cdf566192b2b" ON "__chart__notes" ("date") `); + await queryRunner.query(`CREATE INDEX "IDX_e69096589f11e3baa98ddd64d0" ON "__chart__notes" ("span") `); + await queryRunner.query(`CREATE INDEX "IDX_7036f2957151588b813185c794" ON "__chart__notes" ("group") `); + await queryRunner.query(`CREATE INDEX "IDX_0c9a159c5082cbeef3ca6706b5" ON "__chart__notes" ("span", "date") `); + await queryRunner.query(`CREATE INDEX "IDX_f09d543e3acb16c5976bdb31fa" ON "__chart__notes" ("date", "group") `); + await queryRunner.query(`CREATE INDEX "IDX_924fc196c80ca24bae01dd37e4" ON "__chart__notes" ("span", "date", "group") `); + await queryRunner.query(`CREATE INDEX "IDX_5f86db6492274e07c1a3cdf286" ON "__chart__per_user_drive" ("date") `); + await queryRunner.query(`CREATE INDEX "IDX_328f259961e60c4fa0bfcf55ca" ON "__chart__per_user_drive" ("span") `); + await queryRunner.query(`CREATE INDEX "IDX_e496ca8096d28f6b9b509264dc" ON "__chart__per_user_drive" ("group") `); + await queryRunner.query(`CREATE INDEX "IDX_42ea9381f0fda8dfe0fa1c8b53" ON "__chart__per_user_drive" ("span", "date") `); + await queryRunner.query(`CREATE INDEX "IDX_30bf67687f483ace115c5ca642" ON "__chart__per_user_drive" ("date", "group") `); + await queryRunner.query(`CREATE INDEX "IDX_f2aeafde2ae6fbad38e857631b" ON "__chart__per_user_drive" ("span", "date", "group") `); + await queryRunner.query(`CREATE INDEX "IDX_7af07790712aa3438ff6773f3b" ON "__chart__per_user_following" ("date") `); + await queryRunner.query(`CREATE INDEX "IDX_f92dd6d03f8d994f29987f6214" ON "__chart__per_user_following" ("span") `); + await queryRunner.query(`CREATE INDEX "IDX_4b3593098b6edc9c5afe36b18b" ON "__chart__per_user_following" ("group") `); + await queryRunner.query(`CREATE INDEX "IDX_57b5458d0d3d6d1e7f13d4e57f" ON "__chart__per_user_following" ("span", "date") `); + await queryRunner.query(`CREATE INDEX "IDX_b77d4dd9562c3a899d9a286fcd" ON "__chart__per_user_following" ("date", "group") `); + await queryRunner.query(`CREATE INDEX "IDX_4db3b84c7be0d3464714f3e0b1" ON "__chart__per_user_following" ("span", "date", "group") `); + await queryRunner.query(`CREATE INDEX "IDX_84234bd1abb873f07329681c83" ON "__chart__per_user_notes" ("date") `); + await queryRunner.query(`CREATE INDEX "IDX_8d2cbbc8114d90d19b44d626b6" ON "__chart__per_user_notes" ("span") `); + await queryRunner.query(`CREATE INDEX "IDX_55bf20f366979f2436de99206b" ON "__chart__per_user_notes" ("group") `); + await queryRunner.query(`CREATE INDEX "IDX_046feeb12e9ef5f783f409866a" ON "__chart__per_user_notes" ("span", "date") `); + await queryRunner.query(`CREATE INDEX "IDX_5048e9daccbbbc6d567bb142d3" ON "__chart__per_user_notes" ("date", "group") `); + await queryRunner.query(`CREATE INDEX "IDX_f68a5ab958f9f5fa17a32ac23b" ON "__chart__per_user_notes" ("span", "date", "group") `); + await queryRunner.query(`CREATE INDEX "IDX_f7bf4c62059764c2c2bb40fdab" ON "__chart__per_user_reaction" ("date") `); + await queryRunner.query(`CREATE INDEX "IDX_65633a106bce43fc7c5c30a5c7" ON "__chart__per_user_reaction" ("span") `); + await queryRunner.query(`CREATE INDEX "IDX_8cf3156fd7a6b15c43459c6e3b" ON "__chart__per_user_reaction" ("group") `); + await queryRunner.query(`CREATE INDEX "IDX_edeb73c09c3143a81bcb34d569" ON "__chart__per_user_reaction" ("span", "date") `); + await queryRunner.query(`CREATE INDEX "IDX_229a41ad465f9205f1f5703291" ON "__chart__per_user_reaction" ("date", "group") `); + await queryRunner.query(`CREATE INDEX "IDX_e316f01a6d24eb31db27f88262" ON "__chart__per_user_reaction" ("span", "date", "group") `); + await queryRunner.query(`CREATE INDEX "IDX_0c641990ecf47d2545df4edb75" ON "__chart__test_grouped" ("date") `); + await queryRunner.query(`CREATE INDEX "IDX_2be7ec6cebddc14dc11e206686" ON "__chart__test_grouped" ("span") `); + await queryRunner.query(`CREATE INDEX "IDX_234dff3c0b56a6150b95431ab9" ON "__chart__test_grouped" ("group") `); + await queryRunner.query(`CREATE INDEX "IDX_a5133470f4825902e170328ca5" ON "__chart__test_grouped" ("span", "date") `); + await queryRunner.query(`CREATE INDEX "IDX_b14489029e4b3aaf4bba5fb524" ON "__chart__test_grouped" ("date", "group") `); + await queryRunner.query(`CREATE INDEX "IDX_84e661abb7bd1e51b690d4b017" ON "__chart__test_grouped" ("span", "date", "group") `); + await queryRunner.query(`CREATE INDEX "IDX_437bab3c6061d90f6bb65fd2cc" ON "__chart__test_unique" ("date") `); + await queryRunner.query(`CREATE INDEX "IDX_5c73bf61da4f6e6f15bae88ed1" ON "__chart__test_unique" ("span") `); + await queryRunner.query(`CREATE INDEX "IDX_bbfa573a8181018851ed0b6357" ON "__chart__test_unique" ("group") `); + await queryRunner.query(`CREATE INDEX "IDX_d70c86baedc68326be11f9c0ce" ON "__chart__test_unique" ("span", "date") `); + await queryRunner.query(`CREATE INDEX "IDX_a0cd75442dd10d0643a17c4a49" ON "__chart__test_unique" ("date", "group") `); + await queryRunner.query(`CREATE INDEX "IDX_66e1e1ecd2f29e57778af35b59" ON "__chart__test_unique" ("span", "date", "group") `); + await queryRunner.query(`CREATE INDEX "IDX_b070a906db04b44c67c6c2144d" ON "__chart__test" ("date") `); + await queryRunner.query(`CREATE INDEX "IDX_92255988735563f0fe4aba1f05" ON "__chart__test" ("span") `); + await queryRunner.query(`CREATE INDEX "IDX_d41cce6aee1a50bfc062038f9b" ON "__chart__test" ("group") `); + await queryRunner.query(`CREATE INDEX "IDX_c5870993e25c3d5771f91f5003" ON "__chart__test" ("span", "date") `); + await queryRunner.query(`CREATE INDEX "IDX_a319e5dbf47e8a17497623beae" ON "__chart__test" ("date", "group") `); + await queryRunner.query(`CREATE INDEX "IDX_f170de677ea75ad4533de2723e" ON "__chart__test" ("span", "date", "group") `); + await queryRunner.query(`CREATE INDEX "IDX_845254b3eaf708ae8a6cac3026" ON "__chart__users" ("date") `); + await queryRunner.query(`CREATE INDEX "IDX_7c184198ecf66a8d3ecb253ab3" ON "__chart__users" ("span") `); + await queryRunner.query(`CREATE INDEX "IDX_ed9b95919c672a13008e9487ee" ON "__chart__users" ("group") `); + await queryRunner.query(`CREATE INDEX "IDX_f091abb24193d50c653c6b77fc" ON "__chart__users" ("span", "date") `); + await queryRunner.query(`CREATE INDEX "IDX_337e9599f278bd7537fe30876f" ON "__chart__users" ("date", "group") `); + await queryRunner.query(`CREATE INDEX "IDX_a770a57c70e668cc61590c9161" ON "__chart__users" ("span", "date", "group") `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP INDEX "IDX_a770a57c70e668cc61590c9161"`); + await queryRunner.query(`DROP INDEX "IDX_337e9599f278bd7537fe30876f"`); + await queryRunner.query(`DROP INDEX "IDX_f091abb24193d50c653c6b77fc"`); + await queryRunner.query(`DROP INDEX "IDX_ed9b95919c672a13008e9487ee"`); + await queryRunner.query(`DROP INDEX "IDX_7c184198ecf66a8d3ecb253ab3"`); + await queryRunner.query(`DROP INDEX "IDX_845254b3eaf708ae8a6cac3026"`); + await queryRunner.query(`DROP INDEX "IDX_f170de677ea75ad4533de2723e"`); + await queryRunner.query(`DROP INDEX "IDX_a319e5dbf47e8a17497623beae"`); + await queryRunner.query(`DROP INDEX "IDX_c5870993e25c3d5771f91f5003"`); + await queryRunner.query(`DROP INDEX "IDX_d41cce6aee1a50bfc062038f9b"`); + await queryRunner.query(`DROP INDEX "IDX_92255988735563f0fe4aba1f05"`); + await queryRunner.query(`DROP INDEX "IDX_b070a906db04b44c67c6c2144d"`); + await queryRunner.query(`DROP INDEX "IDX_66e1e1ecd2f29e57778af35b59"`); + await queryRunner.query(`DROP INDEX "IDX_a0cd75442dd10d0643a17c4a49"`); + await queryRunner.query(`DROP INDEX "IDX_d70c86baedc68326be11f9c0ce"`); + await queryRunner.query(`DROP INDEX "IDX_bbfa573a8181018851ed0b6357"`); + await queryRunner.query(`DROP INDEX "IDX_5c73bf61da4f6e6f15bae88ed1"`); + await queryRunner.query(`DROP INDEX "IDX_437bab3c6061d90f6bb65fd2cc"`); + await queryRunner.query(`DROP INDEX "IDX_84e661abb7bd1e51b690d4b017"`); + await queryRunner.query(`DROP INDEX "IDX_b14489029e4b3aaf4bba5fb524"`); + await queryRunner.query(`DROP INDEX "IDX_a5133470f4825902e170328ca5"`); + await queryRunner.query(`DROP INDEX "IDX_234dff3c0b56a6150b95431ab9"`); + await queryRunner.query(`DROP INDEX "IDX_2be7ec6cebddc14dc11e206686"`); + await queryRunner.query(`DROP INDEX "IDX_0c641990ecf47d2545df4edb75"`); + await queryRunner.query(`DROP INDEX "IDX_e316f01a6d24eb31db27f88262"`); + await queryRunner.query(`DROP INDEX "IDX_229a41ad465f9205f1f5703291"`); + await queryRunner.query(`DROP INDEX "IDX_edeb73c09c3143a81bcb34d569"`); + await queryRunner.query(`DROP INDEX "IDX_8cf3156fd7a6b15c43459c6e3b"`); + await queryRunner.query(`DROP INDEX "IDX_65633a106bce43fc7c5c30a5c7"`); + await queryRunner.query(`DROP INDEX "IDX_f7bf4c62059764c2c2bb40fdab"`); + await queryRunner.query(`DROP INDEX "IDX_f68a5ab958f9f5fa17a32ac23b"`); + await queryRunner.query(`DROP INDEX "IDX_5048e9daccbbbc6d567bb142d3"`); + await queryRunner.query(`DROP INDEX "IDX_046feeb12e9ef5f783f409866a"`); + await queryRunner.query(`DROP INDEX "IDX_55bf20f366979f2436de99206b"`); + await queryRunner.query(`DROP INDEX "IDX_8d2cbbc8114d90d19b44d626b6"`); + await queryRunner.query(`DROP INDEX "IDX_84234bd1abb873f07329681c83"`); + await queryRunner.query(`DROP INDEX "IDX_4db3b84c7be0d3464714f3e0b1"`); + await queryRunner.query(`DROP INDEX "IDX_b77d4dd9562c3a899d9a286fcd"`); + await queryRunner.query(`DROP INDEX "IDX_57b5458d0d3d6d1e7f13d4e57f"`); + await queryRunner.query(`DROP INDEX "IDX_4b3593098b6edc9c5afe36b18b"`); + await queryRunner.query(`DROP INDEX "IDX_f92dd6d03f8d994f29987f6214"`); + await queryRunner.query(`DROP INDEX "IDX_7af07790712aa3438ff6773f3b"`); + await queryRunner.query(`DROP INDEX "IDX_f2aeafde2ae6fbad38e857631b"`); + await queryRunner.query(`DROP INDEX "IDX_30bf67687f483ace115c5ca642"`); + await queryRunner.query(`DROP INDEX "IDX_42ea9381f0fda8dfe0fa1c8b53"`); + await queryRunner.query(`DROP INDEX "IDX_e496ca8096d28f6b9b509264dc"`); + await queryRunner.query(`DROP INDEX "IDX_328f259961e60c4fa0bfcf55ca"`); + await queryRunner.query(`DROP INDEX "IDX_5f86db6492274e07c1a3cdf286"`); + await queryRunner.query(`DROP INDEX "IDX_924fc196c80ca24bae01dd37e4"`); + await queryRunner.query(`DROP INDEX "IDX_f09d543e3acb16c5976bdb31fa"`); + await queryRunner.query(`DROP INDEX "IDX_0c9a159c5082cbeef3ca6706b5"`); + await queryRunner.query(`DROP INDEX "IDX_7036f2957151588b813185c794"`); + await queryRunner.query(`DROP INDEX "IDX_e69096589f11e3baa98ddd64d0"`); + await queryRunner.query(`DROP INDEX "IDX_42eb716a37d381cdf566192b2b"`); + await queryRunner.query(`DROP INDEX "IDX_9ff6944f01acb756fdc92d7563"`); + await queryRunner.query(`DROP INDEX "IDX_0a905b992fecd2b5c3fb98759e"`); + await queryRunner.query(`DROP INDEX "IDX_08fac0eb3b11f04c200c0b40dd"`); + await queryRunner.query(`DROP INDEX "IDX_7b5da130992ec9df96712d4290"`); + await queryRunner.query(`DROP INDEX "IDX_f8dd01baeded2ffa833e0a610a"`); + await queryRunner.query(`DROP INDEX "IDX_a1efd3e0048a5f2793a47360dc"`); + await queryRunner.query(`DROP INDEX "IDX_f5448d9633cff74208d850aabe"`); + await queryRunner.query(`DROP INDEX "IDX_39ee857ab2f23493037c6b6631"`); + await queryRunner.query(`DROP INDEX "IDX_d0a4f79af5a97b08f37b547197"`); + await queryRunner.query(`DROP INDEX "IDX_da8a46ba84ca1d8bb5a29bfb63"`); + await queryRunner.query(`DROP INDEX "IDX_c12f0af4a66cdd30c2287ce8aa"`); + await queryRunner.query(`DROP INDEX "IDX_6b8f34a1a64b06014b6fb66824"`); + await queryRunner.query(`DROP INDEX "IDX_6d6f156ceefc6bc5f273a0e370"`); + await queryRunner.query(`DROP INDEX "IDX_25a97c02003338124b2b75fdbc"`); + await queryRunner.query(`DROP INDEX "IDX_49975586f50ed7b800fdd88fbd"`); + await queryRunner.query(`DROP INDEX "IDX_99a7d2faaef84a6f728d714ad6"`); + await queryRunner.query(`DROP INDEX "IDX_fcc181fb8283009c61cc4083ef"`); + await queryRunner.query(`DROP INDEX "IDX_07747a1038c05f532a718fe1de"`); + await queryRunner.query(`DROP INDEX "IDX_e9cd07672b37d8966cf3709283"`); + await queryRunner.query(`DROP INDEX "IDX_dd907becf76104e4b656659e6b"`); + await queryRunner.query(`DROP INDEX "IDX_2d416e6af791a82e338c79d480"`); + await queryRunner.query(`DROP INDEX "IDX_76e87c7bfc5d925fcbba405d84"`); + await queryRunner.query(`DROP INDEX "IDX_e447064455928cf627590ef527"`); + await queryRunner.query(`DROP INDEX "IDX_36cb699c49580d4e6c2e6159f9"`); + await queryRunner.query(`DROP INDEX "IDX_06690fc959f1c9fdaf21928222"`); + await queryRunner.query(`DROP INDEX "IDX_3313d7288855ec105b5bbf6c21"`); + await queryRunner.query(`DROP INDEX "IDX_6e1df243476e20cbf86572ecc0"`); + await queryRunner.query(`DROP INDEX "IDX_7a170f67425e62a8fabb76c872"`); + await queryRunner.query(`DROP INDEX "IDX_3fa0d0f17ca72e3dc80999a032"`); + await queryRunner.query(`DROP INDEX "IDX_13565815f618a1ff53886c5b28"`); + await queryRunner.query(`DROP INDEX "IDX_c26e2c1cbb6e911e0554b27416"`); + await queryRunner.query(`DROP INDEX "IDX_9a3ed15a30ab7e3a37702e6e08"`); + await queryRunner.query(`DROP INDEX "IDX_20f57cc8f142c131340ee16742"`); + await queryRunner.query(`DROP INDEX "IDX_00ed5f86db1f7efafb1978bf21"`); + await queryRunner.query(`DROP INDEX "IDX_15e91a03aeeac9dbccdf43fc06"`); + await queryRunner.query(`DROP INDEX "IDX_0ad37b7ef50f4ddc84363d7ccc"`); + await queryRunner.query(`DROP INDEX "IDX_90148bbc2bf0854428786bfc15"`); + await queryRunner.query(`DROP INDEX "IDX_88937d94d7443d9a99a76fa5c0"`); + await queryRunner.query(`DROP INDEX "IDX_54ebcb6d27222913b908d56fd8"`); + await queryRunner.query(`DROP INDEX "IDX_796a8c03959361f97dc2be1d5c"`); + await queryRunner.query(`DROP INDEX "IDX_25dfc71b0369b003a4cd434d0b"`); + await queryRunner.query(`DROP INDEX "IDX_51c063b6a133a9cb87145450f5"`); + await queryRunner.query(`DROP INDEX "IDX_fa99d777623947a5b05f394cae"`); + await queryRunner.query(`DROP INDEX "IDX_315c779174fe8247ab324f036e"`); + await queryRunner.query(`DROP INDEX "IDX_c5d46cbfda48b1c33ed852e21b"`); + await queryRunner.query(`DROP INDEX "IDX_8cb40cfc8f3c28261e6f887b03"`); + } + +} diff --git a/src/ormconfig.ts b/ormconfig.js similarity index 58% rename from src/ormconfig.ts rename to ormconfig.js index 6b7297b2c3..8a0196947e 100644 --- a/src/ormconfig.ts +++ b/ormconfig.js @@ -1,7 +1,7 @@ -import * as fs from 'fs'; -import config from './config'; +const config = require('./built/config').default; +const entities = require('./built/db/postgre').entities; -const json = { +module.exports = { type: 'postgres', host: config.db.host, port: config.db.port, @@ -9,11 +9,9 @@ const json = { password: config.db.pass, database: config.db.db, extra: config.db.extra, - entities: ['src/models/entities/*.ts'], + entities: entities, migrations: ['migration/*.ts'], cli: { migrationsDir: 'migration' } }; - -fs.writeFileSync('ormconfig.json', JSON.stringify(json)); diff --git a/package.json b/package.json index 264b50d663..bd0d551ea1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "misskey", "author": "syuilo ", - "version": "11.23.1", + "version": "11.24.0", "codename": "daybreak", "repository": { "type": "git", @@ -11,9 +11,9 @@ "private": true, "scripts": { "start": "node ./index.js", - "init": "node ./built/init.js", + "init": "npm run migrate", "ormconfig": "node ./built/ormconfig.js", - "migrate": "npm run ormconfig && ts-node ./node_modules/typeorm/cli.js migration:run", + "migrate": "ts-node ./node_modules/typeorm/cli.js migration:run", "migrateandstart": "npm run migrate && npm run start", "build": "webpack && gulp build", "webpack": "webpack", @@ -30,105 +30,109 @@ "video-thumbnail-generator/lodash": "^4.17.11" }, "dependencies": { - "@elastic/elasticsearch": "7.0.0-rc.2", - "@fortawesome/fontawesome-svg-core": "1.2.15", - "@fortawesome/free-brands-svg-icons": "5.7.2", - "@fortawesome/free-regular-svg-icons": "5.7.2", - "@fortawesome/free-solid-svg-icons": "5.7.2", - "@fortawesome/vue-fontawesome": "0.1.5", - "@koa/cors": "2.2.3", + "@elastic/elasticsearch": "7.1.0", + "@fortawesome/fontawesome-svg-core": "1.2.19", + "@fortawesome/free-brands-svg-icons": "5.9.0", + "@fortawesome/free-regular-svg-icons": "5.9.0", + "@fortawesome/free-solid-svg-icons": "5.9.0", + "@fortawesome/vue-fontawesome": "0.1.6", + "@koa/cors": "3.0.0", + "@typescript-eslint/parser": "1.11.0", "@types/bcryptjs": "2.4.2", - "@types/bull": "3.5.11", + "@types/bull": "3.5.15", + "@types/cbor": "2.0.0", "@types/dateformat": "3.0.0", "@types/deep-equal": "1.0.1", - "@types/double-ended-queue": "2.1.0", + "@types/double-ended-queue": "2.1.1", "@types/gulp": "4.0.6", "@types/gulp-mocha": "0.0.32", "@types/gulp-rename": "0.0.33", "@types/gulp-replace": "0.0.31", "@types/gulp-uglify": "3.0.6", "@types/gulp-util": "3.0.34", - "@types/is-root": "1.0.0", + "@types/is-root": "2.1.2", "@types/is-url": "1.2.28", "@types/js-yaml": "3.12.1", - "@types/jsdom": "12.2.3", + "@types/jsdom": "12.2.4", "@types/katex": "0.10.1", - "@types/koa": "2.0.48", + "@types/koa": "2.0.49", "@types/koa-bodyparser": "5.0.2", "@types/koa-compress": "2.0.9", "@types/koa-cors": "0.0.0", "@types/koa-favicon": "2.0.19", "@types/koa-logger": "3.1.1", - "@types/koa-mount": "3.0.1", + "@types/koa-mount": "4.0.0", "@types/koa-multer": "1.0.0", - "@types/koa-router": "7.0.40", + "@types/koa-router": "7.0.42", "@types/koa-send": "4.1.2", "@types/koa-views": "2.0.3", "@types/koa__cors": "2.2.3", "@types/lolex": "3.1.1", - "@types/minio": "7.0.1", - "@types/mocha": "5.2.6", - "@types/node": "11.13.8", - "@types/nodemailer": "4.6.8", - "@types/nprogress": "0.0.29", + "@types/minio": "7.0.2", + "@types/mocha": "5.2.7", + "@types/node": "12.0.10", + "@types/nodemailer": "6.2.0", + "@types/nprogress": "0.2.0", "@types/oauth": "0.9.1", "@types/parse5": "5.0.0", "@types/parsimmon": "1.10.0", "@types/portscanner": "2.1.0", "@types/pug": "2.0.4", - "@types/qrcode": "1.3.2", + "@types/qrcode": "1.3.3", "@types/random-seed": "0.3.3", "@types/ratelimiter": "2.1.28", - "@types/redis": "2.8.12", + "@types/redis": "2.8.13", "@types/rename": "1.0.1", "@types/request": "2.48.1", "@types/request-promise-native": "1.0.16", "@types/request-stats": "3.0.0", "@types/rimraf": "2.0.2", "@types/seedrandom": "2.4.28", - "@types/sharp": "0.22.1", - "@types/showdown": "1.9.2", + "@types/sharp": "0.22.2", + "@types/showdown": "1.9.3", "@types/speakeasy": "2.0.4", "@types/systeminformation": "3.23.1", - "@types/tinycolor2": "1.4.1", + "@types/tinycolor2": "1.4.2", "@types/tmp": "0.1.0", - "@types/uuid": "3.4.4", + "@types/uuid": "3.4.5", "@types/web-push": "3.3.0", - "@types/webpack": "4.4.29", + "@types/webpack": "4.4.34", "@types/webpack-stream": "3.2.10", "@types/websocket": "0.0.40", "@types/ws": "6.0.1", "animejs": "3.0.1", - "apexcharts": "3.8.0", + "apexcharts": "3.8.1", "autobind-decorator": "2.4.0", "autosize": "4.0.2", "autwh": "0.1.0", "bcryptjs": "2.4.3", + "bootstrap": "4.3.1", "bootstrap-vue": "2.0.0-rc.13", "bull": "3.10.0", "cafy": "15.1.1", + "cbor": "4.1.5", "chai": "4.2.0", "chalk": "2.4.2", "cli-highlight": "2.1.1", "commander": "2.20.0", "content-disposition": "0.5.3", "crc-32": "1.2.0", - "css-loader": "2.1.1", + "css-loader": "3.0.0", "cssnano": "4.1.10", "dateformat": "3.0.3", "deep-equal": "1.0.1", - "diskusage": "1.1.1", + "diskusage": "1.1.2", "double-ended-queue": "2.1.0-0", "emojilib": "2.4.0", - "eslint": "5.16.0", - "eslint-plugin-vue": "5.2.2", - "eventemitter3": "3.1.2", - "feed": "2.0.4", - "file-type": "10.11.0", + "eslint": "6.0.1", + "eslint-plugin-vue": "5.2.3", + "eventemitter3": "4.0.0", + "feed": "3.0.0", + "file-type": "12.0.0", "fuckadblock": "3.2.1", "gulp": "4.0.2", "gulp-cssnano": "2.1.3", - "gulp-imagemin": "5.0.3", + "gulp-imagemin": "6.0.0", "gulp-mocha": "6.0.0", "gulp-rename": "1.4.0", "gulp-replace": "1.0.0", @@ -147,7 +151,8 @@ "js-yaml": "3.13.1", "jsdom": "15.1.1", "json5": "2.1.0", - "json5-loader": "2.0.0", + "json5-loader": "3.0.0", + "jsrsasign": "8.0.12", "katex": "0.10.2", "koa": "2.7.0", "koa-bodyparser": "4.2.1", @@ -163,14 +168,14 @@ "koa-views": "6.2.0", "langmap": "0.0.16", "loader-utils": "1.2.3", - "lolex": "3.1.0", + "lolex": "4.1.0", "lookup-dns-cache": "2.1.0", "minio": "7.0.10", "mocha": "6.1.4", "moji": "0.5.1", "moment": "2.24.0", "ms": "2.1.2", - "nested-property": "0.0.7", + "nested-property": "1.0.1", "node-fetch": "2.6.0", "nodemailer": "6.2.1", "nprogress": "0.2.0", @@ -186,7 +191,7 @@ "promise-any": "0.2.0", "promise-limit": "2.7.0", "promise-sequential": "1.1.1", - "pug": "2.0.3", + "pug": "2.0.4", "punycode": "2.1.1", "pureimage": "0.1.6", "qrcode": "1.3.3", @@ -210,12 +215,12 @@ "showdown": "1.9.0", "showdown-highlightjs-extension": "0.1.2", "speakeasy": "2.0.0", - "stringz": "1.0.0", + "stringz": "2.0.0", "style-loader": "0.23.1", "stylus": "0.54.5", "stylus-loader": "3.0.2", "summaly": "2.3.0", - "systeminformation": "4.11.1", + "systeminformation": "4.13.1", "syuilo-password-strength": "0.0.1", "terser-webpack-plugin": "1.3.0", "textarea-caret": "3.1.0", @@ -223,14 +228,13 @@ "tmp": "0.1.0", "ts-loader": "5.3.3", "ts-node": "7.0.1", - "tslint": "5.15.0", + "tslint": "5.18.0", "tslint-sonarts": "1.9.0", - "typeorm": "0.2.16-rc.1", - "typescript": "3.3.3333", - "typescript-eslint-parser": "22.0.0", + "typeorm": "0.2.18", + "typescript": "3.5.2", "uglify-es": "3.3.9", "ulid": "2.3.0", - "url-loader": "1.1.2", + "url-loader": "2.0.1", "uuid": "3.3.2", "v-animate-css": "0.0.3", "v-debounce": "0.1.2", @@ -238,7 +242,7 @@ "vue": "2.6.10", "vue-color": "2.7.0", "vue-content-loading": "1.6.0", - "vue-cropperjs": "3.0.0", + "vue-cropperjs": "4.0.0", "vue-i18n": "8.11.2", "vue-js-modal": "1.3.31", "vue-json-pretty": "1.6.0", @@ -248,17 +252,17 @@ "vue-router": "3.0.6", "vue-sequential-entrance": "1.1.3", "vue-style-loader": "4.1.2", - "vue-svg-inline-loader": "1.2.15", + "vue-svg-inline-loader": "1.2.16", "vue-template-compiler": "2.6.10", - "vuedraggable": "2.21.0", + "vuedraggable": "2.23.0", "vuewordcloud": "18.7.11", "vuex": "3.1.1", "vuex-persistedstate": "2.5.4", "web-push": "3.3.5", - "webpack": "4.34.0", - "webpack-cli": "3.3.4", + "webpack": "4.35.2", + "webpack-cli": "3.3.5", "websocket": "1.0.28", - "ws": "7.0.0", + "ws": "7.0.1", "xev": "2.0.1" } } diff --git a/src/@types/jsrsasign.d.ts b/src/@types/jsrsasign.d.ts new file mode 100644 index 0000000000..55bebd9bfb --- /dev/null +++ b/src/@types/jsrsasign.d.ts @@ -0,0 +1,803 @@ +// Attention: Partial Type Definition + +declare module 'jsrsasign' { + //// HELPER TYPES + + /** + * Attention: The value might be changed by the function. + */ + type Mutable = T; + + /** + * Deprecated: The function might be deleted in future release. + */ + type Deprecated = T; + + //// COMMON TYPES + + /** + * byte number + */ + type ByteNumber = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255; + + /** + * hexadecimal string /[0-9A-F]/ + */ + type HexString = string; + + /** + * binary string /[01]/ + */ + type BinString = string; + + /** + * base64 string /[A-Za-z0-9+/]=+/ + */ + type Base64String = string; + + /** + * base64 URL encoded string /[A-Za-z0-9_-]/ + */ + type Base64URLString = string; + + /** + * time value (ex. "151231235959Z") + */ + type TimeValue = string; + + /** + * OID string (ex. '1.2.3.4.567') + */ + type OID = string; + + /** + * OID name + */ + type OIDName = string; + + /** + * PEM formatted string + */ + type PEM = string; + + //// ASN1 TYPES + + class ASN1Object { + public isModified: boolean; + + public hTLV: ASN1TLV; + + public hT: ASN1T; + + public hL: ASN1L; + + public hV: ASN1V; + + public getLengthHexFromValue(): HexString; + + public getEncodedHex(): ASN1TLV; + + public getValueHex(): ASN1V; + + public getFreshValueHex(): ASN1V; + } + + class DERAbstractStructured extends ASN1Object { + constructor(params?: Partial>); + + public setByASN1ObjectArray(asn1ObjectArray: ASN1Object[]): void; + + public appendASN1Object(asn1Object: ASN1Object): void; + } + + class DERSequence extends DERAbstractStructured { + constructor(params?: Partial>); + + public getFreshValueHex(): ASN1V; + } + + //// ASN1HEX TYPES + + /** + * ASN.1 DER encoded data (hexadecimal string) + */ + type ASN1S = HexString; + + /** + * index of something + */ + type Idx = ASN1S extends { [idx: string]: unknown } ? string : ASN1S extends { [idx: number]: unknown } ? number : never; + + /** + * byte length of something + */ + type ByteLength = T['length']; + + /** + * ASN.1 L(length) (hexadecimal string) + */ + type ASN1L = HexString; + + /** + * ASN.1 T(tag) (hexadecimal string) + */ + type ASN1T = HexString; + + /** + * ASN.1 V(value) (hexadecimal string) + */ + type ASN1V = HexString; + + /** + * ASN.1 TLV (hexadecimal string) + */ + type ASN1TLV = HexString; + + /** + * ASN.1 object string + */ + type ASN1ObjectString = string; + + /** + * nth + */ + type Nth = number; + + /** + * ASN.1 DER encoded OID value (hexadecimal string) + */ + type ASN1OIDV = HexString; + + class ASN1HEX { + public static getLblen(s: ASN1S, idx: Idx): ByteLength; + + public static getL(s: ASN1S, idx: Idx): ASN1L; + + public static getVblen(s: ASN1S, idx: Idx): ByteLength; + + public static getVidx(s: ASN1S, idx: Idx): Idx; + + public static getV(s: ASN1S, idx: Idx): ASN1V; + + public static getTLV(s: ASN1S, idx: Idx): ASN1TLV; + + public static getNextSiblingIdx(s: ASN1S, idx: Idx): Idx; + + public static getChildIdx(h: ASN1S, pos: Idx): Idx[]; + + public static getNthChildIdx(h: ASN1S, idx: Idx, nth: Nth): Idx; + + public static getIdxbyList(h: ASN1S, currentIndex: Idx, nthList: Mutable, checkingTag?: string): Idx>; + + public static getTLVbyList(h: ASN1S, currentIndex: Idx, nthList: Mutable, checkingTag?: string): ASN1TLV; + + public static getVbyList(h: ASN1S, currentIndex: Idx, nthList: Mutable, checkingTag?: string, removeUnusedbits?: boolean): ASN1V; + + public static hextooidstr(hex: ASN1OIDV): OID; + + public static dump(hexOrObj: ASN1S | ASN1Object, flags?: Record, idx?: Idx, indent?: string): string; + + public static isASN1HEX(hex: string): hex is HexString; + + public static oidname(oidDotOrHex: OID | ASN1OIDV): OIDName; + } + + //// BIG INTEGER TYPES (PARTIAL) + + class BigInteger { + constructor(a: null); + + constructor(a: number, b: SecureRandom); + + constructor(a: number, b: number, c: SecureRandom); + + constructor(a: unknown); + + constructor(a: string, b: number); + + public am(i: number, x: number, w: number, j: number, c: number, n: number): number; + + public DB: number; + + public DM: number; + + public DV: number; + + public FV: number; + + public F1: number; + + public F2: number; + + protected copyTo(r: Mutable): void; + + protected fromInt(x: number): void; + + protected fromString(s: string, b: number): void; + + protected clamp(): void; + + public toString(b: number): string; + + public negate(): BigInteger; + + public abs(): BigInteger; + + public compareTo(a: BigInteger): number; + + public bitLength(): number; + + protected dlShiftTo(n: number, r: Mutable): void; + + protected drShiftTo(n: number, r: Mutable): void; + + protected lShiftTo(n: number, r: Mutable): void; + + protected rShiftTo(n: number, r: Mutable): void; + + protected subTo(a: BigInteger, r: Mutable): void; + + protected multiplyTo(a: BigInteger, r: Mutable): void; + + protected squareTo(r: Mutable): void; + + protected divRemTo(m: BigInteger, q: Mutable, r: Mutable): void; + + public mod(a: BigInteger): BigInteger; + + protected invDigit(): number; + + protected isEven(): boolean; + + protected exp(e: number, z: Classic | Montgomery): BigInteger; + + public modPowInt(e: number, m: BigInteger): BigInteger; + + public static ZERO: BigInteger; + + public static ONE: BigInteger; + } + + class Classic { + constructor(m: BigInteger); + + public convert(x: BigInteger): BigInteger; + + public revert(x: BigInteger): BigInteger; + + public reduce(x: Mutable): void; + + public mulTo(x: BigInteger, r: Mutable): void; + + public sqrTo(x: BigInteger, y: BigInteger, r: Mutable): void; + } + + class Montgomery { + constructor(m: BigInteger); + + public convert(x: BigInteger): BigInteger; + + public revert(x: BigInteger): BigInteger; + + public reduce(x: Mutable): void; + + public mulTo(x: BigInteger, r: Mutable): void; + + public sqrTo(x: BigInteger, y: BigInteger, r: Mutable): void; + } + + //// KEYUTIL TYPES + + type DecryptAES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString; + + type Decrypt3DES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString; + + type DecryptDES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString; + + type EncryptAES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString; + + type Encrypt3DES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString; + + type EncryptDES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString; + + type AlgList = { + 'AES-256-CBC': { 'proc': DecryptAES; 'eproc': EncryptAES; keylen: 32; ivlen: 16; }; + 'AES-192-CBC': { 'proc': DecryptAES; 'eproc': EncryptAES; keylen: 24; ivlen: 16; }; + 'AES-128-CBC': { 'proc': DecryptAES; 'eproc': EncryptAES; keylen: 16; ivlen: 16; }; + 'DES-EDE3-CBC': { 'proc': Decrypt3DES; 'eproc': Encrypt3DES; keylen: 24; ivlen: 8; }; + 'DES-CBC': { 'proc': DecryptDES; 'eproc': EncryptDES; keylen: 8; ivlen: 8; }; + }; + + type AlgName = keyof AlgList; + + type PEMHeadAlgName = 'RSA' | 'EC' | 'DSA'; + + type GetKeyRSAParam = RSAKey | { + n: BigInteger; + e: number; + } | Record<'n' | 'e', HexString> | Record<'n' | 'e', HexString> & Record<'d' | 'p' | 'q' | 'dp' | 'dq' | 'co', HexString | null> | { + n: BigInteger; + e: number; + d: BigInteger; + } | { + kty: 'RSA'; + } & Record<'n' | 'e', Base64URLString> | { + kty: 'RSA'; + } & Record<'n' | 'e' | 'd' | 'p' | 'q' | 'dp' | 'dq' | 'qi', Base64URLString> | { + kty: 'RSA'; + } & Record<'n' | 'e' | 'd', Base64URLString>; + + type GetKeyECDSAParam = KJUR.crypto.ECDSA | { + curve: KJUR.crypto.CurveName; + xy: HexString; + } | { + curve: KJUR.crypto.CurveName; + d: HexString; + } | { + kty: 'EC'; + crv: KJUR.crypto.CurveName; + x: Base64URLString; + y: Base64URLString; + } | { + kty: 'EC'; + crv: KJUR.crypto.CurveName; + x: Base64URLString; + y: Base64URLString; + d: Base64URLString; + }; + + type GetKeyDSAParam = KJUR.crypto.DSA | Record<'p' | 'q' | 'g', BigInteger> & Record<'y', BigInteger | null> | Record<'p' | 'q' | 'g' | 'x', BigInteger> & Record<'y', BigInteger | null>; + + type GetKeyParam = GetKeyRSAParam | GetKeyECDSAParam | GetKeyDSAParam | string; + + class KEYUTIL { + public version: '1.0.0'; + + public parsePKCS5PEM(sPKCS5PEM: PEM): Partial> & (Record<'cipher' | 'ivsalt', string> | Record<'cipher' | 'ivsalt', undefined>); + + public getKeyAndUnusedIvByPasscodeAndIvsalt(algName: AlgName, passcode: string, ivsaltHex: HexString): Record<'keyhex' | 'ivhex', HexString>; + + public decryptKeyB64(privateKeyB64: Base64String, sharedKeyAlgName: AlgName, sharedKeyHex: HexString, ivsaltHex: HexString): Base64String; + + public getDecryptedKeyHex(sEncryptedPEM: PEM, passcode: string): HexString; + + public getEncryptedPKCS5PEMFromPrvKeyHex(pemHeadAlg: PEMHeadAlgName, hPrvKey: string, passcode: string, sharedKeyAlgName?: AlgName | null, ivsaltHex?: HexString | null): PEM; + + public parseHexOfEncryptedPKCS8(sHEX: HexString): { + ciphertext: ASN1V; + encryptionSchemeAlg: 'TripleDES'; + encryptionSchemeIV: ASN1V; + pbkdf2Salt: ASN1V; + pbkdf2Iter: number; + }; + + public getPBKDF2KeyHexFromParam(info: ReturnType, passcode: string): HexString; + + private _getPlainPKCS8HexFromEncryptedPKCS8PEM(pkcs8PEM: PEM, passcode: string): HexString; + + public getKeyFromEncryptedPKCS8PEM(prvKeyHex: HexString): ReturnType; + + public parsePlainPrivatePKCS8Hex(pkcs8PrvHex: HexString): { + algparam: ASN1V | null; + algoid: ASN1V; + keyidx: Idx; + }; + + public getKeyFromPlainPrivatePKCS8PEM(prvKeyHex: HexString): ReturnType; + + public getKeyFromPlainPrivatePKCS8Hex(prvKeyHex: HexString): RSAKey | KJUR.crypto.DSA | KJUR.crypto.ECDSA; + + private _getKeyFromPublicPKCS8Hex(h: HexString): RSAKey | KJUR.crypto.DSA | KJUR.crypto.ECDSA; + + public parsePublicRawRSAKeyHex(pubRawRSAHex: HexString): Record<'n' | 'e', ASN1V>; + + public parsePublicPKCS8Hex(pkcs8PubHex: HexString): { + algparam: ASN1V | Record<'p' | 'q' | 'g', ASN1V> | null; + algoid: ASN1V; + key: ASN1V; + }; + + public static getKey(param: GetKeyRSAParam): RSAKey; + + public static getKey(param: GetKeyECDSAParam): KJUR.crypto.ECDSA; + + public static getKey(param: GetKeyDSAParam): KJUR.crypto.DSA; + + public static getKey(param: string, passcode?: string, hextype?: string): RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA; + + public static generateKeypair(alg: 'RSA', keylen: number): Record<'prvKeyObj' | 'pubKeyObj', RSAKey>; + + public static generateKeypair(alg: 'EC', curve: KJUR.crypto.CurveName): Record<'prvKeyObj' | 'pubKeyObj', KJUR.crypto.ECDSA>; + + public static getPEM(keyObjOrHex: RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA, formatType?: 'PKCS1PRV' | 'PKCS5PRV' | 'PKCS8PRV', passwd?: string, encAlg?: 'DES-CBC' | 'DES-EDE3-CBC' | 'AES-128-CBC' | 'AES-192-CBC' | 'AES-256-CBC', hexType?: string, ivsaltHex?: HexString): object; // To Do + + public static getKeyFromCSRPEM(csrPEM: PEM): RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA; + + public static getKeyFromCSRHex(csrHex: HexString): RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA; + + public static parseCSRHex(csrHex: HexString): Record<'p8pubkeyhex', ASN1TLV>; + + public static getJWKFromKey(keyObj: RSAKey): { + kty: 'RSA'; + } & Record<'n' | 'e' | 'd' | 'p' | 'q' | 'dp' | 'dq' | 'qi', Base64URLString> | { + kty: 'RSA'; + } & Record<'n' | 'e', Base64URLString>; + + public static getJWKFromKey(keyObj: KJUR.crypto.ECDSA): { + kty: 'EC'; + crv: KJUR.crypto.CurveName; + x: Base64URLString; + y: Base64URLString; + d: Base64URLString; + } | { + kty: 'EC'; + crv: KJUR.crypto.CurveName; + x: Base64URLString; + y: Base64URLString; + }; + } + + //// KJUR NAMESPACE (PARTIAL) + + namespace KJUR { + namespace crypto { + type CurveName = 'secp128r1' | 'secp160k1' | 'secp160r1' | 'secp192k1' | 'secp192r1' | 'secp224r1' | 'secp256k1' | 'secp256r1' | 'secp384r1' | 'secp521r1'; + + class DSA { + public p: BigInteger | null; + + public q: BigInteger | null; + + public g: BigInteger | null; + + public y: BigInteger | null; + + public x: BigInteger | null; + + public type: 'DSA'; + + public isPrivate: boolean; + + public isPublic: boolean; + + public setPrivate(p: BigInteger, q: BigInteger, g: BigInteger, y: BigInteger | null, x: BigInteger): void; + + public setPrivateHex(hP: HexString, hQ: HexString, hG: HexString, hY: HexString | null, hX: HexString): void; + + public setPublic(p: BigInteger, q: BigInteger, g: BigInteger, y: BigInteger): void; + + public setPublicHex(hP: HexString, hQ: HexString, hG: HexString, hY: HexString): void; + + public signWithMessageHash(sHashHex: HexString): HexString; + + public verifyWithMessageHash(sHashHex: HexString, hSigVal: HexString): boolean; + + public parseASN1Signature(hSigVal: HexString): [BigInteger, BigInteger]; + + public readPKCS5PrvKeyHex(h: HexString): void; + + public readPKCS8PrvKeyHex(h: HexString): void; + + public readPKCS8PubKeyHex(h: HexString): void; + + public readCertPubKeyHex(h: HexString, nthPKI: number): void; + } + + class ECDSA { + constructor(params?: { + curve?: CurveName; + prv?: HexString; + pub?: HexString; + }); + + public p: BigInteger | null; + + public q: BigInteger | null; + + public g: BigInteger | null; + + public y: BigInteger | null; + + public x: BigInteger | null; + + public type: 'EC'; + + public isPrivate: boolean; + + public isPublic: boolean; + + public getBigRandom(limit: BigInteger): BigInteger; + + public setNamedCurve(curveName: CurveName): void; + + public setPrivateKeyHex(prvKeyHex: HexString): void; + + public setPublicKeyHex(pubKeyHex: HexString): void; + + public getPublicKeyXYHex(): Record<'x' | 'y', HexString>; + + public getShortNISTPCurveName(): 'P-256' | 'P-384' | null; + + public generateKeyPairHex(): Record<'ecprvhex' | 'ecpubhex', HexString>; + + public signWithMessageHash(hashHex: HexString): HexString; + + public signHex(hashHex: HexString, privHex: HexString): HexString; + + public verifyWithMessageHash(sHashHex: HexString, hSigVal: HexString): boolean; + + public parseASN1Signature(hSigVal: HexString): [BigInteger, BigInteger]; + + public readPKCS5PrvKeyHex(h: HexString): void; + + public readPKCS8PrvKeyHex(h: HexString): void; + + public readPKCS8PubKeyHex(h: HexString): void; + + public readCertPubKeyHex(h: HexString, nthPKI: number): void; + + public static parseSigHex(sigHex: HexString): Record<'r' | 's', BigInteger>; + + public static parseSigHexInHexRS(sigHex: HexString): Record<'r' | 's', ASN1V>; + + public static asn1SigToConcatSig(asn1Sig: HexString): HexString; + + public static concatSigToASN1Sig(concatSig: HexString): ASN1TLV; + + public static hexRSSigToASN1Sig(hR: HexString, hS: HexString): ASN1TLV; + + public static biRSSigToASN1Sig(biR: BigInteger, biS: BigInteger): ASN1TLV; + + public static getName(s: CurveName | HexString): 'secp256r1' | 'secp256k1' | 'secp384r1' | null; + } + + class Signature { + constructor(params?: ({ + alg: string; + prov?: string; + } | {}) & ({ + psssaltlen: number; + } | {}) & ({ + prvkeypem: PEM; + prvkeypas?: never; + } | {})); + + private _setAlgNames(): void; + + private _zeroPaddingOfSignature(hex: HexString, bitLength: number): HexString; + + public setAlgAndProvider(alg: string, prov: string): void; + + public init(key: GetKeyParam, pass?: string): void; + + public updateString(str: string): void; + + public updateHex(hex: HexString): void; + + public sign(): HexString; + + public signString(str: string): HexString; + + public signHex(hex: HexString): HexString; + + public verify(hSigVal: string): boolean | 0; + } + } + } + + //// RSAKEY TYPES + + class RSAKey { + public n: BigInteger | null; + + public e: number; + + public d: BigInteger | null; + + public p: BigInteger | null; + + public q: BigInteger | null; + + public dmp1: BigInteger | null; + + public dmq1: BigInteger | null; + + public coeff: BigInteger | null; + + public type: 'RSA'; + + public isPrivate?: boolean; + + public isPublic?: boolean; + + //// RSA PUBLIC + + protected doPublic(x: BigInteger): BigInteger; + + public setPublic(N: BigInteger, E: number): void; + + public setPublic(N: HexString, E: HexString): void; + + public encrypt(text: string): HexString | null; + + public encryptOAEP(text: string, hash?: string, hashLen?: number): HexString | null; + + public encryptOAEP(text: string, hash?: (s: string) => string, hashLen?: number): HexString | null; + + //// RSA PRIVATE + + protected doPrivate(x: BigInteger): BigInteger; + + public setPrivate(N: BigInteger, E: number, D: BigInteger): void; + + public setPrivate(N: HexString, E: HexString, D: HexString): void; + + public setPrivateEx(N: HexString, E: HexString, D?: HexString | null, P?: HexString | null, Q?: HexString | null, DP?: HexString | null, DQ?: HexString | null, C?: HexString | null): void; + + public generate(B: number, E: HexString): void; + + public decrypt(ctext: HexString): string; + + public decryptOAEP(ctext: HexString, hash?: string, hashLen?: number): string | null; + + public encryptOAEP(ctext: HexString, hash?: (s: string) => string, hashLen?: number): string | null; + + //// RSA PEM + + public getPosArrayOfChildrenFromHex(hPrivateKey: PEM): Idx[]; + + public getHexValueArrayOfChildrenFromHex(hPrivateKey: PEM): Idx[]; + + public readPrivateKeyFromPEMString(keyPEM: PEM): void; + + public readPKCS5PrvKeyHex(h: HexString): void; + + public readPKCS8PrvKeyHex(h: HexString): void; + + public readPKCS5PubKeyHex(h: HexString): void; + + public readPKCS8PubKeyHex(h: HexString): void; + + public readCertPubKeyHex(h: HexString, nthPKI: Nth): void; + + //// RSA SIGN + + public sign(s: string, hashAlg: string): HexString; + + public signWithMessageHash(sHashHex: HexString, hashAlg: string): HexString; + + public signPSS(s: string, hashAlg: string, sLen: number): HexString; + + public signWithMessageHashPSS(hHash: HexString, hashAlg: string, sLen: number): HexString; + + public verify(sMsg: string, hSig: HexString): boolean | 0; + + public verifyWithMessageHash(sHashHex: HexString, hSig: HexString): boolean | 0; + + public verifyPSS(sMsg: string, hSig: HexString, hashAlg: string, sLen: number): boolean; + + public verifyWithMessageHashPSS(hHash: HexString, hSig: HexString, hashAlg: string, sLen: number): boolean; + + public static SALT_LEN_HLEN: -1; + + public static SALT_LEN_MAX: -2; + + public static SALT_LEN_RECOVER: -2; + } + + /// RNG TYPES + class SecureRandom { + public nextBytes(ba: Mutable): void; + } + + //// X509 TYPES + + type ExtInfo = { + critical: boolean; + oid: OID; + vidx: Idx; + }; + + type ExtAIAInfo = Record<'ocsp' | 'caissuer', string>; + + type ExtCertificatePolicy = { + id: OIDName; + } & Partial<{ + cps: string; + } | { + unotice: string; + }>; + + class X509 { + public hex: HexString | null; + + public version: number; + + public foffset: number; + + public aExtInfo: null; + + public getVersion(): number; + + public getSerialNumberHex(): ASN1V; + + public getSignatureAlgorithmField(): OIDName; + + public getIssuerHex(): ASN1TLV; + + public getIssuerString(): HexString; + + public getSubjectHex(): ASN1TLV; + + public getSubjectString(): HexString; + + public getNotBefore(): TimeValue; + + public getNotAfter(): TimeValue; + + public getPublicKeyHex(): ASN1TLV; + + public getPublicKeyIdx(): Idx>; + + public getPublicKeyContentIdx(): Idx>; + + public getPublicKey(): RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA; + + public getSignatureAlgorithmName(): OIDName; + + public getSignatureValueHex(): ASN1V; + + public verifySignature(pubKey: GetKeyParam): boolean | 0; + + public parseExt(): void; + + public getExtInfo(oidOrName: OID | string): ExtInfo | undefined; + + public getExtBasicConstraints(): ExtInfo | {} | { + cA: true; + pathLen?: number; + }; + + public getExtKeyUsageBin(): BinString; + + public getExtKeyUsageString(): string; + + public getExtSubjectKeyIdentifier(): ASN1V | undefined; + + public getExtAuthorityKeyIdentifier(): { + kid: ASN1V; + } | undefined; + + public getExtExtKeyUsageName(): OIDName[] | undefined; + + public getExtSubjectAltName(): Deprecated; + + public getExtSubjectAltName2(): ['MAIL' | 'DNS' | 'DN' | 'URI' | 'IP', string][] | undefined; + + public getExtCRLDistributionPointsURI(): string[] | undefined; + + public getExtAIAInfo(): ExtAIAInfo | undefined; + + public getExtCertificatePolicies(): ExtCertificatePolicy[] | undefined; + + public readCertPEM(sCertPEM: PEM): void; + + public readCertHex(sCertHex: HexString): void; + + public getInfo(): string; + + public static hex2dn(hex: HexString, idx?: Idx): string; + + public static hex2rdn(hex: HexString, idx?: Idx): string; + + public static hex2attrTypeValue(hex: HexString, idx?: Idx): string; + + public static getPublicKeyFromCertPEM(sCertPEM: PEM): RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA; + + public static getPublicKeyInfoPropOfCertPEM(sCertPEM: PEM): { + algparam: ASN1V | null; + leyhex: ASN1V; + algoid: ASN1V; + }; + } +} diff --git a/src/boot/master.ts b/src/boot/master.ts index 6c23a528fa..b698548d47 100644 --- a/src/boot/master.ts +++ b/src/boot/master.ts @@ -79,6 +79,7 @@ export async function masterMain() { require('../daemons/server-stats').default(); require('../daemons/notes-stats').default(); require('../daemons/queue-stats').default(); + require('../daemons/janitor').default(); } bootLogger.succ(`Now listening on port ${config.port} on ${config.url}`, null, true); diff --git a/src/client/app/admin/views/db.vue b/src/client/app/admin/views/db.vue index 7818546e76..9f87a749b6 100644 --- a/src/client/app/admin/views/db.vue +++ b/src/client/app/admin/views/db.vue @@ -5,6 +5,14 @@
{{ table }} {{ tables[table].count | number }} {{ tables[table].size | bytes }}
+
+
{{ $t('vacuum') }}
+ {{ $t('vacuum-info') }} + FULL + ANALYZE + {{ $t('vacuum') }} + {{ $t('vacuum-exclamation') }} +
@@ -12,7 +20,7 @@ diff --git a/src/client/app/admin/views/instance.vue b/src/client/app/admin/views/instance.vue index 588b24f6b5..2d0fc9b40c 100644 --- a/src/client/app/admin/views/instance.vue +++ b/src/client/app/admin/views/instance.vue @@ -129,6 +129,7 @@ {{ $t('smtp-pass') }} {{ $t('smtp-secure') }} + {{ $t('test-email') }}
@@ -424,6 +425,24 @@ export default Vue.extend({ }); }, + async testEmail() { + this.$root.api('admin/send-email', { + to: this.maintainerEmail, + subject: 'Test email', + text: 'Yo' + }).then(x => { + this.$root.dialog({ + type: 'success', + splash: true + }); + }).catch(e => { + this.$root.dialog({ + type: 'error', + text: e + }); + }); + }, + updateMeta() { this.$root.api('admin/update-meta', { maintainerName: this.maintainerName, diff --git a/src/client/app/boot.js b/src/client/app/boot.js index 9af58076e7..6cb1488e40 100644 --- a/src/client/app/boot.js +++ b/src/client/app/boot.js @@ -41,7 +41,6 @@ if (`${url.pathname}/`.startsWith('/dev/')) app = 'dev'; if (`${url.pathname}/`.startsWith('/auth/')) app = 'auth'; if (`${url.pathname}/`.startsWith('/admin/')) app = 'admin'; - if (`${url.pathname}/`.startsWith('/test/')) app = 'test'; //#endregion // Script version diff --git a/src/client/app/common/scripts/2fa.ts b/src/client/app/common/scripts/2fa.ts new file mode 100644 index 0000000000..f638cce156 --- /dev/null +++ b/src/client/app/common/scripts/2fa.ts @@ -0,0 +1,5 @@ +export function hexifyAB(buffer) { + return Array.from(new Uint8Array(buffer)) + .map(item => item.toString(16).padStart(2, 0)) + .join(''); +} diff --git a/src/client/app/common/views/components/dialog.vue b/src/client/app/common/views/components/dialog.vue index a577da5a21..ed94fe5f35 100644 --- a/src/client/app/common/views/components/dialog.vue +++ b/src/client/app/common/views/components/dialog.vue @@ -33,7 +33,7 @@ - {{ (showCancelButton || input || select || user) ? $t('@.ok') : $t('@.got-it') }} + {{ (showCancelButton || input || select || user) ? $t('@.ok') : $t('@.got-it') }} {{ $t('@.cancel') }} @@ -99,11 +99,26 @@ export default Vue.extend({ inputValue: this.input && this.input.default ? this.input.default : null, userInputValue: null, selectedValue: this.select ? this.select.items ? this.select.items[0].value : this.select.groupedItems[0].items[0].value : null, + canOk: true, faTimesCircle, faQuestionCircle }; }, + watch: { + userInputValue() { + if (this.user) { + this.$root.api('users/show', parseAcct(this.userInputValue)).then(u => { + this.canOk = u != null; + }).catch(() => { + this.canOk = false; + }); + } + } + }, + mounted() { + if (this.user) this.canOk = false; + this.$nextTick(() => { (this.$refs.bg as any).style.pointerEvents = 'auto'; anime({ @@ -131,6 +146,7 @@ export default Vue.extend({ methods: { async ok() { + if (!this.canOk) return; if (!this.showOkButton) return; if (this.user) { diff --git a/src/client/app/common/views/components/follow-button.vue b/src/client/app/common/views/components/follow-button.vue index cae6067635..074a0c05b6 100644 --- a/src/client/app/common/views/components/follow-button.vue +++ b/src/client/app/common/views/components/follow-button.vue @@ -92,6 +92,14 @@ export default Vue.extend({ try { if (this.isFollowing) { + const { canceled } = await this.$root.dialog({ + type: 'warning', + text: this.$t('@.unfollow-confirm', { name: this.user.name || this.user.username }), + showCancelButton: true + }); + + if (canceled) return; + await this.$root.api('following/delete', { userId: this.user.id }); diff --git a/src/client/app/common/views/components/misskey-flavored-markdown.vue b/src/client/app/common/views/components/misskey-flavored-markdown.vue index 09f2e287f2..fa24f6dfa4 100644 --- a/src/client/app/common/views/components/misskey-flavored-markdown.vue +++ b/src/client/app/common/views/components/misskey-flavored-markdown.vue @@ -15,6 +15,8 @@ export default Vue.extend({ diff --git a/src/client/app/common/views/components/signin.vue b/src/client/app/common/views/components/signin.vue index 03ee51d06e..53cc62c333 100644 --- a/src/client/app/common/views/components/signin.vue +++ b/src/client/app/common/views/components/signin.vue @@ -1,23 +1,40 @@ @@ -26,6 +43,7 @@ import Vue from 'vue'; import i18n from '../../../i18n'; import { apiUrl, host } from '../../../config'; import { toUnicode } from 'punycode'; +import { hexifyAB } from '../../scripts/2fa'; export default Vue.extend({ i18n: i18n('common/views/components/signin.vue'), @@ -47,7 +65,11 @@ export default Vue.extend({ token: '', apiUrl, host: toUnicode(host), - meta: null + meta: null, + totpLogin: false, + credential: null, + challengeData: null, + queryingKey: false, }; }, @@ -68,23 +90,87 @@ export default Vue.extend({ }); }, - onSubmit() { - this.signing = true; - - this.$root.api('signin', { - username: this.username, - password: this.password, - token: this.user && this.user.twoFactorEnabled ? this.token : undefined + queryKey() { + this.queryingKey = true; + return navigator.credentials.get({ + publicKey: { + challenge: Buffer.from( + this.challengeData.challenge + .replace(/\-/g, '+') + .replace(/_/g, '/'), + 'base64' + ), + allowCredentials: this.challengeData.securityKeys.map(key => ({ + id: Buffer.from(key.id, 'hex'), + type: 'public-key', + transports: ['usb', 'ble', 'nfc'] + })), + timeout: 60 * 1000 + } + }).catch(err => { + this.queryingKey = false; + console.warn(err); + return Promise.reject(null); + }).then(credential => { + this.queryingKey = false; + this.signing = true; + return this.$root.api('signin', { + username: this.username, + password: this.password, + signature: hexifyAB(credential.response.signature), + authenticatorData: hexifyAB(credential.response.authenticatorData), + clientDataJSON: hexifyAB(credential.response.clientDataJSON), + credentialId: credential.id, + challengeId: this.challengeData.challengeId + }); }).then(res => { localStorage.setItem('i', res.i); location.reload(); - }).catch(() => { + }).catch(err => { + if(err === null) return; + console.error(err); this.$root.dialog({ type: 'error', text: this.$t('login-failed') }); this.signing = false; }); + }, + + onSubmit() { + this.signing = true; + + if (!this.totpLogin && this.user && this.user.twoFactorEnabled) { + if (window.PublicKeyCredential && this.user.securityKeys) { + this.$root.api('i/2fa/getkeys', { + username: this.username, + password: this.password + }).then(res => { + this.totpLogin = true; + this.signing = false; + this.challengeData = res; + return this.queryKey(); + }); + } else { + this.totpLogin = true; + this.signing = false; + } + } else { + this.$root.api('signin', { + username: this.username, + password: this.password, + token: this.user && this.user.twoFactorEnabled ? this.token : undefined + }).then(res => { + localStorage.setItem('i', res.i); + location.reload(); + }).catch(() => { + this.$root.dialog({ + type: 'error', + text: this.$t('login-failed') + }); + this.signing = false; + }); + } } } }); @@ -94,6 +180,48 @@ export default Vue.extend({ .mk-signin color #555 + .or-hr, + .or-hr .or-msg, + .twofa-group, + .twofa-group p + color var(--text) + + .tap-group > button + margin-bottom 1em + + .securityKeys .or-hr + & + position relative + + .or-msg + &:before + right 100% + margin-right 0.125em + + &:after + left 100% + margin-left 0.125em + + &:before, &:after + content "" + position absolute + top 50% + width 100% + height 2px + background #555 + + & + position relative + margin auto + left 0 + right 0 + top 0 + bottom 0 + font-size 1.5em + height 1.5em + width 3em + text-align center + &.signing &, * cursor wait !important diff --git a/src/client/app/common/views/components/url.vue b/src/client/app/common/views/components/url.vue index b22a10097d..b1ca3f285c 100644 --- a/src/client/app/common/views/components/url.vue +++ b/src/client/app/common/views/components/url.vue @@ -5,6 +5,9 @@ {{ hostname }} :{{ port }} + {{ self ? pathname.substr(1) : pathname }} {{ query }} {{ hash }} @@ -22,6 +25,7 @@ export default Vue.extend({ data() { const isSelf = this.url.startsWith(local); const hasRoute = isSelf && ( + (this.url.substr(local.length) === '/') || this.url.substr(local.length).startsWith('/@') || this.url.substr(local.length).startsWith('/notes/') || this.url.substr(local.length).startsWith('/pages/')); @@ -54,19 +58,28 @@ export default Vue.extend({ diff --git a/src/client/app/common/views/deck/deck.user-column.home.vue b/src/client/app/common/views/deck/deck.user-column.home.vue index c30cb397e4..49cc869eb9 100644 --- a/src/client/app/common/views/deck/deck.user-column.home.vue +++ b/src/client/app/common/views/deck/deck.user-column.home.vue @@ -97,7 +97,9 @@ export default Vue.extend({ const image = [ 'image/jpeg', 'image/png', - 'image/gif' + 'image/gif', + 'image/apng', + 'image/vnd.mozilla.apng', ]; this.$root.api('users/notes', { diff --git a/src/client/app/desktop/api/update-avatar.ts b/src/client/app/desktop/api/update-avatar.ts index e2da55fc97..a095491b69 100644 --- a/src/client/app/desktop/api/update-avatar.ts +++ b/src/client/app/desktop/api/update-avatar.ts @@ -91,6 +91,7 @@ export default ($root: any) => { ? Promise.resolve(file) : $root.$chooseDriveFile({ multiple: false, + type: 'image/*', title: locale['desktop']['choose-avatar'] }); diff --git a/src/client/app/desktop/api/update-banner.ts b/src/client/app/desktop/api/update-banner.ts index e62277338d..c23a325364 100644 --- a/src/client/app/desktop/api/update-banner.ts +++ b/src/client/app/desktop/api/update-banner.ts @@ -91,6 +91,7 @@ export default ($root: any) => { ? Promise.resolve(file) : $root.$chooseDriveFile({ multiple: false, + type: 'image/*', title: locale['desktop']['choose-banner'] }); diff --git a/src/client/app/desktop/script.ts b/src/client/app/desktop/script.ts index ce0b96e0ac..8065241714 100644 --- a/src/client/app/desktop/script.ts +++ b/src/client/app/desktop/script.ts @@ -77,6 +77,7 @@ init(async (launch, os) => { if (document.body.clientWidth > 800) { const w = this.$root.new(MkChooseFileFromDriveWindow, { title: o.title, + type: o.type, multiple: o.multiple, initFolder: o.currentFolder }); diff --git a/src/client/app/desktop/views/components/choose-file-from-drive-window.vue b/src/client/app/desktop/views/components/choose-file-from-drive-window.vue index 8b39e6e00b..71c430edeb 100644 --- a/src/client/app/desktop/views/components/choose-file-from-drive-window.vue +++ b/src/client/app/desktop/views/components/choose-file-from-drive-window.vue @@ -11,6 +11,7 @@ import('./drive.vue').then(m => m.default), }, props: { + type: { + type: String, + required: false, + default: undefined + }, multiple: { default: false } diff --git a/src/client/app/desktop/views/components/crop-window.vue b/src/client/app/desktop/views/components/crop-window.vue index 5d7e0e5e03..856f889b02 100644 --- a/src/client/app/desktop/views/components/crop-window.vue +++ b/src/client/app/desktop/views/components/crop-window.vue @@ -21,6 +21,7 @@ import Vue from 'vue'; import i18n from '../../../i18n'; import VueCropper from 'vue-cropperjs'; +import 'cropperjs/dist/cropper.css'; import * as url from '../../../../../prelude/url'; export default Vue.extend({ diff --git a/src/client/app/desktop/views/components/drive.vue b/src/client/app/desktop/views/components/drive.vue index 4b64d4f22a..2eb9581359 100644 --- a/src/client/app/desktop/views/components/drive.vue +++ b/src/client/app/desktop/views/components/drive.vue @@ -80,6 +80,11 @@ export default Vue.extend({ type: Object, required: false }, + type: { + type: String, + required: false, + default: undefined + }, multiple: { type: Boolean, default: false @@ -540,6 +545,7 @@ export default Vue.extend({ // ファイル一覧取得 this.$root.api('drive/files', { folderId: this.folder ? this.folder.id : null, + type: this.type, limit: filesMax + 1 }).then(files => { if (files.length == filesMax + 1) { @@ -570,6 +576,7 @@ export default Vue.extend({ // ファイル一覧取得 this.$root.api('drive/files', { folderId: this.folder ? this.folder.id : null, + type: this.type, untilId: this.files[this.files.length - 1].id, limit: max + 1 }).then(files => { diff --git a/src/client/app/desktop/views/home/user/user.photos.vue b/src/client/app/desktop/views/home/user/user.photos.vue index 5948e805ac..03abcf865c 100644 --- a/src/client/app/desktop/views/home/user/user.photos.vue +++ b/src/client/app/desktop/views/home/user/user.photos.vue @@ -38,7 +38,9 @@ export default Vue.extend({ const image = [ 'image/jpeg', 'image/png', - 'image/gif' + 'image/gif', + 'image/apng', + 'image/vnd.mozilla.apng', ]; this.$root.api('users/notes', { diff --git a/src/client/app/desktop/views/pages/welcome.vue b/src/client/app/desktop/views/pages/welcome.vue index 8a66728a7b..511e1548e5 100644 --- a/src/client/app/desktop/views/pages/welcome.vue +++ b/src/client/app/desktop/views/pages/welcome.vue @@ -186,7 +186,9 @@ export default Vue.extend({ const image = [ 'image/jpeg', 'image/png', - 'image/gif' + 'image/gif', + 'image/apng', + 'image/vnd.mozilla.apng', ]; this.$root.api('notes/local-timeline', { diff --git a/src/client/app/mobile/views/components/drive-file-chooser.vue b/src/client/app/mobile/views/components/drive-file-chooser.vue index b72c796c1d..8795102f97 100644 --- a/src/client/app/mobile/views/components/drive-file-chooser.vue +++ b/src/client/app/mobile/views/components/drive-file-chooser.vue @@ -8,6 +8,7 @@ import('./drive.vue').then(m => m.default), }, - props: ['multiple'], + props: ['type', 'multiple'], data() { return { files: [] diff --git a/src/client/app/mobile/views/pages/user/home.photos.vue b/src/client/app/mobile/views/pages/user/home.photos.vue index d9817f1a53..b5547c916f 100644 --- a/src/client/app/mobile/views/pages/user/home.photos.vue +++ b/src/client/app/mobile/views/pages/user/home.photos.vue @@ -30,7 +30,9 @@ export default Vue.extend({ const image = [ 'image/jpeg', 'image/png', - 'image/gif' + 'image/gif', + 'image/apng', + 'image/vnd.mozilla.apng', ]; this.$root.api('users/notes', { userId: this.user.id, diff --git a/src/client/app/mobile/views/pages/welcome.vue b/src/client/app/mobile/views/pages/welcome.vue index 48a6650b4f..6cf4a36f90 100644 --- a/src/client/app/mobile/views/pages/welcome.vue +++ b/src/client/app/mobile/views/pages/welcome.vue @@ -110,7 +110,9 @@ export default Vue.extend({ const image = [ 'image/jpeg', 'image/png', - 'image/gif' + 'image/gif', + 'image/apng', + 'image/vnd.mozilla.apng', ]; this.$root.api('notes/local-timeline', { diff --git a/src/client/app/test/script.ts b/src/client/app/test/script.ts deleted file mode 100644 index 44fe224cbc..0000000000 --- a/src/client/app/test/script.ts +++ /dev/null @@ -1,25 +0,0 @@ -import VueRouter from 'vue-router'; - -// Style -import './style.styl'; - -import init from '../init'; -import Index from './views/index.vue'; -import NotFound from '../common/views/pages/not-found.vue'; - -init(launch => { - document.title = 'Misskey'; - - // Init router - const router = new VueRouter({ - mode: 'history', - base: '/test/', - routes: [ - { path: '/', component: Index }, - { path: '*', component: NotFound } - ] - }); - - // Launch the app - launch(router); -}); diff --git a/src/client/app/test/style.styl b/src/client/app/test/style.styl deleted file mode 100644 index ae1a28226a..0000000000 --- a/src/client/app/test/style.styl +++ /dev/null @@ -1,6 +0,0 @@ -@import "../app" -@import "../reset" - -html - height 100% - background var(--bg) diff --git a/src/client/app/test/views/index.vue b/src/client/app/test/views/index.vue deleted file mode 100644 index e0725d42a7..0000000000 --- a/src/client/app/test/views/index.vue +++ /dev/null @@ -1,82 +0,0 @@ - - - - - diff --git a/src/daemons/janitor.ts b/src/daemons/janitor.ts new file mode 100644 index 0000000000..462ebf915c --- /dev/null +++ b/src/daemons/janitor.ts @@ -0,0 +1,18 @@ +const interval = 30 * 60 * 1000; +import { AttestationChallenges } from '../models'; +import { LessThan } from 'typeorm'; + +/** + * Clean up database occasionally + */ +export default function() { + async function tick() { + await AttestationChallenges.delete({ + createdAt: LessThan(new Date(new Date().getTime() - 5 * 60 * 1000)) + }); + } + + tick(); + + setInterval(tick, interval); +} diff --git a/src/db/postgre.ts b/src/db/postgre.ts index c91cd8e1d7..94a19b06be 100644 --- a/src/db/postgre.ts +++ b/src/db/postgre.ts @@ -43,6 +43,8 @@ import { Poll } from '../models/entities/poll'; import { UserKeypair } from '../models/entities/user-keypair'; import { UserPublickey } from '../models/entities/user-publickey'; import { UserProfile } from '../models/entities/user-profile'; +import { UserSecurityKey } from '../models/entities/user-security-key'; +import { AttestationChallenge } from '../models/entities/attestation-challenge'; import { Page } from '../models/entities/page'; import { PageLike } from '../models/entities/page-like'; @@ -80,6 +82,53 @@ class MyCustomLogger implements Logger { } } +export const entities = [ + Meta, + Instance, + App, + AuthSession, + AccessToken, + User, + UserProfile, + UserKeypair, + UserPublickey, + UserList, + UserListJoining, + UserGroup, + UserGroupJoining, + UserGroupInvite, + UserNotePining, + UserSecurityKey, + AttestationChallenge, + Following, + FollowRequest, + Muting, + Blocking, + Note, + NoteFavorite, + NoteReaction, + NoteWatching, + NoteUnread, + Page, + PageLike, + Log, + DriveFile, + DriveFolder, + Poll, + PollVote, + Notification, + Emoji, + Hashtag, + SwSubscription, + AbuseUserReport, + RegistrationTicket, + MessagingMessage, + Signin, + ReversiGame, + ReversiMatching, + ...charts as any +]; + export function initDb(justBorrow = false, sync = false, log = false) { try { const conn = getConnection(); @@ -101,7 +150,7 @@ export function initDb(justBorrow = false, sync = false, log = false) { options: { host: config.redis.host, port: config.redis.port, - options:{ + options: { password: config.redis.pass, prefix: config.redis.prefix, db: config.redis.db || 0 @@ -110,49 +159,6 @@ export function initDb(justBorrow = false, sync = false, log = false) { } : false, logging: log, logger: log ? new MyCustomLogger() : undefined, - entities: [ - Meta, - Instance, - App, - AuthSession, - AccessToken, - User, - UserProfile, - UserKeypair, - UserPublickey, - UserList, - UserListJoining, - UserGroup, - UserGroupJoining, - UserGroupInvite, - UserNotePining, - Following, - FollowRequest, - Muting, - Blocking, - Note, - NoteFavorite, - NoteReaction, - NoteWatching, - NoteUnread, - Page, - PageLike, - Log, - DriveFile, - DriveFolder, - Poll, - PollVote, - Notification, - Emoji, - Hashtag, - SwSubscription, - AbuseUserReport, - RegistrationTicket, - MessagingMessage, - Signin, - ReversiGame, - ReversiMatching, - ...charts as any - ] + entities: entities }); } diff --git a/src/mfm/language.ts b/src/mfm/language.ts index 75a2baaab9..5d5e35fbfd 100644 --- a/src/mfm/language.ts +++ b/src/mfm/language.ts @@ -6,8 +6,8 @@ import { toUnicode } from 'punycode'; import { emojiRegex } from '../misc/emoji-regex'; export function removeOrphanedBrackets(s: string): string { - const openBrackets = ['(', '「']; - const closeBrackets = [')', '」']; + const openBrackets = ['(', '「', '[']; + const closeBrackets = [')', '」', ']']; const xs = cumulativeSum(s.split('').map(c => { if (openBrackets.includes(c)) return 1; if (closeBrackets.includes(c)) return -1; diff --git a/src/misc/detect-mine.ts b/src/misc/detect-mine.ts index 70d58ffe21..b228733697 100644 --- a/src/misc/detect-mine.ts +++ b/src/misc/detect-mine.ts @@ -1,5 +1,5 @@ import * as fs from 'fs'; -import fileType from 'file-type'; +import fileType = require('file-type'); import checkSvg from '../misc/check-svg'; export async function detectMine(path: string) { diff --git a/src/misc/donwload-url.ts b/src/misc/donwload-url.ts index e2f10c0e40..0ba25d812b 100644 --- a/src/misc/donwload-url.ts +++ b/src/misc/donwload-url.ts @@ -5,7 +5,7 @@ import chalk from 'chalk'; import Logger from '../services/logger'; export async function downloadUrl(url: string, path: string) { - const logger = new Logger('download-url'); + const logger = new Logger('download'); await new Promise((res, rej) => { logger.info(`Downloading ${chalk.cyan(url)} ...`); diff --git a/src/misc/schema.ts b/src/misc/schema.ts index 442c95ad98..e14e6e0dd7 100644 --- a/src/misc/schema.ts +++ b/src/misc/schema.ts @@ -1,17 +1,3 @@ -export const types = { - boolean: 'boolean' as 'boolean', - string: 'string' as 'string', - number: 'number' as 'number', - array: 'array' as 'array', - object: 'object' as 'object', - any: 'any' as 'any', -}; - -export const bool = { - true: true as true, - false: false as false, -}; - export type Schema = { type: 'boolean' | 'number' | 'string' | 'array' | 'object' | 'any'; nullable: boolean; diff --git a/src/models/entities/attestation-challenge.ts b/src/models/entities/attestation-challenge.ts new file mode 100644 index 0000000000..942747c02f --- /dev/null +++ b/src/models/entities/attestation-challenge.ts @@ -0,0 +1,46 @@ +import { PrimaryColumn, Entity, JoinColumn, Column, ManyToOne, Index } from 'typeorm'; +import { User } from './user'; +import { id } from '../id'; + +@Entity() +export class AttestationChallenge { + @PrimaryColumn(id()) + public id: string; + + @Index() + @PrimaryColumn(id()) + public userId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public user: User | null; + + @Index() + @Column('varchar', { + length: 64, + comment: 'Hex-encoded sha256 hash of the challenge.' + }) + public challenge: string; + + @Column('timestamp with time zone', { + comment: 'The date challenge was created for expiry purposes.' + }) + public createdAt: Date; + + @Column('boolean', { + comment: + 'Indicates that the challenge is only for registration purposes if true to prevent the challenge for being used as authentication.', + default: false + }) + public registrationChallenge: boolean; + + constructor(data: Partial) { + if (data == null) return; + + for (const [k, v] of Object.entries(data)) { + (this as any)[k] = v; + } + } +} diff --git a/src/models/entities/user-profile.ts b/src/models/entities/user-profile.ts index 7d990b961f..6f960f1b7b 100644 --- a/src/models/entities/user-profile.ts +++ b/src/models/entities/user-profile.ts @@ -76,6 +76,11 @@ export class UserProfile { }) public twoFactorEnabled: boolean; + @Column('boolean', { + default: false, + }) + public securityKeysAvailable: boolean; + @Column('varchar', { length: 128, nullable: true, comment: 'The password hash of the User. It will be null if the origin of the user is local.' diff --git a/src/models/entities/user-security-key.ts b/src/models/entities/user-security-key.ts new file mode 100644 index 0000000000..d54c728e53 --- /dev/null +++ b/src/models/entities/user-security-key.ts @@ -0,0 +1,48 @@ +import { PrimaryColumn, Entity, JoinColumn, Column, ManyToOne, Index } from 'typeorm'; +import { User } from './user'; +import { id } from '../id'; + +@Entity() +export class UserSecurityKey { + @PrimaryColumn('varchar', { + comment: 'Variable-length id given to navigator.credentials.get()' + }) + public id: string; + + @Index() + @Column(id()) + public userId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public user: User | null; + + @Index() + @Column('varchar', { + comment: + 'Variable-length public key used to verify attestations (hex-encoded).' + }) + public publicKey: string; + + @Column('timestamp with time zone', { + comment: + 'The date of the last time the UserSecurityKey was successfully validated.' + }) + public lastUsed: Date; + + @Column('varchar', { + comment: 'User-defined name for this key', + length: 30 + }) + public name: string; + + constructor(data: Partial) { + if (data == null) return; + + for (const [k, v] of Object.entries(data)) { + (this as any)[k] = v; + } + } +} diff --git a/src/models/id.ts b/src/models/id.ts index be2cccfe3b..cdb8259073 100644 --- a/src/models/id.ts +++ b/src/models/id.ts @@ -1,4 +1,4 @@ export const id = () => ({ - type: 'varchar' as 'varchar', + type: 'varchar' as const, length: 32 }); diff --git a/src/models/index.ts b/src/models/index.ts index a60cd10ef9..888fd53f36 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -37,6 +37,8 @@ import { FollowingRepository } from './repositories/following'; import { AbuseUserReportRepository } from './repositories/abuse-user-report'; import { AuthSessionRepository } from './repositories/auth-session'; import { UserProfile } from './entities/user-profile'; +import { AttestationChallenge } from './entities/attestation-challenge'; +import { UserSecurityKey } from './entities/user-security-key'; import { HashtagRepository } from './repositories/hashtag'; import { PageRepository } from './repositories/page'; import { PageLikeRepository } from './repositories/page-like'; @@ -52,6 +54,8 @@ export const PollVotes = getRepository(PollVote); export const Users = getCustomRepository(UserRepository); export const UserProfiles = getRepository(UserProfile); export const UserKeypairs = getRepository(UserKeypair); +export const AttestationChallenges = getRepository(AttestationChallenge); +export const UserSecurityKeys = getRepository(UserSecurityKey); export const UserPublickeys = getRepository(UserPublickey); export const UserLists = getCustomRepository(UserListRepository); export const UserListJoinings = getRepository(UserListJoining); diff --git a/src/models/repositories/app.ts b/src/models/repositories/app.ts index 6562a470be..45d8d16c51 100644 --- a/src/models/repositories/app.ts +++ b/src/models/repositories/app.ts @@ -2,7 +2,7 @@ import { EntityRepository, Repository } from 'typeorm'; import { App } from '../entities/app'; import { AccessTokens } from '..'; import { ensure } from '../../prelude/ensure'; -import { types, bool, SchemaType } from '../../misc/schema'; +import { SchemaType } from '../../misc/schema'; export type PackedApp = SchemaType; @@ -42,37 +42,37 @@ export class AppRepository extends Repository { } export const packedAppSchema = { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { id: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'id', description: 'The unique identifier for this Note.', example: 'xxxxxxxxxx', }, name: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, description: 'アプリケーションの名前' }, callbackUrl: { - type: types.string, - optional: bool.false, nullable: bool.true, + type: 'string' as const, + optional: false as const, nullable: true as const, description: 'コールバックするURL' }, permission: { - type: types.array, - optional: bool.true, nullable: bool.false, + type: 'array' as const, + optional: true as const, nullable: false as const, items: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, } }, secret: { - type: types.string, - optional: bool.true, nullable: bool.false, + type: 'string' as const, + optional: true as const, nullable: false as const, description: 'アプリケーションのシークレットキー' } }, diff --git a/src/models/repositories/blocking.ts b/src/models/repositories/blocking.ts index 6ee31cece8..9ebe6bbf59 100644 --- a/src/models/repositories/blocking.ts +++ b/src/models/repositories/blocking.ts @@ -3,7 +3,7 @@ import { Users } from '..'; import { Blocking } from '../entities/blocking'; import { ensure } from '../../prelude/ensure'; import { awaitAll } from '../../prelude/await-all'; -import { SchemaType, types, bool } from '../../misc/schema'; +import { SchemaType } from '../../misc/schema'; export type PackedBlocking = SchemaType; @@ -34,30 +34,30 @@ export class BlockingRepository extends Repository { } export const packedBlockingSchema = { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { id: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'id', description: 'The unique identifier for this blocking.', example: 'xxxxxxxxxx', }, createdAt: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'date-time', description: 'The date that the blocking was created.' }, blockeeId: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'id', }, blockee: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'User', description: 'The blockee.' }, diff --git a/src/models/repositories/drive-file.ts b/src/models/repositories/drive-file.ts index 5e7e1d40f4..702f195e9d 100644 --- a/src/models/repositories/drive-file.ts +++ b/src/models/repositories/drive-file.ts @@ -5,7 +5,7 @@ import { User } from '../entities/user'; import { toPuny } from '../../misc/convert-host'; import { ensure } from '../../prelude/ensure'; import { awaitAll } from '../../prelude/await-all'; -import { types, bool, SchemaType } from '../../misc/schema'; +import { SchemaType } from '../../misc/schema'; export type PackedDriveFile = SchemaType; @@ -114,63 +114,63 @@ export class DriveFileRepository extends Repository { } export const packedDriveFileSchema = { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { id: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'id', description: 'The unique identifier for this Drive file.', example: 'xxxxxxxxxx', }, createdAt: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'date-time', description: 'The date that the Drive file was created on Misskey.' }, name: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, description: 'The file name with extension.', example: 'lenna.jpg' }, type: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, description: 'The MIME type of this Drive file.', example: 'image/jpeg' }, md5: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'md5', description: 'The MD5 hash of this Drive file.', example: '15eca7fba0480996e2245f5185bf39f2' }, size: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: 'The size of this Drive file. (bytes)', example: 51469 }, url: { - type: types.string, - optional: bool.false, nullable: bool.true, + type: 'string' as const, + optional: false as const, nullable: true as const, format: 'url', description: 'The URL of this Drive file.', }, folderId: { - type: types.string, - optional: bool.false, nullable: bool.true, + type: 'string' as const, + optional: false as const, nullable: true as const, format: 'id', description: 'The parent folder ID of this Drive file.', example: 'xxxxxxxxxx', }, isSensitive: { - type: types.boolean, - optional: bool.false, nullable: bool.false, + type: 'boolean' as const, + optional: false as const, nullable: false as const, description: 'Whether this Drive file is sensitive.', }, }, diff --git a/src/models/repositories/drive-folder.ts b/src/models/repositories/drive-folder.ts index ef920c4326..dee424cccb 100644 --- a/src/models/repositories/drive-folder.ts +++ b/src/models/repositories/drive-folder.ts @@ -3,7 +3,7 @@ import { DriveFolders, DriveFiles } from '..'; import { DriveFolder } from '../entities/drive-folder'; import { ensure } from '../../prelude/ensure'; import { awaitAll } from '../../prelude/await-all'; -import { SchemaType, types, bool } from '../../misc/schema'; +import { SchemaType } from '../../misc/schema'; export type PackedDriveFolder = SchemaType; @@ -53,47 +53,47 @@ export class DriveFolderRepository extends Repository { } export const packedDriveFolderSchema = { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { id: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'id', description: 'The unique identifier for this Drive folder.', example: 'xxxxxxxxxx', }, createdAt: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'date-time', description: 'The date that the Drive folder was created.' }, name: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, description: 'The folder name.', }, foldersCount: { - type: types.number, - optional: bool.true, nullable: bool.false, + type: 'number' as const, + optional: true as const, nullable: false as const, description: 'The count of child folders.', }, filesCount: { - type: types.number, - optional: bool.true, nullable: bool.false, + type: 'number' as const, + optional: true as const, nullable: false as const, description: 'The count of child files.', }, parentId: { - type: types.string, - optional: bool.false, nullable: bool.true, + type: 'string' as const, + optional: false as const, nullable: true as const, format: 'id', description: 'The parent folder ID of this folder.', example: 'xxxxxxxxxx', }, parent: { - type: types.object, - optional: bool.true, nullable: bool.true, + type: 'object' as const, + optional: true as const, nullable: true as const, ref: 'DriveFolder' }, }, diff --git a/src/models/repositories/following.ts b/src/models/repositories/following.ts index 88fee749ae..58728a3380 100644 --- a/src/models/repositories/following.ts +++ b/src/models/repositories/following.ts @@ -3,7 +3,7 @@ import { Users } from '..'; import { Following } from '../entities/following'; import { ensure } from '../../prelude/ensure'; import { awaitAll } from '../../prelude/await-all'; -import { SchemaType, types, bool } from '../../misc/schema'; +import { SchemaType } from '../../misc/schema'; type LocalFollowerFollowing = Following & { followerHost: null; @@ -88,41 +88,41 @@ export class FollowingRepository extends Repository { } export const packedFollowingSchema = { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { id: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'id', description: 'The unique identifier for this following.', example: 'xxxxxxxxxx', }, createdAt: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'date-time', description: 'The date that the following was created.' }, followeeId: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'id', }, followee: { - type: types.object, - optional: bool.true, nullable: bool.false, + type: 'object' as const, + optional: true as const, nullable: false as const, ref: 'User', description: 'The followee.' }, followerId: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'id', }, follower: { - type: types.object, - optional: bool.true, nullable: bool.false, + type: 'object' as const, + optional: true as const, nullable: false as const, ref: 'User', description: 'The follower.' }, diff --git a/src/models/repositories/hashtag.ts b/src/models/repositories/hashtag.ts index a990fa3dcf..fb54a12e13 100644 --- a/src/models/repositories/hashtag.ts +++ b/src/models/repositories/hashtag.ts @@ -1,6 +1,6 @@ import { EntityRepository, Repository } from 'typeorm'; import { Hashtag } from '../entities/hashtag'; -import { SchemaType, types, bool } from '../../misc/schema'; +import { SchemaType } from '../../misc/schema'; export type PackedHashtag = SchemaType; @@ -28,43 +28,43 @@ export class HashtagRepository extends Repository { } export const packedHashtagSchema = { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { tag: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, description: 'The hashtag name. No # prefixed.', example: 'misskey', }, mentionedUsersCount: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: 'Number of all users using this hashtag.' }, mentionedLocalUsersCount: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: 'Number of local users using this hashtag.' }, mentionedRemoteUsersCount: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: 'Number of remote users using this hashtag.' }, attachedUsersCount: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: 'Number of all users who attached this hashtag to profile.' }, attachedLocalUsersCount: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: 'Number of local users who attached this hashtag to profile.' }, attachedRemoteUsersCount: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: 'Number of remote users who attached this hashtag to profile.' }, } diff --git a/src/models/repositories/messaging-message.ts b/src/models/repositories/messaging-message.ts index 7f0b2047ad..d70e95bc12 100644 --- a/src/models/repositories/messaging-message.ts +++ b/src/models/repositories/messaging-message.ts @@ -2,7 +2,7 @@ import { EntityRepository, Repository } from 'typeorm'; import { MessagingMessage } from '../entities/messaging-message'; import { Users, DriveFiles, UserGroups } from '..'; import { ensure } from '../../prelude/ensure'; -import { types, bool, SchemaType } from '../../misc/schema'; +import { SchemaType } from '../../misc/schema'; export type PackedMessagingMessage = SchemaType; @@ -46,76 +46,76 @@ export class MessagingMessageRepository extends Repository { } export const packedMessagingMessageSchema = { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { id: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'id', description: 'The unique identifier for this MessagingMessage.', example: 'xxxxxxxxxx', }, createdAt: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'date-time', description: 'The date that the MessagingMessage was created.' }, userId: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'id', }, user: { - type: types.object, + type: 'object' as const, ref: 'User', - optional: bool.true, nullable: bool.false, + optional: true as const, nullable: false as const, }, text: { - type: types.string, - optional: bool.false, nullable: bool.true, + type: 'string' as const, + optional: false as const, nullable: true as const, }, fileId: { - type: types.string, - optional: bool.true, nullable: bool.true, + type: 'string' as const, + optional: true as const, nullable: true as const, format: 'id', }, file: { - type: types.object, - optional: bool.true, nullable: bool.true, + type: 'object' as const, + optional: true as const, nullable: true as const, ref: 'DriveFile', }, recipientId: { - type: types.string, - optional: bool.false, nullable: bool.true, + type: 'string' as const, + optional: false as const, nullable: true as const, format: 'id', }, recipient: { - type: types.object, - optional: bool.true, nullable: bool.true, + type: 'object' as const, + optional: true as const, nullable: true as const, ref: 'User' }, groupId: { - type: types.string, - optional: bool.false, nullable: bool.true, + type: 'string' as const, + optional: false as const, nullable: true as const, format: 'id', }, group: { - type: types.object, - optional: bool.true, nullable: bool.true, + type: 'object' as const, + optional: true as const, nullable: true as const, ref: 'UserGroup' }, isRead: { - type: types.boolean, - optional: bool.true, nullable: bool.false, + type: 'boolean' as const, + optional: true as const, nullable: false as const, }, reads: { - type: types.array, - optional: bool.true, nullable: bool.false, + type: 'array' as const, + optional: true as const, nullable: false as const, items: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'id' } }, diff --git a/src/models/repositories/muting.ts b/src/models/repositories/muting.ts index 9d99e08a7c..763e04bb3d 100644 --- a/src/models/repositories/muting.ts +++ b/src/models/repositories/muting.ts @@ -3,7 +3,7 @@ import { Users } from '..'; import { Muting } from '../entities/muting'; import { ensure } from '../../prelude/ensure'; import { awaitAll } from '../../prelude/await-all'; -import { types, bool, SchemaType } from '../../misc/schema'; +import { SchemaType } from '../../misc/schema'; export type PackedMuting = SchemaType; @@ -34,30 +34,30 @@ export class MutingRepository extends Repository { } export const packedMutingSchema = { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { id: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'id', description: 'The unique identifier for this muting.', example: 'xxxxxxxxxx', }, createdAt: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'date-time', description: 'The date that the muting was created.' }, muteeId: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'id', }, mutee: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'User', description: 'The mutee.' }, diff --git a/src/models/repositories/note-favorite.ts b/src/models/repositories/note-favorite.ts index 01064f3065..37cfbc0025 100644 --- a/src/models/repositories/note-favorite.ts +++ b/src/models/repositories/note-favorite.ts @@ -2,7 +2,6 @@ import { EntityRepository, Repository } from 'typeorm'; import { NoteFavorite } from '../entities/note-favorite'; import { Notes } from '..'; import { ensure } from '../../prelude/ensure'; -import { types, bool } from '../../misc/schema'; @EntityRepository(NoteFavorite) export class NoteFavoriteRepository extends Repository { @@ -29,30 +28,30 @@ export class NoteFavoriteRepository extends Repository { } export const packedNoteFavoriteSchema = { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { id: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'id', description: 'The unique identifier for this favorite.', example: 'xxxxxxxxxx', }, createdAt: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'date-time', description: 'The date that the favorite was created.' }, note: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'Note', }, noteId: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'id', }, }, diff --git a/src/models/repositories/note-reaction.ts b/src/models/repositories/note-reaction.ts index 85e5b6b0a1..79b4989a20 100644 --- a/src/models/repositories/note-reaction.ts +++ b/src/models/repositories/note-reaction.ts @@ -2,7 +2,7 @@ import { EntityRepository, Repository } from 'typeorm'; import { NoteReaction } from '../entities/note-reaction'; import { Users } from '..'; import { ensure } from '../../prelude/ensure'; -import { types, bool, SchemaType } from '../../misc/schema'; +import { SchemaType } from '../../misc/schema'; export type PackedNoteReaction = SchemaType; @@ -24,31 +24,31 @@ export class NoteReactionRepository extends Repository { } export const packedNoteReactionSchema = { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { id: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'id', description: 'The unique identifier for this reaction.', example: 'xxxxxxxxxx', }, createdAt: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'date-time', description: 'The date that the reaction was created.' }, user: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'User', description: 'User who performed this reaction.' }, type: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, description: 'The reaction type.' }, }, diff --git a/src/models/repositories/note.ts b/src/models/repositories/note.ts index e5bf8d3f3a..03a50fecec 100644 --- a/src/models/repositories/note.ts +++ b/src/models/repositories/note.ts @@ -5,7 +5,7 @@ import { unique, concat } from '../../prelude/array'; import { nyaize } from '../../misc/nyaize'; import { Emojis, Users, Apps, PollVotes, DriveFiles, NoteReactions, Followings, Polls } from '..'; import { ensure } from '../../prelude/ensure'; -import { SchemaType, types, bool } from '../../misc/schema'; +import { SchemaType } from '../../misc/schema'; import { awaitAll } from '../../prelude/await-all'; export type PackedNote = SchemaType; @@ -144,8 +144,8 @@ export class NoteRepository extends Repository { let text = note.text; - if (note.name) { - text = `【${note.name}】\n${note.text}`; + if (note.name && note.uri) { + text = `【${note.name}】\n${(note.text || '').trim()}\n${note.uri}`; } const reactionEmojis = unique(concat([note.emojis, Object.keys(note.reactions)])); @@ -218,125 +218,125 @@ export class NoteRepository extends Repository { } export const packedNoteSchema = { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { id: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'id', description: 'The unique identifier for this Note.', example: 'xxxxxxxxxx', }, createdAt: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'date-time', description: 'The date that the Note was created on Misskey.' }, text: { - type: types.string, - optional: bool.false, nullable: bool.true, + type: 'string' as const, + optional: false as const, nullable: true as const, }, cw: { - type: types.string, - optional: bool.true, nullable: bool.true, + type: 'string' as const, + optional: true as const, nullable: true as const, }, userId: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'id', }, user: { - type: types.object, + type: 'object' as const, ref: 'User', - optional: bool.false, nullable: bool.false, + optional: false as const, nullable: false as const, }, replyId: { - type: types.string, - optional: bool.true, nullable: bool.true, + type: 'string' as const, + optional: true as const, nullable: true as const, format: 'id', example: 'xxxxxxxxxx', }, renoteId: { - type: types.string, - optional: bool.true, nullable: bool.true, + type: 'string' as const, + optional: true as const, nullable: true as const, format: 'id', example: 'xxxxxxxxxx', }, reply: { - type: types.object, - optional: bool.true, nullable: bool.true, + type: 'object' as const, + optional: true as const, nullable: true as const, ref: 'Note' }, renote: { - type: types.object, - optional: bool.true, nullable: bool.true, + type: 'object' as const, + optional: true as const, nullable: true as const, ref: 'Note' }, viaMobile: { - type: types.boolean, - optional: bool.true, nullable: bool.false, + type: 'boolean' as const, + optional: true as const, nullable: false as const, }, isHidden: { - type: types.boolean, - optional: bool.true, nullable: bool.false, + type: 'boolean' as const, + optional: true as const, nullable: false as const, }, visibility: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, }, mentions: { - type: types.array, - optional: bool.true, nullable: bool.false, + type: 'array' as const, + optional: true as const, nullable: false as const, items: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'id' } }, visibleUserIds: { - type: types.array, - optional: bool.true, nullable: bool.false, + type: 'array' as const, + optional: true as const, nullable: false as const, items: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'id' } }, fileIds: { - type: types.array, - optional: bool.true, nullable: bool.false, + type: 'array' as const, + optional: true as const, nullable: false as const, items: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'id' } }, files: { - type: types.array, - optional: bool.true, nullable: bool.false, + type: 'array' as const, + optional: true as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'DriveFile' } }, tags: { - type: types.array, - optional: bool.true, nullable: bool.false, + type: 'array' as const, + optional: true as const, nullable: false as const, items: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, } }, poll: { - type: types.object, - optional: bool.true, nullable: bool.true, + type: 'object' as const, + optional: true as const, nullable: true as const, }, geo: { - type: types.object, - optional: bool.true, nullable: bool.true, + type: 'object' as const, + optional: true as const, nullable: true as const, }, }, }; diff --git a/src/models/repositories/notification.ts b/src/models/repositories/notification.ts index 54eec87cff..96293d4a68 100644 --- a/src/models/repositories/notification.ts +++ b/src/models/repositories/notification.ts @@ -3,7 +3,7 @@ import { Users, Notes } from '..'; import { Notification } from '../entities/notification'; import { ensure } from '../../prelude/ensure'; import { awaitAll } from '../../prelude/await-all'; -import { types, bool, SchemaType } from '../../misc/schema'; +import { SchemaType } from '../../misc/schema'; export type PackedNotification = SchemaType; @@ -51,37 +51,37 @@ export class NotificationRepository extends Repository { } export const packedNotificationSchema = { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { id: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'id', description: 'The unique identifier for this notification.', example: 'xxxxxxxxxx', }, createdAt: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'date-time', description: 'The date that the notification was created.' }, type: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, enum: ['follow', 'receiveFollowRequest', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote'], description: 'The type of the notification.' }, userId: { - type: types.string, - optional: bool.true, nullable: bool.true, + type: 'string' as const, + optional: true as const, nullable: true as const, format: 'id', }, user: { - type: types.object, + type: 'object' as const, ref: 'User', - optional: bool.true, nullable: bool.true, + optional: true as const, nullable: true as const, }, } }; diff --git a/src/models/repositories/page.ts b/src/models/repositories/page.ts index 3b41420025..33126274a1 100644 --- a/src/models/repositories/page.ts +++ b/src/models/repositories/page.ts @@ -1,6 +1,6 @@ import { EntityRepository, Repository } from 'typeorm'; import { Page } from '../entities/page'; -import { SchemaType, types, bool } from '../../misc/schema'; +import { SchemaType } from '../../misc/schema'; import { Users, DriveFiles, PageLikes } from '..'; import { awaitAll } from '../../prelude/await-all'; import { DriveFile } from '../entities/drive-file'; @@ -89,54 +89,54 @@ export class PageRepository extends Repository { } export const packedPageSchema = { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { id: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'id', example: 'xxxxxxxxxx', }, createdAt: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'date-time', }, updatedAt: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'date-time', }, title: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, }, name: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, }, summary: { - type: types.string, - optional: bool.false, nullable: bool.true, + type: 'string' as const, + optional: false as const, nullable: true as const, }, content: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, }, variables: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, }, userId: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'id', }, user: { - type: types.object, + type: 'object' as const, ref: 'User', - optional: bool.false, nullable: bool.false, + optional: false as const, nullable: false as const, }, } }; diff --git a/src/models/repositories/user-group.ts b/src/models/repositories/user-group.ts index 38174ce235..5ff75047c8 100644 --- a/src/models/repositories/user-group.ts +++ b/src/models/repositories/user-group.ts @@ -2,7 +2,7 @@ import { EntityRepository, Repository } from 'typeorm'; import { UserGroup } from '../entities/user-group'; import { ensure } from '../../prelude/ensure'; import { UserGroupJoinings } from '..'; -import { bool, types, SchemaType } from '../../misc/schema'; +import { SchemaType } from '../../misc/schema'; export type PackedUserGroup = SchemaType; @@ -28,38 +28,38 @@ export class UserGroupRepository extends Repository { } export const packedUserGroupSchema = { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { id: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'id', description: 'The unique identifier for this UserGroup.', example: 'xxxxxxxxxx', }, createdAt: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'date-time', description: 'The date that the UserGroup was created.' }, name: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, description: 'The name of the UserGroup.' }, ownerId: { - type: types.string, - nullable: bool.false, optional: bool.false, + type: 'string' as const, + nullable: false as const, optional: false as const, format: 'id', }, userIds: { - type: types.array, - nullable: bool.false, optional: bool.true, + type: 'array' as const, + nullable: false as const, optional: true as const, items: { - type: types.string, - nullable: bool.false, optional: bool.false, + type: 'string' as const, + nullable: false as const, optional: false as const, format: 'id', } }, diff --git a/src/models/repositories/user-list.ts b/src/models/repositories/user-list.ts index 54231c7f85..8842118be4 100644 --- a/src/models/repositories/user-list.ts +++ b/src/models/repositories/user-list.ts @@ -2,7 +2,7 @@ import { EntityRepository, Repository } from 'typeorm'; import { UserList } from '../entities/user-list'; import { ensure } from '../../prelude/ensure'; import { UserListJoinings } from '..'; -import { bool, types, SchemaType } from '../../misc/schema'; +import { SchemaType } from '../../misc/schema'; export type PackedUserList = SchemaType; @@ -27,33 +27,33 @@ export class UserListRepository extends Repository { } export const packedUserListSchema = { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { id: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'id', description: 'The unique identifier for this UserList.', example: 'xxxxxxxxxx', }, createdAt: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'date-time', description: 'The date that the UserList was created.' }, name: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, description: 'The name of the UserList.' }, userIds: { - type: types.array, - nullable: bool.false, optional: bool.true, + type: 'array' as const, + nullable: false as const, optional: true as const, items: { - type: types.string, - nullable: bool.false, optional: bool.false, + type: 'string' as const, + nullable: false as const, optional: false as const, format: 'id', } }, diff --git a/src/models/repositories/user.ts b/src/models/repositories/user.ts index bd48f04ea3..cc89b674c5 100644 --- a/src/models/repositories/user.ts +++ b/src/models/repositories/user.ts @@ -1,10 +1,10 @@ import $ from 'cafy'; import { EntityRepository, Repository, In } from 'typeorm'; import { User, ILocalUser, IRemoteUser } from '../entities/user'; -import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles, UserGroupJoinings } from '..'; +import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles, UserSecurityKeys, UserGroupJoinings } from '..'; import { ensure } from '../../prelude/ensure'; import config from '../../config'; -import { SchemaType, bool, types } from '../../misc/schema'; +import { SchemaType } from '../../misc/schema'; import { awaitAll } from '../../prelude/await-all'; export type PackedUser = SchemaType; @@ -156,6 +156,11 @@ export class UserRepository extends Repository { detail: true }), twoFactorEnabled: profile!.twoFactorEnabled, + securityKeys: profile!.twoFactorEnabled + ? UserSecurityKeys.count({ + userId: user.id + }).then(result => result >= 1) + : false, twitter: profile!.twitter ? { id: profile!.twitterUserId, screenName: profile!.twitterScreenName @@ -195,6 +200,15 @@ export class UserRepository extends Repository { clientData: profile!.clientData, email: profile!.email, emailVerified: profile!.emailVerified, + securityKeysList: profile!.twoFactorEnabled + ? UserSecurityKeys.find({ + where: { + userId: user.id + }, + select: ['id', 'name', 'lastUsed'] + }) + : [] + } : {}), ...(relation ? { @@ -243,150 +257,150 @@ export class UserRepository extends Repository { } export const packedUserSchema = { - type: types.object, - nullable: bool.false, optional: bool.false, + type: 'object' as const, + nullable: false as const, optional: false as const, properties: { id: { - type: types.string, - nullable: bool.false, optional: bool.false, + type: 'string' as const, + nullable: false as const, optional: false as const, format: 'id', description: 'The unique identifier for this User.', example: 'xxxxxxxxxx', }, username: { - type: types.string, - nullable: bool.false, optional: bool.false, + type: 'string' as const, + nullable: false as const, optional: false as const, description: 'The screen name, handle, or alias that this user identifies themselves with.', example: 'ai' }, name: { - type: types.string, - nullable: bool.true, optional: bool.false, + type: 'string' as const, + nullable: true as const, optional: false as const, description: 'The name of the user, as they’ve defined it.', example: '藍' }, url: { - type: types.string, + type: 'string' as const, format: 'url', - nullable: bool.true, optional: bool.true, + nullable: true as const, optional: true as const, }, avatarUrl: { - type: types.string, + type: 'string' as const, format: 'url', - nullable: bool.true, optional: bool.false, + nullable: true as const, optional: false as const, }, avatarColor: { - type: types.any, - nullable: bool.true, optional: bool.false, + type: 'any' as const, + nullable: true as const, optional: false as const, }, bannerUrl: { - type: types.string, + type: 'string' as const, format: 'url', - nullable: bool.true, optional: bool.true, + nullable: true as const, optional: true as const, }, bannerColor: { - type: types.any, - nullable: bool.true, optional: bool.true, + type: 'any' as const, + nullable: true as const, optional: true as const, }, emojis: { - type: types.any, - nullable: bool.true, optional: bool.false, + type: 'any' as const, + nullable: true as const, optional: false as const, }, host: { - type: types.string, - nullable: bool.true, optional: bool.false, + type: 'string' as const, + nullable: true as const, optional: false as const, example: 'misskey.example.com' }, description: { - type: types.string, - nullable: bool.true, optional: bool.true, + type: 'string' as const, + nullable: true as const, optional: true as const, description: 'The user-defined UTF-8 string describing their account.', example: 'Hi masters, I am Ai!' }, birthday: { - type: types.string, - nullable: bool.true, optional: bool.true, + type: 'string' as const, + nullable: true as const, optional: true as const, example: '2018-03-12' }, createdAt: { - type: types.string, - nullable: bool.false, optional: bool.true, + type: 'string' as const, + nullable: false as const, optional: true as const, format: 'date-time', description: 'The date that the user account was created on Misskey.' }, updatedAt: { - type: types.string, - nullable: bool.true, optional: bool.true, + type: 'string' as const, + nullable: true as const, optional: true as const, format: 'date-time', }, location: { - type: types.string, - nullable: bool.true, optional: bool.true, + type: 'string' as const, + nullable: true as const, optional: true as const, }, followersCount: { - type: types.number, - nullable: bool.false, optional: bool.true, + type: 'number' as const, + nullable: false as const, optional: true as const, description: 'The number of followers this account currently has.' }, followingCount: { - type: types.number, - nullable: bool.false, optional: bool.true, + type: 'number' as const, + nullable: false as const, optional: true as const, description: 'The number of users this account is following.' }, notesCount: { - type: types.number, - nullable: bool.false, optional: bool.true, + type: 'number' as const, + nullable: false as const, optional: true as const, description: 'The number of Notes (including renotes) issued by the user.' }, isBot: { - type: types.boolean, - nullable: bool.false, optional: bool.true, + type: 'boolean' as const, + nullable: false as const, optional: true as const, description: 'Whether this account is a bot.' }, pinnedNoteIds: { - type: types.array, - nullable: bool.false, optional: bool.true, + type: 'array' as const, + nullable: false as const, optional: true as const, items: { - type: types.string, - nullable: bool.false, optional: bool.false, + type: 'string' as const, + nullable: false as const, optional: false as const, format: 'id', } }, pinnedNotes: { - type: types.array, - nullable: bool.false, optional: bool.true, + type: 'array' as const, + nullable: false as const, optional: true as const, items: { - type: types.object, - nullable: bool.false, optional: bool.false, + type: 'object' as const, + nullable: false as const, optional: false as const, ref: 'Note' } }, isCat: { - type: types.boolean, - nullable: bool.false, optional: bool.true, + type: 'boolean' as const, + nullable: false as const, optional: true as const, description: 'Whether this account is a cat.' }, isAdmin: { - type: types.boolean, - nullable: bool.false, optional: bool.true, + type: 'boolean' as const, + nullable: false as const, optional: true as const, description: 'Whether this account is the admin.' }, isModerator: { - type: types.boolean, - nullable: bool.false, optional: bool.true, + type: 'boolean' as const, + nullable: false as const, optional: true as const, description: 'Whether this account is a moderator.' }, isLocked: { - type: types.boolean, - nullable: bool.false, optional: bool.true, + type: 'boolean' as const, + nullable: false as const, optional: true as const, }, hasUnreadSpecifiedNotes: { - type: types.boolean, - nullable: bool.false, optional: bool.true, + type: 'boolean' as const, + nullable: false as const, optional: true as const, }, hasUnreadMentions: { - type: types.boolean, - nullable: bool.false, optional: bool.true, + type: 'boolean' as const, + nullable: false as const, optional: true as const, }, }, }; diff --git a/src/queue/index.ts b/src/queue/index.ts index 70233d8318..a7e9b9814f 100644 --- a/src/queue/index.ts +++ b/src/queue/index.ts @@ -194,6 +194,13 @@ export function createDeleteObjectStorageFileJob(key: string) { }); } +export function createCleanRemoteFilesJob() { + return objectStorageQueue.add('cleanRemoteFiles', {}, { + removeOnComplete: true, + removeOnFail: true + }); +} + export default function() { if (!program.onlyServer) { deliverQueue.process(128, processDeliver); diff --git a/src/queue/processors/db/delete-drive-files.ts b/src/queue/processors/db/delete-drive-files.ts index 5ecee9139a..a2fd9050a9 100644 --- a/src/queue/processors/db/delete-drive-files.ts +++ b/src/queue/processors/db/delete-drive-files.ts @@ -1,7 +1,7 @@ import * as Bull from 'bull'; import { queueLogger } from '../../logger'; -import { deleteFile } from '../../../services/drive/delete-file'; +import { deleteFileSync } from '../../../services/drive/delete-file'; import { Users, DriveFiles } from '../../../models'; import { MoreThan } from 'typeorm'; @@ -39,7 +39,7 @@ export async function deleteDriveFiles(job: Bull.Job, done: any): Promise cursor = files[files.length - 1].id; for (const file of files) { - await deleteFile(file); + await deleteFileSync(file); deletedCount++; } diff --git a/src/queue/processors/object-storage/clean-remote-files.ts b/src/queue/processors/object-storage/clean-remote-files.ts new file mode 100644 index 0000000000..7b34892e1f --- /dev/null +++ b/src/queue/processors/object-storage/clean-remote-files.ts @@ -0,0 +1,50 @@ +import * as Bull from 'bull'; + +import { queueLogger } from '../../logger'; +import { deleteFileSync } from '../../../services/drive/delete-file'; +import { DriveFiles } from '../../../models'; +import { MoreThan, Not, IsNull } from 'typeorm'; + +const logger = queueLogger.createSubLogger('clean-remote-files'); + +export default async function cleanRemoteFiles(job: Bull.Job, done: any): Promise { + logger.info(`Deleting cached remote files...`); + + let deletedCount = 0; + let cursor: any = null; + + while (true) { + const files = await DriveFiles.find({ + where: { + userHost: Not(IsNull()), + isLink: false, + ...(cursor ? { id: MoreThan(cursor) } : {}) + }, + take: 8, + order: { + id: 1 + } + }); + + if (files.length === 0) { + job.progress(100); + break; + } + + cursor = files[files.length - 1].id; + + await Promise.all(files.map(file => deleteFileSync(file, true))); + + deletedCount += 8; + + const total = await DriveFiles.count({ + userHost: Not(IsNull()), + isLink: false, + }); + + job.progress(deletedCount / total); + } + + logger.succ(`All cahced remote files has been deleted.`); + done(); +} diff --git a/src/queue/processors/object-storage/delete-file.ts b/src/queue/processors/object-storage/delete-file.ts index 8e6b5f959e..f899df7d2e 100644 --- a/src/queue/processors/object-storage/delete-file.ts +++ b/src/queue/processors/object-storage/delete-file.ts @@ -1,22 +1,10 @@ import * as Bull from 'bull'; -import * as Minio from 'minio'; -import { fetchMeta } from '../../../misc/fetch-meta'; +import { deleteObjectStorageFile } from '../../../services/drive/delete-file'; export default async (job: Bull.Job) => { - const meta = await fetchMeta(); - - const minio = new Minio.Client({ - endPoint: meta.objectStorageEndpoint!, - region: meta.objectStorageRegion ? meta.objectStorageRegion : undefined, - port: meta.objectStoragePort ? meta.objectStoragePort : undefined, - useSSL: meta.objectStorageUseSSL, - accessKey: meta.objectStorageAccessKey!, - secretKey: meta.objectStorageSecretKey!, - }); - const key: string = job.data.key; - await minio.removeObject(meta.objectStorageBucket!, key); + await deleteObjectStorageFile(key); return 'Success'; }; diff --git a/src/queue/processors/object-storage/index.ts b/src/queue/processors/object-storage/index.ts index f2fc08f1ab..33ef665b38 100644 --- a/src/queue/processors/object-storage/index.ts +++ b/src/queue/processors/object-storage/index.ts @@ -1,12 +1,14 @@ import * as Bull from 'bull'; import deleteFile from './delete-file'; +import cleanRemoteFiles from './clean-remote-files'; const jobs = { deleteFile, + cleanRemoteFiles, } as any; export default function(q: Bull.Queue) { for (const [k, v] of Object.entries(jobs)) { - q.process(k, v as any); + q.process(k, 16, v as any); } } diff --git a/src/remote/activitypub/kernel/announce/index.ts b/src/remote/activitypub/kernel/announce/index.ts index ebd5a27b92..68fce52e17 100644 --- a/src/remote/activitypub/kernel/announce/index.ts +++ b/src/remote/activitypub/kernel/announce/index.ts @@ -1,13 +1,13 @@ import Resolver from '../../resolver'; import { IRemoteUser } from '../../../../models/entities/user'; import announceNote from './note'; -import { IAnnounce, INote } from '../../type'; +import { IAnnounce, INote, validPost, getApId } from '../../type'; import { apLogger } from '../../logger'; const logger = apLogger; export default async (actor: IRemoteUser, activity: IAnnounce): Promise => { - const uri = activity.id || activity; + const uri = getApId(activity); logger.info(`Announce: ${uri}`); @@ -22,15 +22,9 @@ export default async (actor: IRemoteUser, activity: IAnnounce): Promise => throw e; } - switch (object.type) { - case 'Note': - case 'Question': - case 'Article': + if (validPost.includes(object.type)) { announceNote(resolver, actor, activity, object as INote); - break; - - default: + } else { logger.warn(`Unknown announce type: ${object.type}`); - break; } }; diff --git a/src/remote/activitypub/kernel/announce/note.ts b/src/remote/activitypub/kernel/announce/note.ts index 6843041475..2a07f50c8a 100644 --- a/src/remote/activitypub/kernel/announce/note.ts +++ b/src/remote/activitypub/kernel/announce/note.ts @@ -1,7 +1,7 @@ import Resolver from '../../resolver'; import post from '../../../../services/note/create'; import { IRemoteUser, User } from '../../../../models/entities/user'; -import { IAnnounce, INote } from '../../type'; +import { IAnnounce, INote, getApId, getApIds } from '../../type'; import { fetchNote, resolveNote } from '../../models/note'; import { resolvePerson } from '../../models/person'; import { apLogger } from '../../logger'; @@ -14,17 +14,13 @@ const logger = apLogger; * アナウンスアクティビティを捌きます */ export default async function(resolver: Resolver, actor: IRemoteUser, activity: IAnnounce, note: INote): Promise { - const uri = activity.id || activity; + const uri = getApId(activity); // アナウンサーが凍結されていたらスキップ if (actor.isSuspended) { return; } - if (typeof uri !== 'string') { - throw new Error('invalid announce'); - } - // アナウンス先をブロックしてたら中断 const meta = await fetchMeta(); if (meta.blockedHosts.includes(extractDbHost(uri))) return; @@ -52,11 +48,14 @@ export default async function(resolver: Resolver, actor: IRemoteUser, activity: logger.info(`Creating the (Re)Note: ${uri}`); //#region Visibility - const visibility = getVisibility(activity.to || [], activity.cc || [], actor); + const to = getApIds(activity.to); + const cc = getApIds(activity.cc); + + const visibility = getVisibility(to, cc, actor); let visibleUsers: User[] = []; if (visibility == 'specified') { - visibleUsers = await Promise.all((note.to || []).map(uri => resolvePerson(uri))); + visibleUsers = await Promise.all(to.map(uri => resolvePerson(uri))); } //#endergion diff --git a/src/remote/activitypub/kernel/create/image.ts b/src/remote/activitypub/kernel/create/image.ts deleted file mode 100644 index 7720e8f1bd..0000000000 --- a/src/remote/activitypub/kernel/create/image.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { IRemoteUser } from '../../../../models/entities/user'; -import { createImage } from '../../models/image'; - -export default async function(actor: IRemoteUser, image: any): Promise { - await createImage(image.url, actor); -} diff --git a/src/remote/activitypub/kernel/create/index.ts b/src/remote/activitypub/kernel/create/index.ts index 0326b591f8..a6fa2336f3 100644 --- a/src/remote/activitypub/kernel/create/index.ts +++ b/src/remote/activitypub/kernel/create/index.ts @@ -1,14 +1,13 @@ import Resolver from '../../resolver'; import { IRemoteUser } from '../../../../models/entities/user'; -import createImage from './image'; import createNote from './note'; -import { ICreate } from '../../type'; +import { ICreate, getApId, validPost } from '../../type'; import { apLogger } from '../../logger'; const logger = apLogger; export default async (actor: IRemoteUser, activity: ICreate): Promise => { - const uri = activity.id || activity; + const uri = getApId(activity); logger.info(`Create: ${uri}`); @@ -23,19 +22,9 @@ export default async (actor: IRemoteUser, activity: ICreate): Promise => { throw e; } - switch (object.type) { - case 'Image': - createImage(actor, object); - break; - - case 'Note': - case 'Question': - case 'Article': + if (validPost.includes(object.type)) { createNote(resolver, actor, object); - break; - - default: + } else { logger.warn(`Unknown type: ${object.type}`); - break; } }; diff --git a/src/remote/activitypub/kernel/delete/index.ts b/src/remote/activitypub/kernel/delete/index.ts index 199a6a6187..be7779e026 100644 --- a/src/remote/activitypub/kernel/delete/index.ts +++ b/src/remote/activitypub/kernel/delete/index.ts @@ -1,9 +1,8 @@ import Resolver from '../../resolver'; import deleteNote from './note'; import { IRemoteUser } from '../../../../models/entities/user'; -import { IDelete } from '../../type'; +import { IDelete, getApId, validPost } from '../../type'; import { apLogger } from '../../logger'; -import { Notes } from '../../../../models'; /** * 削除アクティビティを捌きます @@ -17,24 +16,11 @@ export default async (actor: IRemoteUser, activity: IDelete): Promise => { const object = await resolver.resolve(activity.object); - const uri = (object as any).id; + const uri = getApId(object); - switch (object.type) { - case 'Note': - case 'Question': - case 'Article': - deleteNote(actor, uri); - break; - - case 'Tombstone': - const note = await Notes.findOne({ uri }); - if (note != null) { - deleteNote(actor, uri); - } - break; - - default: - apLogger.warn(`Unknown type: ${object.type}`); - break; + if (validPost.includes(object.type) || object.type === 'Tombstone') { + deleteNote(actor, uri); + } else { + apLogger.warn(`Unknown type: ${object.type}`); } }; diff --git a/src/remote/activitypub/models/note.ts b/src/remote/activitypub/models/note.ts index bb9465d90d..14425d7496 100644 --- a/src/remote/activitypub/models/note.ts +++ b/src/remote/activitypub/models/note.ts @@ -17,7 +17,7 @@ import { deliverQuestionUpdate } from '../../../services/note/polls/update'; import { extractDbHost, toPuny } from '../../../misc/convert-host'; import { Notes, Emojis, Polls } from '../../../models'; import { Note } from '../../../models/entities/note'; -import { IObject, INote } from '../type'; +import { IObject, INote, getApIds, getOneApId, getApId, validPost } from '../type'; import { Emoji } from '../../../models/entities/emoji'; import { genId } from '../../../misc/gen-id'; import { fetchMeta } from '../../../misc/fetch-meta'; @@ -32,7 +32,7 @@ export function validateNote(object: any, uri: string) { return new Error('invalid Note: object is null'); } - if (!['Note', 'Question', 'Article'].includes(object.type)) { + if (!validPost.includes(object.type)) { return new Error(`invalid Note: invalied object type ${object.type}`); } @@ -40,7 +40,7 @@ export function validateNote(object: any, uri: string) { return new Error(`invalid Note: id has different host. expected: ${expectHost}, actual: ${extractDbHost(object.id)}`); } - if (object.attributedTo && extractDbHost(object.attributedTo) !== expectHost) { + if (object.attributedTo && extractDbHost(getOneApId(object.attributedTo)) !== expectHost) { return new Error(`invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${extractDbHost(object.attributedTo)}`); } @@ -53,8 +53,7 @@ export function validateNote(object: any, uri: string) { * Misskeyに対象のNoteが登録されていればそれを返します。 */ export async function fetchNote(value: string | IObject, resolver?: Resolver): Promise { - const uri = typeof value == 'string' ? value : value.id; - if (uri == null) throw new Error('missing uri'); + const uri = getApId(value); // URIがこのサーバーを指しているならデータベースからフェッチ if (uri.startsWith(config.url + '/')) { @@ -76,12 +75,12 @@ export async function fetchNote(value: string | IObject, resolver?: Resolver): P /** * Noteを作成します。 */ -export async function createNote(value: any, resolver?: Resolver, silent = false): Promise { +export async function createNote(value: string | IObject, resolver?: Resolver, silent = false): Promise { if (resolver == null) resolver = new Resolver(); const object: any = await resolver.resolve(value); - const entryUri = value.id || value; + const entryUri = getApId(value); const err = validateNote(object, entryUri); if (err) { logger.error(`${err.message}`, { @@ -101,7 +100,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false logger.info(`Creating the Note: ${note.id}`); // 投稿者をフェッチ - const actor = await resolvePerson(note.attributedTo, resolver) as IRemoteUser; + const actor = await resolvePerson(getOneApId(note.attributedTo), resolver) as IRemoteUser; // 投稿者が凍結されていたらスキップ if (actor.isSuspended) { @@ -109,24 +108,24 @@ export async function createNote(value: any, resolver?: Resolver, silent = false } //#region Visibility - note.to = note.to == null ? [] : typeof note.to == 'string' ? [note.to] : note.to; - note.cc = note.cc == null ? [] : typeof note.cc == 'string' ? [note.cc] : note.cc; + const to = getApIds(note.to); + const cc = getApIds(note.cc); let visibility = 'public'; let visibleUsers: User[] = []; - if (!note.to.includes('https://www.w3.org/ns/activitystreams#Public')) { - if (note.cc.includes('https://www.w3.org/ns/activitystreams#Public')) { + if (!to.includes('https://www.w3.org/ns/activitystreams#Public')) { + if (cc.includes('https://www.w3.org/ns/activitystreams#Public')) { visibility = 'home'; - } else if (note.to.includes(`${actor.uri}/followers`)) { // TODO: person.followerと照合するべき? + } else if (to.includes(`${actor.uri}/followers`)) { // TODO: person.followerと照合するべき? visibility = 'followers'; } else { visibility = 'specified'; - visibleUsers = await Promise.all(note.to.map(uri => resolvePerson(uri, resolver))); + visibleUsers = await Promise.all(to.map(uri => resolvePerson(uri, resolver))); } } //#endergion - const apMentions = await extractMentionedUsers(actor, note.to, note.cc, resolver); + const apMentions = await extractMentionedUsers(actor, to, cc, resolver); const apHashtags = await extractHashtags(note.tag); @@ -217,11 +216,11 @@ export async function createNote(value: any, resolver?: Resolver, silent = false const apEmojis = emojis.map(emoji => emoji.name); const questionUri = note._misskey_question; - const poll = await extractPollFromQuestion(note._misskey_question || note).catch(() => undefined); + const poll = await extractPollFromQuestion(note._misskey_question || note, resolver).catch(() => undefined); // ユーザーの情報が古かったらついでに更新しておく if (actor.lastFetchedAt == null || Date.now() - actor.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { - updatePerson(note.attributedTo); + if (actor.uri) updatePerson(actor.uri); } return await post(actor, { diff --git a/src/remote/activitypub/models/question.ts b/src/remote/activitypub/models/question.ts index 5c126c3a56..01086a7cf8 100644 --- a/src/remote/activitypub/models/question.ts +++ b/src/remote/activitypub/models/question.ts @@ -1,12 +1,19 @@ import config from '../../../config'; import Resolver from '../resolver'; -import { IQuestion } from '../type'; +import { IObject, IQuestion, isQuestion, } from '../type'; import { apLogger } from '../logger'; import { Notes, Polls } from '../../../models'; import { IPoll } from '../../../models/entities/poll'; -export async function extractPollFromQuestion(source: string | IQuestion): Promise { - const question = typeof source === 'string' ? await new Resolver().resolve(source) as IQuestion : source; +export async function extractPollFromQuestion(source: string | IObject, resolver?: Resolver): Promise { + if (resolver == null) resolver = new Resolver(); + + const question = await resolver.resolve(source); + + if (!isQuestion(question)) { + throw new Error('invalid type'); + } + const multiple = !question.oneOf; const expiresAt = question.endTime ? new Date(question.endTime) : null; diff --git a/src/remote/activitypub/type.ts b/src/remote/activitypub/type.ts index 95c69fb8ac..66163d39b0 100644 --- a/src/remote/activitypub/type.ts +++ b/src/remote/activitypub/type.ts @@ -6,9 +6,9 @@ export interface IObject { id?: string; summary?: string; published?: string; - cc?: string[]; - to?: string[]; - attributedTo: string; + cc?: IObject | string | (IObject | string)[]; + to?: IObject | string | (IObject | string)[]; + attributedTo: IObject | string | (IObject | string)[]; attachment?: any[]; inReplyTo?: any; replies?: ICollection; @@ -23,6 +23,32 @@ export interface IObject { sensitive?: boolean; } +/** + * Get array of ActivityStreams Objects id + */ +export function getApIds(value: IObject | string | (IObject | string)[] | undefined): string[] { + if (value == null) return []; + const array = Array.isArray(value) ? value : [value]; + return array.map(x => getApId(x)); +} + +/** + * Get first ActivityStreams Object id + */ +export function getOneApId(value: IObject | string | (IObject | string)[]): string { + const firstOne = Array.isArray(value) ? value[0] : value; + return getApId(firstOne); +} + +/** + * Get ActivityStreams Object id + */ +export function getApId(value: string | IObject): string { + if (typeof value === 'string') return value; + if (typeof value.id === 'string') return value.id; + throw new Error(`cannot detemine id`); +} + export interface IActivity extends IObject { //type: 'Activity'; actor: IObject | string; @@ -42,8 +68,10 @@ export interface IOrderedCollection extends IObject { orderedItems: IObject | string | IObject[] | string[]; } +export const validPost = ['Note', 'Question', 'Article', 'Audio', 'Document', 'Image', 'Page', 'Video']; + export interface INote extends IObject { - type: 'Note' | 'Question'; + type: 'Note' | 'Question' | 'Article' | 'Audio' | 'Document' | 'Image' | 'Page' | 'Video'; _misskey_content?: string; _misskey_quote?: string; _misskey_question?: string; @@ -59,6 +87,9 @@ export interface IQuestion extends IObject { endTime?: Date; } +export const isQuestion = (object: IObject): object is IQuestion => + object.type === 'Note' || object.type === 'Question'; + interface IQuestionChoice { name?: string; replies?: ICollection; diff --git a/src/server/api/2fa.ts b/src/server/api/2fa.ts new file mode 100644 index 0000000000..3bc4627a62 --- /dev/null +++ b/src/server/api/2fa.ts @@ -0,0 +1,422 @@ +import * as crypto from 'crypto'; +import config from '../../config'; +import * as jsrsasign from 'jsrsasign'; + +const ECC_PRELUDE = Buffer.from([0x04]); +const NULL_BYTE = Buffer.from([0]); +const PEM_PRELUDE = Buffer.from( + '3059301306072a8648ce3d020106082a8648ce3d030107034200', + 'hex' +); + +// Android Safetynet attestations are signed with this cert: +const GSR2 = `-----BEGIN CERTIFICATE----- +MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G +A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp +Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1 +MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG +A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL +v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8 +eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq +tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd +C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa +zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB +mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH +V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n +bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG +3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs +J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO +291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS +ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd +AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 +TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== +-----END CERTIFICATE-----\n`; + +function base64URLDecode(source: string) { + return Buffer.from(source.replace(/\-/g, '+').replace(/_/g, '/'), 'base64'); +} + +function getCertSubject(certificate: string) { + const subjectCert = new jsrsasign.X509(); + subjectCert.readCertPEM(certificate); + + const subjectString = subjectCert.getSubjectString(); + const subjectFields = subjectString.slice(1).split('/'); + + const fields = {} as Record; + for (const field of subjectFields) { + const eqIndex = field.indexOf('='); + fields[field.substring(0, eqIndex)] = field.substring(eqIndex + 1); + } + + return fields; +} + +function verifyCertificateChain(certificates: string[]) { + let valid = true; + + for (let i = 0; i < certificates.length; i++) { + const Cert = certificates[i]; + const certificate = new jsrsasign.X509(); + certificate.readCertPEM(Cert); + + const CACert = i + 1 >= certificates.length ? Cert : certificates[i + 1]; + + const certStruct = jsrsasign.ASN1HEX.getTLVbyList(certificate.hex!, 0, [0]); + const algorithm = certificate.getSignatureAlgorithmField(); + const signatureHex = certificate.getSignatureValueHex(); + + // Verify against CA + const Signature = new jsrsasign.KJUR.crypto.Signature({alg: algorithm}); + Signature.init(CACert); + Signature.updateHex(certStruct); + valid = valid && !!Signature.verify(signatureHex); // true if CA signed the certificate + } + + return valid; +} + +function PEMString(pemBuffer: Buffer, type = 'CERTIFICATE') { + if (pemBuffer.length == 65 && pemBuffer[0] == 0x04) { + pemBuffer = Buffer.concat([PEM_PRELUDE, pemBuffer], 91); + type = 'PUBLIC KEY'; + } + const cert = pemBuffer.toString('base64'); + + const keyParts = []; + const max = Math.ceil(cert.length / 64); + let start = 0; + for (let i = 0; i < max; i++) { + keyParts.push(cert.substring(start, start + 64)); + start += 64; + } + + return ( + `-----BEGIN ${type}-----\n` + + keyParts.join('\n') + + `\n-----END ${type}-----\n` + ); +} + +export function hash(data: Buffer) { + return crypto + .createHash('sha256') + .update(data) + .digest(); +} + +export function verifyLogin({ + publicKey, + authenticatorData, + clientDataJSON, + clientData, + signature, + challenge +}: { + publicKey: Buffer, + authenticatorData: Buffer, + clientDataJSON: Buffer, + clientData: any, + signature: Buffer, + challenge: string +}) { + if (clientData.type != 'webauthn.get') { + throw new Error('type is not webauthn.get'); + } + + if (hash(clientData.challenge).toString('hex') != challenge) { + throw new Error('challenge mismatch'); + } + if (clientData.origin != config.scheme + '://' + config.host) { + throw new Error('origin mismatch'); + } + + const verificationData = Buffer.concat( + [authenticatorData, hash(clientDataJSON)], + 32 + authenticatorData.length + ); + + return crypto + .createVerify('SHA256') + .update(verificationData) + .verify(PEMString(publicKey), signature); +} + +export const procedures = { + none: { + verify({publicKey}: {publicKey: Map}) { + const negTwo = publicKey.get(-2); + + if (!negTwo || negTwo.length != 32) { + throw new Error('invalid or no -2 key given'); + } + const negThree = publicKey.get(-3); + if (!negThree || negThree.length != 32) { + throw new Error('invalid or no -3 key given'); + } + + const publicKeyU2F = Buffer.concat( + [ECC_PRELUDE, negTwo, negThree], + 1 + 32 + 32 + ); + + return { + publicKey: publicKeyU2F, + valid: true + }; + } + }, + 'android-key': { + verify({ + attStmt, + authenticatorData, + clientDataHash, + publicKey, + rpIdHash, + credentialId + }: { + attStmt: any, + authenticatorData: Buffer, + clientDataHash: Buffer, + publicKey: Map; + rpIdHash: Buffer, + credentialId: Buffer, + }) { + if (attStmt.alg != -7) { + throw new Error('alg mismatch'); + } + + const verificationData = Buffer.concat([ + authenticatorData, + clientDataHash + ]); + + const attCert: Buffer = attStmt.x5c[0]; + + const negTwo = publicKey.get(-2); + + if (!negTwo || negTwo.length != 32) { + throw new Error('invalid or no -2 key given'); + } + const negThree = publicKey.get(-3); + if (!negThree || negThree.length != 32) { + throw new Error('invalid or no -3 key given'); + } + + const publicKeyData = Buffer.concat( + [ECC_PRELUDE, negTwo, negThree], + 1 + 32 + 32 + ); + + if (!attCert.equals(publicKeyData)) { + throw new Error('public key mismatch'); + } + + const isValid = crypto + .createVerify('SHA256') + .update(verificationData) + .verify(PEMString(attCert), attStmt.sig); + + // TODO: Check 'attestationChallenge' field in extension of cert matches hash(clientDataJSON) + + return { + valid: isValid, + publicKey: publicKeyData + }; + } + }, + // what a stupid attestation + 'android-safetynet': { + verify({ + attStmt, + authenticatorData, + clientDataHash, + publicKey, + rpIdHash, + credentialId + }: { + attStmt: any, + authenticatorData: Buffer, + clientDataHash: Buffer, + publicKey: Map; + rpIdHash: Buffer, + credentialId: Buffer, + }) { + const verificationData = hash( + Buffer.concat([authenticatorData, clientDataHash]) + ); + + const jwsParts = attStmt.response.toString('utf-8').split('.'); + + const header = JSON.parse(base64URLDecode(jwsParts[0]).toString('utf-8')); + const response = JSON.parse( + base64URLDecode(jwsParts[1]).toString('utf-8') + ); + const signature = jwsParts[2]; + + if (!verificationData.equals(Buffer.from(response.nonce, 'base64'))) { + throw new Error('invalid nonce'); + } + + const certificateChain = header.x5c + .map((key: any) => PEMString(key)) + .concat([GSR2]); + + if (getCertSubject(certificateChain[0]).CN != 'attest.android.com') { + throw new Error('invalid common name'); + } + + if (!verifyCertificateChain(certificateChain)) { + throw new Error('Invalid certificate chain!'); + } + + const signatureBase = Buffer.from( + jwsParts[0] + '.' + jwsParts[1], + 'utf-8' + ); + + const valid = crypto + .createVerify('sha256') + .update(signatureBase) + .verify(certificateChain[0], base64URLDecode(signature)); + + const negTwo = publicKey.get(-2); + + if (!negTwo || negTwo.length != 32) { + throw new Error('invalid or no -2 key given'); + } + const negThree = publicKey.get(-3); + if (!negThree || negThree.length != 32) { + throw new Error('invalid or no -3 key given'); + } + + const publicKeyData = Buffer.concat( + [ECC_PRELUDE, negTwo, negThree], + 1 + 32 + 32 + ); + return { + valid, + publicKey: publicKeyData + }; + } + }, + packed: { + verify({ + attStmt, + authenticatorData, + clientDataHash, + publicKey, + rpIdHash, + credentialId + }: { + attStmt: any, + authenticatorData: Buffer, + clientDataHash: Buffer, + publicKey: Map; + rpIdHash: Buffer, + credentialId: Buffer, + }) { + const verificationData = Buffer.concat([ + authenticatorData, + clientDataHash + ]); + + if (attStmt.x5c) { + const attCert = attStmt.x5c[0]; + + const validSignature = crypto + .createVerify('SHA256') + .update(verificationData) + .verify(PEMString(attCert), attStmt.sig); + + const negTwo = publicKey.get(-2); + + if (!negTwo || negTwo.length != 32) { + throw new Error('invalid or no -2 key given'); + } + const negThree = publicKey.get(-3); + if (!negThree || negThree.length != 32) { + throw new Error('invalid or no -3 key given'); + } + + const publicKeyData = Buffer.concat( + [ECC_PRELUDE, negTwo, negThree], + 1 + 32 + 32 + ); + + return { + valid: validSignature, + publicKey: publicKeyData + }; + } else if (attStmt.ecdaaKeyId) { + // https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-ecdaa-algorithm-v2.0-id-20180227.html#ecdaa-verify-operation + throw new Error('ECDAA-Verify is not supported'); + } else { + if (attStmt.alg != -7) throw new Error('alg mismatch'); + + throw new Error('self attestation is not supported'); + } + } + }, + + 'fido-u2f': { + verify({ + attStmt, + authenticatorData, + clientDataHash, + publicKey, + rpIdHash, + credentialId + }: { + attStmt: any, + authenticatorData: Buffer, + clientDataHash: Buffer, + publicKey: Map, + rpIdHash: Buffer, + credentialId: Buffer + }) { + const x5c: Buffer[] = attStmt.x5c; + if (x5c.length != 1) { + throw new Error('x5c length does not match expectation'); + } + + const attCert = x5c[0]; + + // TODO: make sure attCert is an Elliptic Curve (EC) public key over the P-256 curve + + const negTwo: Buffer = publicKey.get(-2); + + if (!negTwo || negTwo.length != 32) { + throw new Error('invalid or no -2 key given'); + } + const negThree: Buffer = publicKey.get(-3); + if (!negThree || negThree.length != 32) { + throw new Error('invalid or no -3 key given'); + } + + const publicKeyU2F = Buffer.concat( + [ECC_PRELUDE, negTwo, negThree], + 1 + 32 + 32 + ); + + const verificationData = Buffer.concat([ + NULL_BYTE, + rpIdHash, + clientDataHash, + credentialId, + publicKeyU2F + ]); + + const validSignature = crypto + .createVerify('SHA256') + .update(verificationData) + .verify(PEMString(attCert), attStmt.sig); + + return { + valid: validSignature, + publicKey: publicKeyU2F + }; + } + } +}; diff --git a/src/server/api/endpoints/admin/drive/clean-remote-files.ts b/src/server/api/endpoints/admin/drive/clean-remote-files.ts index 69cfe0db94..e837ae1bb6 100644 --- a/src/server/api/endpoints/admin/drive/clean-remote-files.ts +++ b/src/server/api/endpoints/admin/drive/clean-remote-files.ts @@ -1,7 +1,5 @@ -import { Not, IsNull } from 'typeorm'; import define from '../../../define'; -import { deleteFile } from '../../../../../services/drive/delete-file'; -import { DriveFiles } from '../../../../../models'; +import { createCleanRemoteFilesJob } from '../../../../../queue'; export const meta = { tags: ['admin'], @@ -11,12 +9,5 @@ export const meta = { }; export default define(meta, async (ps, me) => { - const files = await DriveFiles.find({ - userHost: Not(IsNull()), - isLink: false, - }); - - for (const file of files) { - deleteFile(file, true); - } + createCleanRemoteFilesJob(); }); diff --git a/src/server/api/endpoints/admin/send-email.ts b/src/server/api/endpoints/admin/send-email.ts new file mode 100644 index 0000000000..fed4f2df14 --- /dev/null +++ b/src/server/api/endpoints/admin/send-email.ts @@ -0,0 +1,26 @@ +import $ from 'cafy'; +import define from '../../define'; +import { sendEmail } from '../../../../services/send-email'; + +export const meta = { + tags: ['admin'], + + requireCredential: true, + requireModerator: true, + + params: { + to: { + validator: $.str, + }, + subject: { + validator: $.str, + }, + text: { + validator: $.str, + }, + } +}; + +export default define(meta, async (ps) => { + await sendEmail(ps.to, ps.subject, ps.text); +}); diff --git a/src/server/api/endpoints/admin/vacuum.ts b/src/server/api/endpoints/admin/vacuum.ts new file mode 100644 index 0000000000..6990706282 --- /dev/null +++ b/src/server/api/endpoints/admin/vacuum.ts @@ -0,0 +1,33 @@ +import $ from 'cafy'; +import define from '../../define'; +import { getConnection } from 'typeorm'; + +export const meta = { + tags: ['admin'], + + requireCredential: true, + requireModerator: true, + + params: { + full: { + validator: $.bool, + }, + analyze: { + validator: $.bool, + }, + } +}; + +export default define(meta, async (ps) => { + const params: string[] = []; + + if (ps.full) { + params.push('FULL'); + } + + if (ps.analyze) { + params.push('ANALYZE'); + } + + getConnection().query('VACUUM ' + params.join(' ')); +}); diff --git a/src/server/api/endpoints/ap/show.ts b/src/server/api/endpoints/ap/show.ts index 9724a044b1..bbaa1fa109 100644 --- a/src/server/api/endpoints/ap/show.ts +++ b/src/server/api/endpoints/ap/show.ts @@ -10,7 +10,7 @@ import { Users, Notes } from '../../../../models'; import { Note } from '../../../../models/entities/note'; import { User } from '../../../../models/entities/user'; import { fetchMeta } from '../../../../misc/fetch-meta'; -import { validActor } from '../../../../remote/activitypub/type'; +import { validActor, validPost } from '../../../../remote/activitypub/type'; export const meta = { tags: ['federation'], @@ -145,7 +145,7 @@ async function fetchAny(uri: string) { }; } - if (['Note', 'Question', 'Article'].includes(object.type)) { + if (validPost.includes(object.type)) { const note = await createNote(object.id, undefined, true); return { type: 'Note', diff --git a/src/server/api/endpoints/app/create.ts b/src/server/api/endpoints/app/create.ts index 833d5060c5..81c851f3a3 100644 --- a/src/server/api/endpoints/app/create.ts +++ b/src/server/api/endpoints/app/create.ts @@ -4,7 +4,6 @@ import define from '../../define'; import { Apps } from '../../../../models'; import { genId } from '../../../../misc/gen-id'; import { unique } from '../../../../prelude/array'; -import { types, bool } from '../../../../misc/schema'; export const meta = { tags: ['app'], @@ -53,8 +52,8 @@ export const meta = { }, res: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'App', }, }; diff --git a/src/server/api/endpoints/app/show.ts b/src/server/api/endpoints/app/show.ts index e7d3e84388..2c8cdbe396 100644 --- a/src/server/api/endpoints/app/show.ts +++ b/src/server/api/endpoints/app/show.ts @@ -3,7 +3,6 @@ import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import { ApiError } from '../../error'; import { Apps } from '../../../../models'; -import { types, bool } from '../../../../misc/schema'; export const meta = { tags: ['app'], @@ -15,8 +14,8 @@ export const meta = { }, res: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'App', }, diff --git a/src/server/api/endpoints/auth/session/generate.ts b/src/server/api/endpoints/auth/session/generate.ts index 9bf27c8e77..b38c275deb 100644 --- a/src/server/api/endpoints/auth/session/generate.ts +++ b/src/server/api/endpoints/auth/session/generate.ts @@ -5,7 +5,6 @@ import define from '../../../define'; import { ApiError } from '../../../error'; import { Apps, AuthSessions } from '../../../../../models'; import { genId } from '../../../../../misc/gen-id'; -import { types, bool } from '../../../../../misc/schema'; export const meta = { tags: ['auth'], @@ -28,17 +27,17 @@ export const meta = { }, res: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { token: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, description: 'セッションのトークン' }, url: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, format: 'url', description: 'セッションのURL' }, diff --git a/src/server/api/endpoints/auth/session/userkey.ts b/src/server/api/endpoints/auth/session/userkey.ts index b7a58c4750..1dc78eeabd 100644 --- a/src/server/api/endpoints/auth/session/userkey.ts +++ b/src/server/api/endpoints/auth/session/userkey.ts @@ -3,7 +3,6 @@ import define from '../../../define'; import { ApiError } from '../../../error'; import { Apps, AuthSessions, AccessTokens, Users } from '../../../../../models'; import { ensure } from '../../../../../prelude/ensure'; -import { types, bool } from '../../../../../misc/schema'; export const meta = { tags: ['auth'], @@ -29,18 +28,18 @@ export const meta = { }, res: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { accessToken: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, description: 'ユーザーのアクセストークン', }, user: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'User', description: '認証したユーザー' }, diff --git a/src/server/api/endpoints/blocking/list.ts b/src/server/api/endpoints/blocking/list.ts index 5ff1dc0c49..c99ba09df0 100644 --- a/src/server/api/endpoints/blocking/list.ts +++ b/src/server/api/endpoints/blocking/list.ts @@ -3,7 +3,6 @@ import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import { Blockings } from '../../../../models'; import { makePaginationQuery } from '../../common/make-pagination-query'; -import { types, bool } from '../../../../misc/schema'; export const meta = { desc: { @@ -33,11 +32,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'Blocking', } }, diff --git a/src/server/api/endpoints/drive.ts b/src/server/api/endpoints/drive.ts index 4d4516bd80..bb85bab148 100644 --- a/src/server/api/endpoints/drive.ts +++ b/src/server/api/endpoints/drive.ts @@ -1,7 +1,6 @@ import define from '../define'; import { fetchMeta } from '../../../misc/fetch-meta'; import { DriveFiles } from '../../../models'; -import { types, bool } from '../../../misc/schema'; export const meta = { desc: { @@ -16,16 +15,16 @@ export const meta = { kind: 'read:drive', res: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { capacity: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, }, usage: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, } } } diff --git a/src/server/api/endpoints/drive/files.ts b/src/server/api/endpoints/drive/files.ts index d10c2a3ef4..77cefdfbe3 100644 --- a/src/server/api/endpoints/drive/files.ts +++ b/src/server/api/endpoints/drive/files.ts @@ -3,7 +3,6 @@ import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import { DriveFiles } from '../../../../models'; import { makePaginationQuery } from '../../common/make-pagination-query'; -import { types, bool } from '../../../../misc/schema'; export const meta = { desc: { @@ -42,11 +41,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'DriveFile', } }, diff --git a/src/server/api/endpoints/drive/files/attached-notes.ts b/src/server/api/endpoints/drive/files/attached-notes.ts index f770bc7136..2b84e114b3 100644 --- a/src/server/api/endpoints/drive/files/attached-notes.ts +++ b/src/server/api/endpoints/drive/files/attached-notes.ts @@ -3,7 +3,6 @@ import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; import { ApiError } from '../../../error'; import { DriveFiles, Notes } from '../../../../../models'; -import { types, bool } from '../../../../../misc/schema'; export const meta = { stability: 'stable', @@ -30,11 +29,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'Note', } }, diff --git a/src/server/api/endpoints/drive/files/check-existence.ts b/src/server/api/endpoints/drive/files/check-existence.ts index ab19566f1c..a6cd14caf2 100644 --- a/src/server/api/endpoints/drive/files/check-existence.ts +++ b/src/server/api/endpoints/drive/files/check-existence.ts @@ -1,7 +1,6 @@ import $ from 'cafy'; import define from '../../../define'; import { DriveFiles } from '../../../../../models'; -import { types, bool } from '../../../../../misc/schema'; export const meta = { desc: { @@ -25,8 +24,8 @@ export const meta = { }, res: { - type: types.boolean, - optional: bool.false, nullable: bool.false, + type: 'boolean' as const, + optional: false as const, nullable: false as const, }, }; diff --git a/src/server/api/endpoints/drive/files/create.ts b/src/server/api/endpoints/drive/files/create.ts index 0f81a1da99..664a2b87b2 100644 --- a/src/server/api/endpoints/drive/files/create.ts +++ b/src/server/api/endpoints/drive/files/create.ts @@ -6,7 +6,6 @@ import define from '../../../define'; import { apiLogger } from '../../../logger'; import { ApiError } from '../../../error'; import { DriveFiles } from '../../../../../models'; -import { types, bool } from '../../../../../misc/schema'; export const meta = { desc: { @@ -57,8 +56,8 @@ export const meta = { }, res: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'DriveFile', }, diff --git a/src/server/api/endpoints/drive/files/find-by-hash.ts b/src/server/api/endpoints/drive/files/find-by-hash.ts index d56e63bc59..84cc4f92b1 100644 --- a/src/server/api/endpoints/drive/files/find-by-hash.ts +++ b/src/server/api/endpoints/drive/files/find-by-hash.ts @@ -1,7 +1,6 @@ import $ from 'cafy'; import define from '../../../define'; import { DriveFiles } from '../../../../../models'; -import { types, bool } from '../../../../../misc/schema'; export const meta = { desc: { @@ -24,11 +23,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'DriveFile', } }, diff --git a/src/server/api/endpoints/drive/files/find.ts b/src/server/api/endpoints/drive/files/find.ts index 82b9a97b6d..732596a33f 100644 --- a/src/server/api/endpoints/drive/files/find.ts +++ b/src/server/api/endpoints/drive/files/find.ts @@ -2,7 +2,6 @@ import $ from 'cafy'; import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; import { DriveFiles } from '../../../../../models'; -import { types, bool } from '../../../../../misc/schema'; export const meta = { requireCredential: true, @@ -26,11 +25,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'DriveFile', } }, diff --git a/src/server/api/endpoints/drive/files/show.ts b/src/server/api/endpoints/drive/files/show.ts index 8e74361f9c..4384b2114d 100644 --- a/src/server/api/endpoints/drive/files/show.ts +++ b/src/server/api/endpoints/drive/files/show.ts @@ -4,7 +4,6 @@ import define from '../../../define'; import { ApiError } from '../../../error'; import { DriveFile } from '../../../../../models/entities/drive-file'; import { DriveFiles } from '../../../../../models'; -import { types, bool } from '../../../../../misc/schema'; export const meta = { stability: 'stable', @@ -39,8 +38,8 @@ export const meta = { }, res: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'DriveFile', }, diff --git a/src/server/api/endpoints/drive/folders.ts b/src/server/api/endpoints/drive/folders.ts index dc3174cd2a..e2d22e7081 100644 --- a/src/server/api/endpoints/drive/folders.ts +++ b/src/server/api/endpoints/drive/folders.ts @@ -3,7 +3,6 @@ import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import { DriveFolders } from '../../../../models'; import { makePaginationQuery } from '../../common/make-pagination-query'; -import { types, bool } from '../../../../misc/schema'; export const meta = { desc: { @@ -38,11 +37,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'DriveFolder', } }, diff --git a/src/server/api/endpoints/drive/folders/find.ts b/src/server/api/endpoints/drive/folders/find.ts index 0368d026c3..04bec1b170 100644 --- a/src/server/api/endpoints/drive/folders/find.ts +++ b/src/server/api/endpoints/drive/folders/find.ts @@ -2,7 +2,6 @@ import $ from 'cafy'; import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; import { DriveFolders } from '../../../../../models'; -import { types, bool } from '../../../../../misc/schema'; export const meta = { tags: ['drive'], @@ -26,11 +25,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'DriveFolder', } }, diff --git a/src/server/api/endpoints/drive/folders/show.ts b/src/server/api/endpoints/drive/folders/show.ts index a020b46aa9..f48f21d730 100644 --- a/src/server/api/endpoints/drive/folders/show.ts +++ b/src/server/api/endpoints/drive/folders/show.ts @@ -3,7 +3,6 @@ import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; import { ApiError } from '../../../error'; import { DriveFolders } from '../../../../../models'; -import { types, bool } from '../../../../../misc/schema'; export const meta = { stability: 'stable', @@ -30,8 +29,8 @@ export const meta = { }, res: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'DriveFolder', }, diff --git a/src/server/api/endpoints/drive/stream.ts b/src/server/api/endpoints/drive/stream.ts index f75c4273c3..b93ee11a14 100644 --- a/src/server/api/endpoints/drive/stream.ts +++ b/src/server/api/endpoints/drive/stream.ts @@ -3,7 +3,6 @@ import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import { DriveFiles } from '../../../../models'; import { makePaginationQuery } from '../../common/make-pagination-query'; -import { types, bool } from '../../../../misc/schema'; export const meta = { tags: ['drive'], @@ -32,11 +31,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'DriveFile', } }, diff --git a/src/server/api/endpoints/hashtags/list.ts b/src/server/api/endpoints/hashtags/list.ts index 9023f11913..9bc2677793 100644 --- a/src/server/api/endpoints/hashtags/list.ts +++ b/src/server/api/endpoints/hashtags/list.ts @@ -1,7 +1,6 @@ import $ from 'cafy'; import define from '../../define'; import { Hashtags } from '../../../../models'; -import { types, bool } from '../../../../misc/schema'; export const meta = { tags: ['hashtags'], @@ -48,11 +47,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'Hashtag', } }, diff --git a/src/server/api/endpoints/hashtags/search.ts b/src/server/api/endpoints/hashtags/search.ts index 0d2704d01c..7caaf34846 100644 --- a/src/server/api/endpoints/hashtags/search.ts +++ b/src/server/api/endpoints/hashtags/search.ts @@ -1,7 +1,6 @@ import $ from 'cafy'; import define from '../../define'; import { Hashtags } from '../../../../models'; -import { types, bool } from '../../../../misc/schema'; export const meta = { desc: { @@ -38,11 +37,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, } }, }; diff --git a/src/server/api/endpoints/hashtags/show.ts b/src/server/api/endpoints/hashtags/show.ts index 72a4cc7c87..5de906fb1f 100644 --- a/src/server/api/endpoints/hashtags/show.ts +++ b/src/server/api/endpoints/hashtags/show.ts @@ -2,7 +2,6 @@ import $ from 'cafy'; import define from '../../define'; import { ApiError } from '../../error'; import { Hashtags } from '../../../../models'; -import { types, bool } from '../../../../misc/schema'; export const meta = { desc: { @@ -24,8 +23,8 @@ export const meta = { }, res: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'Hashtag', }, diff --git a/src/server/api/endpoints/hashtags/trend.ts b/src/server/api/endpoints/hashtags/trend.ts index 53a3514718..3154461e5a 100644 --- a/src/server/api/endpoints/hashtags/trend.ts +++ b/src/server/api/endpoints/hashtags/trend.ts @@ -2,7 +2,6 @@ import define from '../../define'; import { fetchMeta } from '../../../../misc/fetch-meta'; import { Notes } from '../../../../models'; import { Note } from '../../../../models/entities/note'; -import { types, bool } from '../../../../misc/schema'; /* トレンドに載るためには「『直近a分間のユニーク投稿数が今からa分前~今からb分前の間のユニーク投稿数のn倍以上』のハッシュタグの上位5位以内に入る」ことが必要 @@ -24,27 +23,27 @@ export const meta = { requireCredential: false, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { tag: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, }, chart: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, } }, usersCount: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, } } } diff --git a/src/server/api/endpoints/hashtags/users.ts b/src/server/api/endpoints/hashtags/users.ts index b842f9de64..59210f4604 100644 --- a/src/server/api/endpoints/hashtags/users.ts +++ b/src/server/api/endpoints/hashtags/users.ts @@ -1,7 +1,6 @@ import $ from 'cafy'; import define from '../../define'; import { Users } from '../../../../models'; -import { types, bool } from '../../../../misc/schema'; export const meta = { requireCredential: false, @@ -48,11 +47,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'User', } }, diff --git a/src/server/api/endpoints/i.ts b/src/server/api/endpoints/i.ts index 4ecd507e16..20a0c604f6 100644 --- a/src/server/api/endpoints/i.ts +++ b/src/server/api/endpoints/i.ts @@ -1,6 +1,5 @@ import define from '../define'; import { Users } from '../../../models'; -import { types, bool } from '../../../misc/schema'; export const meta = { stability: 'stable', @@ -16,8 +15,8 @@ export const meta = { params: {}, res: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'User', }, }; diff --git a/src/server/api/endpoints/i/2fa/getkeys.ts b/src/server/api/endpoints/i/2fa/getkeys.ts new file mode 100644 index 0000000000..bb1585d795 --- /dev/null +++ b/src/server/api/endpoints/i/2fa/getkeys.ts @@ -0,0 +1,67 @@ +import $ from 'cafy'; +import * as bcrypt from 'bcryptjs'; +import * as crypto from 'crypto'; +import define from '../../../define'; +import { UserProfiles, UserSecurityKeys, AttestationChallenges } from '../../../../../models'; +import { ensure } from '../../../../../prelude/ensure'; +import { promisify } from 'util'; +import { hash } from '../../../2fa'; +import { genId } from '../../../../../misc/gen-id'; + +export const meta = { + requireCredential: true, + + secure: true, + + params: { + password: { + validator: $.str + } + } +}; + +const randomBytes = promisify(crypto.randomBytes); + +export default define(meta, async (ps, user) => { + const profile = await UserProfiles.findOne(user.id).then(ensure); + + // Compare password + const same = await bcrypt.compare(ps.password, profile.password!); + + if (!same) { + throw new Error('incorrect password'); + } + + const keys = await UserSecurityKeys.find({ + userId: user.id + }); + + if (keys.length === 0) { + throw new Error('no keys found'); + } + + // 32 byte challenge + const entropy = await randomBytes(32); + const challenge = entropy.toString('base64') + .replace(/=/g, '') + .replace(/\+/g, '-') + .replace(/\//g, '_'); + + const challengeId = genId(); + + await AttestationChallenges.save({ + userId: user.id, + id: challengeId, + challenge: hash(Buffer.from(challenge, 'utf-8')).toString('hex'), + createdAt: new Date(), + registrationChallenge: false + }); + + return { + challenge, + challengeId, + securityKeys: keys.map(key => ({ + id: key.id + })) + }; +}); diff --git a/src/server/api/endpoints/i/2fa/key-done.ts b/src/server/api/endpoints/i/2fa/key-done.ts new file mode 100644 index 0000000000..d751dabc41 --- /dev/null +++ b/src/server/api/endpoints/i/2fa/key-done.ts @@ -0,0 +1,151 @@ +import $ from 'cafy'; +import * as bcrypt from 'bcryptjs'; +import { promisify } from 'util'; +import * as cbor from 'cbor'; +import define from '../../../define'; +import { + UserProfiles, + UserSecurityKeys, + AttestationChallenges, + Users +} from '../../../../../models'; +import { ensure } from '../../../../../prelude/ensure'; +import config from '../../../../../config'; +import { procedures, hash } from '../../../2fa'; +import { publishMainStream } from '../../../../../services/stream'; + +const cborDecodeFirst = promisify(cbor.decodeFirst) as any; + +export const meta = { + requireCredential: true, + + secure: true, + + params: { + clientDataJSON: { + validator: $.str + }, + attestationObject: { + validator: $.str + }, + password: { + validator: $.str + }, + challengeId: { + validator: $.str + }, + name: { + validator: $.str + } + } +}; + +const rpIdHashReal = hash(Buffer.from(config.hostname, 'utf-8')); + +export default define(meta, async (ps, user) => { + const profile = await UserProfiles.findOne(user.id).then(ensure); + + // Compare password + const same = await bcrypt.compare(ps.password, profile.password!); + + if (!same) { + throw new Error('incorrect password'); + } + + if (!profile.twoFactorEnabled) { + throw new Error('2fa not enabled'); + } + + const clientData = JSON.parse(ps.clientDataJSON); + + if (clientData.type != 'webauthn.create') { + throw new Error('not a creation attestation'); + } + if (clientData.origin != config.scheme + '://' + config.host) { + throw new Error('origin mismatch'); + } + + const clientDataJSONHash = hash(Buffer.from(ps.clientDataJSON, 'utf-8')); + + const attestation = await cborDecodeFirst(ps.attestationObject); + + const rpIdHash = attestation.authData.slice(0, 32); + if (!rpIdHashReal.equals(rpIdHash)) { + throw new Error('rpIdHash mismatch'); + } + + const flags = attestation.authData[32]; + + // tslint:disable-next-line:no-bitwise + if (!(flags & 1)) { + throw new Error('user not present'); + } + + const authData = Buffer.from(attestation.authData); + const credentialIdLength = authData.readUInt16BE(53); + const credentialId = authData.slice(55, 55 + credentialIdLength); + const publicKeyData = authData.slice(55 + credentialIdLength); + const publicKey: Map = await cborDecodeFirst(publicKeyData); + if (publicKey.get(3) != -7) { + throw new Error('alg mismatch'); + } + + if (!(procedures as any)[attestation.fmt]) { + throw new Error('unsupported fmt'); + } + + const verificationData = (procedures as any)[attestation.fmt].verify({ + attStmt: attestation.attStmt, + authenticatorData: authData, + clientDataHash: clientDataJSONHash, + credentialId, + publicKey, + rpIdHash + }); + if (!verificationData.valid) throw new Error('signature invalid'); + + const attestationChallenge = await AttestationChallenges.findOne({ + userId: user.id, + id: ps.challengeId, + registrationChallenge: true, + challenge: hash(clientData.challenge).toString('hex') + }); + + if (!attestationChallenge) { + throw new Error('non-existent challenge'); + } + + await AttestationChallenges.delete({ + userId: user.id, + id: ps.challengeId + }); + + // Expired challenge (> 5min old) + if ( + new Date().getTime() - attestationChallenge.createdAt.getTime() >= + 5 * 60 * 1000 + ) { + throw new Error('expired challenge'); + } + + const credentialIdString = credentialId.toString('hex'); + + await UserSecurityKeys.save({ + userId: user.id, + id: credentialIdString, + lastUsed: new Date(), + name: ps.name, + publicKey: verificationData.publicKey.toString('hex') + }); + + // Publish meUpdated event + publishMainStream(user.id, 'meUpdated', await Users.pack(user.id, user, { + detail: true, + includeSecrets: true + })); + + return { + id: credentialIdString, + name: ps.name + }; +}); diff --git a/src/server/api/endpoints/i/2fa/register-key.ts b/src/server/api/endpoints/i/2fa/register-key.ts new file mode 100644 index 0000000000..1c2cc32e37 --- /dev/null +++ b/src/server/api/endpoints/i/2fa/register-key.ts @@ -0,0 +1,60 @@ +import $ from 'cafy'; +import * as bcrypt from 'bcryptjs'; +import define from '../../../define'; +import { UserProfiles, AttestationChallenges } from '../../../../../models'; +import { ensure } from '../../../../../prelude/ensure'; +import { promisify } from 'util'; +import * as crypto from 'crypto'; +import { genId } from '../../../../../misc/gen-id'; +import { hash } from '../../../2fa'; + +const randomBytes = promisify(crypto.randomBytes); + +export const meta = { + requireCredential: true, + + secure: true, + + params: { + password: { + validator: $.str + } + } +}; + +export default define(meta, async (ps, user) => { + const profile = await UserProfiles.findOne(user.id).then(ensure); + + // Compare password + const same = await bcrypt.compare(ps.password, profile.password!); + + if (!same) { + throw new Error('incorrect password'); + } + + if (!profile.twoFactorEnabled) { + throw new Error('2fa not enabled'); + } + + // 32 byte challenge + const entropy = await randomBytes(32); + const challenge = entropy.toString('base64') + .replace(/=/g, '') + .replace(/\+/g, '-') + .replace(/\//g, '_'); + + const challengeId = genId(); + + await AttestationChallenges.save({ + userId: user.id, + id: challengeId, + challenge: hash(Buffer.from(challenge, 'utf-8')).toString('hex'), + createdAt: new Date(), + registrationChallenge: true + }); + + return { + challengeId, + challenge + }; +}); diff --git a/src/server/api/endpoints/i/2fa/remove-key.ts b/src/server/api/endpoints/i/2fa/remove-key.ts new file mode 100644 index 0000000000..cb28c8fbfb --- /dev/null +++ b/src/server/api/endpoints/i/2fa/remove-key.ts @@ -0,0 +1,46 @@ +import $ from 'cafy'; +import * as bcrypt from 'bcryptjs'; +import define from '../../../define'; +import { UserProfiles, UserSecurityKeys, Users } from '../../../../../models'; +import { ensure } from '../../../../../prelude/ensure'; +import { publishMainStream } from '../../../../../services/stream'; + +export const meta = { + requireCredential: true, + + secure: true, + + params: { + password: { + validator: $.str + }, + credentialId: { + validator: $.str + }, + } +}; + +export default define(meta, async (ps, user) => { + const profile = await UserProfiles.findOne(user.id).then(ensure); + + // Compare password + const same = await bcrypt.compare(ps.password, profile.password!); + + if (!same) { + throw new Error('incorrect password'); + } + + // Make sure we only delete the user's own creds + await UserSecurityKeys.delete({ + userId: user.id, + id: ps.credentialId + }); + + // Publish meUpdated event + publishMainStream(user.id, 'meUpdated', await Users.pack(user.id, user, { + detail: true, + includeSecrets: true + })); + + return {}; +}); diff --git a/src/server/api/endpoints/i/favorites.ts b/src/server/api/endpoints/i/favorites.ts index d1e90dd15c..1bbc16256a 100644 --- a/src/server/api/endpoints/i/favorites.ts +++ b/src/server/api/endpoints/i/favorites.ts @@ -3,7 +3,6 @@ import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import { NoteFavorites } from '../../../../models'; import { makePaginationQuery } from '../../common/make-pagination-query'; -import { types, bool } from '../../../../misc/schema'; export const meta = { desc: { @@ -33,11 +32,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'NoteFavorite', } }, diff --git a/src/server/api/endpoints/i/notifications.ts b/src/server/api/endpoints/i/notifications.ts index 41513e5daa..aa72e9a176 100644 --- a/src/server/api/endpoints/i/notifications.ts +++ b/src/server/api/endpoints/i/notifications.ts @@ -4,7 +4,6 @@ import { readNotification } from '../../common/read-notification'; import define from '../../define'; import { makePaginationQuery } from '../../common/make-pagination-query'; import { Notifications, Followings, Mutings } from '../../../../models'; -import { types, bool } from '../../../../misc/schema'; export const meta = { desc: { @@ -54,11 +53,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'Notification', } }, diff --git a/src/server/api/endpoints/messaging/history.ts b/src/server/api/endpoints/messaging/history.ts index 833ec37e4c..2c143c26b5 100644 --- a/src/server/api/endpoints/messaging/history.ts +++ b/src/server/api/endpoints/messaging/history.ts @@ -3,7 +3,6 @@ import define from '../../define'; import { MessagingMessage } from '../../../../models/entities/messaging-message'; import { MessagingMessages, Mutings, UserGroupJoinings } from '../../../../models'; import { Brackets } from 'typeorm'; -import { types, bool } from '../../../../misc/schema'; export const meta = { desc: { @@ -30,11 +29,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'MessagingMessage', } }, diff --git a/src/server/api/endpoints/messaging/messages.ts b/src/server/api/endpoints/messaging/messages.ts index ffd58c714e..b0b3e20d02 100644 --- a/src/server/api/endpoints/messaging/messages.ts +++ b/src/server/api/endpoints/messaging/messages.ts @@ -5,7 +5,6 @@ import { ApiError } from '../../error'; import { getUser } from '../../common/getters'; import { MessagingMessages, UserGroups, UserGroupJoinings } from '../../../../models'; import { makePaginationQuery } from '../../common/make-pagination-query'; -import { types, bool } from '../../../../misc/schema'; import { Brackets } from 'typeorm'; import { readUserMessagingMessage, readGroupMessagingMessage } from '../../common/read-messaging-message'; @@ -58,11 +57,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'MessagingMessage', } }, diff --git a/src/server/api/endpoints/messaging/messages/create.ts b/src/server/api/endpoints/messaging/messages/create.ts index feffc9a0c6..d0c1ee4941 100644 --- a/src/server/api/endpoints/messaging/messages/create.ts +++ b/src/server/api/endpoints/messaging/messages/create.ts @@ -9,7 +9,6 @@ import { getUser } from '../../../common/getters'; import { MessagingMessages, DriveFiles, Mutings, UserGroups, UserGroupJoinings } from '../../../../../models'; import { MessagingMessage } from '../../../../../models/entities/messaging-message'; import { genId } from '../../../../../misc/gen-id'; -import { types, bool } from '../../../../../misc/schema'; import { User } from '../../../../../models/entities/user'; import { UserGroup } from '../../../../../models/entities/user-group'; import { Not } from 'typeorm'; @@ -53,8 +52,8 @@ export const meta = { }, res: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'MessagingMessage', }, diff --git a/src/server/api/endpoints/meta.ts b/src/server/api/endpoints/meta.ts index 1aa9a855dd..a3390a011d 100644 --- a/src/server/api/endpoints/meta.ts +++ b/src/server/api/endpoints/meta.ts @@ -5,7 +5,6 @@ import define from '../define'; import { fetchMeta } from '../../../misc/fetch-meta'; import * as pkg from '../../../../package.json'; import { Emojis } from '../../../models'; -import { types, bool } from '../../../misc/schema'; import { getConnection } from 'typeorm'; import redis from '../../../db/redis'; @@ -29,40 +28,40 @@ export const meta = { }, res: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { version: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, description: 'The version of Misskey of this instance.', example: pkg.version }, name: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, description: 'The name of this instance.', }, description: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, description: 'The description of this instance.', }, announcements: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { title: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, description: 'The title of the announcement.', }, text: { - type: types.string, - optional: bool.false, nullable: bool.false, + type: 'string' as const, + optional: false as const, nullable: false as const, description: 'The text of the announcement. (can be HTML)', }, } @@ -70,23 +69,23 @@ export const meta = { description: 'The announcements of this instance.', }, disableRegistration: { - type: types.boolean, - optional: bool.false, nullable: bool.false, + type: 'boolean' as const, + optional: false as const, nullable: false as const, description: 'Whether disabled open registration.', }, disableLocalTimeline: { - type: types.boolean, - optional: bool.false, nullable: bool.false, + type: 'boolean' as const, + optional: false as const, nullable: false as const, description: 'Whether disabled LTL and STL.', }, disableGlobalTimeline: { - type: types.boolean, - optional: bool.false, nullable: bool.false, + type: 'boolean' as const, + optional: false as const, nullable: false as const, description: 'Whether disabled GTL.', }, enableEmojiReaction: { - type: types.boolean, - optional: bool.false, nullable: bool.false, + type: 'boolean' as const, + optional: false as const, nullable: false as const, description: 'Whether enabled emoji reaction.', }, } diff --git a/src/server/api/endpoints/mute/list.ts b/src/server/api/endpoints/mute/list.ts index f9ea380c76..1afc120f5f 100644 --- a/src/server/api/endpoints/mute/list.ts +++ b/src/server/api/endpoints/mute/list.ts @@ -3,7 +3,6 @@ import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import { makePaginationQuery } from '../../common/make-pagination-query'; import { Mutings } from '../../../../models'; -import { types, bool } from '../../../../misc/schema'; export const meta = { desc: { @@ -33,11 +32,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'Muting', } }, diff --git a/src/server/api/endpoints/notes.ts b/src/server/api/endpoints/notes.ts index 7aa19c9a2b..fab8455d78 100644 --- a/src/server/api/endpoints/notes.ts +++ b/src/server/api/endpoints/notes.ts @@ -3,7 +3,6 @@ import { ID } from '../../../misc/cafy-id'; import define from '../define'; import { makePaginationQuery } from '../common/make-pagination-query'; import { Notes } from '../../../models'; -import { types, bool } from '../../../misc/schema'; export const meta = { desc: { @@ -63,11 +62,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'Note', } }, diff --git a/src/server/api/endpoints/notes/children.ts b/src/server/api/endpoints/notes/children.ts index e8861eafa1..bc8407f31c 100644 --- a/src/server/api/endpoints/notes/children.ts +++ b/src/server/api/endpoints/notes/children.ts @@ -6,7 +6,6 @@ import { generateVisibilityQuery } from '../../common/generate-visibility-query' import { generateMuteQuery } from '../../common/generate-mute-query'; import { Brackets } from 'typeorm'; import { Notes } from '../../../../models'; -import { types, bool } from '../../../../misc/schema'; export const meta = { desc: { @@ -42,11 +41,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'Note', } }, diff --git a/src/server/api/endpoints/notes/conversation.ts b/src/server/api/endpoints/notes/conversation.ts index acd3ac75ef..245d427923 100644 --- a/src/server/api/endpoints/notes/conversation.ts +++ b/src/server/api/endpoints/notes/conversation.ts @@ -5,7 +5,6 @@ import { ApiError } from '../../error'; import { getNote } from '../../common/getters'; import { Note } from '../../../../models/entities/note'; import { Notes } from '../../../../models'; -import { types, bool } from '../../../../misc/schema'; export const meta = { desc: { @@ -38,11 +37,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'Note', } }, diff --git a/src/server/api/endpoints/notes/create.ts b/src/server/api/endpoints/notes/create.ts index 46db274581..1650de9071 100644 --- a/src/server/api/endpoints/notes/create.ts +++ b/src/server/api/endpoints/notes/create.ts @@ -10,7 +10,6 @@ import { User } from '../../../../models/entities/user'; import { Users, DriveFiles, Notes } from '../../../../models'; import { DriveFile } from '../../../../models/entities/drive-file'; import { Note } from '../../../../models/entities/note'; -import { types, bool } from '../../../../misc/schema'; let maxNoteTextLength = 1000; @@ -175,12 +174,12 @@ export const meta = { }, res: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { createdNote: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'Note', description: '作成した投稿' } diff --git a/src/server/api/endpoints/notes/featured.ts b/src/server/api/endpoints/notes/featured.ts index 64750815b0..0a1d8668b0 100644 --- a/src/server/api/endpoints/notes/featured.ts +++ b/src/server/api/endpoints/notes/featured.ts @@ -2,7 +2,6 @@ import $ from 'cafy'; import define from '../../define'; import { generateMuteQuery } from '../../common/generate-mute-query'; import { Notes } from '../../../../models'; -import { types, bool } from '../../../../misc/schema'; export const meta = { desc: { @@ -25,11 +24,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'Note', } }, diff --git a/src/server/api/endpoints/notes/global-timeline.ts b/src/server/api/endpoints/notes/global-timeline.ts index f46fa208df..8654cf889a 100644 --- a/src/server/api/endpoints/notes/global-timeline.ts +++ b/src/server/api/endpoints/notes/global-timeline.ts @@ -7,7 +7,6 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; import { Notes } from '../../../../models'; import { generateMuteQuery } from '../../common/generate-mute-query'; import { activeUsersChart } from '../../../../services/chart'; -import { types, bool } from '../../../../misc/schema'; export const meta = { desc: { @@ -47,11 +46,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'Note', } }, diff --git a/src/server/api/endpoints/notes/hybrid-timeline.ts b/src/server/api/endpoints/notes/hybrid-timeline.ts index 7be13fc47f..8c4c7a60b9 100644 --- a/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -9,7 +9,6 @@ import { Brackets } from 'typeorm'; import { generateVisibilityQuery } from '../../common/generate-visibility-query'; import { generateMuteQuery } from '../../common/generate-mute-query'; import { activeUsersChart } from '../../../../services/chart'; -import { types, bool } from '../../../../misc/schema'; export const meta = { desc: { @@ -90,11 +89,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'Note', } }, diff --git a/src/server/api/endpoints/notes/local-timeline.ts b/src/server/api/endpoints/notes/local-timeline.ts index 73cbebace2..c688b9325e 100644 --- a/src/server/api/endpoints/notes/local-timeline.ts +++ b/src/server/api/endpoints/notes/local-timeline.ts @@ -9,7 +9,6 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; import { generateVisibilityQuery } from '../../common/generate-visibility-query'; import { activeUsersChart } from '../../../../services/chart'; import { Brackets } from 'typeorm'; -import { types, bool } from '../../../../misc/schema'; export const meta = { desc: { @@ -64,11 +63,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'Note', } }, diff --git a/src/server/api/endpoints/notes/mentions.ts b/src/server/api/endpoints/notes/mentions.ts index 02e44492ba..fd3767f632 100644 --- a/src/server/api/endpoints/notes/mentions.ts +++ b/src/server/api/endpoints/notes/mentions.ts @@ -7,7 +7,6 @@ import { generateVisibilityQuery } from '../../common/generate-visibility-query' import { generateMuteQuery } from '../../common/generate-mute-query'; import { makePaginationQuery } from '../../common/make-pagination-query'; import { Brackets } from 'typeorm'; -import { types, bool } from '../../../../misc/schema'; export const meta = { desc: { @@ -44,11 +43,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'Note', } }, diff --git a/src/server/api/endpoints/notes/reactions.ts b/src/server/api/endpoints/notes/reactions.ts index 0773b4faa2..7bea24d316 100644 --- a/src/server/api/endpoints/notes/reactions.ts +++ b/src/server/api/endpoints/notes/reactions.ts @@ -4,7 +4,6 @@ import define from '../../define'; import { getNote } from '../../common/getters'; import { ApiError } from '../../error'; import { NoteReactions } from '../../../../models'; -import { types, bool } from '../../../../misc/schema'; export const meta = { desc: { @@ -45,11 +44,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'NoteReaction', } }, diff --git a/src/server/api/endpoints/notes/renotes.ts b/src/server/api/endpoints/notes/renotes.ts index 00dfac3770..a5db706e32 100644 --- a/src/server/api/endpoints/notes/renotes.ts +++ b/src/server/api/endpoints/notes/renotes.ts @@ -7,7 +7,6 @@ import { generateVisibilityQuery } from '../../common/generate-visibility-query' import { generateMuteQuery } from '../../common/generate-mute-query'; import { makePaginationQuery } from '../../common/make-pagination-query'; import { Notes } from '../../../../models'; -import { types, bool } from '../../../../misc/schema'; export const meta = { desc: { @@ -43,11 +42,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'Note', } }, diff --git a/src/server/api/endpoints/notes/replies.ts b/src/server/api/endpoints/notes/replies.ts index 5fb0fd989f..cd38d41652 100644 --- a/src/server/api/endpoints/notes/replies.ts +++ b/src/server/api/endpoints/notes/replies.ts @@ -5,7 +5,6 @@ import { Notes } from '../../../../models'; import { makePaginationQuery } from '../../common/make-pagination-query'; import { generateVisibilityQuery } from '../../common/generate-visibility-query'; import { generateMuteQuery } from '../../common/generate-mute-query'; -import { types, bool } from '../../../../misc/schema'; export const meta = { desc: { @@ -47,11 +46,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'Note', } }, diff --git a/src/server/api/endpoints/notes/search-by-tag.ts b/src/server/api/endpoints/notes/search-by-tag.ts index 0b49f896ad..7f53b26995 100644 --- a/src/server/api/endpoints/notes/search-by-tag.ts +++ b/src/server/api/endpoints/notes/search-by-tag.ts @@ -6,7 +6,6 @@ import { Notes } from '../../../../models'; import { generateMuteQuery } from '../../common/generate-mute-query'; import { generateVisibilityQuery } from '../../common/generate-visibility-query'; import { Brackets } from 'typeorm'; -import { types, bool } from '../../../../misc/schema'; export const meta = { desc: { @@ -82,11 +81,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'Note', } }, diff --git a/src/server/api/endpoints/notes/search.ts b/src/server/api/endpoints/notes/search.ts index 65ce20074a..d3fb33c420 100644 --- a/src/server/api/endpoints/notes/search.ts +++ b/src/server/api/endpoints/notes/search.ts @@ -4,7 +4,6 @@ import define from '../../define'; import { ApiError } from '../../error'; import { Notes } from '../../../../models'; import { In } from 'typeorm'; -import { types, bool } from '../../../../misc/schema'; import { ID } from '../../../../misc/cafy-id'; export const meta = { @@ -44,11 +43,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'Note', } }, diff --git a/src/server/api/endpoints/notes/show.ts b/src/server/api/endpoints/notes/show.ts index 54b420813d..75abbae55f 100644 --- a/src/server/api/endpoints/notes/show.ts +++ b/src/server/api/endpoints/notes/show.ts @@ -4,7 +4,6 @@ import define from '../../define'; import { getNote } from '../../common/getters'; import { ApiError } from '../../error'; import { Notes } from '../../../../models'; -import { types, bool } from '../../../../misc/schema'; export const meta = { stability: 'stable', @@ -29,8 +28,8 @@ export const meta = { }, res: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'Note', }, diff --git a/src/server/api/endpoints/notes/timeline.ts b/src/server/api/endpoints/notes/timeline.ts index f9442f8b90..25876f655a 100644 --- a/src/server/api/endpoints/notes/timeline.ts +++ b/src/server/api/endpoints/notes/timeline.ts @@ -7,7 +7,6 @@ import { generateVisibilityQuery } from '../../common/generate-visibility-query' import { generateMuteQuery } from '../../common/generate-mute-query'; import { activeUsersChart } from '../../../../services/chart'; import { Brackets } from 'typeorm'; -import { types, bool } from '../../../../misc/schema'; export const meta = { desc: { @@ -89,11 +88,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'Note', } }, diff --git a/src/server/api/endpoints/notes/user-list-timeline.ts b/src/server/api/endpoints/notes/user-list-timeline.ts index c16018d434..f66221537d 100644 --- a/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/src/server/api/endpoints/notes/user-list-timeline.ts @@ -6,7 +6,6 @@ import { UserLists, UserListJoinings, Notes } from '../../../../models'; import { makePaginationQuery } from '../../common/make-pagination-query'; import { generateVisibilityQuery } from '../../common/generate-visibility-query'; import { activeUsersChart } from '../../../../services/chart'; -import { types, bool } from '../../../../misc/schema'; export const meta = { desc: { @@ -95,11 +94,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'Note', } }, diff --git a/src/server/api/endpoints/pages/create.ts b/src/server/api/endpoints/pages/create.ts index e6b813648b..ffe0d38ea6 100644 --- a/src/server/api/endpoints/pages/create.ts +++ b/src/server/api/endpoints/pages/create.ts @@ -2,7 +2,6 @@ import $ from 'cafy'; import * as ms from 'ms'; import define from '../../define'; import { ID } from '../../../../misc/cafy-id'; -import { types, bool } from '../../../../misc/schema'; import { Pages, DriveFiles } from '../../../../models'; import { genId } from '../../../../misc/gen-id'; import { Page } from '../../../../models/entities/page'; @@ -61,8 +60,8 @@ export const meta = { }, res: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'Page', }, diff --git a/src/server/api/endpoints/pages/show.ts b/src/server/api/endpoints/pages/show.ts index e3d6e6a15f..84808418f3 100644 --- a/src/server/api/endpoints/pages/show.ts +++ b/src/server/api/endpoints/pages/show.ts @@ -2,7 +2,6 @@ import $ from 'cafy'; import define from '../../define'; import { ApiError } from '../../error'; import { Pages, Users } from '../../../../models'; -import { types, bool } from '../../../../misc/schema'; import { ID } from '../../../../misc/cafy-id'; import { Page } from '../../../../models/entities/page'; @@ -34,8 +33,8 @@ export const meta = { }, res: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'Page', }, diff --git a/src/server/api/endpoints/pinned-users.ts b/src/server/api/endpoints/pinned-users.ts index de0e17a2ec..853e1cd4b8 100644 --- a/src/server/api/endpoints/pinned-users.ts +++ b/src/server/api/endpoints/pinned-users.ts @@ -1,6 +1,5 @@ import define from '../define'; import { Users } from '../../../models'; -import { types, bool } from '../../../misc/schema'; import { fetchMeta } from '../../../misc/fetch-meta'; import parseAcct from '../../../misc/acct/parse'; import { User } from '../../../models/entities/user'; @@ -14,11 +13,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'User', } }, diff --git a/src/server/api/endpoints/stats.ts b/src/server/api/endpoints/stats.ts index 4dca62445f..c86d893d5e 100644 --- a/src/server/api/endpoints/stats.ts +++ b/src/server/api/endpoints/stats.ts @@ -1,7 +1,6 @@ import define from '../define'; import { Notes, Users } from '../../../models'; import { federationChart, driveChart } from '../../../services/chart'; -import { bool, types } from '../../../misc/schema'; export const meta = { requireCredential: false, @@ -16,32 +15,32 @@ export const meta = { }, res: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { notesCount: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: 'The count of all (local/remote) notes of this instance.', }, originalNotesCount: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: 'The count of all local notes of this instance.', }, usersCount: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: 'The count of all (local/remote) accounts of this instance.', }, originalUsersCount: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: 'The count of all local accounts of this instance.', }, instances: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: 'The count of federated instances.', }, } diff --git a/src/server/api/endpoints/users.ts b/src/server/api/endpoints/users.ts index 539f2ef897..93cf01a6f8 100644 --- a/src/server/api/endpoints/users.ts +++ b/src/server/api/endpoints/users.ts @@ -2,7 +2,6 @@ import $ from 'cafy'; import define from '../define'; import { Users } from '../../../models'; import { generateMuteQueryForUsers } from '../common/generate-mute-query'; -import { types, bool } from '../../../misc/schema'; export const meta = { tags: ['users'], @@ -53,11 +52,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'User', } }, diff --git a/src/server/api/endpoints/users/followers.ts b/src/server/api/endpoints/users/followers.ts index 465b71e2e6..68c32fe983 100644 --- a/src/server/api/endpoints/users/followers.ts +++ b/src/server/api/endpoints/users/followers.ts @@ -5,7 +5,6 @@ import { ApiError } from '../../error'; import { Users, Followings } from '../../../../models'; import { makePaginationQuery } from '../../common/make-pagination-query'; import { toPunyNullable } from '../../../../misc/convert-host'; -import { types, bool } from '../../../../misc/schema'; export const meta = { desc: { @@ -49,11 +48,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'Following', } }, diff --git a/src/server/api/endpoints/users/following.ts b/src/server/api/endpoints/users/following.ts index 2a7748ac64..eb699b2903 100644 --- a/src/server/api/endpoints/users/following.ts +++ b/src/server/api/endpoints/users/following.ts @@ -5,7 +5,6 @@ import { ApiError } from '../../error'; import { Users, Followings } from '../../../../models'; import { makePaginationQuery } from '../../common/make-pagination-query'; import { toPunyNullable } from '../../../../misc/convert-host'; -import { types, bool } from '../../../../misc/schema'; export const meta = { desc: { @@ -49,11 +48,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'Following', } }, diff --git a/src/server/api/endpoints/users/get-frequently-replied-users.ts b/src/server/api/endpoints/users/get-frequently-replied-users.ts index 24d1bd194c..1a17b488f0 100644 --- a/src/server/api/endpoints/users/get-frequently-replied-users.ts +++ b/src/server/api/endpoints/users/get-frequently-replied-users.ts @@ -6,7 +6,6 @@ import { ApiError } from '../../error'; import { getUser } from '../../common/getters'; import { Not, In, IsNull } from 'typeorm'; import { Notes, Users } from '../../../../models'; -import { types, bool } from '../../../../misc/schema'; export const meta = { tags: ['users'], @@ -29,11 +28,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'User', } }, diff --git a/src/server/api/endpoints/users/groups/create.ts b/src/server/api/endpoints/users/groups/create.ts index ee6cade8d0..2a6e5135e5 100644 --- a/src/server/api/endpoints/users/groups/create.ts +++ b/src/server/api/endpoints/users/groups/create.ts @@ -3,7 +3,6 @@ import define from '../../../define'; import { UserGroups, UserGroupJoinings } from '../../../../../models'; import { genId } from '../../../../../misc/gen-id'; import { UserGroup } from '../../../../../models/entities/user-group'; -import { types, bool } from '../../../../../misc/schema'; import { UserGroupJoining } from '../../../../../models/entities/user-group-joining'; export const meta = { @@ -25,8 +24,8 @@ export const meta = { }, res: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'UserGroup', }, }; diff --git a/src/server/api/endpoints/users/groups/joined.ts b/src/server/api/endpoints/users/groups/joined.ts index 97d168e527..c60ba57c09 100644 --- a/src/server/api/endpoints/users/groups/joined.ts +++ b/src/server/api/endpoints/users/groups/joined.ts @@ -1,6 +1,5 @@ import define from '../../../define'; import { UserGroups, UserGroupJoinings } from '../../../../../models'; -import { types, bool } from '../../../../../misc/schema'; import { Not, In } from 'typeorm'; export const meta = { @@ -15,11 +14,11 @@ export const meta = { kind: 'read:user-groups', res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'UserGroup', } }, diff --git a/src/server/api/endpoints/users/groups/owned.ts b/src/server/api/endpoints/users/groups/owned.ts index 6cf39a142b..e2c0bf2fc1 100644 --- a/src/server/api/endpoints/users/groups/owned.ts +++ b/src/server/api/endpoints/users/groups/owned.ts @@ -1,6 +1,5 @@ import define from '../../../define'; import { UserGroups } from '../../../../../models'; -import { types, bool } from '../../../../../misc/schema'; export const meta = { desc: { @@ -14,11 +13,11 @@ export const meta = { kind: 'read:user-groups', res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'UserGroup', } }, diff --git a/src/server/api/endpoints/users/groups/show.ts b/src/server/api/endpoints/users/groups/show.ts index 4f8374a222..643f1acf7a 100644 --- a/src/server/api/endpoints/users/groups/show.ts +++ b/src/server/api/endpoints/users/groups/show.ts @@ -3,7 +3,6 @@ import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; import { ApiError } from '../../../error'; import { UserGroups, UserGroupJoinings } from '../../../../../models'; -import { types, bool } from '../../../../../misc/schema'; export const meta = { desc: { @@ -24,8 +23,8 @@ export const meta = { }, res: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'UserGroup', }, diff --git a/src/server/api/endpoints/users/groups/transfer.ts b/src/server/api/endpoints/users/groups/transfer.ts index b4284ab484..012f9f4ef3 100644 --- a/src/server/api/endpoints/users/groups/transfer.ts +++ b/src/server/api/endpoints/users/groups/transfer.ts @@ -4,7 +4,6 @@ import define from '../../../define'; import { ApiError } from '../../../error'; import { getUser } from '../../../common/getters'; import { UserGroups, UserGroupJoinings } from '../../../../../models'; -import { types, bool } from '../../../../../misc/schema'; export const meta = { desc: { @@ -33,8 +32,8 @@ export const meta = { }, res: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'UserGroup', }, diff --git a/src/server/api/endpoints/users/groups/update.ts b/src/server/api/endpoints/users/groups/update.ts index bc974621a3..2fc0a803a1 100644 --- a/src/server/api/endpoints/users/groups/update.ts +++ b/src/server/api/endpoints/users/groups/update.ts @@ -3,7 +3,6 @@ import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; import { ApiError } from '../../../error'; import { UserGroups } from '../../../../../models'; -import { types, bool } from '../../../../../misc/schema'; export const meta = { desc: { @@ -36,8 +35,8 @@ export const meta = { }, res: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'UserGroup', }, diff --git a/src/server/api/endpoints/users/lists/create.ts b/src/server/api/endpoints/users/lists/create.ts index 79efffbf9e..28af3c8d86 100644 --- a/src/server/api/endpoints/users/lists/create.ts +++ b/src/server/api/endpoints/users/lists/create.ts @@ -3,7 +3,6 @@ import define from '../../../define'; import { UserLists } from '../../../../../models'; import { genId } from '../../../../../misc/gen-id'; import { UserList } from '../../../../../models/entities/user-list'; -import { types, bool } from '../../../../../misc/schema'; export const meta = { desc: { @@ -24,8 +23,8 @@ export const meta = { }, res: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'UserList', }, }; diff --git a/src/server/api/endpoints/users/lists/list.ts b/src/server/api/endpoints/users/lists/list.ts index 684086b5c6..7b89d34314 100644 --- a/src/server/api/endpoints/users/lists/list.ts +++ b/src/server/api/endpoints/users/lists/list.ts @@ -1,6 +1,5 @@ import define from '../../../define'; import { UserLists } from '../../../../../models'; -import { types, bool } from '../../../../../misc/schema'; export const meta = { desc: { @@ -14,11 +13,11 @@ export const meta = { kind: 'read:account', res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'UserList', } }, diff --git a/src/server/api/endpoints/users/lists/show.ts b/src/server/api/endpoints/users/lists/show.ts index 395f9352d4..01d03d1bfb 100644 --- a/src/server/api/endpoints/users/lists/show.ts +++ b/src/server/api/endpoints/users/lists/show.ts @@ -3,7 +3,6 @@ import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; import { ApiError } from '../../../error'; import { UserLists } from '../../../../../models'; -import { types, bool } from '../../../../../misc/schema'; export const meta = { desc: { @@ -24,8 +23,8 @@ export const meta = { }, res: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'UserList', }, diff --git a/src/server/api/endpoints/users/notes.ts b/src/server/api/endpoints/users/notes.ts index fdc50e4dae..ef564b3c34 100644 --- a/src/server/api/endpoints/users/notes.ts +++ b/src/server/api/endpoints/users/notes.ts @@ -8,7 +8,6 @@ import { generateVisibilityQuery } from '../../common/generate-visibility-query' import { Notes } from '../../../../models'; import { generateMuteQuery } from '../../common/generate-mute-query'; import { Brackets } from 'typeorm'; -import { types, bool } from '../../../../misc/schema'; export const meta = { desc: { @@ -120,11 +119,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'Note', } }, diff --git a/src/server/api/endpoints/users/recommendation.ts b/src/server/api/endpoints/users/recommendation.ts index 38b420c332..73d8fe6208 100644 --- a/src/server/api/endpoints/users/recommendation.ts +++ b/src/server/api/endpoints/users/recommendation.ts @@ -3,7 +3,6 @@ import $ from 'cafy'; import define from '../../define'; import { Users, Followings } from '../../../../models'; import { generateMuteQueryForUsers } from '../../common/generate-mute-query'; -import { types, bool } from '../../../../misc/schema'; export const meta = { desc: { @@ -29,11 +28,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'User', } }, diff --git a/src/server/api/endpoints/users/search.ts b/src/server/api/endpoints/users/search.ts index 5c413defbc..9aa9e398e3 100644 --- a/src/server/api/endpoints/users/search.ts +++ b/src/server/api/endpoints/users/search.ts @@ -2,7 +2,6 @@ import $ from 'cafy'; import define from '../../define'; import { Users } from '../../../../models'; import { User } from '../../../../models/entities/user'; -import { bool, types } from '../../../../misc/schema'; export const meta = { desc: { @@ -55,11 +54,11 @@ export const meta = { }, res: { - type: types.array, - optional: bool.false, nullable: bool.false, + type: 'array' as const, + optional: false as const, nullable: false as const, items: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'User', } }, diff --git a/src/server/api/endpoints/users/show.ts b/src/server/api/endpoints/users/show.ts index 820c44e81b..d17dd51c0a 100644 --- a/src/server/api/endpoints/users/show.ts +++ b/src/server/api/endpoints/users/show.ts @@ -6,7 +6,6 @@ import { ApiError } from '../../error'; import { ID } from '../../../../misc/cafy-id'; import { Users } from '../../../../models'; import { In } from 'typeorm'; -import { bool, types } from '../../../../misc/schema'; export const meta = { desc: { @@ -43,8 +42,8 @@ export const meta = { }, res: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, ref: 'User', }, @@ -53,7 +52,7 @@ export const meta = { message: 'Failed to resolve remote user.', code: 'FAILED_TO_RESOLVE_REMOTE_USER', id: 'ef7b9be4-9cba-4e6f-ab41-90ed171c7d3c', - kind: 'server' as 'server' + kind: 'server' as const }, noSuchUser: { diff --git a/src/server/api/private/signin.ts b/src/server/api/private/signin.ts index 02361a139d..cd9fe5bb9d 100644 --- a/src/server/api/private/signin.ts +++ b/src/server/api/private/signin.ts @@ -4,10 +4,11 @@ import * as speakeasy from 'speakeasy'; import { publishMainStream } from '../../../services/stream'; import signin from '../common/signin'; import config from '../../../config'; -import { Users, Signins, UserProfiles } from '../../../models'; +import { Users, Signins, UserProfiles, UserSecurityKeys, AttestationChallenges } from '../../../models'; import { ILocalUser } from '../../../models/entities/user'; import { genId } from '../../../misc/gen-id'; import { ensure } from '../../../prelude/ensure'; +import { verifyLogin, hash } from '../2fa'; export default async (ctx: Koa.BaseContext) => { ctx.set('Access-Control-Allow-Origin', config.url); @@ -51,40 +52,116 @@ export default async (ctx: Koa.BaseContext) => { // Compare password const same = await bcrypt.compare(password, profile.password!); - if (same) { - if (profile.twoFactorEnabled) { - const verified = (speakeasy as any).totp.verify({ - secret: profile.twoFactorSecret, - encoding: 'base32', - token: token - }); - - if (verified) { - signin(ctx, user); - } else { - ctx.throw(403, { - error: 'invalid token' - }); - } - } else { - signin(ctx, user); - } - } else { - ctx.throw(403, { - error: 'incorrect password' + async function fail(status?: number, failure?: {error: string}) { + // Append signin history + const record = await Signins.save({ + id: genId(), + createdAt: new Date(), + userId: user.id, + ip: ctx.ip, + headers: ctx.headers, + success: !!(status || failure) }); + + // Publish signin event + publishMainStream(user.id, 'signin', await Signins.pack(record)); + + if (status && failure) { + ctx.throw(status, failure); + } } - // Append signin history - const record = await Signins.save({ - id: genId(), - createdAt: new Date(), - userId: user.id, - ip: ctx.ip, - headers: ctx.headers, - success: same - }); + if (!same) { + await fail(403, { + error: 'incorrect password' + }); + return; + } - // Publish signin event - publishMainStream(user.id, 'signin', await Signins.pack(record)); + if (!profile.twoFactorEnabled) { + signin(ctx, user); + return; + } + + if (token) { + const verified = (speakeasy as any).totp.verify({ + secret: profile.twoFactorSecret, + encoding: 'base32', + token: token + }); + + if (verified) { + signin(ctx, user); + return; + } else { + await fail(403, { + error: 'invalid token' + }); + return; + } + } else { + const clientDataJSON = Buffer.from(body.clientDataJSON, 'hex'); + const clientData = JSON.parse(clientDataJSON.toString('utf-8')); + const challenge = await AttestationChallenges.findOne({ + userId: user.id, + id: body.challengeId, + registrationChallenge: false, + challenge: hash(clientData.challenge).toString('hex') + }); + + if (!challenge) { + await fail(403, { + error: 'non-existent challenge' + }); + return; + } + + await AttestationChallenges.delete({ + userId: user.id, + id: body.challengeId + }); + + if (new Date().getTime() - challenge.createdAt.getTime() >= 5 * 60 * 1000) { + await fail(403, { + error: 'non-existent challenge' + }); + return; + } + + const securityKey = await UserSecurityKeys.findOne({ + id: Buffer.from( + body.credentialId + .replace(/\-/g, '+') + .replace(/_/g, '/'), + 'base64' + ).toString('hex') + }); + + if (!securityKey) { + await fail(403, { + error: 'invalid credentialId' + }); + return; + } + + const isValid = verifyLogin({ + publicKey: Buffer.from(securityKey.publicKey, 'hex'), + authenticatorData: Buffer.from(body.authenticatorData, 'hex'), + clientDataJSON, + clientData, + signature: Buffer.from(body.signature, 'hex'), + challenge: challenge.challenge + }); + + if (isValid) { + signin(ctx, user); + } else { + await fail(403, { + error: 'invalid challenge data' + }); + return; + } + } + + await fail(); }; diff --git a/src/server/proxy/proxy-media.ts b/src/server/proxy/proxy-media.ts index 4535a0fb5d..eadfab54a3 100644 --- a/src/server/proxy/proxy-media.ts +++ b/src/server/proxy/proxy-media.ts @@ -21,9 +21,9 @@ export async function proxyMedia(ctx: Koa.BaseContext) { let image: IImage; - if ('static' in ctx.query && ['image/png', 'image/gif'].includes(type)) { + if ('static' in ctx.query && ['image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng'].includes(type)) { image = await convertToPng(path, 498, 280); - } else if ('preview' in ctx.query && ['image/jpeg', 'image/png', 'image/gif'].includes(type)) { + } else if ('preview' in ctx.query && ['image/jpeg', 'image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng'].includes(type)) { image = await convertToJpeg(path, 200, 200); } else { image = { diff --git a/src/server/web/feed.ts b/src/server/web/feed.ts index 88a61af7cc..9c7591d744 100644 --- a/src/server/web/feed.ts +++ b/src/server/web/feed.ts @@ -6,7 +6,7 @@ import { In } from 'typeorm'; import { ensure } from '../../prelude/ensure'; export default async function(user: User) { - const author: Author = { + const author = { link: `${config.url}/@${user.username}`, name: user.name || user.username }; @@ -30,13 +30,14 @@ export default async function(user: User) { generator: 'Misskey', description: `${user.notesCount} Notes, ${user.followingCount} Following, ${user.followersCount} Followers${profile.description ? ` · ${profile.description}` : ''}`, link: author.link, - image: user.avatarUrl, + image: user.avatarUrl ? user.avatarUrl : undefined, feedLinks: { json: `${author.link}.json`, atom: `${author.link}.atom`, }, - author - } as FeedOptions); + author, + copyright: user.name || user.username + }); for (const note of notes) { const files = note.fileIds.length > 0 ? await DriveFiles.find({ diff --git a/src/services/chart/charts/schemas/active-users.ts b/src/services/chart/charts/schemas/active-users.ts index f10393bc36..6e26bb4698 100644 --- a/src/services/chart/charts/schemas/active-users.ts +++ b/src/services/chart/charts/schemas/active-users.ts @@ -1,12 +1,10 @@ -import { types, bool } from '../../../../misc/schema'; - export const logSchema = { /** * アクティブユーザー数 */ count: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: 'アクティブユーザー数', }, }; @@ -15,17 +13,17 @@ export const logSchema = { * アクティブユーザーに関するチャート */ export const schema = { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { local: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: logSchema }, remote: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: logSchema }, } diff --git a/src/services/chart/charts/schemas/drive.ts b/src/services/chart/charts/schemas/drive.ts index 3327950635..dcc50df8cf 100644 --- a/src/services/chart/charts/schemas/drive.ts +++ b/src/services/chart/charts/schemas/drive.ts @@ -1,12 +1,10 @@ -import { types, bool } from '../../../../misc/schema'; - const logSchema = { /** * 集計期間時点での、全ドライブファイル数 */ totalCount: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '集計期間時点での、全ドライブファイル数' }, @@ -14,8 +12,8 @@ const logSchema = { * 集計期間時点での、全ドライブファイルの合計サイズ */ totalSize: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '集計期間時点での、全ドライブファイルの合計サイズ' }, @@ -23,8 +21,8 @@ const logSchema = { * 増加したドライブファイル数 */ incCount: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '増加したドライブファイル数' }, @@ -32,8 +30,8 @@ const logSchema = { * 増加したドライブ使用量 */ incSize: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '増加したドライブ使用量' }, @@ -41,8 +39,8 @@ const logSchema = { * 減少したドライブファイル数 */ decCount: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '減少したドライブファイル数' }, @@ -50,24 +48,24 @@ const logSchema = { * 減少したドライブ使用量 */ decSize: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '減少したドライブ使用量' }, }; export const schema = { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { local: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: logSchema }, remote: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: logSchema }, } diff --git a/src/services/chart/charts/schemas/federation.ts b/src/services/chart/charts/schemas/federation.ts index 41ae977cbd..2f2d22c0d3 100644 --- a/src/services/chart/charts/schemas/federation.ts +++ b/src/services/chart/charts/schemas/federation.ts @@ -1,29 +1,27 @@ -import { types, bool } from '../../../../misc/schema'; - /** * フェデレーションに関するチャート */ export const schema = { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { instance: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { total: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: 'インスタンス数の合計' }, inc: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '増加インスタンス数' }, dec: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '減少インスタンス数' }, } diff --git a/src/services/chart/charts/schemas/hashtag.ts b/src/services/chart/charts/schemas/hashtag.ts index 918242f66e..4dfd61c97f 100644 --- a/src/services/chart/charts/schemas/hashtag.ts +++ b/src/services/chart/charts/schemas/hashtag.ts @@ -1,12 +1,10 @@ -import { types, bool } from '../../../../misc/schema'; - export const logSchema = { /** * 投稿された数 */ count: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '投稿された数', }, }; @@ -15,17 +13,17 @@ export const logSchema = { * ハッシュタグに関するチャート */ export const schema = { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { local: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: logSchema }, remote: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: logSchema }, } diff --git a/src/services/chart/charts/schemas/instance.ts b/src/services/chart/charts/schemas/instance.ts index d97f49ea72..d2152a4ae7 100644 --- a/src/services/chart/charts/schemas/instance.ts +++ b/src/services/chart/charts/schemas/instance.ts @@ -1,73 +1,71 @@ -import { types, bool } from '../../../../misc/schema'; - /** * インスタンスごとのチャート */ export const schema = { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { requests: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { failed: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '失敗したリクエスト数' }, succeeded: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '成功したリクエスト数' }, received: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '受信したリクエスト数' }, } }, notes: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { total: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '集計期間時点での、全投稿数' }, inc: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '増加した投稿数' }, dec: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '減少した投稿数' }, diffs: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { normal: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '通常の投稿数の差分' }, reply: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: 'リプライの投稿数の差分' }, renote: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: 'Renoteの投稿数の差分' }, } @@ -76,103 +74,103 @@ export const schema = { }, users: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { total: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '集計期間時点での、全ユーザー数' }, inc: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '増加したユーザー数' }, dec: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '減少したユーザー数' }, } }, following: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { total: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '集計期間時点での、全フォロー数' }, inc: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '増加したフォロー数' }, dec: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '減少したフォロー数' }, } }, followers: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { total: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '集計期間時点での、全フォロワー数' }, inc: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '増加したフォロワー数' }, dec: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '減少したフォロワー数' }, } }, drive: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { totalFiles: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '集計期間時点での、全ドライブファイル数' }, totalUsage: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '集計期間時点での、全ドライブファイルの合計サイズ' }, incFiles: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '増加したドライブファイル数' }, incUsage: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '増加したドライブ使用量' }, decFiles: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '減少したドライブファイル数' }, decUsage: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '減少したドライブ使用量' }, } diff --git a/src/services/chart/charts/schemas/network.ts b/src/services/chart/charts/schemas/network.ts index 819e2a145e..30123c5d90 100644 --- a/src/services/chart/charts/schemas/network.ts +++ b/src/services/chart/charts/schemas/network.ts @@ -1,35 +1,33 @@ -import { types, bool } from '../../../../misc/schema'; - /** * ネットワークに関するチャート */ export const schema = { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { incomingRequests: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '受信したリクエスト数' }, outgoingRequests: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '送信したリクエスト数' }, totalTime: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '応答時間の合計' // TIP: (totalTime / incomingRequests) でひとつのリクエストに平均でどれくらいの時間がかかったか知れる }, incomingBytes: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '合計受信データ量' }, outgoingBytes: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '合計送信データ量' }, } diff --git a/src/services/chart/charts/schemas/notes.ts b/src/services/chart/charts/schemas/notes.ts index aabe49edd9..6dbf321b7c 100644 --- a/src/services/chart/charts/schemas/notes.ts +++ b/src/services/chart/charts/schemas/notes.ts @@ -1,43 +1,41 @@ -import { types, bool } from '../../../../misc/schema'; - const logSchema = { total: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '集計期間時点での、全投稿数' }, inc: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '増加した投稿数' }, dec: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '減少した投稿数' }, diffs: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { normal: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '通常の投稿数の差分' }, reply: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: 'リプライの投稿数の差分' }, renote: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: 'Renoteの投稿数の差分' }, } @@ -45,17 +43,17 @@ const logSchema = { }; export const schema = { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { local: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: logSchema }, remote: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: logSchema }, } diff --git a/src/services/chart/charts/schemas/per-user-drive.ts b/src/services/chart/charts/schemas/per-user-drive.ts index 62e7795167..21312e82d8 100644 --- a/src/services/chart/charts/schemas/per-user-drive.ts +++ b/src/services/chart/charts/schemas/per-user-drive.ts @@ -1,15 +1,13 @@ -import { types, bool } from '../../../../misc/schema'; - export const schema = { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { /** * 集計期間時点での、全ドライブファイル数 */ totalCount: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '集計期間時点での、全ドライブファイル数' }, @@ -17,8 +15,8 @@ export const schema = { * 集計期間時点での、全ドライブファイルの合計サイズ */ totalSize: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '集計期間時点での、全ドライブファイルの合計サイズ' }, @@ -26,8 +24,8 @@ export const schema = { * 増加したドライブファイル数 */ incCount: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '増加したドライブファイル数' }, @@ -35,8 +33,8 @@ export const schema = { * 増加したドライブ使用量 */ incSize: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '増加したドライブ使用量' }, @@ -44,8 +42,8 @@ export const schema = { * 減少したドライブファイル数 */ decCount: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '減少したドライブファイル数' }, @@ -53,8 +51,8 @@ export const schema = { * 減少したドライブ使用量 */ decSize: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '減少したドライブ使用量' }, } diff --git a/src/services/chart/charts/schemas/per-user-following.ts b/src/services/chart/charts/schemas/per-user-following.ts index e57869e79a..dffd9c2572 100644 --- a/src/services/chart/charts/schemas/per-user-following.ts +++ b/src/services/chart/charts/schemas/per-user-following.ts @@ -1,19 +1,17 @@ -import { types, bool } from '../../../../misc/schema'; - export const logSchema = { /** * フォローしている */ followings: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { /** * フォローしている合計 */ total: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: 'フォローしている合計', }, @@ -21,8 +19,8 @@ export const logSchema = { * フォローした数 */ inc: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: 'フォローした数', }, @@ -30,8 +28,8 @@ export const logSchema = { * フォロー解除した数 */ dec: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: 'フォロー解除した数', }, } @@ -41,15 +39,15 @@ export const logSchema = { * フォローされている */ followers: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { /** * フォローされている合計 */ total: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: 'フォローされている合計', }, @@ -57,8 +55,8 @@ export const logSchema = { * フォローされた数 */ inc: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: 'フォローされた数', }, @@ -66,8 +64,8 @@ export const logSchema = { * フォロー解除された数 */ dec: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: 'フォロー解除された数', }, } @@ -75,17 +73,17 @@ export const logSchema = { }; export const schema = { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { local: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: logSchema }, remote: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: logSchema }, } diff --git a/src/services/chart/charts/schemas/per-user-notes.ts b/src/services/chart/charts/schemas/per-user-notes.ts index 7632450073..778f498c6e 100644 --- a/src/services/chart/charts/schemas/per-user-notes.ts +++ b/src/services/chart/charts/schemas/per-user-notes.ts @@ -1,46 +1,44 @@ -import { types, bool } from '../../../../misc/schema'; - export const schema = { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { total: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '集計期間時点での、全投稿数' }, inc: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '増加した投稿数' }, dec: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '減少した投稿数' }, diffs: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { normal: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '通常の投稿数の差分' }, reply: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: 'リプライの投稿数の差分' }, renote: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: 'Renoteの投稿数の差分' }, } diff --git a/src/services/chart/charts/schemas/per-user-reactions.ts b/src/services/chart/charts/schemas/per-user-reactions.ts index df7d5a6dbb..d103124ccb 100644 --- a/src/services/chart/charts/schemas/per-user-reactions.ts +++ b/src/services/chart/charts/schemas/per-user-reactions.ts @@ -1,12 +1,10 @@ -import { types, bool } from '../../../../misc/schema'; - export const logSchema = { /** * フォローしている合計 */ count: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: 'リアクションされた数', }, }; @@ -15,17 +13,17 @@ export const logSchema = { * ユーザーごとのリアクションに関するチャート */ export const schema = { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { local: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: logSchema }, remote: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: logSchema }, } diff --git a/src/services/chart/charts/schemas/test-grouped.ts b/src/services/chart/charts/schemas/test-grouped.ts index 893458e90a..163ec5bba3 100644 --- a/src/services/chart/charts/schemas/test-grouped.ts +++ b/src/services/chart/charts/schemas/test-grouped.ts @@ -1,28 +1,26 @@ -import { types, bool } from '../../../../misc/schema'; - export const schema = { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { foo: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { total: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '' }, inc: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '' }, dec: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '' }, } diff --git a/src/services/chart/charts/schemas/test-unique.ts b/src/services/chart/charts/schemas/test-unique.ts index 2b3f445ff0..075a8092d9 100644 --- a/src/services/chart/charts/schemas/test-unique.ts +++ b/src/services/chart/charts/schemas/test-unique.ts @@ -1,12 +1,10 @@ -import { types, bool } from '../../../../misc/schema'; - export const schema = { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { foo: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '' }, } diff --git a/src/services/chart/charts/schemas/test.ts b/src/services/chart/charts/schemas/test.ts index 9bdc05990b..6142434a89 100644 --- a/src/services/chart/charts/schemas/test.ts +++ b/src/services/chart/charts/schemas/test.ts @@ -1,28 +1,26 @@ -import { types, bool } from '../../../../misc/schema'; - export const schema = { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { foo: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { total: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '' }, inc: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '' }, dec: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '' }, } diff --git a/src/services/chart/charts/schemas/users.ts b/src/services/chart/charts/schemas/users.ts index 6ae6133350..1153369983 100644 --- a/src/services/chart/charts/schemas/users.ts +++ b/src/services/chart/charts/schemas/users.ts @@ -1,12 +1,10 @@ -import { types, bool } from '../../../../misc/schema'; - const logSchema = { /** * 集計期間時点での、全ユーザー数 */ total: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '集計期間時点での、全ユーザー数' }, @@ -14,8 +12,8 @@ const logSchema = { * 増加したユーザー数 */ inc: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '増加したユーザー数' }, @@ -23,24 +21,24 @@ const logSchema = { * 減少したユーザー数 */ dec: { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, description: '減少したユーザー数' }, }; export const schema = { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: { local: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: logSchema }, remote: { - type: types.object, - optional: bool.false, nullable: bool.false, + type: 'object' as const, + optional: false as const, nullable: false as const, properties: logSchema }, } diff --git a/src/services/chart/core.ts b/src/services/chart/core.ts index 6564ef7813..d5fd9a5c5c 100644 --- a/src/services/chart/core.ts +++ b/src/services/chart/core.ts @@ -8,7 +8,7 @@ import * as moment from 'moment'; import * as nestedProperty from 'nested-property'; import autobind from 'autobind-decorator'; import Logger from '../logger'; -import { Schema, bool, types } from '../../misc/schema'; +import { Schema } from '../../misc/schema'; import { EntitySchema, getRepository, Repository, LessThan, MoreThanOrEqual } from 'typeorm'; import { isDuplicateKeyValueError } from '../../misc/is-duplicate-key-value-error'; @@ -462,8 +462,8 @@ export function convertLog(logSchema: Schema): Schema { if (v.type === 'number') { v.type = 'array'; v.items = { - type: types.number, - optional: bool.false, nullable: bool.false, + type: 'number' as const, + optional: false as const, nullable: false as const, }; } else if (v.type === 'object') { for (const k of Object.keys(v.properties!)) { diff --git a/src/services/drive/add-file.ts b/src/services/drive/add-file.ts index a2143ca608..e7b1e2a812 100644 --- a/src/services/drive/add-file.ts +++ b/src/services/drive/add-file.ts @@ -46,6 +46,8 @@ async function save(file: DriveFile, path: string, name: string, type: string, h if (type === 'image/jpeg') ext = '.jpg'; if (type === 'image/png') ext = '.png'; if (type === 'image/webp') ext = '.webp'; + if (type === 'image/apng') ext = '.apng'; + if (type === 'image/vnd.mozilla.apng') ext = '.apng'; } const baseUrl = meta.objectStorageBaseUrl @@ -181,6 +183,8 @@ export async function generateAlts(path: string, type: string, generateWeb: bool thumbnail = await convertToPng(path, 498, 280); } else if (['image/gif'].includes(type)) { thumbnail = await convertToGif(path); + } else if (['image/apng', 'image/vnd.mozilla.apng'].includes(type)) { + thumbnail = await convertToApng(path); } else if (type.startsWith('video/')) { try { thumbnail = await GenerateVideoThumbnail(path); @@ -356,7 +360,7 @@ export default async function( let propPromises: Promise[] = []; - const isImage = ['image/jpeg', 'image/gif', 'image/png', 'image/webp'].includes(mime); + const isImage = ['image/jpeg', 'image/gif', 'image/png', 'image/apng', 'image/vnd.mozilla.apng', 'image/webp'].includes(mime); if (isImage) { const img = sharp(path); diff --git a/src/services/drive/delete-file.ts b/src/services/drive/delete-file.ts index 258c0853fb..5b44a0817a 100644 --- a/src/services/drive/delete-file.ts +++ b/src/services/drive/delete-file.ts @@ -1,8 +1,10 @@ +import * as Minio from 'minio'; import { DriveFile } from '../../models/entities/drive-file'; import { InternalStorage } from './internal-storage'; import { DriveFiles, Instances, Notes } from '../../models'; import { driveChart, perUserDriveChart, instanceChart } from '../chart'; import { createDeleteObjectStorageFileJob } from '../../queue'; +import { fetchMeta } from '../../misc/fetch-meta'; export async function deleteFile(file: DriveFile, isExpired = false) { if (file.storedInternal) { @@ -27,6 +29,40 @@ export async function deleteFile(file: DriveFile, isExpired = false) { } } + postProcess(file, isExpired); +} + +export async function deleteFileSync(file: DriveFile, isExpired = false) { + if (file.storedInternal) { + InternalStorage.del(file.accessKey!); + + if (file.thumbnailUrl) { + InternalStorage.del(file.thumbnailAccessKey!); + } + + if (file.webpublicUrl) { + InternalStorage.del(file.webpublicAccessKey!); + } + } else if (!file.isLink) { + const promises = []; + + promises.push(deleteObjectStorageFile(file.accessKey!)); + + if (file.thumbnailUrl) { + promises.push(deleteObjectStorageFile(file.thumbnailAccessKey!)); + } + + if (file.webpublicUrl) { + promises.push(deleteObjectStorageFile(file.webpublicAccessKey!)); + } + + await Promise.all(promises); + } + + postProcess(file, isExpired); +} + +function postProcess(file: DriveFile, isExpired = false) { // リモートファイル期限切れ削除後は直リンクにする if (isExpired && file.userHost !== null && file.uri != null) { DriveFiles.update(file.id, { @@ -53,3 +89,18 @@ export async function deleteFile(file: DriveFile, isExpired = false) { Instances.decrement({ host: file.userHost }, 'driveFiles', 1); } } + +export async function deleteObjectStorageFile(key: string) { + const meta = await fetchMeta(); + + const minio = new Minio.Client({ + endPoint: meta.objectStorageEndpoint!, + region: meta.objectStorageRegion ? meta.objectStorageRegion : undefined, + port: meta.objectStoragePort ? meta.objectStoragePort : undefined, + useSSL: meta.objectStorageUseSSL, + accessKey: meta.objectStorageAccessKey!, + secretKey: meta.objectStorageSecretKey!, + }); + + await minio.removeObject(meta.objectStorageBucket!, key); +} diff --git a/src/services/drive/image-processor.ts b/src/services/drive/image-processor.ts index 4b8db0e0c8..919d9ce138 100644 --- a/src/services/drive/image-processor.ts +++ b/src/services/drive/image-processor.ts @@ -97,6 +97,6 @@ export async function convertToApng(path: string): Promise { return { data, ext: 'apng', - type: 'image/apng' + type: 'image/vnd.mozilla.apng' }; } diff --git a/src/tools/accept-migration.ts b/src/tools/accept-migration.ts new file mode 100644 index 0000000000..addce86f1f --- /dev/null +++ b/src/tools/accept-migration.ts @@ -0,0 +1,25 @@ +// ex) node built/tools/accept-migration Yo 1000000000001 + +import { createConnection } from 'typeorm'; +import config from '../config'; + +createConnection({ + type: 'postgres', + host: config.db.host, + port: config.db.port, + username: config.db.user, + password: config.db.pass, + database: config.db.db, + extra: config.db.extra, + synchronize: false, + dropSchema: false, +}).then(c => { + c.query(`INSERT INTO migrations(timestamp,name) VALUES (${process.argv[3]}, '${process.argv[2]}${process.argv[3]}');`).then(() => { + console.log('done'); + process.exit(0); + }).catch(e => { + console.log('ERROR:'); + console.log(e); + process.exit(1); + }); +}); diff --git a/test/mfm.ts b/test/mfm.ts index be8b65264a..ccef31cd2c 100644 --- a/test/mfm.ts +++ b/test/mfm.ts @@ -843,6 +843,15 @@ describe('MFM', () => { ]); }); + it('ignore parent []', () => { + const tokens = parse('foo [https://example.com/foo] bar'); + assert.deepStrictEqual(tokens, [ + text('foo ['), + leaf('url', { url: 'https://example.com/foo' }), + text('] bar') + ]); + }); + it('ignore parent brackets 2', () => { const tokens = parse('(foo https://example.com/foo)'); assert.deepStrictEqual(tokens, [ diff --git a/webpack.config.ts b/webpack.config.ts index 5ec0a60d1a..24d2337a7e 100644 --- a/webpack.config.ts +++ b/webpack.config.ts @@ -45,7 +45,6 @@ module.exports = { dev: './src/client/app/dev/script.ts', auth: './src/client/app/auth/script.ts', admin: './src/client/app/admin/script.ts', - test: './src/client/app/test/script.ts', sw: './src/client/app/sw.js' }, module: {