diff --git a/.config/example.yml b/.config/example.yml index 8534d3e1e..ca47c3141 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 9db60222a..3a220319e 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 0a0193fa1..12861d31b 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 179cf5304..894122b04 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 940a22d0f..40ad068a4 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 a2d90a3e1..2941074c2 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 ff7b730c2..ac30e9512 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 3b667258f..f1f3751c0 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 2541e1ca6..e6ba5fa9d 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 fd8e96588..d4311d09a 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 174b69110..991377fcc 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 fe593c24f..d0e06544d 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 88673e804..15cb30997 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 5272973ec..2938dd6e6 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 e7a34c4ac..891dd0727 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 6ff353f20..b8c073200 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 000000000..ce54d8a7e --- /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 000000000..fc1f0c042 --- /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 000000000..b870ad3b7 --- /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 6b7297b2c..8a0196947 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 264b50d66..bd0d551ea 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 000000000..55bebd9bf --- /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 6c23a528f..b698548d4 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 7818546e7..9f87a749b 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 588b24f6b..2d0fc9b40 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 9af58076e..6cb1488e4 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 000000000..f638cce15 --- /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 a577da5a2..ed94fe5f3 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 cae606763..074a0c05b 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 09f2e287f..fa24f6dfa 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 03ee51d06..53cc62c33 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 b22a10097..b1ca3f285 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 c30cb397e..49cc869eb 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 e2da55fc9..a095491b6 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 e62277338..c23a32536 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 ce0b96e0a..806524171 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 8b39e6e00..71c430ede 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 5d7e0e5e0..856f889b0 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 4b64d4f22..2eb958135 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 5948e805a..03abcf865 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 8a66728a7..511e1548e 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 b72c796c1..8795102f9 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 d9817f1a5..b5547c916 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 48a6650b4..6cf4a36f9 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 44fe224cb..000000000 --- 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 ae1a28226..000000000 --- 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 e0725d42a..000000000 --- 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 000000000..462ebf915 --- /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 c91cd8e1d..94a19b06b 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 75a2baaab..5d5e35fbf 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 70d58ffe2..b22873369 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 e2f10c0e4..0ba25d812 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 442c95ad9..e14e6e0dd 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 000000000..942747c02 --- /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 7d990b961..6f960f1b7 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 000000000..d54c728e5 --- /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 be2cccfe3..cdb825907 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 a60cd10ef..888fd53f3 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 6562a470b..45d8d16c5 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 6ee31cece..9ebe6bbf5 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 5e7e1d40f..702f195e9 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 ef920c432..dee424ccc 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 88fee749a..58728a338 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 a990fa3dc..fb54a12e1 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 7f0b2047a..d70e95bc1 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 9d99e08a7..763e04bb3 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 01064f306..37cfbc002 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 85e5b6b0a..79b4989a2 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 e5bf8d3f3..03a50fece 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 54eec87cf..96293d4a6 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 3b4142002..33126274a 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 38174ce23..5ff75047c 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 54231c7f8..8842118be 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 bd48f04ea..cc89b674c 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 70233d831..a7e9b9814 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 5ecee9139..a2fd9050a 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 000000000..7b34892e1 --- /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 8e6b5f959..f899df7d2 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 f2fc08f1a..33ef665b3 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 ebd5a27b9..68fce52e1 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 684304147..2a07f50c8 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 7720e8f1b..000000000 --- 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 0326b591f..a6fa2336f 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 199a6a618..be7779e02 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 bb9465d90..14425d749 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 5c126c3a5..01086a7cf 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 95c69fb8a..66163d39b 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 000000000..3bc4627a6 --- /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 69cfe0db9..e837ae1bb 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 000000000..fed4f2df1 --- /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 000000000..699070628 --- /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 9724a044b..bbaa1fa10 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 833d5060c..81c851f3a 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 e7d3e8438..2c8cdbe39 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 9bf27c8e7..b38c275de 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 b7a58c475..1dc78eeab 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 5ff1dc0c4..c99ba09df 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 4d4516bd8..bb85bab14 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 d10c2a3ef..77cefdfbe 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 f770bc713..2b84e114b 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 ab19566f1..a6cd14caf 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 0f81a1da9..664a2b87b 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 d56e63bc5..84cc4f92b 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 82b9a97b6..732596a33 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 8e74361f9..4384b2114 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 dc3174cd2..e2d22e708 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 0368d026c..04bec1b17 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 a020b46aa..f48f21d73 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 f75c4273c..b93ee11a1 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 9023f1191..9bc267779 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 0d2704d01..7caaf3484 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 72a4cc7c8..5de906fb1 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 53a351471..3154461e5 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 b842f9de6..59210f460 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 4ecd507e1..20a0c604f 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 000000000..bb1585d79 --- /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 000000000..d751dabc4 --- /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 000000000..1c2cc32e3 --- /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 000000000..cb28c8fbf --- /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 d1e90dd15..1bbc16256 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 41513e5da..aa72e9a17 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 833ec37e4..2c143c26b 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 ffd58c714..b0b3e20d0 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 feffc9a0c..d0c1ee494 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 1aa9a855d..a3390a011 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 f9ea380c7..1afc120f5 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 7aa19c9a2..fab8455d7 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 e8861eafa..bc8407f31 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 acd3ac75e..245d42792 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 46db27458..1650de907 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 64750815b..0a1d8668b 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 f46fa208d..8654cf889 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 7be13fc47..8c4c7a60b 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 73cbebace..c688b9325 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 02e44492b..fd3767f63 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 0773b4faa..7bea24d31 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 00dfac377..a5db706e3 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 5fb0fd989..cd38d4165 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 0b49f896a..7f53b2699 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 65ce20074..d3fb33c42 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 54b420813..75abbae55 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 f9442f8b9..25876f655 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 c16018d43..f66221537 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 e6b813648..ffe0d38ea 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 e3d6e6a15..84808418f 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 de0e17a2e..853e1cd4b 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 4dca62445..c86d893d5 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 539f2ef89..93cf01a6f 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 465b71e2e..68c32fe98 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 2a7748ac6..eb699b290 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 24d1bd194..1a17b488f 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 ee6cade8d..2a6e5135e 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 97d168e52..c60ba57c0 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 6cf39a142..e2c0bf2fc 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 4f8374a22..643f1acf7 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 b4284ab48..012f9f4ef 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 bc974621a..2fc0a803a 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 79efffbf9..28af3c8d8 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 684086b5c..7b89d3431 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 395f9352d..01d03d1bf 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 fdc50e4da..ef564b3c3 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 38b420c33..73d8fe620 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 5c413defb..9aa9e398e 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 820c44e81..d17dd51c0 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 02361a139..cd9fe5bb9 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 4535a0fb5..eadfab54a 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 88a61af7c..9c7591d74 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 f10393bc3..6e26bb469 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 332795063..dcc50df8c 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 41ae977cb..2f2d22c0d 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 918242f66..4dfd61c97 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 d97f49ea7..d2152a4ae 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 819e2a145..30123c5d9 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 aabe49edd..6dbf321b7 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 62e779516..21312e82d 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 e57869e79..dffd9c257 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 763245007..778f498c6 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 df7d5a6db..d103124cc 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 893458e90..163ec5bba 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 2b3f445ff..075a8092d 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 9bdc05990..6142434a8 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 6ae613335..115336998 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 6564ef781..d5fd9a5c5 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 a2143ca60..e7b1e2a81 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 258c0853f..5b44a0817 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 4b8db0e0c..919d9ce13 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 000000000..addce86f1 --- /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 be8b65264..ccef31cd2 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 5ec0a60d1..24d2337a7 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: {