diff --git a/CHANGELOG.md b/CHANGELOG.md index 188405268d..f538a41060 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,27 @@ You should also include the user name that made the change. --> +## 12.109.0 (2022/04/02) + +### Improvements +- Webhooks @syuilo +- Bull Dashboardを組み込み、ジョブキューの確認や操作を行えるように @syuilo + - Bull Dashboardを開くには、最初だけ一旦ログアウトしてから再度管理者権限を持つアカウントでログインする必要があります +- Check that installed Node.js version fulfills version requirement @ThatOneCalculator +- Server: overall performance improvements @syuilo +- Federation: avoid duplicate activity delivery @Johann150 +- Federation: limit federation of reactions on direct notes @Johann150 +- Client: タッチパッド・タッチスクリーンでのデッキの操作性を向上 @tamaina + +### Bugfixes +- email address validation was not working @ybw2016v +- API: fix endpoint endpoint @Johann150 +- API: fix admin/meta endpoint @syuilo +- API: improved validation and documentation for endpoints that accept different variants of input @Johann150 +- API: `notes/create`: The `mediaIds` property is now deprecated. @Johann150 + - Use `fileIds` instead, it has the same behaviour. +- Client: URIエンコーディングが異常でdecodeURIComponentが失敗するとURLが表示できなくなる問題を修正 @tamaina + ## 12.108.1 (2022/03/12) ### Bugfixes diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6e0f500be5..a696bc5ceb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,6 +6,9 @@ Also, you might receive comments on your Issue/PR in Japanese, but you do not ne The accuracy of machine translation into Japanese is not high, so it will be easier for us to understand if you write it in the original language. It will also allow the reader to use the translation tool of their preference if necessary. +## Roadmap +See [ROADMAP.md](./ROADMAP.md) + ## Issues Before creating an issue, please check the following: - To avoid duplication, please search for similar issues before creating a new issue. @@ -198,11 +201,13 @@ MongoDBの時とは違い、findOneでレコードを取得する時に対象レ MongoDBは`null`で返してきてたので、その感覚で`if (x === null)`とか書くとバグる。代わりに`if (x == null)`と書いてください ### Migration作成方法 -``` -npx ts-node ./node_modules/typeorm/cli.js migration:generate -n 変更の名前 -o +packages/backendで: +```sh +npx typeorm migration:generate -d ormconfig.js -o ``` -作成されたスクリプトは不必要な変更を含むため除去してください。 +- 生成後、ファイルをmigration下に移してください +- 作成されたスクリプトは不必要な変更を含むため除去してください ### コネクションには`markRaw`せよ **Vueのコンポーネントのdataオプションとして**misskey.jsのコネクションを設定するとき、必ず`markRaw`でラップしてください。インスタンスが不必要にリアクティブ化されることで、misskey.js内の処理で不具合が発生するとともに、パフォーマンス上の問題にも繋がる。なお、Composition APIを使う場合はこの限りではない(リアクティブ化はマニュアルなため)。 diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 0000000000..3ccc098d32 --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,36 @@ +# Roadmap +The order of individual tasks is a guide only and is subject to change depending on the situation. +Also, the later tasks are more indefinite and are subject to change as development progresses. + +## (1) Improve maintainability \ +This is the phase we are at now. We need to make a high-maintenance environment that can withstand future development. + +- Make the number of type errors zero (backend) + - Probably need to switch some libraries to others that make it difficult to reduce type errors + - e.g. koa to fastify https://github.com/misskey-dev/misskey/issues/7537 +- Improve CI + - Fix tests + - mocha, jest, etc. do not support the combination of `TypeScript + ESM + Path alias`, and the tests currently do not work. + - Fix random test failures - https://github.com/misskey-dev/misskey/issues/7985 and https://github.com/misskey-dev/misskey/issues/7986 + - Add more tests + - May need to implement a mechanism that allows for DI +- Improve documentation + +## (2) Improve functionality +Once Phase 1 is complete and an environment conducive to the development of a stable system is in place, the implementation of new functions can begin gradually. + +- OAuth2 support https://github.com/misskey-dev/misskey/issues/8262 +- GraphQL support? + +## (3) Improve scalability +Once the development of the feature has settled down, this may be an opportunity to make larger modifications. + +- Rewriting in Rust? + +## (4) Change the world +It is time to promote Misskey and change the world. + +- Become more major than services such as Twitter and become critical infrastructure for the world +- MiOS will be developed and integrated into various systems - What is MiOS? +- Letting Ai-chan interfere with the real world +- Make Misskey a member of GAFA; Misskey's office must be a reinforced concrete brutalist building with a courtyard. diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml index def791f9d4..69d9327837 100644 --- a/locales/ar-SA.yml +++ b/locales/ar-SA.yml @@ -189,7 +189,7 @@ clearCachedFiles: "امسح التخزين المؤقت" clearCachedFilesConfirm: "أتريد حذف التخزين المؤقت للملفات البعيدة؟" blockedInstances: "المثلاء المحجوبون" blockedInstancesDescription: "قائمة بالمثلاء التي تريد حظرها بحيث كل نطاق في سطر لوحده. بعد إدراجهم لن يتمكنوا من التفاعل مع هذا المثيل." -muteAndBlock: "تم كتمها / تم حجبها" +muteAndBlock: "المكتومون والمحجوبون" mutedUsers: "الحسابات المكتومة" blockedUsers: "الحسابات المحجوبة" noUsers: "ليس هناك مستخدمون" @@ -490,7 +490,7 @@ none: "لا شيء" showInPage: "اعرض في الصفحة" popout: "منبثقة" volume: "مستوى الصوت" -masterVolume: "القرص الرئيسي" +masterVolume: "حجم الصوت الرئيس" details: "التفاصيل" chooseEmoji: "اختر إيموجي" unableToProcess: "يتعذر إكمال العملية" @@ -521,6 +521,7 @@ divider: "فاصل" addItem: "إضافة عنصر" relays: "المُرَحلات" addRelay: "إضافة مُرحّل" +inboxUrl: "رابط صندوق الوارد" addedRelays: "المرحلات المضافة" serviceworkerInfo: "يجب أن يفعل لإرسال الإشعارات." deletedNote: "ملاحظة محذوفة" @@ -533,6 +534,8 @@ enablePlayer: "افتح مشغل الفيديو" disablePlayer: "أغلق مشغل الفيديو" themeEditor: "مصمم القوالب" description: "الوصف" +describeFile: "أضف تعليقًا توضيحيًا" +enterFileDescription: "أدخل تعليقًا توضيحيًا" author: "الكاتب" leaveConfirm: "لديك تغييرات غير محفوظة. أتريد المتابعة دون حفظها؟" manage: "إدارة " @@ -564,6 +567,9 @@ smtpPass: "الكلمة السرية" emptyToDisableSmtpAuth: "اترك اسم المستخدم وكلمة المرور فارغين لتعطيل التحقق من SMTP" smtpSecureInfo: "عطل هذا الخيار عند استخدام STARTTLS" wordMute: "حظر الكلمات" +regexpError: "خطأ في التعبير النمطي" +instanceMute: "المثلاء المكتومون" +userSaysSomething: "كتب {name} شيءً" makeActive: "تفعيل" display: "المظهر" copy: "نسخ" @@ -590,10 +596,16 @@ reportAbuse: "أبلغ" reportAbuseOf: "أبلغ عن {name}" fillAbuseReportDescription: "أكتب بالتفصيل سبب البلاغ، إذا كنت تبلغ عن ملاحظة أرفق رابط لها." abuseReported: "أُرسل البلاغ، شكرًا لك" +reporter: "المُبلّغ" +reporteeOrigin: "أصل البلاغ" +reporterOrigin: "أصل المُبلّغ" +forwardReport: "وجّه البلاغ إلى المثيل البعيد" +forwardReportIsAnonymous: "في المثيل البعيد سيظهر المبلّغ كحساب مجهول." send: "أرسل" abuseMarkAsResolved: "علّم البلاغ كمحلول" openInNewTab: "افتح في لسان جديد" defaultNavigationBehaviour: "سلوك الملاحة الافتراضي" +editTheseSettingsMayBreakAccount: "تعديل هذه الإعدادات قد يسبب عطبًا لحسابك" instanceTicker: "معلومات المثيل الأصلي للملاحظات" waitingFor: "في انتظار {x}" random: "عشوائي" @@ -624,10 +636,15 @@ no: "لا" driveFilesCount: "عدد الملفات في قرص التخزين" driveUsage: "المستغل من قرص التخزين" noCrawleDescription: "يطلب من محركات البحث ألّا يُفهرسوا ملفك الشخصي وملاحظات وصفحاتك وما شابه." +alwaysMarkSensitive: "علّم افتراضيًا جميع ملاحظاتي كذات محتوى حساس" +loadRawImages: "حمّل الصور الأصلية بدلًا من المصغرات" disableShowingAnimatedImages: "لا تشغّل الصور المتحركة" +verificationEmailSent: "أُرسل بريد التحقق. أنقر على الرابط المضمن لإكمال التحقق." notSet: "لم يعيّن" emailVerified: "تُحقّق من بريدك الإلكتروني" noteFavoritesCount: "عدد الملاحظات المفضلة" +pageLikesCount: "عدد الصفحات التي أعجبت بها" +pageLikedCount: "عدد صفحاتك المُعجب بها" contact: "التواصل" useSystemFont: "استخدم الخط الافتراضية للنظام" clips: "مشابك" @@ -635,6 +652,7 @@ experimentalFeatures: "ميّزات اختبارية" developer: "المطور" makeExplorable: "أظهر الحساب في صفحة \"استكشاف\"" makeExplorableDescription: "بتعطيل هذا الخيار لن يظهر حسابك في صفحة \"استكشاف\"" +showGapBetweenNotesInTimeline: "أظهر فجوات بين المشاركات في الخيط الزمني" wide: "عريض" narrow: "رفيع" reloadToApplySetting: "سيُطبق هذا الإعداد بعد إعادة تحميل الصفحة، أتريد إعادة تحميلها الآن؟" @@ -782,6 +800,7 @@ tenMinutes: "10 دقائق" oneHour: "ساعة" oneDay: "يوم" oneWeek: "أسبوع" +failedToFetchAccountInformation: "تعذر جلب معلومات الحساب" _emailUnavailable: used: "هذا البريد الإلكتروني مستخدم" format: "صيغة البريد الإلكتروني غير صالحة" @@ -860,6 +879,7 @@ _mfm: centerDescription: "يمركز المحتوى في الوَسَط." quote: "اقتبس" emoji: "إيموجي مخصص" + emojiDescription: "إحاطة اسم الإيموجي بنقطتي تفسير سيستبدله بصورة الإيموجي." search: "البحث" flip: "اقلب" flipDescription: "يقلب المحتوى عموديًا أو أفقيًا" @@ -871,15 +891,27 @@ _mfm: jumpDescription: "يمنح للمحتوى حركة قفز." bounce: "تأثير (ارتداد)" bounceDescription: "يمنح للمحتوى حركة ارتدادية" + shake: "تأثير (اهتزاز)" + shakeDescription: "يمنح المحتوى حركة اهتزازية." + spin: "تأثير (دوران)" + spinDescription: "يمنح المحتوى حركة دورانية." x2: "كبير" + x2Description: "يُكبر المحتوى" x3: "كبير جداً" + x3Description: "يُضخم المحتوى" + x4: "هائل" + x4Description: "يُضخم المحتوى أكثر مما سبق." blur: "طمس" + blurDescription: "يطمس المحتوى، لكن بالتمرير فوقه سيظهر بوضوح." font: "الخط" + fontDescription: "الخط المستخدم لعرض المحتوى." rainbow: "قوس قزح" rainbowDescription: "اجعل المحتوى يظهر بألوان الطيف" rotate: "تدوير" _instanceTicker: + none: "لا تظهره بتاتًا" remote: "أظهر للمستخدمين البِعاد" + always: "أظهره دائمًا" _serverDisconnectedBehavior: reload: "إعادة تحميل تلقائية" dialog: "أظهر مربع حوار التحذيرات" @@ -899,12 +931,18 @@ _menuDisplay: hide: "إخفاء" _wordMute: muteWords: "الكلمات المحظورة" + muteWordsDescription: "افصل بينهم بمسافة لاستخدام معامل \"و\" أو بسطر لاستخدام معامل \"أو\"." muteWordsDescription2: "احصر الكلمات المفتاحية بين بين شرطتين مائلتين لاستخدامها كتعابير نمطية" softDescription: "اخف الملاحظات التي تستوف الشروط من الخيط الزمني." hardDescription: "اخف الملاحظات التي تستوف الشروط من الخيط الزمني.بالإضافة إلى أن هذه الملاحظات ستبقى مخفية حتى وإن تغيرت الشروط." soft: "لينة" hard: "قاسية" mutedNotes: "الملاحظات المكتومة" +_instanceMute: + instanceMuteDescription: "هذه سيحجب كل ملاحظات الخوادم المحجوبة ومشاركاتها والردود على تلك الملاحظات حتى وإن كانت من خادم غير محجوب." + instanceMuteDescription2: "مدخلة لكل سطر" + title: "يخفي ملاحظات الخوادم المسرودة." + heading: "قائمة الخوادم المحجوبة" _theme: explore: "استكشف قوالب المظهر" install: "تنصيب قالب" diff --git a/locales/de-DE.yml b/locales/de-DE.yml index e70249da16..1f558787ab 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -478,8 +478,8 @@ promote: "Werbung schalten" numberOfDays: "Anzahl der Tage" hideThisNote: "Diese Notiz verstecken" showFeaturedNotesInTimeline: "Beliebte Notizen in der Chronik anzeigen" -objectStorage: "Objektspeicher" -useObjectStorage: "Objektspeicher verwenden" +objectStorage: "Object Storage" +useObjectStorage: "Object Storage verwenden" objectStorageBaseUrl: "Basis-URL" objectStorageBaseUrlDesc: "Die als Referenz verwendete URL. Verwendest du einen CDN oder Proxy, gib dessen URL an. Für S3 verwende 'https://.s3.amazonaws.com'. Für GCS o.ä. verwende 'https://storage.googleapis.com/'." objectStorageBucket: "Bucket" @@ -827,7 +827,7 @@ overridedDeviceKind: "Gerätetyp" smartphone: "Smartphone" tablet: "Tablet" auto: "Automatisch" -themeColor: "Instanzfarbe" +themeColor: "Farbe der Instanz-Information" size: "Größe" numberOfColumn: "Spaltenanzahl" searchByGoogle: "Googlen" @@ -840,6 +840,8 @@ tenMinutes: "10 Minuten" oneHour: "Eine Stunde" oneDay: "Einen Tag" oneWeek: "Eine Woche" +reflectMayTakeTime: "Es kann etwas dauern, bis sich dies widerspiegelt." +failedToFetchAccountInformation: "Benutzerkontoinformationen konnten nicht abgefragt werden" _emailUnavailable: used: "Diese Email-Adresse wird bereits verwendet" format: "Das Format dieser Email-Adresse ist ungültig" diff --git a/locales/en-US.yml b/locales/en-US.yml index 5ec97f05f5..99fe05375b 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -827,7 +827,7 @@ overridedDeviceKind: "Device type" smartphone: "Smartphone" tablet: "Tablet" auto: "Auto" -themeColor: "Theme Color" +themeColor: "Instance Ticker Color" size: "Size" numberOfColumn: "Number of columns" searchByGoogle: "Google" @@ -840,6 +840,8 @@ tenMinutes: "10 minutes" oneHour: "One hour" oneDay: "One day" oneWeek: "One week" +reflectMayTakeTime: "It may take some time for this to be reflected." +failedToFetchAccountInformation: "Could not fetch account information" _emailUnavailable: used: "This email address is already being used" format: "The format of this email address is invalid" diff --git a/locales/eo-UY.yml b/locales/eo-UY.yml index 934ffd2d43..72d81b4ac1 100644 --- a/locales/eo-UY.yml +++ b/locales/eo-UY.yml @@ -7,7 +7,7 @@ search: "Serĉi" notifications: "Sciigoj" username: "Uzantnomo" password: "Pasvorto" -forgotPassword: "Ĉu vi forgesis pasvorton?" +forgotPassword: "Ĉu vi forgesis vian pasvorton?" fetchingAsApObject: "Informpetado de la Fediverso…" ok: "Okej" gotIt: "Kompreni" @@ -71,7 +71,7 @@ lists: "Listoj" noLists: "Neniu listo" note: "Noti" notes: "Notoj" -following: "Sekvatoj" +following: "Sekvi" followers: "Sekvantoj" followsYou: "Sekvas vin" createList: "Krei liston" @@ -138,7 +138,7 @@ cacheRemoteFiles: "Stapli forajn dosierojn" flagAsBot: "Marki kiel esti uzanto de roboto" flagAsCat: "Marki kiel esti kato" flagAsCatDescription: "Flagu por montri ke la konton havas kato." -flagShowTimelineReplies: "Montri respondon de notoj en templinio." +flagShowTimelineReplies: "Montri la respondojn en la templinio" autoAcceptFollowed: "Aŭtomate akcepti la peton de sekvado far uzantoj kiujn vi sekvas" addAccount: "Aldoni konton" loginFailed: "Saluto malsukcesis" @@ -239,7 +239,7 @@ agreeTo: "Mi akceptas {0}" tos: "Kondiĉoj de uzado" start: "Komenciĝi" home: "Hejma" -remoteUserCaution: "Pro fora uzanto, la infomoj ne estas tuto." +remoteUserCaution: "La informoj eblas nekompletaj ĉar estas fora uzanto." activity: "Aktiveco" images: "Bildoj" birthday: "Naskiĝdato" @@ -412,7 +412,7 @@ usernameInvalidFormat: "La uzantnomo povas enhavi minusklajn kaj majusklajn lite tooShort: "Tro mallonga" tooLong: "Tro longa" weakPassword: "Malforta pasvorto" -normalPassword: "Normala pasvorto" +normalPassword: "Meza pasvorto" strongPassword: "Forta pasvorto" passwordMatched: "Konforma" passwordNotMatched: "Nekonforma" @@ -493,11 +493,11 @@ deletedNote: "Forviŝita noto" invisibleNote: "Malpublikigita noto" enableInfiniteScroll: "Ebligi infinitan rulumon" visibility: "Videbleco" -poll: "Enketo" +poll: "Balot-enketo" useCw: "Kaŝi enhavo" enablePlayer: "Vidigi la filmeton" disablePlayer: "Malfermi la filmeton" -expandTweet: "Disvolvi pepon" +expandTweet: "Disvolvi la pepon" themeEditor: "Redaktilo de koloraroj" description: "Priskribo" describeFile: "Priskribi la bildon" @@ -506,7 +506,7 @@ author: "Aŭtoro" manage: "Bonteni" plugins: "Kromaĵoj" deck: "Kartaro" -useFullReactionPicker: "Uzi la tuton de la elektilon de reagoj" +useFullReactionPicker: "Uzi la tuton de la elektilo de reagoj" width: "Larĝeco" height: "Alteco" large: "Granda" @@ -530,6 +530,7 @@ smtpPort: "Pordo" smtpUser: "Uzantnomo" smtpPass: "Pasvorto" wordMute: "Silentigi specifajn vortojn" +instanceMute: "Nodoj silentigitaj" userSaysSomething: "{name} diras ion" makeActive: "Aktivigi" display: "Vidi" @@ -548,7 +549,11 @@ regenerateLoginToken: "Regeneri la aŭtentikigan pecon" fileIdOrUrl: "Dosiera identigilo aŭ URL" behavior: "Konduto" sample: "Ekzemplo" +abuseReports: "Raportoj" +reportAbuse: "Raportoj" +reportAbuseOf: "raporti {name}n" reporter: "Informanto" +reporterOrigin: "Raportanto" send: "Sendi" openInNewTab: "Malfermi en nova langeto" editTheseSettingsMayBreakAccount: "Redakti tiujn agordojn povas damaĝi vian konton." @@ -634,6 +639,7 @@ offline: "Forkonektita" notRecommended: "Evitindaj" instanceBlocking: "Bloki specifajn nodojn" selectAccount: "Elekti konton" +switchAccount: "Ŝanĝi konton" user: "Uzantoj" administration: "Bontenado" accounts: "Kontoj" @@ -644,6 +650,7 @@ shareWithNote: "Kundividi en noto" ads: "Reklamaĵo" expiration: "Limtempo" memo: "Memorigilo" +priority: "Prioritato" high: "Alta" middle: "Meza" low: "Malalta" @@ -677,6 +684,7 @@ unmuteThread: "Malsilentigi la mesaĝaron" ffVisibility: "Videbleco de viaj sekvatoj/sekvantoj" ffVisibilityDescription: "Oni permesas agordi tiuln kiuj povas vidi la homojn kiujn vi sekvas, kaj la homojn kiuj sekvas vin." continueThread: "Pli vidi la mesaĝaron" +deleteAccountConfirm: "La konto estos forviŝita. Ĉu vi daŭrigas fari?" incorrectPassword: "Nevalida pasvorto" voteConfirm: "Ĉu vi voĉdonas {choice}n?" hide: "Kaŝi" @@ -684,14 +692,19 @@ leaveGroup: "Eliĝi el la grupo" leaveGroupConfirm: "Ĉu vi certas ke vi volas eliĝi el la grupo {name}?" welcomeBackWithName: "Bonrevenon, {name}!" clickToFinishEmailVerification: "Volu klaki [{ok}] por fini konfirmon de via retadreso." +overridedDeviceKind: "tipo de aparato" smartphone: "Saĝtelefono" tablet: "Platkomputilo" auto: "Aŭtomate" +size: "Grandeco" searchByGoogle: "Serĉi en Google-Serĉo" -tenMinutes: "10 minutoj" -oneHour: "1 horo" -oneDay: "1 tago" -oneWeek: "1 semajno" +mutePeriod: "Daŭro de silentigo" +indefinitely: "Sen limdato" +tenMinutes: "Je 10 minutoj" +oneHour: "Je 1 horo" +oneDay: "Je 1 tago" +oneWeek: "Je 1 semajno" +failedToFetchAccountInformation: "Malsukcesas akiri informon de konto" _emailUnavailable: used: "La retpoŝto jam estas uzita." format: "Nevalida formato." @@ -834,7 +847,6 @@ _ago: _time: second: "sek" minute: "min" - hour: "hor" day: "Tago" _tutorial: title: "Uzado de Misskey" @@ -893,16 +905,15 @@ _cw: chars: "{count} literoj" files: "{count} dosiero(j)" _poll: - choiceN: "Ebla voĉdono {n}" + choiceN: "Balotilo {n}" noMore: "Oni ne povas aldoni pli" canMultipleVote: "Permesi plurelekton" expiration: "Limtempo" - deadlineTime: "hor" - duration: "Daŭro" + infinite: "Por ĉiam" votesCount: "{n} voĉoj" totalVotes: "Sume {n} voĉoj" vote: "Voĉdoni" - showResult: "Vidi la rezultojn" + showResult: "Vidi rezultojn" voted: "Voĉdonita" closed: "Finita" _visibility: @@ -923,7 +934,7 @@ _postForm: _placeholders: a: "Kiel vi fartas?" b: "Kio okazis ĉirkaŭ vi?" - c: "Kio estas sur via penso?" + c: "Kion vi pensas?" d: "Kion vi volas diri?" e: "Komencu skribi tie" _profile: @@ -1115,6 +1126,7 @@ _notification: youReceivedFollowRequest: "Vi ricevis peton de sekvado" yourFollowRequestAccepted: "Via peto de sekvado estis akceptita." youWereInvitedToGroup: "Invitita al grupo" + pollEnded: "La rezulto de la balot-enketo estas disponebla" _types: all: "Ĉio" follow: "Novaj sekvantoj" @@ -1123,7 +1135,8 @@ _notification: renote: "Plusendoj" quote: "Citi" reaction: "Reagoj" - pollVote: "Voĉdonoj en balotoj" + pollVote: "Voĉdonoj en balot-enketo" + pollEnded: "Enketo finiĝis" receiveFollowRequest: "Ricevi peton de sekvado" followRequestAccepted: "Akceptita peto de sekvado" groupInvited: "Invitita al grupo" diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index 5ccf1b2b6e..1fe74fa9ab 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -1216,7 +1216,7 @@ _poll: votesCount: "{n} votes" totalVotes: "{n} votes au total" vote: "Voter" - showResult: "Voir les résultats" + showResult: "Voir résultats" voted: "Déjà voté" closed: "Terminé" remainingDays: "{d} jours, {h} heures restantes" diff --git a/locales/id-ID.yml b/locales/id-ID.yml index cf89158676..11dff184cd 100644 --- a/locales/id-ID.yml +++ b/locales/id-ID.yml @@ -592,6 +592,7 @@ smtpSecure: "Gunakan SSL/TLS implisit untuk koneksi SMTP" smtpSecureInfo: "Matikan ini ketika menggunakan STARTTLS" testEmail: "Tes pengiriman surel" wordMute: "Bisukan kata" +regexpError: "Kesalahan ekspresi reguler" instanceMute: "Bisuka instansi" userSaysSomething: "{name} mengatakan sesuatu" makeActive: "Aktifkan" @@ -825,8 +826,20 @@ overridedDeviceKind: "Tipe perangkat" smartphone: "Ponsel" tablet: "Tablet" auto: "Otomatis" +themeColor: "Warna Tema" +size: "Ukuran" +numberOfColumn: "Jumlah per kolom" searchByGoogle: "Penelusuran" +instanceDefaultLightTheme: "Bawaan instan tema terang" +instanceDefaultDarkTheme: "Bawaan instan tema gelap" +instanceDefaultThemeDescription: "Masukkan kode tema di format obyek." +mutePeriod: "Batas waktu bisu" indefinitely: "Selamanya" +tenMinutes: "10 Menit" +oneHour: "1 Jam" +oneDay: "1 Hari" +oneWeek: "1 Bulan" +failedToFetchAccountInformation: "Gagal untuk mendapatkan informasi akun" _emailUnavailable: used: "Alamat surel ini telah digunakan" format: "Format tidak valid." @@ -1599,6 +1612,7 @@ _notification: youReceivedFollowRequest: "Kamu menerima permintaan mengikuti" yourFollowRequestAccepted: "Permintaan mengikuti kamu telah diterima" youWereInvitedToGroup: "Telah diundang ke grup" + pollEnded: "Hasil Kuesioner telah keluar" _types: all: "Semua" follow: "Ikuti" @@ -1608,6 +1622,7 @@ _notification: quote: "Kutip" reaction: "Reaksi" pollVote: "Memilih di angket" + pollEnded: "Jajak pendapat berakhir" receiveFollowRequest: "Permintaan mengikuti diterima" followRequestAccepted: "Permintaan mengikuti disetujui" groupInvited: "Diundang ke grup" diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index e2cf11b49d..6326094dd8 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -840,6 +840,8 @@ tenMinutes: "10分" oneHour: "1時間" oneDay: "1日" oneWeek: "1週間" +reflectMayTakeTime: "反映されるまで時間がかかる場合があります。" +failedToFetchAccountInformation: "アカウント情報の取得に失敗しました" _emailUnavailable: used: "既に使用されています" diff --git a/locales/nl-NL.yml b/locales/nl-NL.yml index a00716fe05..f4e4a62182 100644 --- a/locales/nl-NL.yml +++ b/locales/nl-NL.yml @@ -119,6 +119,23 @@ unblock: "Deblokkeren" suspend: "Opschorten" unsuspend: "Heractiveren" blockConfirm: "Weet je zeker dat je dit account wil blokkeren?" +unblockConfirm: "Ben je zeker dat je deze account wil blokkeren?" +suspendConfirm: "Ben je zeker dat je deze account wil suspenderen?" +unsuspendConfirm: "Ben je zeker dat je deze account wil opnieuw aanstellen?" +flagAsBot: "Markeer dit account als een robot." +flagAsBotDescription: "Als dit account van een programma wordt beheerd, zet deze vlag aan. Het aanzetten helpt andere ontwikkelaars om bijvoorbeeld onbedoelde feedback loops te doorbreken of om Misskey meer geschikt te maken." +flagAsCat: "Markeer dit account als een kat." +flagAsCatDescription: "Zet deze vlag aan als je wilt aangeven dat dit account een kat is." +flagShowTimelineReplies: "Toon antwoorden op de tijdlijn." +flagShowTimelineRepliesDescription: "Als je dit vlag aanzet, toont de tijdlijn ook antwoorden op andere en niet alleen jouw eigen notities." +autoAcceptFollowed: "Accepteer verzoeken om jezelf te volgen vanzelf als je de verzoeker al volgt." +addAccount: "Account toevoegen" +loginFailed: "Aanmelding mislukt." +showOnRemote: "Toon op de externe instantie." +general: "Algemeen" +wallpaper: "Achtergrond" +setWallpaper: "Achtergrond instellen" +removeWallpaper: "Achtergrond verwijderen" searchWith: "Zoeken: {q}" youHaveNoLists: "Je hebt geen lijsten" followConfirm: "Weet je zeker dat je {name} wilt volgen?" @@ -205,6 +222,8 @@ resetAreYouSure: "Resetten?" saved: "Opgeslagen" messaging: "Chat" upload: "Uploaden" +keepOriginalUploading: "Origineel beeld behouden." +keepOriginalUploadingDescription: "Bewaar de originele versie bij het uploaden van afbeeldingen. Indien uitgeschakeld, wordt bij het uploaden een alternatieve versie voor webpublicatie genereert." fromDrive: "Van schijf" fromUrl: "Van URL" uploadFromUrl: "Uploaden vanaf een URL" @@ -245,9 +264,36 @@ renameFile: "Wijzig bestandsnaam" folderName: "Mapnaam" createFolder: "Map aanmaken" renameFolder: "Map hernoemen" +deleteFolder: "Map verwijderen" +addFile: "Bestand toevoegen" +emptyDrive: "Jouw Drive is leeg." +emptyFolder: "Deze map is leeg" +unableToDelete: "Kan niet worden verwijderd" +inputNewFileName: "Voer een nieuwe naam in" +copyUrl: "URL kopiëren" +rename: "Hernoemen" +avatar: "Avatar" +banner: "Banner" nsfw: "NSFW" +whenServerDisconnected: "Wanneer de verbinding met de server wordt onderbroken" +disconnectedFromServer: "Verbinding met de server onderbroken." +inMb: "in megabytes" pinnedNotes: "Vastgemaakte notitie" userList: "Lijsten" +aboutMisskey: "Over Misskey" +administrator: "Beheerder" +token: "Token" +securityKeyName: "Sleutelnaam" +registerSecurityKey: "Zekerheids-Sleutel registreren" +lastUsed: "Laatst gebruikt" +unregister: "Uitschrijven" +passwordLessLogin: "Inloggen zonder wachtwoord" +resetPassword: "Wachtwoord terugzetten" +newPasswordIs: "Het nieuwe wachtwoord is „{password}”." +reduceUiAnimation: "Verminder beweging in de UI" +share: "Delen" +notFound: "Niet gevonden" +cacheClear: "Cache verwijderen" smtpHost: "Server" smtpUser: "Gebruikersnaam" smtpPass: "Wachtwoord" diff --git a/locales/pt-PT.yml b/locales/pt-PT.yml index 0f12155e33..104e4ceb7c 100644 --- a/locales/pt-PT.yml +++ b/locales/pt-PT.yml @@ -1,6 +1,7 @@ --- _lang_: "Português" headlineMisskey: "Rede conectada por notas" +introMisskey: "Bem-vindo! Misskey é um serviço de microblogue descentralizado de código aberto.\nCria \"notas\" e partilha o que te ocorre com todos à tua volta. 📡\nCom \"reações\" podes também expressar logo o que sentes às notas de todos. 👍\nExploremos um novo mundo! 🚀" monthAndDay: "{day}/{month}" search: "Pesquisar" notifications: "Notificações" @@ -22,6 +23,7 @@ otherSettings: "Outras configurações" openInWindow: "Abrir numa janela" profile: "Perfil" timeline: "Timeline" +noAccountDescription: "Este usuário não tem uma descrição." login: "Iniciar sessão" loggingIn: "Iniciando sessão…" logout: "Sair" @@ -29,8 +31,12 @@ signup: "Registrar-se" uploading: "Enviando…" save: "Guardar" users: "Usuários" +addUser: "Adicionar usuário" favorite: "Favoritar" favorites: "Favoritar" +unfavorite: "Remover dos favoritos" +favorited: "Adicionado aos favoritos." +alreadyFavorited: "Já adicionado aos favoritos." showMore: "Ver mais" youGotNewFollower: "Você tem um novo seguidor" followRequestAccepted: "Pedido de seguir aceito" diff --git a/locales/ro-RO.yml b/locales/ro-RO.yml index 7f8ed82b8d..8909a72ec7 100644 --- a/locales/ro-RO.yml +++ b/locales/ro-RO.yml @@ -449,6 +449,45 @@ groupInvited: "Ai fost invitat într-un grup" aboutX: "Despre {x}" useOsNativeEmojis: "Folosește emojiuri native OS-ului" disableDrawer: "Nu folosi meniuri în stil sertar" +youHaveNoGroups: "Nu ai niciun grup" +joinOrCreateGroup: "Primește o invitație într-un grup sau creează unul nou." +noHistory: "Nu există istoric" +signinHistory: "Istoric autentificări" +disableAnimatedMfm: "Dezactivează MFM cu animații" +doing: "Se procesează..." +category: "Categorie" +tags: "Etichete" +docSource: "Sursa acestui document" +createAccount: "Creează un cont" +existingAccount: "Cont existent" +regenerate: "Regenerează" +fontSize: "Mărimea fontului" +noFollowRequests: "Nu ai nicio cerere de urmărire în așteptare" +openImageInNewTab: "Deschide imaginile în taburi noi" +dashboard: "Panou de control" +local: "Local" +remote: "Extern" +total: "Total" +weekOverWeekChanges: "Schimbări până săptămâna trecută" +dayOverDayChanges: "Schimbări până ieri" +appearance: "Aspect" +clientSettings: "Setări client" +accountSettings: "Setări cont" +promotion: "Promovat" +promote: "Promovează" +numberOfDays: "Numărul zilelor" +hideThisNote: "Ascunde această notă" +showFeaturedNotesInTimeline: "Arată notele recomandate în cronologii" +objectStorage: "Object Storage" +useObjectStorage: "Folosește Object Storage" +objectStorageBaseUrl: "URL de bază" +objectStorageBucket: "Bucket" +objectStorageBucketDesc: "Te rog specifică numele bucket-ului furnizorului tău." +objectStoragePrefix: "Prefix" +objectStoragePrefixDesc: "Fișierele vor fi stocate sub directoare cu acest prefix." +objectStorageEndpoint: "Endpoint" +objectStorageRegion: "Regiune" +objectStorageUseSSL: "Folosește SSl" sounds: "Sunete" listen: "Ascultă" none: "Nimic" @@ -471,6 +510,18 @@ sort: "Sortează" ascendingOrder: "Crescător" descendingOrder: "Descrescător" scratchpad: "Scratchpad" +scratchpadDescription: "Scratchpad-ul oferă un mediu de experimentare în AiScript. Poți scrie, executa și verifica rezultatele acestuia interacționând cu Misskey în el." +output: "Ieșire" +script: "Script" +disablePagesScript: "Dezactivează AiScript în Pagini" +updateRemoteUser: "Actualizează informațiile utilizatorului extern" +deleteAllFiles: "Șterge toate fișierele" +deleteAllFilesConfirm: "Ești sigur că vrei să ștergi toate fișierele?" +removeAllFollowing: "Dezurmărește toți utilizatorii urmăriți" +removeAllFollowingDescription: "Asta va dez-urmări toate conturile din {host}. Te rog execută asta numai dacă instanța, de ex., nu mai există." +userSuspended: "Acest utilizator a fost suspendat." +userSilenced: "Acest utilizator a fost setat silențios." +yourAccountSuspendedTitle: "Acest cont a fost suspendat" smtpHost: "Gazdă" smtpUser: "Nume de utilizator" smtpPass: "Parolă" diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml index db45365a51..c6f2f59bdf 100644 --- a/locales/sk-SK.yml +++ b/locales/sk-SK.yml @@ -839,6 +839,8 @@ tenMinutes: "10 minút" oneHour: "1 hodina" oneDay: "1 deň" oneWeek: "1 týždeň" +reflectMayTakeTime: "Zmeny môžu chvíľu trvať kým sa prejavia." +failedToFetchAccountInformation: "Nepodarilo sa načítať informácie o účte." _emailUnavailable: used: "Táto emailová adresa sa už používa" format: "Formát emailovej adresy je nesprávny" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index 483faba0d1..f644585835 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -8,12 +8,12 @@ notifications: "通知" username: "用户名" password: "密码" forgotPassword: "忘记密码" -fetchingAsApObject: "联合查询" +fetchingAsApObject: "在联邦宇宙查询中..." ok: "OK" gotIt: "我明白了" cancel: "取消" enterUsername: "输入用户名" -renotedBy: "由 {user} 转推" +renotedBy: "由 {user} 转贴" noNotes: "没有帖文" noNotifications: "无通知" instance: "实例" @@ -69,7 +69,7 @@ exportRequested: "导出请求已提交,这可能需要花一些时间,导 importRequested: "导入请求已提交,这可能需要花一点时间。" lists: "列表" noLists: "列表为空" -note: "帖子" +note: "发帖" notes: "帖子" following: "关注中" followers: "关注者" @@ -85,7 +85,7 @@ serverIsDead: "服务器没有响应。 请稍等片刻,然后重试。" youShouldUpgradeClient: "请重新加载并使用新版本的客户端查看此页面。" enterListName: "输入列表名称" privacy: "隐私" -makeFollowManuallyApprove: "关注者的关注请求需要批准" +makeFollowManuallyApprove: "关注请求需要批准" defaultNoteVisibility: "默认可见性" follow: "关注" followRequest: "关注申请" @@ -143,7 +143,7 @@ flagAsCat: "将这个账户设定为一只猫" flagAsCatDescription: "如果您想表明此帐户是一只猫,请打开此标志。" flagShowTimelineReplies: "在时间线上显示帖子的回复" flagShowTimelineRepliesDescription: "启用时,时间线除了显示用户的帖子外,还会显示其他用户对帖子的回复。" -autoAcceptFollowed: "自动允许关注" +autoAcceptFollowed: "自动允许关注者的关注" addAccount: "添加账户" loginFailed: "登录失败" showOnRemote: "转到所在实例显示" @@ -162,7 +162,7 @@ recipient: "收件人" annotation: "注解" federation: "联合" instances: "实例" -registeredAt: "初次观察" +registeredAt: "初次观测" latestRequestSentAt: "上次发送的请求" latestRequestReceivedAt: "上次收到的请求" latestStatus: "最后状态" @@ -254,7 +254,7 @@ agreeTo: "{0}人同意" tos: "服务条款" start: "开始" home: "首页" -remoteUserCaution: "由于是远程用户,信息不完整。" +remoteUserCaution: "由于此用户来自其它实例,显示的信息可能不完整。" activity: "活动" images: "图片" birthday: "生日" @@ -372,7 +372,7 @@ recentlyUpdatedUsers: "最近投稿的用户" recentlyRegisteredUsers: "最近登录的用户" recentlyDiscoveredUsers: "最近发现的用户" exploreUsersCount: "有{count}个用户" -exploreFediverse: "探索Fediverse" +exploreFediverse: "探索联邦宇宙" popularTags: "热门标签" userList: "列表" about: "关于" @@ -561,7 +561,7 @@ manage: "管理" plugins: "插件" deck: "Deck" undeck: "取消Deck" -useBlurEffectForModal: "模态框使用模糊效果" +useBlurEffectForModal: "对话框使用模糊效果" useFullReactionPicker: "使用全功能的回应工具栏" width: "宽度" height: "高度" @@ -840,6 +840,8 @@ tenMinutes: "10分钟" oneHour: "1小时" oneDay: "1天" oneWeek: "1周" +reflectMayTakeTime: "可能需要一些时间才能体现出效果。" +failedToFetchAccountInformation: "获取账户信息失败" _emailUnavailable: used: "已经被使用过" format: "无效的格式" @@ -904,7 +906,7 @@ _nsfw: _mfm: cheatSheet: "MFM代码速查表" intro: "MFM是一种在Misskey中的各个位置使用的专用标记语言。在这里您可以看到MFM中可用的语法列表。" - dummy: "通过Misskey扩展Fediverse的世界" + dummy: "通过Misskey扩展联邦宇宙的世界" mention: "提及" mentionDescription: "可以使用 @+用户名 来指示特定用户" hashtag: "话题标签" @@ -967,7 +969,7 @@ _mfm: rotateDescription: "旋转指定的角度。" _instanceTicker: none: "不显示" - remote: "仅显示远程用户的" + remote: "仅远程用户" always: "始终显示" _serverDisconnectedBehavior: reload: "自动重载" @@ -1051,7 +1053,7 @@ _theme: mention: "提及" mentionMe: "提及" renote: "转发" - modalBg: "模态框背景" + modalBg: "对话框背景" divider: "分割线" scrollbarHandle: "滚动条" scrollbarHandleHover: "滚动条(悬停)" @@ -1238,7 +1240,7 @@ _visibility: publicDescription: "您的帖子将出现在全局时间线上" home: "首页" homeDescription: "仅发送至首页的时间线" - followers: "关注者" + followers: "仅关注者" followersDescription: "仅发送至关注者" specified: "指定用户" specifiedDescription: "仅发送至指定用户" diff --git a/package.json b/package.json index 65aa436cf3..77c822bf2a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "12.108.1", + "version": "12.109.0", "codename": "indigo", "repository": { "type": "git", @@ -13,8 +13,7 @@ "start": "cd packages/backend && node --experimental-json-modules ./built/index.js", "start:test": "cd packages/backend && cross-env NODE_ENV=test node --experimental-json-modules ./built/index.js", "init": "npm run migrate", - "ormconfig": "node ./packages/backend/ormconfig.js", - "migrate": "cd packages/backend && npx typeorm migration:run", + "migrate": "cd packages/backend && npx typeorm migration:run -d ormconfig.js", "migrateandstart": "npm run migrate && npm run start", "gulp": "gulp build", "watch": "npm run dev", @@ -42,10 +41,10 @@ "js-yaml": "4.1.0" }, "devDependencies": { - "@typescript-eslint/parser": "5.14.0", + "@typescript-eslint/parser": "5.17.0", "cross-env": "7.0.3", - "cypress": "9.5.0", + "cypress": "9.5.3", "start-server-and-test": "1.14.0", - "typescript": "4.6.2" + "typescript": "4.6.3" } } diff --git a/packages/backend/migration/1648548247382-webhook.js b/packages/backend/migration/1648548247382-webhook.js new file mode 100644 index 0000000000..aea369a5cc --- /dev/null +++ b/packages/backend/migration/1648548247382-webhook.js @@ -0,0 +1,19 @@ +export class webhook1648548247382 { + name = 'webhook1648548247382' + + async up(queryRunner) { + await queryRunner.query(`CREATE TABLE "webhook" ("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, "on" character varying(128) array NOT NULL DEFAULT '{}', "url" character varying(1024) NOT NULL, "secret" character varying(1024) NOT NULL, "active" boolean NOT NULL DEFAULT true, CONSTRAINT "PK_e6765510c2d078db49632b59020" PRIMARY KEY ("id")); COMMENT ON COLUMN "webhook"."createdAt" IS 'The created date of the Antenna.'; COMMENT ON COLUMN "webhook"."userId" IS 'The owner ID.'; COMMENT ON COLUMN "webhook"."name" IS 'The name of the Antenna.'`); + await queryRunner.query(`CREATE INDEX "IDX_f272c8c8805969e6a6449c77b3" ON "webhook" ("userId") `); + await queryRunner.query(`CREATE INDEX "IDX_8063a0586ed1dfbe86e982d961" ON "webhook" ("on") `); + await queryRunner.query(`CREATE INDEX "IDX_5a056076f76b2efe08216ba655" ON "webhook" ("active") `); + await queryRunner.query(`ALTER TABLE "webhook" ADD CONSTRAINT "FK_f272c8c8805969e6a6449c77b3c" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "webhook" DROP CONSTRAINT "FK_f272c8c8805969e6a6449c77b3c"`); + await queryRunner.query(`DROP INDEX "public"."IDX_5a056076f76b2efe08216ba655"`); + await queryRunner.query(`DROP INDEX "public"."IDX_8063a0586ed1dfbe86e982d961"`); + await queryRunner.query(`DROP INDEX "public"."IDX_f272c8c8805969e6a6449c77b3"`); + await queryRunner.query(`DROP TABLE "webhook"`); + } +} diff --git a/packages/backend/migration/1648816172177-webhook-2.js b/packages/backend/migration/1648816172177-webhook-2.js new file mode 100644 index 0000000000..2feb68d611 --- /dev/null +++ b/packages/backend/migration/1648816172177-webhook-2.js @@ -0,0 +1,14 @@ + +export class webhook21648816172177 { + name = 'webhook21648816172177' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "webhook" ADD "latestSentAt" TIMESTAMP WITH TIME ZONE`); + await queryRunner.query(`ALTER TABLE "webhook" ADD "latestStatus" integer`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "webhook" DROP COLUMN "latestStatus"`); + await queryRunner.query(`ALTER TABLE "webhook" DROP COLUMN "latestSentAt"`); + } +} diff --git a/packages/backend/ormconfig.js b/packages/backend/ormconfig.js index b8150f7014..a4e903abad 100644 --- a/packages/backend/ormconfig.js +++ b/packages/backend/ormconfig.js @@ -1,7 +1,8 @@ +import { DataSource } from 'typeorm'; import config from './built/config/index.js'; import { entities } from './built/db/postgre.js'; -export default { +export default new DataSource({ type: 'postgres', host: config.db.host, port: config.db.port, @@ -11,7 +12,4 @@ export default { extra: config.db.extra, entities: entities, migrations: ['migration/*.js'], - cli: { - migrationsDir: 'migration' - } -}; +}); diff --git a/packages/backend/package.json b/packages/backend/package.json index 87712faf7c..d6490f1cb6 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -3,7 +3,6 @@ "private": true, "type": "module", "scripts": { - "init": "npm run migrate", "build": "tsc -p tsconfig.json || echo done. && tsc-alias -p tsconfig.json", "watch": "node watch.mjs", "lint": "eslint --quiet src/**/*.ts", @@ -15,7 +14,7 @@ "lodash": "^4.17.21" }, "dependencies": { - "@discordapp/twemoji": "13.1.0", + "@discordapp/twemoji": "13.1.1", "@elastic/elasticsearch": "7.11.0", "@koa/cors": "3.1.0", "@koa/multer": "3.0.0", @@ -31,7 +30,7 @@ "@types/jsdom": "16.2.14", "@types/jsonld": "1.5.6", "@types/koa": "2.13.4", - "@types/koa-bodyparser": "4.3.6", + "@types/koa-bodyparser": "4.3.7", "@types/koa-cors": "0.0.2", "@types/koa-favicon": "2.0.21", "@types/koa-logger": "3.1.2", @@ -42,7 +41,7 @@ "@types/koa__multer": "2.0.4", "@types/koa__router": "8.0.11", "@types/mocha": "9.1.0", - "@types/node": "17.0.21", + "@types/node": "17.0.23", "@types/node-fetch": "3.0.3", "@types/nodemailer": "6.4.4", "@types/oauth": "0.9.1", @@ -56,8 +55,8 @@ "@types/redis": "4.0.11", "@types/rename": "1.0.4", "@types/sanitize-html": "2.6.2", - "@types/sharp": "0.29.5", - "@types/sinonjs__fake-timers": "8.1.1", + "@types/sharp": "0.30.0", + "@types/sinonjs__fake-timers": "8.1.2", "@types/speakeasy": "2.0.7", "@types/throttle-debounce": "2.1.0", "@types/tinycolor2": "1.4.3", @@ -65,19 +64,20 @@ "@types/uuid": "8.3.4", "@types/web-push": "3.3.2", "@types/websocket": "1.0.5", - "@types/ws": "8.5.2", - "@typescript-eslint/eslint-plugin": "5.14.0", - "@typescript-eslint/parser": "5.14.0", + "@types/ws": "8.5.3", + "@typescript-eslint/eslint-plugin": "5.17.0", + "@typescript-eslint/parser": "5.17.0", + "@bull-board/koa": "3.10.2", "abort-controller": "3.0.0", - "ajv": "8.10.0", + "ajv": "8.11.0", "archiver": "5.3.0", "autobind-decorator": "2.4.0", "autwh": "0.1.0", - "aws-sdk": "2.1079.0", + "aws-sdk": "2.1105.0", "bcryptjs": "2.4.3", "blurhash": "1.1.5", "broadcast-channel": "4.10.0", - "bull": "4.7.0", + "bull": "4.8.1", "cacheable-lookup": "6.0.4", "cafy": "15.2.1", "cbor": "8.1.0", @@ -89,19 +89,19 @@ "date-fns": "2.28.0", "deep-email-validator": "0.1.21", "escape-regexp": "0.0.1", - "eslint": "8.10.0", + "eslint": "8.12.0", "eslint-plugin-import": "2.25.4", "feed": "4.2.2", "file-type": "17.1.1", "fluent-ffmpeg": "2.1.2", - "got": "12.0.1", + "got": "12.0.3", "hpagent": "0.1.2", "http-signature": "1.3.6", "ip-cidr": "3.0.4", "is-svg": "4.3.2", "js-yaml": "4.1.0", "jsdom": "19.0.0", - "json5": "2.2.0", + "json5": "2.2.1", "json5-loader": "4.0.1", "jsonld": "5.2.0", "jsrsasign": "8.0.20", @@ -115,14 +115,14 @@ "koa-slow": "2.1.0", "koa-views": "7.0.2", "mfm-js": "0.21.0", - "mime-types": "2.1.34", + "mime-types": "2.1.35", "misskey-js": "0.0.14", - "mocha": "9.2.1", + "mocha": "9.2.2", "ms": "3.0.0-canary.1", "multer": "1.4.4", "nested-property": "4.0.0", - "node-fetch": "3.2.2", - "nodemailer": "6.7.2", + "node-fetch": "3.2.3", + "nodemailer": "6.7.3", "os-utils": "0.0.14", "parse5": "6.0.1", "pg": "8.7.3", @@ -145,24 +145,25 @@ "rndstr": "1.0.0", "s-age": "1.1.2", "sanitize-html": "2.7.0", - "sharp": "0.30.2", + "semver": "7.3.5", + "sharp": "0.30.3", "speakeasy": "2.0.0", "strict-event-emitter-types": "2.0.0", "stringz": "2.1.0", "style-loader": "3.3.1", "summaly": "2.5.0", "syslog-pro": "1.0.0", - "systeminformation": "5.11.6", + "systeminformation": "5.11.9", "throttle-debounce": "3.0.1", "tinycolor2": "1.4.2", "tmp": "0.2.1", - "ts-loader": "9.2.7", + "ts-loader": "9.2.8", "ts-node": "10.7.0", "tsc-alias": "1.4.1", - "tsconfig-paths": "3.13.0", - "twemoji-parser": "13.1.0", - "typeorm": "0.2.45", - "typescript": "4.6.2", + "tsconfig-paths": "3.14.1", + "twemoji-parser": "14.0.0", + "typeorm": "0.3.4", + "typescript": "4.6.3", "ulid": "2.3.0", "unzipper": "0.10.11", "uuid": "8.3.2", @@ -172,7 +173,7 @@ "xev": "2.0.1" }, "devDependencies": { - "@redocly/openapi-core": "1.0.0-beta.83", + "@redocly/openapi-core": "1.0.0-beta.91", "@types/fluent-ffmpeg": "2.1.20", "cross-env": "7.0.3", "execa": "6.1.0" diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts index 1c909dff13..09d20f9361 100644 --- a/packages/backend/src/boot/master.ts +++ b/packages/backend/src/boot/master.ts @@ -6,7 +6,7 @@ import cluster from 'node:cluster'; import chalk from 'chalk'; import chalkTemplate from 'chalk-template'; import * as portscanner from 'portscanner'; -import { getConnection } from 'typeorm'; +import semver from 'semver'; import Logger from '@/services/logger.js'; import loadConfig from '@/config/load.js'; @@ -14,7 +14,7 @@ import { Config } from '@/config/types.js'; import { lessThan } from '@/prelude/array.js'; import { envOption } from '../env.js'; import { showMachineInfo } from '@/misc/show-machine-info.js'; -import { initDb } from '../db/postgre.js'; +import { db, initDb } from '../db/postgre.js'; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); @@ -88,10 +88,6 @@ export async function masterMain() { } } -const runningNodejsVersion = process.version.slice(1).split('.').map(x => parseInt(x, 10)); -const requiredNodejsVersion = [11, 7, 0]; -const satisfyNodejsVersion = !lessThan(runningNodejsVersion, requiredNodejsVersion); - function showEnvironment(): void { const env = process.env.NODE_ENV; const logger = bootLogger.createSubLogger('env'); @@ -108,10 +104,11 @@ function showEnvironment(): void { function showNodejsVersion(): void { const nodejsLogger = bootLogger.createSubLogger('nodejs'); - nodejsLogger.info(`Version ${runningNodejsVersion.join('.')}`); + nodejsLogger.info(`Version ${process.version} detected.`); - if (!satisfyNodejsVersion) { - nodejsLogger.error(`Node.js version is less than ${requiredNodejsVersion.join('.')}. Please upgrade it.`, null, true); + const minVersion = fs.readFileSync(`${_dirname}/../../../../.node-version`, 'utf-8').trim(); + if (semver.lt(process.version, minVersion)) { + nodejsLogger.error(`At least Node.js ${minVersion} required!`); process.exit(1); } } @@ -146,7 +143,7 @@ async function connectDb(): Promise { try { dbLogger.info('Connecting...'); await initDb(); - const v = await getConnection().query('SHOW server_version').then(x => x[0].server_version); + const v = await db.query('SHOW server_version').then(x => x[0].server_version); dbLogger.succ(`Connected: v${v}`); } catch (e) { dbLogger.error('Cannot connect', null, true); diff --git a/packages/backend/src/db/postgre.ts b/packages/backend/src/db/postgre.ts index 066a3c6739..f7638a53d0 100644 --- a/packages/backend/src/db/postgre.ts +++ b/packages/backend/src/db/postgre.ts @@ -2,9 +2,10 @@ import pg from 'pg'; pg.types.setTypeParser(20, Number); -import { createConnection, Logger, getConnection } from 'typeorm'; +import { Logger, DataSource } from 'typeorm'; import * as highlight from 'cli-highlight'; import config from '@/config/index.js'; +import { envOption } from '../env.js'; import { dbLogger } from './logger.js'; @@ -61,7 +62,6 @@ import { Antenna } from '@/models/entities/antenna.js'; import { AntennaNote } from '@/models/entities/antenna-note.js'; import { PromoNote } from '@/models/entities/promo-note.js'; import { PromoRead } from '@/models/entities/promo-read.js'; -import { envOption } from '../env.js'; import { Relay } from '@/models/entities/relay.js'; import { MutedNote } from '@/models/entities/muted-note.js'; import { Channel } from '@/models/entities/channel.js'; @@ -73,8 +73,9 @@ import { PasswordResetRequest } from '@/models/entities/password-reset-request.j import { UserPending } from '@/models/entities/user-pending.js'; import { entities as charts } from '@/services/chart/entities.js'; +import { Webhook } from '@/models/entities/webhook.js'; -const sqlLogger = dbLogger.createSubLogger('sql', 'white', false); +const sqlLogger = dbLogger.createSubLogger('sql', 'gray', false); class MyCustomLogger implements Logger { private highlight(sql: string) { @@ -84,9 +85,7 @@ class MyCustomLogger implements Logger { } public logQuery(query: string, parameters?: any[]) { - if (envOption.verbose) { - sqlLogger.info(this.highlight(query)); - } + sqlLogger.info(this.highlight(query).substring(0, 100)); } public logQueryError(error: string, query: string, parameters?: any[]) { @@ -173,58 +172,55 @@ export const entities = [ Ad, PasswordResetRequest, UserPending, + Webhook, ...charts, ]; -export function initDb(justBorrow = false, sync = false, forceRecreate = false) { - if (!forceRecreate) { - try { - const conn = getConnection(); - return Promise.resolve(conn); - } catch (e) {} - } +const log = process.env.NODE_ENV !== 'production'; - const log = process.env.NODE_ENV !== 'production'; - - return createConnection({ - type: 'postgres', - host: config.db.host, - port: config.db.port, - username: config.db.user, - password: config.db.pass, - database: config.db.db, - extra: { - statement_timeout: 1000 * 10, - ...config.db.extra, +export const db = new DataSource({ + type: 'postgres', + host: config.db.host, + port: config.db.port, + username: config.db.user, + password: config.db.pass, + database: config.db.db, + extra: { + statement_timeout: 1000 * 10, + ...config.db.extra, + }, + synchronize: process.env.NODE_ENV === 'test', + dropSchema: process.env.NODE_ENV === 'test', + cache: !config.db.disableCache ? { + type: 'redis', + options: { + host: config.redis.host, + port: config.redis.port, + password: config.redis.pass, + prefix: `${config.redis.prefix}:query:`, + db: config.redis.db || 0, }, - synchronize: process.env.NODE_ENV === 'test' || sync, - dropSchema: process.env.NODE_ENV === 'test' && !justBorrow, - cache: !config.db.disableCache ? { - type: 'redis', - options: { - host: config.redis.host, - port: config.redis.port, - password: config.redis.pass, - prefix: `${config.redis.prefix}:query:`, - db: config.redis.db || 0, - }, - } : false, - logging: log, - logger: log ? new MyCustomLogger() : undefined, - entities: entities, - }); + } : false, + logging: log, + logger: log ? new MyCustomLogger() : undefined, + maxQueryExecutionTime: 300, + entities: entities, + migrations: ['../../migration/*.js'], +}); + +export async function initDb() { + await db.connect(); } export async function resetDb() { const reset = async () => { - const conn = await getConnection(); - const tables = await conn.query(`SELECT relname AS "table" + const tables = await db.query(`SELECT relname AS "table" FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) WHERE nspname NOT IN ('pg_catalog', 'information_schema') AND C.relkind = 'r' AND nspname !~ '^pg_toast';`); for (const table of tables) { - await conn.query(`DELETE FROM "${table.table}" CASCADE`); + await db.query(`DELETE FROM "${table.table}" CASCADE`); } }; diff --git a/packages/backend/src/misc/cache.ts b/packages/backend/src/misc/cache.ts index 76835b44b1..01bbe98a85 100644 --- a/packages/backend/src/misc/cache.ts +++ b/packages/backend/src/misc/cache.ts @@ -1,5 +1,5 @@ export class Cache { - private cache: Map; + public cache: Map; private lifetime: number; constructor(lifetime: Cache['lifetime']) { @@ -28,16 +28,52 @@ export class Cache { this.cache.delete(key); } - public async fetch(key: string | null, fetcher: () => Promise): Promise { + /** + * キャッシュがあればそれを返し、無ければfetcherを呼び出して結果をキャッシュ&返します + * optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします + */ + public async fetch(key: string | null, fetcher: () => Promise, validator?: (cachedValue: T) => boolean): Promise { const cachedValue = this.get(key); if (cachedValue !== undefined) { - // Cache HIT - return cachedValue; + if (validator) { + if (validator(cachedValue)) { + // Cache HIT + return cachedValue; + } + } else { + // Cache HIT + return cachedValue; + } } // Cache MISS const value = await fetcher(); - this.set(key, value); + return value; + } + + /** + * キャッシュがあればそれを返し、無ければfetcherを呼び出して結果をキャッシュ&返します + * optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします + */ + public async fetchMaybe(key: string | null, fetcher: () => Promise, validator?: (cachedValue: T) => boolean): Promise { + const cachedValue = this.get(key); + if (cachedValue !== undefined) { + if (validator) { + if (validator(cachedValue)) { + // Cache HIT + return cachedValue; + } + } else { + // Cache HIT + return cachedValue; + } + } + + // Cache MISS + const value = await fetcher(); + if (value !== undefined) { + this.set(key, value); + } return value; } } diff --git a/packages/backend/src/misc/check-hit-antenna.ts b/packages/backend/src/misc/check-hit-antenna.ts index ceb74d6904..d9cedee7df 100644 --- a/packages/backend/src/misc/check-hit-antenna.ts +++ b/packages/backend/src/misc/check-hit-antenna.ts @@ -1,17 +1,26 @@ import { Antenna } from '@/models/entities/antenna.js'; import { Note } from '@/models/entities/note.js'; import { User } from '@/models/entities/user.js'; -import { UserListJoinings, UserGroupJoinings } from '@/models/index.js'; +import { UserListJoinings, UserGroupJoinings, Blockings } from '@/models/index.js'; import { getFullApAccount } from './convert-host.js'; import * as Acct from '@/misc/acct.js'; import { Packed } from './schema.js'; +import { Cache } from './cache.js'; + +const blockingCache = new Cache(1000 * 60 * 5); + +// NOTE: フォローしているユーザーのノート、リストのユーザーのノート、グループのユーザーのノート指定はパフォーマンス上の理由で無効になっている /** * noteUserFollowers / antennaUserFollowing はどちらか一方が指定されていればよい */ -export async function checkHitAntenna(antenna: Antenna, note: (Note | Packed<'Note'>), noteUser: { username: string; host: string | null; }, noteUserFollowers?: User['id'][], antennaUserFollowing?: User['id'][]): Promise { +export async function checkHitAntenna(antenna: Antenna, note: (Note | Packed<'Note'>), noteUser: { id: User['id']; username: string; host: string | null; }, noteUserFollowers?: User['id'][], antennaUserFollowing?: User['id'][]): Promise { if (note.visibility === 'specified') return false; + // アンテナ作成者がノート作成者にブロックされていたらスキップ + const blockings = await blockingCache.fetch(noteUser.id, () => Blockings.findBy({ blockerId: noteUser.id }).then(res => res.map(x => x.blockeeId))); + if (blockings.some(blocking => blocking === antenna.userId)) return false; + if (note.visibility === 'followers') { if (noteUserFollowers && !noteUserFollowers.includes(antenna.userId)) return false; if (antennaUserFollowing && !antennaUserFollowing.includes(note.userId)) return false; @@ -23,15 +32,15 @@ export async function checkHitAntenna(antenna: Antenna, note: (Note | Packed<'No if (noteUserFollowers && !noteUserFollowers.includes(antenna.userId)) return false; if (antennaUserFollowing && !antennaUserFollowing.includes(note.userId)) return false; } else if (antenna.src === 'list') { - const listUsers = (await UserListJoinings.find({ + const listUsers = (await UserListJoinings.findBy({ userListId: antenna.userListId!, })).map(x => x.userId); if (!listUsers.includes(note.userId)) return false; } else if (antenna.src === 'group') { - const joining = await UserGroupJoinings.findOneOrFail(antenna.userGroupJoiningId!); + const joining = await UserGroupJoinings.findOneByOrFail({ id: antenna.userGroupJoiningId! }); - const groupUsers = (await UserGroupJoinings.find({ + const groupUsers = (await UserGroupJoinings.findBy({ userGroupId: joining.userGroupId, })).map(x => x.userId); diff --git a/packages/backend/src/misc/fetch-meta.ts b/packages/backend/src/misc/fetch-meta.ts index 9f85d3d1db..5417c10962 100644 --- a/packages/backend/src/misc/fetch-meta.ts +++ b/packages/backend/src/misc/fetch-meta.ts @@ -1,19 +1,21 @@ +import { db } from '@/db/postgre.js'; import { Meta } from '@/models/entities/meta.js'; -import { getConnection } from 'typeorm'; let cache: Meta; export async function fetchMeta(noCache = false): Promise { if (!noCache && cache) return cache; - return await getConnection().transaction(async transactionalEntityManager => { + return await db.transaction(async transactionalEntityManager => { // 過去のバグでレコードが複数出来てしまっている可能性があるので新しいIDを優先する - const meta = await transactionalEntityManager.findOne(Meta, { + const metas = await transactionalEntityManager.find(Meta, { order: { id: 'DESC', }, }); + const meta = metas[0]; + if (meta) { cache = meta; return meta; diff --git a/packages/backend/src/misc/fetch-proxy-account.ts b/packages/backend/src/misc/fetch-proxy-account.ts index ed8a4c794d..b61bba264b 100644 --- a/packages/backend/src/misc/fetch-proxy-account.ts +++ b/packages/backend/src/misc/fetch-proxy-account.ts @@ -5,5 +5,5 @@ import { Users } from '@/models/index.js'; export async function fetchProxyAccount(): Promise { const meta = await fetchMeta(); if (meta.proxyAccountId == null) return null; - return await Users.findOneOrFail(meta.proxyAccountId) as ILocalUser; + return await Users.findOneByOrFail({ id: meta.proxyAccountId }) as ILocalUser; } diff --git a/packages/backend/src/misc/keypair-store.ts b/packages/backend/src/misc/keypair-store.ts index 3d505c0abb..1183b9a781 100644 --- a/packages/backend/src/misc/keypair-store.ts +++ b/packages/backend/src/misc/keypair-store.ts @@ -6,5 +6,5 @@ import { Cache } from './cache.js'; const cache = new Cache(Infinity); export async function getUserKeypair(userId: User['id']): Promise { - return await cache.fetch(userId, () => UserKeypairs.findOneOrFail(userId)); + return await cache.fetch(userId, () => UserKeypairs.findOneByOrFail({ userId: userId })); } diff --git a/packages/backend/src/misc/populate-emojis.ts b/packages/backend/src/misc/populate-emojis.ts index 4953c6890b..86f1356c31 100644 --- a/packages/backend/src/misc/populate-emojis.ts +++ b/packages/backend/src/misc/populate-emojis.ts @@ -1,4 +1,4 @@ -import { In } from 'typeorm'; +import { In, IsNull } from 'typeorm'; import { Emojis } from '@/models/index.js'; import { Emoji } from '@/models/entities/emoji.js'; import { Note } from '@/models/entities/note.js'; @@ -52,9 +52,9 @@ export async function populateEmoji(emojiName: string, noteUserHost: string | nu const { name, host } = parseEmojiStr(emojiName, noteUserHost); if (name == null) return null; - const queryOrNull = async () => (await Emojis.findOne({ + const queryOrNull = async () => (await Emojis.findOneBy({ name, - host, + host: host ?? IsNull(), })) || null; const emoji = await cache.fetch(`${name} ${host}`, queryOrNull); @@ -112,7 +112,7 @@ export async function prefetchEmojis(emojis: { name: string; host: string | null for (const host of hosts) { emojisQuery.push({ name: In(notCachedEmojis.filter(e => e.host === host).map(e => e.name)), - host: host, + host: host ?? IsNull(), }); } const _emojis = emojisQuery.length > 0 ? await Emojis.find({ diff --git a/packages/backend/src/misc/reaction-lib.ts b/packages/backend/src/misc/reaction-lib.ts index 086944ccfb..fefc2781f3 100644 --- a/packages/backend/src/misc/reaction-lib.ts +++ b/packages/backend/src/misc/reaction-lib.ts @@ -3,6 +3,7 @@ import { emojiRegex } from './emoji-regex.js'; import { fetchMeta } from './fetch-meta.js'; import { Emojis } from '@/models/index.js'; import { toPunyNullable } from './convert-host.js'; +import { IsNull } from 'typeorm'; const legacies: Record = { 'like': '👍', @@ -74,8 +75,8 @@ export async function toDbReaction(reaction?: string | null, reacterHost?: strin const custom = reaction.match(/^:([\w+-]+)(?:@\.)?:$/); if (custom) { const name = custom[1]; - const emoji = await Emojis.findOne({ - host: reacterHost || null, + const emoji = await Emojis.findOneBy({ + host: reacterHost ?? IsNull(), name, }); diff --git a/packages/backend/src/misc/webhook-cache.ts b/packages/backend/src/misc/webhook-cache.ts new file mode 100644 index 0000000000..4bd2333661 --- /dev/null +++ b/packages/backend/src/misc/webhook-cache.ts @@ -0,0 +1,49 @@ +import { Webhooks } from '@/models/index.js'; +import { Webhook } from '@/models/entities/webhook.js'; +import { subsdcriber } from '../db/redis.js'; + +let webhooksFetched = false; +let webhooks: Webhook[] = []; + +export async function getActiveWebhooks() { + if (!webhooksFetched) { + webhooks = await Webhooks.findBy({ + active: true, + }); + webhooksFetched = true; + } + + return webhooks; +} + +subsdcriber.on('message', async (_, data) => { + const obj = JSON.parse(data); + + if (obj.channel === 'internal') { + const { type, body } = obj.message; + switch (type) { + case 'webhookCreated': + if (body.active) { + webhooks.push(body); + } + break; + case 'webhookUpdated': + if (body.active) { + const i = webhooks.findIndex(a => a.id === body.id); + if (i > -1) { + webhooks[i] = body; + } else { + webhooks.push(body); + } + } else { + webhooks = webhooks.filter(a => a.id !== body.id); + } + break; + case 'webhookDeleted': + webhooks = webhooks.filter(a => a.id !== body.id); + break; + default: + break; + } + } +}); diff --git a/packages/backend/src/models/entities/user.ts b/packages/backend/src/models/entities/user.ts index 9d5db10eb3..c76824c977 100644 --- a/packages/backend/src/models/entities/user.ts +++ b/packages/backend/src/models/entities/user.ts @@ -234,3 +234,9 @@ export interface ILocalUser extends User { export interface IRemoteUser extends User { host: string; } + +export type CacheableLocalUser = ILocalUser; + +export type CacheableRemoteUser = IRemoteUser; + +export type CacheableUser = CacheableLocalUser | CacheableRemoteUser; diff --git a/packages/backend/src/models/entities/webhook.ts b/packages/backend/src/models/entities/webhook.ts new file mode 100644 index 0000000000..56b411f879 --- /dev/null +++ b/packages/backend/src/models/entities/webhook.ts @@ -0,0 +1,73 @@ +import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; +import { User } from './user.js'; +import { id } from '../id.js'; + +export const webhookEventTypes = ['mention', 'unfollow', 'follow', 'followed', 'note', 'reply', 'renote', 'reaction'] as const; + +@Entity() +export class Webhook { + @PrimaryColumn(id()) + public id: string; + + @Column('timestamp with time zone', { + comment: 'The created date of the Antenna.', + }) + public createdAt: Date; + + @Index() + @Column({ + ...id(), + comment: 'The owner ID.', + }) + public userId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE', + }) + @JoinColumn() + public user: User | null; + + @Column('varchar', { + length: 128, + comment: 'The name of the Antenna.', + }) + public name: string; + + @Index() + @Column('varchar', { + length: 128, array: true, default: '{}', + }) + public on: (typeof webhookEventTypes)[number][]; + + @Column('varchar', { + length: 1024, + }) + public url: string; + + @Column('varchar', { + length: 1024, + }) + public secret: string; + + @Index() + @Column('boolean', { + default: true, + }) + public active: boolean; + + /** + * 直近のリクエスト送信日時 + */ + @Column('timestamp with time zone', { + nullable: true, + }) + public latestSentAt: Date | null; + + /** + * 直近のリクエスト送信時のHTTPステータスコード + */ + @Column('integer', { + nullable: true, + }) + public latestStatus: number | null; +} diff --git a/packages/backend/src/models/index.ts b/packages/backend/src/models/index.ts index e7b6854886..814b37d448 100644 --- a/packages/backend/src/models/index.ts +++ b/packages/backend/src/models/index.ts @@ -1,4 +1,6 @@ -import { getRepository, getCustomRepository } from 'typeorm'; +import { } from 'typeorm'; +import { db } from '@/db/postgre.js'; + import { Announcement } from './entities/announcement.js'; import { AnnouncementRead } from './entities/announcement-read.js'; import { Instance } from './entities/instance.js'; @@ -62,66 +64,68 @@ import { Ad } from './entities/ad.js'; import { PasswordResetRequest } from './entities/password-reset-request.js'; import { UserPending } from './entities/user-pending.js'; import { InstanceRepository } from './repositories/instance.js'; +import { Webhook } from './entities/webhook.js'; -export const Announcements = getRepository(Announcement); -export const AnnouncementReads = getRepository(AnnouncementRead); -export const Apps = getCustomRepository(AppRepository); -export const Notes = getCustomRepository(NoteRepository); -export const NoteFavorites = getCustomRepository(NoteFavoriteRepository); -export const NoteWatchings = getRepository(NoteWatching); -export const NoteThreadMutings = getRepository(NoteThreadMuting); -export const NoteReactions = getCustomRepository(NoteReactionRepository); -export const NoteUnreads = getRepository(NoteUnread); -export const Polls = getRepository(Poll); -export const PollVotes = getRepository(PollVote); -export const Users = getCustomRepository(UserRepository); -export const UserProfiles = getRepository(UserProfile); -export const UserKeypairs = getRepository(UserKeypair); -export const UserPendings = getRepository(UserPending); -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); -export const UserGroups = getCustomRepository(UserGroupRepository); -export const UserGroupJoinings = getRepository(UserGroupJoining); -export const UserGroupInvitations = getCustomRepository(UserGroupInvitationRepository); -export const UserNotePinings = getRepository(UserNotePining); -export const UsedUsernames = getRepository(UsedUsername); -export const Followings = getCustomRepository(FollowingRepository); -export const FollowRequests = getCustomRepository(FollowRequestRepository); -export const Instances = getCustomRepository(InstanceRepository); -export const Emojis = getCustomRepository(EmojiRepository); -export const DriveFiles = getCustomRepository(DriveFileRepository); -export const DriveFolders = getCustomRepository(DriveFolderRepository); -export const Notifications = getCustomRepository(NotificationRepository); -export const Metas = getRepository(Meta); -export const Mutings = getCustomRepository(MutingRepository); -export const Blockings = getCustomRepository(BlockingRepository); -export const SwSubscriptions = getRepository(SwSubscription); -export const Hashtags = getCustomRepository(HashtagRepository); -export const AbuseUserReports = getCustomRepository(AbuseUserReportRepository); -export const RegistrationTickets = getRepository(RegistrationTicket); -export const AuthSessions = getCustomRepository(AuthSessionRepository); -export const AccessTokens = getRepository(AccessToken); -export const Signins = getCustomRepository(SigninRepository); -export const MessagingMessages = getCustomRepository(MessagingMessageRepository); -export const Pages = getCustomRepository(PageRepository); -export const PageLikes = getCustomRepository(PageLikeRepository); -export const GalleryPosts = getCustomRepository(GalleryPostRepository); -export const GalleryLikes = getCustomRepository(GalleryLikeRepository); -export const ModerationLogs = getCustomRepository(ModerationLogRepository); -export const Clips = getCustomRepository(ClipRepository); -export const ClipNotes = getRepository(ClipNote); -export const Antennas = getCustomRepository(AntennaRepository); -export const AntennaNotes = getRepository(AntennaNote); -export const PromoNotes = getRepository(PromoNote); -export const PromoReads = getRepository(PromoRead); -export const Relays = getCustomRepository(RelayRepository); -export const MutedNotes = getRepository(MutedNote); -export const Channels = getCustomRepository(ChannelRepository); -export const ChannelFollowings = getRepository(ChannelFollowing); -export const ChannelNotePinings = getRepository(ChannelNotePining); -export const RegistryItems = getRepository(RegistryItem); -export const Ads = getRepository(Ad); -export const PasswordResetRequests = getRepository(PasswordResetRequest); +export const Announcements = db.getRepository(Announcement); +export const AnnouncementReads = db.getRepository(AnnouncementRead); +export const Apps = (AppRepository); +export const Notes = (NoteRepository); +export const NoteFavorites = (NoteFavoriteRepository); +export const NoteWatchings = db.getRepository(NoteWatching); +export const NoteThreadMutings = db.getRepository(NoteThreadMuting); +export const NoteReactions = (NoteReactionRepository); +export const NoteUnreads = db.getRepository(NoteUnread); +export const Polls = db.getRepository(Poll); +export const PollVotes = db.getRepository(PollVote); +export const Users = (UserRepository); +export const UserProfiles = db.getRepository(UserProfile); +export const UserKeypairs = db.getRepository(UserKeypair); +export const UserPendings = db.getRepository(UserPending); +export const AttestationChallenges = db.getRepository(AttestationChallenge); +export const UserSecurityKeys = db.getRepository(UserSecurityKey); +export const UserPublickeys = db.getRepository(UserPublickey); +export const UserLists = (UserListRepository); +export const UserListJoinings = db.getRepository(UserListJoining); +export const UserGroups = (UserGroupRepository); +export const UserGroupJoinings = db.getRepository(UserGroupJoining); +export const UserGroupInvitations = (UserGroupInvitationRepository); +export const UserNotePinings = db.getRepository(UserNotePining); +export const UsedUsernames = db.getRepository(UsedUsername); +export const Followings = (FollowingRepository); +export const FollowRequests = (FollowRequestRepository); +export const Instances = (InstanceRepository); +export const Emojis = (EmojiRepository); +export const DriveFiles = (DriveFileRepository); +export const DriveFolders = (DriveFolderRepository); +export const Notifications = (NotificationRepository); +export const Metas = db.getRepository(Meta); +export const Mutings = (MutingRepository); +export const Blockings = (BlockingRepository); +export const SwSubscriptions = db.getRepository(SwSubscription); +export const Hashtags = (HashtagRepository); +export const AbuseUserReports = (AbuseUserReportRepository); +export const RegistrationTickets = db.getRepository(RegistrationTicket); +export const AuthSessions = (AuthSessionRepository); +export const AccessTokens = db.getRepository(AccessToken); +export const Signins = (SigninRepository); +export const MessagingMessages = (MessagingMessageRepository); +export const Pages = (PageRepository); +export const PageLikes = (PageLikeRepository); +export const GalleryPosts = (GalleryPostRepository); +export const GalleryLikes = (GalleryLikeRepository); +export const ModerationLogs = (ModerationLogRepository); +export const Clips = (ClipRepository); +export const ClipNotes = db.getRepository(ClipNote); +export const Antennas = (AntennaRepository); +export const AntennaNotes = db.getRepository(AntennaNote); +export const PromoNotes = db.getRepository(PromoNote); +export const PromoReads = db.getRepository(PromoRead); +export const Relays = (RelayRepository); +export const MutedNotes = db.getRepository(MutedNote); +export const Channels = (ChannelRepository); +export const ChannelFollowings = db.getRepository(ChannelFollowing); +export const ChannelNotePinings = db.getRepository(ChannelNotePining); +export const RegistryItems = db.getRepository(RegistryItem); +export const Webhooks = db.getRepository(Webhook); +export const Ads = db.getRepository(Ad); +export const PasswordResetRequests = db.getRepository(PasswordResetRequest); diff --git a/packages/backend/src/models/repositories/abuse-user-report.ts b/packages/backend/src/models/repositories/abuse-user-report.ts index 348f88b3a2..36d7ab90c5 100644 --- a/packages/backend/src/models/repositories/abuse-user-report.ts +++ b/packages/backend/src/models/repositories/abuse-user-report.ts @@ -1,14 +1,13 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { Users } from '../index.js'; import { AbuseUserReport } from '@/models/entities/abuse-user-report.js'; import { awaitAll } from '@/prelude/await-all.js'; -@EntityRepository(AbuseUserReport) -export class AbuseUserReportRepository extends Repository { - public async pack( +export const AbuseUserReportRepository = db.getRepository(AbuseUserReport).extend({ + async pack( src: AbuseUserReport['id'] | AbuseUserReport, ) { - const report = typeof src === 'object' ? src : await this.findOneOrFail(src); + const report = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); return await awaitAll({ id: report.id, @@ -29,11 +28,11 @@ export class AbuseUserReportRepository extends Repository { }) : null, forwarded: report.forwarded, }); - } + }, - public packMany( + packMany( reports: any[], ) { return Promise.all(reports.map(x => this.pack(x))); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/antenna.ts b/packages/backend/src/models/repositories/antenna.ts index 3440ca1871..70180e2dec 100644 --- a/packages/backend/src/models/repositories/antenna.ts +++ b/packages/backend/src/models/repositories/antenna.ts @@ -1,17 +1,16 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { Antenna } from '@/models/entities/antenna.js'; import { Packed } from '@/misc/schema.js'; import { AntennaNotes, UserGroupJoinings } from '../index.js'; -@EntityRepository(Antenna) -export class AntennaRepository extends Repository { - public async pack( +export const AntennaRepository = db.getRepository(Antenna).extend({ + async pack( src: Antenna['id'] | Antenna, ): Promise> { - const antenna = typeof src === 'object' ? src : await this.findOneOrFail(src); + const antenna = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); - const hasUnreadNote = (await AntennaNotes.findOne({ antennaId: antenna.id, read: false })) != null; - const userGroupJoining = antenna.userGroupJoiningId ? await UserGroupJoinings.findOne(antenna.userGroupJoiningId) : null; + const hasUnreadNote = (await AntennaNotes.findOneBy({ antennaId: antenna.id, read: false })) != null; + const userGroupJoining = antenna.userGroupJoiningId ? await UserGroupJoinings.findOneBy({ id: antenna.userGroupJoiningId }) : null; return { id: antenna.id, @@ -29,5 +28,5 @@ export class AntennaRepository extends Repository { withFile: antenna.withFile, hasUnreadNote, }; - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/app.ts b/packages/backend/src/models/repositories/app.ts index 4c3c488da0..e08dd6f0e3 100644 --- a/packages/backend/src/models/repositories/app.ts +++ b/packages/backend/src/models/repositories/app.ts @@ -1,12 +1,11 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { App } from '@/models/entities/app.js'; import { AccessTokens } from '../index.js'; import { Packed } from '@/misc/schema.js'; import { User } from '../entities/user.js'; -@EntityRepository(App) -export class AppRepository extends Repository { - public async pack( +export const AppRepository = db.getRepository(App).extend({ + async pack( src: App['id'] | App, me?: { id: User['id'] } | null | undefined, options?: { @@ -21,7 +20,7 @@ export class AppRepository extends Repository { includeProfileImageIds: false, }, options); - const app = typeof src === 'object' ? src : await this.findOneOrFail(src); + const app = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); return { id: app.id, @@ -30,11 +29,11 @@ export class AppRepository extends Repository { permission: app.permission, ...(opts.includeSecret ? { secret: app.secret } : {}), ...(me ? { - isAuthorized: await AccessTokens.count({ + isAuthorized: await AccessTokens.countBy({ appId: app.id, userId: me.id, }).then(count => count > 0), } : {}), }; - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/auth-session.ts b/packages/backend/src/models/repositories/auth-session.ts index 7a7bd3a1ed..3f1f6f4897 100644 --- a/packages/backend/src/models/repositories/auth-session.ts +++ b/packages/backend/src/models/repositories/auth-session.ts @@ -1,21 +1,20 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { Apps } from '../index.js'; import { AuthSession } from '@/models/entities/auth-session.js'; import { awaitAll } from '@/prelude/await-all.js'; import { User } from '@/models/entities/user.js'; -@EntityRepository(AuthSession) -export class AuthSessionRepository extends Repository { - public async pack( +export const AuthSessionRepository = db.getRepository(AuthSession).extend({ + async pack( src: AuthSession['id'] | AuthSession, me?: { id: User['id'] } | null | undefined ) { - const session = typeof src === 'object' ? src : await this.findOneOrFail(src); + const session = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); return await awaitAll({ id: session.id, app: Apps.pack(session.appId, me), token: session.token, }); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/blocking.ts b/packages/backend/src/models/repositories/blocking.ts index b155bf944b..1d569fb875 100644 --- a/packages/backend/src/models/repositories/blocking.ts +++ b/packages/backend/src/models/repositories/blocking.ts @@ -1,17 +1,16 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { Users } from '../index.js'; import { Blocking } from '@/models/entities/blocking.js'; import { awaitAll } from '@/prelude/await-all.js'; import { Packed } from '@/misc/schema.js'; import { User } from '@/models/entities/user.js'; -@EntityRepository(Blocking) -export class BlockingRepository extends Repository { - public async pack( +export const BlockingRepository = db.getRepository(Blocking).extend({ + async pack( src: Blocking['id'] | Blocking, me?: { id: User['id'] } | null | undefined ): Promise> { - const blocking = typeof src === 'object' ? src : await this.findOneOrFail(src); + const blocking = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); return await awaitAll({ id: blocking.id, @@ -21,12 +20,12 @@ export class BlockingRepository extends Repository { detail: true, }), }); - } + }, - public packMany( + packMany( blockings: any[], me: { id: User['id'] } ) { return Promise.all(blockings.map(x => this.pack(x, me))); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/channel.ts b/packages/backend/src/models/repositories/channel.ts index cc13d7c1e6..213ac3671a 100644 --- a/packages/backend/src/models/repositories/channel.ts +++ b/packages/backend/src/models/repositories/channel.ts @@ -1,23 +1,22 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { Channel } from '@/models/entities/channel.js'; import { Packed } from '@/misc/schema.js'; import { DriveFiles, ChannelFollowings, NoteUnreads } from '../index.js'; import { User } from '@/models/entities/user.js'; -@EntityRepository(Channel) -export class ChannelRepository extends Repository { - public async pack( +export const ChannelRepository = db.getRepository(Channel).extend({ + async pack( src: Channel['id'] | Channel, me?: { id: User['id'] } | null | undefined, ): Promise> { - const channel = typeof src === 'object' ? src : await this.findOneOrFail(src); + const channel = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); const meId = me ? me.id : null; - const banner = channel.bannerId ? await DriveFiles.findOne(channel.bannerId) : null; + const banner = channel.bannerId ? await DriveFiles.findOneBy({ id: channel.bannerId }) : null; - const hasUnreadNote = meId ? (await NoteUnreads.findOne({ noteChannelId: channel.id, userId: meId })) != null : undefined; + const hasUnreadNote = meId ? (await NoteUnreads.findOneBy({ noteChannelId: channel.id, userId: meId })) != null : undefined; - const following = meId ? await ChannelFollowings.findOne({ + const following = meId ? await ChannelFollowings.findOneBy({ followerId: meId, followeeId: channel.id, }) : null; @@ -38,5 +37,5 @@ export class ChannelRepository extends Repository { hasUnreadNote, } : {}), }; - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/clip.ts b/packages/backend/src/models/repositories/clip.ts index 9e1979729a..b4a342905e 100644 --- a/packages/backend/src/models/repositories/clip.ts +++ b/packages/backend/src/models/repositories/clip.ts @@ -1,15 +1,14 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { Clip } from '@/models/entities/clip.js'; import { Packed } from '@/misc/schema.js'; import { Users } from '../index.js'; import { awaitAll } from '@/prelude/await-all.js'; -@EntityRepository(Clip) -export class ClipRepository extends Repository { - public async pack( +export const ClipRepository = db.getRepository(Clip).extend({ + async pack( src: Clip['id'] | Clip, ): Promise> { - const clip = typeof src === 'object' ? src : await this.findOneOrFail(src); + const clip = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); return await awaitAll({ id: clip.id, @@ -20,12 +19,12 @@ export class ClipRepository extends Repository { description: clip.description, isPublic: clip.isPublic, }); - } + }, - public packMany( + packMany( clips: Clip[], ) { return Promise.all(clips.map(x => this.pack(x))); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/drive-file.ts b/packages/backend/src/models/repositories/drive-file.ts index 6452632db7..69dc1721c2 100644 --- a/packages/backend/src/models/repositories/drive-file.ts +++ b/packages/backend/src/models/repositories/drive-file.ts @@ -1,4 +1,4 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { DriveFile } from '@/models/entities/drive-file.js'; import { Users, DriveFolders } from '../index.js'; import { User } from '@/models/entities/user.js'; @@ -16,9 +16,8 @@ type PackOptions = { withUser?: boolean, }; -@EntityRepository(DriveFile) -export class DriveFileRepository extends Repository { - public validateFileName(name: string): boolean { +export const DriveFileRepository = db.getRepository(DriveFile).extend({ + validateFileName(name: string): boolean { return ( (name.trim().length > 0) && (name.length <= 200) && @@ -26,9 +25,9 @@ export class DriveFileRepository extends Repository { (name.indexOf('/') === -1) && (name.indexOf('..') === -1) ); - } + }, - public getPublicProperties(file: DriveFile): DriveFile['properties'] { + getPublicProperties(file: DriveFile): DriveFile['properties'] { if (file.properties.orientation != null) { const properties = JSON.parse(JSON.stringify(file.properties)); if (file.properties.orientation >= 5) { @@ -39,9 +38,9 @@ export class DriveFileRepository extends Repository { } return file.properties; - } + }, - public getPublicUrl(file: DriveFile, thumbnail = false): string | null { + getPublicUrl(file: DriveFile, thumbnail = false): string | null { // リモートかつメディアプロキシ if (file.uri != null && file.userHost != null && config.mediaProxy != null) { return appendQuery(config.mediaProxy, query({ @@ -62,9 +61,9 @@ export class DriveFileRepository extends Repository { const isImage = file.type && ['image/png', 'image/apng', 'image/gif', 'image/jpeg', 'image/webp', 'image/svg+xml'].includes(file.type); return thumbnail ? (file.thumbnailUrl || (isImage ? (file.webpublicUrl || file.url) : null)) : (file.webpublicUrl || file.url); - } + }, - public async calcDriveUsageOf(user: User['id'] | { id: User['id'] }): Promise { + async calcDriveUsageOf(user: User['id'] | { id: User['id'] }): Promise { const id = typeof user === 'object' ? user.id : user; const { sum } = await this @@ -75,9 +74,9 @@ export class DriveFileRepository extends Repository { .getRawOne(); return parseInt(sum, 10) || 0; - } + }, - public async calcDriveUsageOfHost(host: string): Promise { + async calcDriveUsageOfHost(host: string): Promise { const { sum } = await this .createQueryBuilder('file') .where('file.userHost = :host', { host: toPuny(host) }) @@ -86,9 +85,9 @@ export class DriveFileRepository extends Repository { .getRawOne(); return parseInt(sum, 10) || 0; - } + }, - public async calcDriveUsageOfLocal(): Promise { + async calcDriveUsageOfLocal(): Promise { const { sum } = await this .createQueryBuilder('file') .where('file.userHost IS NULL') @@ -97,9 +96,9 @@ export class DriveFileRepository extends Repository { .getRawOne(); return parseInt(sum, 10) || 0; - } + }, - public async calcDriveUsageOfRemote(): Promise { + async calcDriveUsageOfRemote(): Promise { const { sum } = await this .createQueryBuilder('file') .where('file.userHost IS NOT NULL') @@ -108,11 +107,9 @@ export class DriveFileRepository extends Repository { .getRawOne(); return parseInt(sum, 10) || 0; - } + }, - public async pack(src: DriveFile['id'], options?: PackOptions): Promise | null>; - public async pack(src: DriveFile, options?: PackOptions): Promise>; - public async pack( + async pack( src: DriveFile['id'] | DriveFile, options?: PackOptions ): Promise | null> { @@ -121,11 +118,9 @@ export class DriveFileRepository extends Repository { self: false, }, options); - const file = typeof src === 'object' ? src : await this.findOne(src); + const file = typeof src === 'object' ? src : await this.findOneBy({ id: src }); if (file == null) return null; - const meta = await fetchMeta(); - return await awaitAll>({ id: file.id, createdAt: file.createdAt.toISOString(), @@ -146,13 +141,13 @@ export class DriveFileRepository extends Repository { userId: opts.withUser ? file.userId : null, user: (opts.withUser && file.userId) ? Users.pack(file.userId) : null, }); - } + }, - public async packMany( + async packMany( files: (DriveFile['id'] | DriveFile)[], options?: PackOptions ) { const items = await Promise.all(files.map(f => this.pack(f, options))); return items.filter(x => x != null); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/drive-folder.ts b/packages/backend/src/models/repositories/drive-folder.ts index b0e09eedf5..ab5f3dab63 100644 --- a/packages/backend/src/models/repositories/drive-folder.ts +++ b/packages/backend/src/models/repositories/drive-folder.ts @@ -1,12 +1,11 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { DriveFolders, DriveFiles } from '../index.js'; import { DriveFolder } from '@/models/entities/drive-folder.js'; import { awaitAll } from '@/prelude/await-all.js'; import { Packed } from '@/misc/schema.js'; -@EntityRepository(DriveFolder) -export class DriveFolderRepository extends Repository { - public async pack( +export const DriveFolderRepository = db.getRepository(DriveFolder).extend({ + async pack( src: DriveFolder['id'] | DriveFolder, options?: { detail: boolean @@ -16,7 +15,7 @@ export class DriveFolderRepository extends Repository { detail: false, }, options); - const folder = typeof src === 'object' ? src : await this.findOneOrFail(src); + const folder = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); return await awaitAll({ id: folder.id, @@ -25,10 +24,10 @@ export class DriveFolderRepository extends Repository { parentId: folder.parentId, ...(opts.detail ? { - foldersCount: DriveFolders.count({ + foldersCount: DriveFolders.countBy({ parentId: folder.id, }), - filesCount: DriveFiles.count({ + filesCount: DriveFiles.countBy({ folderId: folder.id, }), @@ -39,5 +38,5 @@ export class DriveFolderRepository extends Repository { } : {}), } : {}), }); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/emoji.ts b/packages/backend/src/models/repositories/emoji.ts index 3b13832a35..a0d390d793 100644 --- a/packages/backend/src/models/repositories/emoji.ts +++ b/packages/backend/src/models/repositories/emoji.ts @@ -1,13 +1,12 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { Emoji } from '@/models/entities/emoji.js'; import { Packed } from '@/misc/schema.js'; -@EntityRepository(Emoji) -export class EmojiRepository extends Repository { - public async pack( +export const EmojiRepository = db.getRepository(Emoji).extend({ + async pack( src: Emoji['id'] | Emoji, ): Promise> { - const emoji = typeof src === 'object' ? src : await this.findOneOrFail(src); + const emoji = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); return { id: emoji.id, @@ -18,11 +17,11 @@ export class EmojiRepository extends Repository { // || emoji.originalUrl してるのは後方互換性のため url: emoji.publicUrl || emoji.originalUrl, }; - } + }, - public packMany( + packMany( emojis: any[], ) { return Promise.all(emojis.map(x => this.pack(x))); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/follow-request.ts b/packages/backend/src/models/repositories/follow-request.ts index 1da1f875ea..c4a7203aa1 100644 --- a/packages/backend/src/models/repositories/follow-request.ts +++ b/packages/backend/src/models/repositories/follow-request.ts @@ -1,20 +1,19 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { FollowRequest } from '@/models/entities/follow-request.js'; import { Users } from '../index.js'; import { User } from '@/models/entities/user.js'; -@EntityRepository(FollowRequest) -export class FollowRequestRepository extends Repository { - public async pack( +export const FollowRequestRepository = db.getRepository(FollowRequest).extend({ + async pack( src: FollowRequest['id'] | FollowRequest, me?: { id: User['id'] } | null | undefined ) { - const request = typeof src === 'object' ? src : await this.findOneOrFail(src); + const request = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); return { id: request.id, follower: await Users.pack(request.followerId, me), followee: await Users.pack(request.followeeId, me), }; - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/following.ts b/packages/backend/src/models/repositories/following.ts index f25289d19c..46109244fa 100644 --- a/packages/backend/src/models/repositories/following.ts +++ b/packages/backend/src/models/repositories/following.ts @@ -1,4 +1,4 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { Users } from '../index.js'; import { Following } from '@/models/entities/following.js'; import { awaitAll } from '@/prelude/await-all.js'; @@ -29,25 +29,24 @@ type RemoteFolloweeFollowing = Following & { followeeSharedInbox: string; }; -@EntityRepository(Following) -export class FollowingRepository extends Repository { - public isLocalFollower(following: Following): following is LocalFollowerFollowing { +export const FollowingRepository = db.getRepository(Following).extend({ + isLocalFollower(following: Following): following is LocalFollowerFollowing { return following.followerHost == null; - } + }, - public isRemoteFollower(following: Following): following is RemoteFollowerFollowing { + isRemoteFollower(following: Following): following is RemoteFollowerFollowing { return following.followerHost != null; - } + }, - public isLocalFollowee(following: Following): following is LocalFolloweeFollowing { + isLocalFollowee(following: Following): following is LocalFolloweeFollowing { return following.followeeHost == null; - } + }, - public isRemoteFollowee(following: Following): following is RemoteFolloweeFollowing { + isRemoteFollowee(following: Following): following is RemoteFolloweeFollowing { return following.followeeHost != null; - } + }, - public async pack( + async pack( src: Following['id'] | Following, me?: { id: User['id'] } | null | undefined, opts?: { @@ -55,7 +54,7 @@ export class FollowingRepository extends Repository { populateFollower?: boolean; } ): Promise> { - const following = typeof src === 'object' ? src : await this.findOneOrFail(src); + const following = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); if (opts == null) opts = {}; @@ -71,9 +70,9 @@ export class FollowingRepository extends Repository { detail: true, }) : undefined, }); - } + }, - public packMany( + packMany( followings: any[], me?: { id: User['id'] } | null | undefined, opts?: { @@ -82,5 +81,5 @@ export class FollowingRepository extends Repository { } ) { return Promise.all(followings.map(x => this.pack(x, me, opts))); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/gallery-like.ts b/packages/backend/src/models/repositories/gallery-like.ts index 545186fa19..08ca4962b8 100644 --- a/packages/backend/src/models/repositories/gallery-like.ts +++ b/packages/backend/src/models/repositories/gallery-like.ts @@ -1,25 +1,24 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { GalleryLike } from '@/models/entities/gallery-like.js'; import { GalleryPosts } from '../index.js'; -@EntityRepository(GalleryLike) -export class GalleryLikeRepository extends Repository { - public async pack( +export const GalleryLikeRepository = db.getRepository(GalleryLike).extend({ + async pack( src: GalleryLike['id'] | GalleryLike, me?: any ) { - const like = typeof src === 'object' ? src : await this.findOneOrFail(src); + const like = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); return { id: like.id, post: await GalleryPosts.pack(like.post || like.postId, me), }; - } + }, - public packMany( + packMany( likes: any[], me: any ) { return Promise.all(likes.map(x => this.pack(x, me))); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/gallery-post.ts b/packages/backend/src/models/repositories/gallery-post.ts index bbb036dd09..bb8d40b75e 100644 --- a/packages/backend/src/models/repositories/gallery-post.ts +++ b/packages/backend/src/models/repositories/gallery-post.ts @@ -1,18 +1,17 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { GalleryPost } from '@/models/entities/gallery-post.js'; import { Packed } from '@/misc/schema.js'; import { Users, DriveFiles, GalleryLikes } from '../index.js'; import { awaitAll } from '@/prelude/await-all.js'; import { User } from '@/models/entities/user.js'; -@EntityRepository(GalleryPost) -export class GalleryPostRepository extends Repository { - public async pack( +export const GalleryPostRepository = db.getRepository(GalleryPost).extend({ + async pack( src: GalleryPost['id'] | GalleryPost, me?: { id: User['id'] } | null | undefined, ): Promise> { const meId = me ? me.id : null; - const post = typeof src === 'object' ? src : await this.findOneOrFail(src); + const post = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); return await awaitAll({ id: post.id, @@ -27,14 +26,14 @@ export class GalleryPostRepository extends Repository { tags: post.tags.length > 0 ? post.tags : undefined, isSensitive: post.isSensitive, likedCount: post.likedCount, - isLiked: meId ? await GalleryLikes.findOne({ postId: post.id, userId: meId }).then(x => x != null) : undefined, + isLiked: meId ? await GalleryLikes.findOneBy({ postId: post.id, userId: meId }).then(x => x != null) : undefined, }); - } + }, - public packMany( + packMany( posts: GalleryPost[], me?: { id: User['id'] } | null | undefined, ) { return Promise.all(posts.map(x => this.pack(x, me))); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/hashtag.ts b/packages/backend/src/models/repositories/hashtag.ts index 0548e19ee3..e6c0e36f00 100644 --- a/packages/backend/src/models/repositories/hashtag.ts +++ b/packages/backend/src/models/repositories/hashtag.ts @@ -1,10 +1,9 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { Hashtag } from '@/models/entities/hashtag.js'; import { Packed } from '@/misc/schema.js'; -@EntityRepository(Hashtag) -export class HashtagRepository extends Repository { - public async pack( +export const HashtagRepository = db.getRepository(Hashtag).extend({ + async pack( src: Hashtag, ): Promise> { return { @@ -16,11 +15,11 @@ export class HashtagRepository extends Repository { attachedLocalUsersCount: src.attachedLocalUsersCount, attachedRemoteUsersCount: src.attachedRemoteUsersCount, }; - } + }, - public packMany( + packMany( hashtags: Hashtag[], ) { return Promise.all(hashtags.map(x => this.pack(x))); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/instance.ts b/packages/backend/src/models/repositories/instance.ts index 358e055aaa..4ddf717098 100644 --- a/packages/backend/src/models/repositories/instance.ts +++ b/packages/backend/src/models/repositories/instance.ts @@ -1,10 +1,9 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { Instance } from '@/models/entities/instance.js'; import { Packed } from '@/misc/schema.js'; -@EntityRepository(Instance) -export class InstanceRepository extends Repository { - public async pack( +export const InstanceRepository = db.getRepository(Instance).extend({ + async pack( instance: Instance, ): Promise> { return { @@ -29,11 +28,11 @@ export class InstanceRepository extends Repository { iconUrl: instance.iconUrl, infoUpdatedAt: instance.infoUpdatedAt ? instance.infoUpdatedAt.toISOString() : null, }; - } + }, - public packMany( + packMany( instances: Instance[], ) { return Promise.all(instances.map(x => this.pack(x))); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/messaging-message.ts b/packages/backend/src/models/repositories/messaging-message.ts index 3f51707008..6c51c93ff7 100644 --- a/packages/backend/src/models/repositories/messaging-message.ts +++ b/packages/backend/src/models/repositories/messaging-message.ts @@ -1,12 +1,11 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { MessagingMessage } from '@/models/entities/messaging-message.js'; import { Users, DriveFiles, UserGroups } from '../index.js'; import { Packed } from '@/misc/schema.js'; import { User } from '@/models/entities/user.js'; -@EntityRepository(MessagingMessage) -export class MessagingMessageRepository extends Repository { - public async pack( +export const MessagingMessageRepository = db.getRepository(MessagingMessage).extend({ + async pack( src: MessagingMessage['id'] | MessagingMessage, me?: { id: User['id'] } | null | undefined, options?: { @@ -19,7 +18,7 @@ export class MessagingMessageRepository extends Repository { populateGroup: true, }; - const message = typeof src === 'object' ? src : await this.findOneOrFail(src); + const message = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); return { id: message.id, @@ -36,5 +35,5 @@ export class MessagingMessageRepository extends Repository { isRead: message.isRead, reads: message.reads, }; - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/moderation-logs.ts b/packages/backend/src/models/repositories/moderation-logs.ts index ea78104960..1488b1eabe 100644 --- a/packages/backend/src/models/repositories/moderation-logs.ts +++ b/packages/backend/src/models/repositories/moderation-logs.ts @@ -1,14 +1,13 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { Users } from '../index.js'; import { ModerationLog } from '@/models/entities/moderation-log.js'; import { awaitAll } from '@/prelude/await-all.js'; -@EntityRepository(ModerationLog) -export class ModerationLogRepository extends Repository { - public async pack( +export const ModerationLogRepository = db.getRepository(ModerationLog).extend({ + async pack( src: ModerationLog['id'] | ModerationLog, ) { - const log = typeof src === 'object' ? src : await this.findOneOrFail(src); + const log = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); return await awaitAll({ id: log.id, @@ -20,11 +19,11 @@ export class ModerationLogRepository extends Repository { detail: true, }), }); - } + }, - public packMany( + packMany( reports: any[], ) { return Promise.all(reports.map(x => this.pack(x))); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/muting.ts b/packages/backend/src/models/repositories/muting.ts index 643e0b68ee..7891b10fb0 100644 --- a/packages/backend/src/models/repositories/muting.ts +++ b/packages/backend/src/models/repositories/muting.ts @@ -1,17 +1,16 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { Users } from '../index.js'; import { Muting } from '@/models/entities/muting.js'; import { awaitAll } from '@/prelude/await-all.js'; import { Packed } from '@/misc/schema.js'; import { User } from '@/models/entities/user.js'; -@EntityRepository(Muting) -export class MutingRepository extends Repository { - public async pack( +export const MutingRepository = db.getRepository(Muting).extend({ + async pack( src: Muting['id'] | Muting, me?: { id: User['id'] } | null | undefined ): Promise> { - const muting = typeof src === 'object' ? src : await this.findOneOrFail(src); + const muting = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); return await awaitAll({ id: muting.id, @@ -22,12 +21,12 @@ export class MutingRepository extends Repository { detail: true, }), }); - } + }, - public packMany( + packMany( mutings: any[], me: { id: User['id'] } ) { return Promise.all(mutings.map(x => this.pack(x, me))); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/note-favorite.ts b/packages/backend/src/models/repositories/note-favorite.ts index d7a7925ebc..9bd97f9880 100644 --- a/packages/backend/src/models/repositories/note-favorite.ts +++ b/packages/backend/src/models/repositories/note-favorite.ts @@ -1,15 +1,14 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { NoteFavorite } from '@/models/entities/note-favorite.js'; import { Notes } from '../index.js'; import { User } from '@/models/entities/user.js'; -@EntityRepository(NoteFavorite) -export class NoteFavoriteRepository extends Repository { - public async pack( +export const NoteFavoriteRepository = db.getRepository(NoteFavorite).extend({ + async pack( src: NoteFavorite['id'] | NoteFavorite, me?: { id: User['id'] } | null | undefined ) { - const favorite = typeof src === 'object' ? src : await this.findOneOrFail(src); + const favorite = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); return { id: favorite.id, @@ -17,12 +16,12 @@ export class NoteFavoriteRepository extends Repository { noteId: favorite.noteId, note: await Notes.pack(favorite.note || favorite.noteId, me), }; - } + }, - public packMany( + packMany( favorites: any[], me: { id: User['id'] } ) { return Promise.all(favorites.map(x => this.pack(x, me))); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/note-reaction.ts b/packages/backend/src/models/repositories/note-reaction.ts index a212b0d3e9..4deae51c93 100644 --- a/packages/backend/src/models/repositories/note-reaction.ts +++ b/packages/backend/src/models/repositories/note-reaction.ts @@ -1,13 +1,12 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { NoteReaction } from '@/models/entities/note-reaction.js'; import { Notes, Users } from '../index.js'; import { Packed } from '@/misc/schema.js'; import { convertLegacyReaction } from '@/misc/reaction-lib.js'; import { User } from '@/models/entities/user.js'; -@EntityRepository(NoteReaction) -export class NoteReactionRepository extends Repository { - public async pack( +export const NoteReactionRepository = db.getRepository(NoteReaction).extend({ + async pack( src: NoteReaction['id'] | NoteReaction, me?: { id: User['id'] } | null | undefined, options?: { @@ -18,7 +17,7 @@ export class NoteReactionRepository extends Repository { withNote: false, }, options); - const reaction = typeof src === 'object' ? src : await this.findOneOrFail(src); + const reaction = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); return { id: reaction.id, @@ -29,5 +28,5 @@ export class NoteReactionRepository extends Repository { note: await Notes.pack(reaction.note ?? reaction.noteId, me), } : {}), }; - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/note.ts b/packages/backend/src/models/repositories/note.ts index 418d6e2346..cf5fcb1787 100644 --- a/packages/backend/src/models/repositories/note.ts +++ b/packages/backend/src/models/repositories/note.ts @@ -1,4 +1,4 @@ -import { EntityRepository, Repository, In } from 'typeorm'; +import { In } from 'typeorm'; import * as mfm from 'mfm-js'; import { Note } from '@/models/entities/note.js'; import { User } from '@/models/entities/user.js'; @@ -9,10 +9,133 @@ import { awaitAll } from '@/prelude/await-all.js'; import { convertLegacyReaction, convertLegacyReactions, decodeReaction } from '@/misc/reaction-lib.js'; import { NoteReaction } from '@/models/entities/note-reaction.js'; import { aggregateNoteEmojis, populateEmojis, prefetchEmojis } from '@/misc/populate-emojis.js'; +import { db } from '@/db/postgre.js'; -@EntityRepository(Note) -export class NoteRepository extends Repository { - public async isVisibleForMe(note: Note, meId: User['id'] | null): Promise { +async function hideNote(packedNote: Packed<'Note'>, meId: User['id'] | null) { + // TODO: isVisibleForMe を使うようにしても良さそう(型違うけど) + let hide = false; + + // visibility が specified かつ自分が指定されていなかったら非表示 + if (packedNote.visibility === 'specified') { + if (meId == null) { + hide = true; + } else if (meId === packedNote.userId) { + hide = false; + } else { + // 指定されているかどうか + const specified = packedNote.visibleUserIds!.some((id: any) => meId === id); + + if (specified) { + hide = false; + } else { + hide = true; + } + } + } + + // visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示 + if (packedNote.visibility === 'followers') { + if (meId == null) { + hide = true; + } else if (meId === packedNote.userId) { + hide = false; + } else if (packedNote.reply && (meId === packedNote.reply.userId)) { + // 自分の投稿に対するリプライ + hide = false; + } else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) { + // 自分へのメンション + hide = false; + } else { + // フォロワーかどうか + const following = await Followings.findOneBy({ + followeeId: packedNote.userId, + followerId: meId, + }); + + if (following == null) { + hide = true; + } else { + hide = false; + } + } + } + + if (hide) { + packedNote.visibleUserIds = undefined; + packedNote.fileIds = []; + packedNote.files = []; + packedNote.text = null; + packedNote.poll = undefined; + packedNote.cw = null; + packedNote.isHidden = true; + } +} + +async function populatePoll(note: Note, meId: User['id'] | null) { + const poll = await Polls.findOneByOrFail({ noteId: note.id }); + const choices = poll.choices.map(c => ({ + text: c, + votes: poll.votes[poll.choices.indexOf(c)], + isVoted: false, + })); + + if (meId) { + if (poll.multiple) { + const votes = await PollVotes.findBy({ + userId: meId, + noteId: note.id, + }); + + const myChoices = votes.map(v => v.choice); + for (const myChoice of myChoices) { + choices[myChoice].isVoted = true; + } + } else { + const vote = await PollVotes.findOneBy({ + userId: meId, + noteId: note.id, + }); + + if (vote) { + choices[vote.choice].isVoted = true; + } + } + } + + return { + multiple: poll.multiple, + expiresAt: poll.expiresAt, + choices, + }; +} + +async function populateMyReaction(note: Note, meId: User['id'], _hint_?: { + myReactions: Map; +}) { + if (_hint_?.myReactions) { + const reaction = _hint_.myReactions.get(note.id); + if (reaction) { + return convertLegacyReaction(reaction.reaction); + } else if (reaction === null) { + return undefined; + } + // 実装上抜けがあるだけかもしれないので、「ヒントに含まれてなかったら(=undefinedなら)return」のようにはしない + } + + const reaction = await NoteReactions.findOneBy({ + userId: meId, + noteId: note.id, + }); + + if (reaction) { + return convertLegacyReaction(reaction.reaction); + } + + return undefined; +} + +export const NoteRepository = db.getRepository(Note).extend({ + async isVisibleForMe(note: Note, meId: User['id'] | null): Promise { // visibility が specified かつ自分が指定されていなかったら非表示 if (note.visibility === 'specified') { if (meId == null) { @@ -45,7 +168,7 @@ export class NoteRepository extends Repository { return true; } else { // フォロワーかどうか - const following = await Followings.findOne({ + const following = await Followings.findOneBy({ followeeId: note.userId, followerId: meId, }); @@ -59,69 +182,9 @@ export class NoteRepository extends Repository { } return true; - } + }, - private async hideNote(packedNote: Packed<'Note'>, meId: User['id'] | null) { - // TODO: isVisibleForMe を使うようにしても良さそう(型違うけど) - let hide = false; - - // visibility が specified かつ自分が指定されていなかったら非表示 - if (packedNote.visibility === 'specified') { - if (meId == null) { - hide = true; - } else if (meId === packedNote.userId) { - hide = false; - } else { - // 指定されているかどうか - const specified = packedNote.visibleUserIds!.some((id: any) => meId === id); - - if (specified) { - hide = false; - } else { - hide = true; - } - } - } - - // visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示 - if (packedNote.visibility === 'followers') { - if (meId == null) { - hide = true; - } else if (meId === packedNote.userId) { - hide = false; - } else if (packedNote.reply && (meId === packedNote.reply.userId)) { - // 自分の投稿に対するリプライ - hide = false; - } else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) { - // 自分へのメンション - hide = false; - } else { - // フォロワーかどうか - const following = await Followings.findOne({ - followeeId: packedNote.userId, - followerId: meId, - }); - - if (following == null) { - hide = true; - } else { - hide = false; - } - } - } - - if (hide) { - packedNote.visibleUserIds = undefined; - packedNote.fileIds = []; - packedNote.files = []; - packedNote.text = null; - packedNote.poll = undefined; - packedNote.cw = null; - packedNote.isHidden = true; - } - } - - public async pack( + async pack( src: Note['id'] | Note, me?: { id: User['id'] } | null | undefined, options?: { @@ -138,68 +201,9 @@ export class NoteRepository extends Repository { }, options); const meId = me ? me.id : null; - const note = typeof src === 'object' ? src : await this.findOneOrFail(src); + const note = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); const host = note.userHost; - async function populatePoll() { - const poll = await Polls.findOneOrFail(note.id); - const choices = poll.choices.map(c => ({ - text: c, - votes: poll.votes[poll.choices.indexOf(c)], - isVoted: false, - })); - - if (poll.multiple) { - const votes = await PollVotes.find({ - userId: meId!, - noteId: note.id, - }); - - const myChoices = votes.map(v => v.choice); - for (const myChoice of myChoices) { - choices[myChoice].isVoted = true; - } - } else { - const vote = await PollVotes.findOne({ - userId: meId!, - noteId: note.id, - }); - - if (vote) { - choices[vote.choice].isVoted = true; - } - } - - return { - multiple: poll.multiple, - expiresAt: poll.expiresAt, - choices, - }; - } - - async function populateMyReaction() { - if (options?._hint_?.myReactions) { - const reaction = options._hint_.myReactions.get(note.id); - if (reaction) { - return convertLegacyReaction(reaction.reaction); - } else if (reaction === null) { - return undefined; - } - // 実装上抜けがあるだけかもしれないので、「ヒントに含まれてなかったら(=undefinedなら)return」のようにはしない - } - - const reaction = await NoteReactions.findOne({ - userId: meId!, - noteId: note.id, - }); - - if (reaction) { - return convertLegacyReaction(reaction.reaction); - } - - return undefined; - } - let text = note.text; if (note.name && (note.url ?? note.uri)) { @@ -209,7 +213,7 @@ export class NoteRepository extends Repository { const channel = note.channelId ? note.channel ? note.channel - : await Channels.findOne(note.channelId) + : await Channels.findOneBy({ id: note.channelId }) : null; const reactionEmojiNames = Object.keys(note.reactions).filter(x => x?.startsWith(':')).map(x => decodeReaction(x).reaction).map(x => x.replace(/:/g, '')); @@ -255,10 +259,10 @@ export class NoteRepository extends Repository { _hint_: options?._hint_, }) : undefined, - poll: note.hasPoll ? populatePoll() : undefined, + poll: note.hasPoll ? populatePoll(note, meId) : undefined, ...(meId ? { - myReaction: populateMyReaction(), + myReaction: populateMyReaction(note, meId, options?._hint_), } : {}), } : {}), }); @@ -275,13 +279,13 @@ export class NoteRepository extends Repository { } if (!opts.skipHide) { - await this.hideNote(packed, meId); + await hideNote(packed, meId); } return packed; - } + }, - public async packMany( + async packMany( notes: Note[], me?: { id: User['id'] } | null | undefined, options?: { @@ -296,7 +300,7 @@ export class NoteRepository extends Repository { if (meId) { const renoteIds = notes.filter(n => n.renoteId != null).map(n => n.renoteId!); const targets = [...notes.map(n => n.id), ...renoteIds]; - const myReactions = await NoteReactions.find({ + const myReactions = await NoteReactions.findBy({ userId: meId, noteId: In(targets), }); @@ -314,5 +318,5 @@ export class NoteRepository extends Repository { myReactions: myReactionsMap, }, }))); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/notification.ts b/packages/backend/src/models/repositories/notification.ts index 8e72d8aabd..42b47ab150 100644 --- a/packages/backend/src/models/repositories/notification.ts +++ b/packages/backend/src/models/repositories/notification.ts @@ -1,4 +1,4 @@ -import { EntityRepository, In, Repository } from 'typeorm'; +import { In, Repository } from 'typeorm'; import { Users, Notes, UserGroupInvitations, AccessTokens, NoteReactions } from '../index.js'; import { Notification } from '@/models/entities/notification.js'; import { awaitAll } from '@/prelude/await-all.js'; @@ -8,10 +8,10 @@ import { NoteReaction } from '@/models/entities/note-reaction.js'; import { User } from '@/models/entities/user.js'; import { aggregateNoteEmojis, prefetchEmojis } from '@/misc/populate-emojis.js'; import { notificationTypes } from '@/types.js'; +import { db } from '@/db/postgre.js'; -@EntityRepository(Notification) -export class NotificationRepository extends Repository { - public async pack( +export const NotificationRepository = db.getRepository(Notification).extend({ + async pack( src: Notification['id'] | Notification, options: { _hintForEachNotes_?: { @@ -19,8 +19,8 @@ export class NotificationRepository extends Repository { }; } ): Promise> { - const notification = typeof src === 'object' ? src : await this.findOneOrFail(src); - const token = notification.appAccessTokenId ? await AccessTokens.findOneOrFail(notification.appAccessTokenId) : null; + const notification = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); + const token = notification.appAccessTokenId ? await AccessTokens.findOneByOrFail({ id: notification.appAccessTokenId }) : null; return await awaitAll({ id: notification.id, @@ -82,9 +82,9 @@ export class NotificationRepository extends Repository { icon: notification.customIcon || token?.iconUrl, } : {}), }); - } + }, - public async packMany( + async packMany( notifications: Notification[], meId: User['id'] ) { @@ -95,7 +95,7 @@ export class NotificationRepository extends Repository { const myReactionsMap = new Map(); const renoteIds = notes.filter(n => n.renoteId != null).map(n => n.renoteId!); const targets = [...noteIds, ...renoteIds]; - const myReactions = await NoteReactions.find({ + const myReactions = await NoteReactions.findBy({ userId: meId, noteId: In(targets), }); @@ -111,5 +111,5 @@ export class NotificationRepository extends Repository { myReactions: myReactionsMap, }, }))); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/page-like.ts b/packages/backend/src/models/repositories/page-like.ts index 66d780584f..87d6accc34 100644 --- a/packages/backend/src/models/repositories/page-like.ts +++ b/packages/backend/src/models/repositories/page-like.ts @@ -1,26 +1,25 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { PageLike } from '@/models/entities/page-like.js'; import { Pages } from '../index.js'; import { User } from '@/models/entities/user.js'; -@EntityRepository(PageLike) -export class PageLikeRepository extends Repository { - public async pack( +export const PageLikeRepository = db.getRepository(PageLike).extend({ + async pack( src: PageLike['id'] | PageLike, me?: { id: User['id'] } | null | undefined ) { - const like = typeof src === 'object' ? src : await this.findOneOrFail(src); + const like = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); return { id: like.id, page: await Pages.pack(like.page || like.pageId, me), }; - } + }, - public packMany( + packMany( likes: any[], me: { id: User['id'] } ) { return Promise.all(likes.map(x => this.pack(x, me))); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/page.ts b/packages/backend/src/models/repositories/page.ts index 037c13c434..1bffb23fa2 100644 --- a/packages/backend/src/models/repositories/page.ts +++ b/packages/backend/src/models/repositories/page.ts @@ -1,4 +1,4 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { Page } from '@/models/entities/page.js'; import { Packed } from '@/misc/schema.js'; import { Users, DriveFiles, PageLikes } from '../index.js'; @@ -6,20 +6,19 @@ import { awaitAll } from '@/prelude/await-all.js'; import { DriveFile } from '@/models/entities/drive-file.js'; import { User } from '@/models/entities/user.js'; -@EntityRepository(Page) -export class PageRepository extends Repository { - public async pack( +export const PageRepository = db.getRepository(Page).extend({ + async pack( src: Page['id'] | Page, me?: { id: User['id'] } | null | undefined, ): Promise> { const meId = me ? me.id : null; - const page = typeof src === 'object' ? src : await this.findOneOrFail(src); + const page = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); const attachedFiles: Promise[] = []; const collectFile = (xs: any[]) => { for (const x of xs) { if (x.type === 'image') { - attachedFiles.push(DriveFiles.findOne({ + attachedFiles.push(DriveFiles.findOneBy({ id: x.fileId, userId: page.userId, })); @@ -76,14 +75,14 @@ export class PageRepository extends Repository { eyeCatchingImage: page.eyeCatchingImageId ? await DriveFiles.pack(page.eyeCatchingImageId) : null, attachedFiles: DriveFiles.packMany(await Promise.all(attachedFiles)), likedCount: page.likedCount, - isLiked: meId ? await PageLikes.findOne({ pageId: page.id, userId: meId }).then(x => x != null) : undefined, + isLiked: meId ? await PageLikes.findOneBy({ pageId: page.id, userId: meId }).then(x => x != null) : undefined, }); - } + }, - public packMany( + packMany( pages: Page[], me?: { id: User['id'] } | null | undefined, ) { return Promise.all(pages.map(x => this.pack(x, me))); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/relay.ts b/packages/backend/src/models/repositories/relay.ts index 160ca60f7b..fa1c8f4d8d 100644 --- a/packages/backend/src/models/repositories/relay.ts +++ b/packages/backend/src/models/repositories/relay.ts @@ -1,6 +1,5 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { Relay } from '@/models/entities/relay.js'; -@EntityRepository(Relay) -export class RelayRepository extends Repository { -} +export const RelayRepository = db.getRepository(Relay).extend({ +}); diff --git a/packages/backend/src/models/repositories/signin.ts b/packages/backend/src/models/repositories/signin.ts index a0e2ce1526..94410ec58a 100644 --- a/packages/backend/src/models/repositories/signin.ts +++ b/packages/backend/src/models/repositories/signin.ts @@ -1,11 +1,10 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { Signin } from '@/models/entities/signin.js'; -@EntityRepository(Signin) -export class SigninRepository extends Repository { - public async pack( +export const SigninRepository = db.getRepository(Signin).extend({ + async pack( src: Signin, ) { return src; - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/user-group-invitation.ts b/packages/backend/src/models/repositories/user-group-invitation.ts index e338242c64..79ad019c99 100644 --- a/packages/backend/src/models/repositories/user-group-invitation.ts +++ b/packages/backend/src/models/repositories/user-group-invitation.ts @@ -1,23 +1,22 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { UserGroupInvitation } from '@/models/entities/user-group-invitation.js'; import { UserGroups } from '../index.js'; -@EntityRepository(UserGroupInvitation) -export class UserGroupInvitationRepository extends Repository { - public async pack( +export const UserGroupInvitationRepository = db.getRepository(UserGroupInvitation).extend({ + async pack( src: UserGroupInvitation['id'] | UserGroupInvitation, ) { - const invitation = typeof src === 'object' ? src : await this.findOneOrFail(src); + const invitation = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); return { id: invitation.id, group: await UserGroups.pack(invitation.userGroup || invitation.userGroupId), }; - } + }, - public packMany( + packMany( invitations: any[], ) { return Promise.all(invitations.map(x => this.pack(x))); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/user-group.ts b/packages/backend/src/models/repositories/user-group.ts index a9ffe7369e..6eb9234244 100644 --- a/packages/backend/src/models/repositories/user-group.ts +++ b/packages/backend/src/models/repositories/user-group.ts @@ -1,16 +1,15 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { UserGroup } from '@/models/entities/user-group.js'; import { UserGroupJoinings } from '../index.js'; import { Packed } from '@/misc/schema.js'; -@EntityRepository(UserGroup) -export class UserGroupRepository extends Repository { - public async pack( +export const UserGroupRepository = db.getRepository(UserGroup).extend({ + async pack( src: UserGroup['id'] | UserGroup, ): Promise> { - const userGroup = typeof src === 'object' ? src : await this.findOneOrFail(src); + const userGroup = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); - const users = await UserGroupJoinings.find({ + const users = await UserGroupJoinings.findBy({ userGroupId: userGroup.id, }); @@ -21,5 +20,5 @@ export class UserGroupRepository extends Repository { ownerId: userGroup.userId, userIds: users.map(x => x.userId), }; - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/user-list.ts b/packages/backend/src/models/repositories/user-list.ts index 0ea26427fe..2b6f411ef6 100644 --- a/packages/backend/src/models/repositories/user-list.ts +++ b/packages/backend/src/models/repositories/user-list.ts @@ -1,16 +1,15 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { UserList } from '@/models/entities/user-list.js'; import { UserListJoinings } from '../index.js'; import { Packed } from '@/misc/schema.js'; -@EntityRepository(UserList) -export class UserListRepository extends Repository { - public async pack( +export const UserListRepository = db.getRepository(UserList).extend({ + async pack( src: UserList['id'] | UserList, ): Promise> { - const userList = typeof src === 'object' ? src : await this.findOneOrFail(src); + const userList = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); - const users = await UserListJoinings.find({ + const users = await UserListJoinings.findBy({ userListId: userList.id, }); @@ -20,5 +19,5 @@ export class UserListRepository extends Repository { name: userList.name, userIds: users.map(x => x.userId), }; - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/user.ts b/packages/backend/src/models/repositories/user.ts index a909ab3ba6..2f4b7d6787 100644 --- a/packages/backend/src/models/repositories/user.ts +++ b/packages/backend/src/models/repositories/user.ts @@ -8,6 +8,11 @@ import { awaitAll, Promiseable } from '@/prelude/await-all.js'; import { populateEmojis } from '@/misc/populate-emojis.js'; import { getAntennas } from '@/misc/antenna-cache.js'; import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from '@/const.js'; +import { Cache } from '@/misc/cache.js'; +import { Instance } from '../entities/instance.js'; +import { db } from '@/db/postgre.js'; + +const userInstanceCache = new Cache(1000 * 60 * 60 * 3); type IsUserDetailed = Detailed extends true ? Packed<'UserDetailed'> : Packed<'UserLite'>; type IsMeAndIsUserDetailed = @@ -19,51 +24,69 @@ type IsMeAndIsUserDetailed { - public localUsernameSchema = { type: 'string', pattern: /^\w{1,20}$/.toString().slice(1, -1) } as const; - public passwordSchema = { type: 'string', minLength: 1 } as const; - public nameSchema = { type: 'string', minLength: 1, maxLength: 50 } as const; - public descriptionSchema = { type: 'string', minLength: 1, maxLength: 500 } as const; - public locationSchema = { type: 'string', minLength: 1, maxLength: 50 } as const; - public birthdaySchema = { type: 'string', pattern: /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.toString().slice(1, -1) } as const; +const localUsernameSchema = { type: 'string', pattern: /^\w{1,20}$/.toString().slice(1, -1) } as const; +const passwordSchema = { type: 'string', minLength: 1 } as const; +const nameSchema = { type: 'string', minLength: 1, maxLength: 50 } as const; +const descriptionSchema = { type: 'string', minLength: 1, maxLength: 500 } as const; +const locationSchema = { type: 'string', minLength: 1, maxLength: 50 } as const; +const birthdaySchema = { type: 'string', pattern: /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.toString().slice(1, -1) } as const; + +function isLocalUser(user: User): user is ILocalUser; +function isLocalUser(user: T): user is T & { host: null; }; +function isLocalUser(user: User | { host: User['host'] }): boolean { + return user.host == null; +} + +function isRemoteUser(user: User): user is IRemoteUser; +function isRemoteUser(user: T): user is T & { host: string; }; +function isRemoteUser(user: User | { host: User['host'] }): boolean { + return !isLocalUser(user); +} + +export const UserRepository = db.getRepository(User).extend({ + localUsernameSchema, + passwordSchema, + nameSchema, + descriptionSchema, + locationSchema, + birthdaySchema, //#region Validators - public validateLocalUsername = ajv.compile(this.localUsernameSchema); - public validatePassword = ajv.compile(this.passwordSchema); - public validateName = ajv.compile(this.nameSchema); - public validateDescription = ajv.compile(this.descriptionSchema); - public validateLocation = ajv.compile(this.locationSchema); - public validateBirthday = ajv.compile(this.birthdaySchema); + validateLocalUsername: ajv.compile(localUsernameSchema), + validatePassword: ajv.compile(passwordSchema), + validateName: ajv.compile(nameSchema), + validateDescription: ajv.compile(descriptionSchema), + validateLocation: ajv.compile(locationSchema), + validateBirthday: ajv.compile(birthdaySchema), //#endregion - public async getRelation(me: User['id'], target: User['id']) { + async getRelation(me: User['id'], target: User['id']) { const [following1, following2, followReq1, followReq2, toBlocking, fromBlocked, mute] = await Promise.all([ - Followings.findOne({ + Followings.findOneBy({ followerId: me, followeeId: target, }), - Followings.findOne({ + Followings.findOneBy({ followerId: target, followeeId: me, }), - FollowRequests.findOne({ + FollowRequests.findOneBy({ followerId: me, followeeId: target, }), - FollowRequests.findOne({ + FollowRequests.findOneBy({ followerId: target, followeeId: me, }), - Blockings.findOne({ + Blockings.findOneBy({ blockerId: me, blockeeId: target, }), - Blockings.findOne({ + Blockings.findOneBy({ blockerId: target, blockeeId: me, }), - Mutings.findOne({ + Mutings.findOneBy({ muterId: me, muteeId: target, }), @@ -79,14 +102,14 @@ export class UserRepository extends Repository { isBlocked: fromBlocked != null, isMuted: mute != null, }; - } + }, - public async getHasUnreadMessagingMessage(userId: User['id']): Promise { - const mute = await Mutings.find({ + async getHasUnreadMessagingMessage(userId: User['id']): Promise { + const mute = await Mutings.findBy({ muterId: userId, }); - const joinings = await UserGroupJoinings.find({ userId: userId }); + const joinings = await UserGroupJoinings.findBy({ userId: userId }); const groupQs = Promise.all(joinings.map(j => MessagingMessages.createQueryBuilder('message') .where(`message.groupId = :groupId`, { groupId: j.userGroupId }) @@ -108,44 +131,44 @@ export class UserRepository extends Repository { ]); return withUser || withGroups.some(x => x); - } + }, - public async getHasUnreadAnnouncement(userId: User['id']): Promise { - const reads = await AnnouncementReads.find({ + async getHasUnreadAnnouncement(userId: User['id']): Promise { + const reads = await AnnouncementReads.findBy({ userId: userId, }); - const count = await Announcements.count(reads.length > 0 ? { + const count = await Announcements.countBy(reads.length > 0 ? { id: Not(In(reads.map(read => read.announcementId))), } : {}); return count > 0; - } + }, - public async getHasUnreadAntenna(userId: User['id']): Promise { + async getHasUnreadAntenna(userId: User['id']): Promise { const myAntennas = (await getAntennas()).filter(a => a.userId === userId); - const unread = myAntennas.length > 0 ? await AntennaNotes.findOne({ + const unread = myAntennas.length > 0 ? await AntennaNotes.findOneBy({ antennaId: In(myAntennas.map(x => x.id)), read: false, }) : null; return unread != null; - } + }, - public async getHasUnreadChannel(userId: User['id']): Promise { - const channels = await ChannelFollowings.find({ followerId: userId }); + async getHasUnreadChannel(userId: User['id']): Promise { + const channels = await ChannelFollowings.findBy({ followerId: userId }); - const unread = channels.length > 0 ? await NoteUnreads.findOne({ + const unread = channels.length > 0 ? await NoteUnreads.findOneBy({ userId: userId, noteChannelId: In(channels.map(x => x.followeeId)), }) : null; return unread != null; - } + }, - public async getHasUnreadNotification(userId: User['id']): Promise { - const mute = await Mutings.find({ + async getHasUnreadNotification(userId: User['id']): Promise { + const mute = await Mutings.findBy({ muterId: userId, }); const mutedUserIds = mute.map(m => m.muteeId); @@ -160,17 +183,17 @@ export class UserRepository extends Repository { }); return count > 0; - } + }, - public async getHasPendingReceivedFollowRequest(userId: User['id']): Promise { - const count = await FollowRequests.count({ + async getHasPendingReceivedFollowRequest(userId: User['id']): Promise { + const count = await FollowRequests.countBy({ followeeId: userId, }); return count > 0; - } + }, - public getOnlineStatus(user: User): 'unknown' | 'online' | 'active' | 'offline' { + getOnlineStatus(user: User): 'unknown' | 'online' | 'active' | 'offline' { if (user.hideOnlineStatus) return 'unknown'; if (user.lastActiveDate == null) return 'unknown'; const elapsed = Date.now() - user.lastActiveDate.getTime(); @@ -179,22 +202,22 @@ export class UserRepository extends Repository { elapsed < USER_ACTIVE_THRESHOLD ? 'active' : 'offline' ); - } + }, - public getAvatarUrl(user: User): string { + getAvatarUrl(user: User): string { // TODO: avatarIdがあるがavatarがない(JOINされてない)場合のハンドリング if (user.avatar) { return DriveFiles.getPublicUrl(user.avatar, true) || this.getIdenticonUrl(user.id); } else { return this.getIdenticonUrl(user.id); } - } + }, - public getIdenticonUrl(userId: User['id']): string { + getIdenticonUrl(userId: User['id']): string { return `${config.url}/identicon/${userId}`; - } + }, - public async pack( + async pack( src: User['id'] | User, me?: { id: User['id'] } | null | undefined, options?: { @@ -211,11 +234,15 @@ export class UserRepository extends Repository { if (typeof src === 'object') { user = src; - if (src.avatar === undefined && src.avatarId) src.avatar = await DriveFiles.findOne(src.avatarId) ?? null; - if (src.banner === undefined && src.bannerId) src.banner = await DriveFiles.findOne(src.bannerId) ?? null; + if (src.avatar === undefined && src.avatarId) src.avatar = await DriveFiles.findOneBy({ id: src.avatarId }) ?? null; + if (src.banner === undefined && src.bannerId) src.banner = await DriveFiles.findOneBy({ id: src.bannerId }) ?? null; } else { - user = await this.findOneOrFail(src, { - relations: ['avatar', 'banner'], + user = await this.findOneOrFail({ + where: { id: src }, + relations: { + avatar: true, + banner: true, + }, }); } @@ -228,7 +255,7 @@ export class UserRepository extends Repository { .innerJoinAndSelect('pin.note', 'note') .orderBy('pin.id', 'DESC') .getMany() : []; - const profile = opts.detail ? await UserProfiles.findOneOrFail(user.id) : null; + const profile = opts.detail ? await UserProfiles.findOneByOrFail({ userId: user.id }) : null; const followingCount = profile == null ? null : (profile.ffVisibility === 'public') || isMe ? user.followingCount : @@ -254,8 +281,10 @@ export class UserRepository extends Repository { isModerator: user.isModerator || falsy, isBot: user.isBot || falsy, isCat: user.isCat || falsy, - showTimelineReplies: user.showTimelineReplies || falsy, - instance: user.host ? Instances.findOne({ host: user.host }).then(instance => instance ? { + instance: user.host ? userInstanceCache.fetch(user.host, + () => Instances.findOneBy({ host: user.host! }), + v => v != null + ).then(instance => instance ? { name: instance.name, softwareName: instance.softwareName, softwareVersion: instance.softwareVersion, @@ -297,7 +326,7 @@ export class UserRepository extends Repository { twoFactorEnabled: profile!.twoFactorEnabled, usePasswordLessLogin: profile!.usePasswordLessLogin, securityKeys: profile!.twoFactorEnabled - ? UserSecurityKeys.count({ + ? UserSecurityKeys.countBy({ userId: user.id, }).then(result => result >= 1) : false, @@ -334,6 +363,7 @@ export class UserRepository extends Repository { mutedInstances: profile!.mutedInstances, mutingNotificationTypes: profile!.mutingNotificationTypes, emailNotificationTypes: profile!.emailNotificationTypes, + showTimelineReplies: user.showTimelineReplies || falsy, } : {}), ...(opts.includeSecrets ? { @@ -344,7 +374,11 @@ export class UserRepository extends Repository { where: { userId: user.id, }, - select: ['id', 'name', 'lastUsed'], + select: { + id: true, + name: true, + lastUsed: true, + }, }) : [], } : {}), @@ -361,9 +395,9 @@ export class UserRepository extends Repository { } as Promiseable> as Promiseable>; return await awaitAll(packed); - } + }, - public packMany( + packMany( users: (User['id'] | User)[], me?: { id: User['id'] } | null | undefined, options?: { @@ -372,17 +406,8 @@ export class UserRepository extends Repository { } ): Promise[]> { return Promise.all(users.map(u => this.pack(u, me, options))); - } + }, - public isLocalUser(user: User): user is ILocalUser; - public isLocalUser(user: T): user is T & { host: null; }; - public isLocalUser(user: User | { host: User['host'] }): boolean { - return user.host == null; - } - - public isRemoteUser(user: User): user is IRemoteUser; - public isRemoteUser(user: T): user is T & { host: string; }; - public isRemoteUser(user: User | { host: User['host'] }): boolean { - return !this.isLocalUser(user); - } -} + isLocalUser, + isRemoteUser, +}); diff --git a/packages/backend/src/models/schema/emoji.ts b/packages/backend/src/models/schema/emoji.ts index 5f9af88db4..e97fdd5ef6 100644 --- a/packages/backend/src/models/schema/emoji.ts +++ b/packages/backend/src/models/schema/emoji.ts @@ -27,6 +27,7 @@ export const packedEmojiSchema = { host: { type: 'string', optional: false, nullable: true, + description: 'The local host is represented with `null`.', }, url: { type: 'string', diff --git a/packages/backend/src/models/schema/user.ts b/packages/backend/src/models/schema/user.ts index 616bedc0dc..253681695d 100644 --- a/packages/backend/src/models/schema/user.ts +++ b/packages/backend/src/models/schema/user.ts @@ -21,6 +21,7 @@ export const packedUserLiteSchema = { type: 'string', nullable: true, optional: false, example: 'misskey.example.com', + description: 'The local host is represented with `null`.', }, avatarUrl: { type: 'string', diff --git a/packages/backend/src/queue/index.ts b/packages/backend/src/queue/index.ts index 50bcccbb7f..a570400b7b 100644 --- a/packages/backend/src/queue/index.ts +++ b/packages/backend/src/queue/index.ts @@ -8,13 +8,15 @@ import processInbox from './processors/inbox.js'; import processDb from './processors/db/index.js'; import processObjectStorage from './processors/object-storage/index.js'; import processSystemQueue from './processors/system/index.js'; +import processWebhookDeliver from './processors/webhook-deliver.js'; import { endedPollNotification } from './processors/ended-poll-notification.js'; import { queueLogger } from './logger.js'; import { DriveFile } from '@/models/entities/drive-file.js'; import { getJobInfo } from './get-job-info.js'; -import { systemQueue, dbQueue, deliverQueue, inboxQueue, objectStorageQueue, endedPollNotificationQueue } from './queues.js'; +import { systemQueue, dbQueue, deliverQueue, inboxQueue, objectStorageQueue, endedPollNotificationQueue, webhookDeliverQueue } from './queues.js'; import { ThinUser } from './types.js'; import { IActivity } from '@/remote/activitypub/type.js'; +import { Webhook } from '@/models/entities/webhook.js'; function renderError(e: Error): any { return { @@ -26,6 +28,7 @@ function renderError(e: Error): any { const systemLogger = queueLogger.createSubLogger('system'); const deliverLogger = queueLogger.createSubLogger('deliver'); +const webhookLogger = queueLogger.createSubLogger('webhook'); const inboxLogger = queueLogger.createSubLogger('inbox'); const dbLogger = queueLogger.createSubLogger('db'); const objectStorageLogger = queueLogger.createSubLogger('objectStorage'); @@ -70,6 +73,14 @@ objectStorageQueue .on('error', (job: any, err: Error) => objectStorageLogger.error(`error ${err}`, { job, e: renderError(err) })) .on('stalled', (job) => objectStorageLogger.warn(`stalled id=${job.id}`)); +webhookDeliverQueue + .on('waiting', (jobId) => webhookLogger.debug(`waiting id=${jobId}`)) + .on('active', (job) => webhookLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`)) + .on('completed', (job, result) => webhookLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`)) + .on('failed', (job, err) => webhookLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job.data.to}`)) + .on('error', (job: any, err: Error) => webhookLogger.error(`error ${err}`, { job, e: renderError(err) })) + .on('stalled', (job) => webhookLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`)); + export function deliver(user: ThinUser, content: unknown, to: string | null) { if (content == null) return null; if (to == null) return null; @@ -251,18 +262,39 @@ export function createCleanRemoteFilesJob() { }); } +export function webhookDeliver(webhook: Webhook, content: unknown) { + const data = { + content, + webhookId: webhook.id, + to: webhook.url, + secret: webhook.secret, + }; + + return webhookDeliverQueue.add(data, { + attempts: 4, + timeout: 1 * 60 * 1000, // 1min + backoff: { + type: 'apBackoff', + }, + removeOnComplete: true, + removeOnFail: true, + }); +} + export default function() { if (envOption.onlyServer) return; deliverQueue.process(config.deliverJobConcurrency || 128, processDeliver); inboxQueue.process(config.inboxJobConcurrency || 16, processInbox); endedPollNotificationQueue.process(endedPollNotification); + webhookDeliverQueue.process(64, processWebhookDeliver); processDb(dbQueue); processObjectStorage(objectStorageQueue); systemQueue.add('tickCharts', { }, { repeat: { cron: '55 * * * *' }, + removeOnComplete: true, }); systemQueue.add('resyncCharts', { @@ -278,6 +310,7 @@ export default function() { systemQueue.add('checkExpiredMutings', { }, { repeat: { cron: '*/5 * * * *' }, + removeOnComplete: true, }); processSystemQueue(systemQueue); diff --git a/packages/backend/src/queue/processors/db/delete-account.ts b/packages/backend/src/queue/processors/db/delete-account.ts index dbc1f16a46..c1657b4be6 100644 --- a/packages/backend/src/queue/processors/db/delete-account.ts +++ b/packages/backend/src/queue/processors/db/delete-account.ts @@ -13,7 +13,7 @@ const logger = queueLogger.createSubLogger('delete-account'); export async function deleteAccount(job: Bull.Job): Promise { logger.info(`Deleting account of ${job.data.user.id} ...`); - const user = await Users.findOne(job.data.user.id); + const user = await Users.findOneBy({ id: job.data.user.id }); if (user == null) { return; } @@ -75,7 +75,7 @@ export async function deleteAccount(job: Bull.Job): Promise } { // Send email notification - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); if (profile.email && profile.emailVerified) { sendEmail(profile.email, 'Account deleted', `Your account has been deleted.`, diff --git a/packages/backend/src/queue/processors/db/delete-drive-files.ts b/packages/backend/src/queue/processors/db/delete-drive-files.ts index f6a8699855..b3832d9f04 100644 --- a/packages/backend/src/queue/processors/db/delete-drive-files.ts +++ b/packages/backend/src/queue/processors/db/delete-drive-files.ts @@ -11,7 +11,7 @@ const logger = queueLogger.createSubLogger('delete-drive-files'); export async function deleteDriveFiles(job: Bull.Job, done: any): Promise { logger.info(`Deleting drive files of ${job.data.user.id} ...`); - const user = await Users.findOne(job.data.user.id); + const user = await Users.findOneBy({ id: job.data.user.id }); if (user == null) { done(); return; @@ -44,7 +44,7 @@ export async function deleteDriveFiles(job: Bull.Job, done: any): deletedCount++; } - const total = await DriveFiles.count({ + const total = await DriveFiles.countBy({ userId: user.id, }); diff --git a/packages/backend/src/queue/processors/db/export-blocking.ts b/packages/backend/src/queue/processors/db/export-blocking.ts index 83f1ec8fd6..166c9e4cd3 100644 --- a/packages/backend/src/queue/processors/db/export-blocking.ts +++ b/packages/backend/src/queue/processors/db/export-blocking.ts @@ -15,7 +15,7 @@ const logger = queueLogger.createSubLogger('export-blocking'); export async function exportBlocking(job: Bull.Job, done: any): Promise { logger.info(`Exporting blocking of ${job.data.user.id} ...`); - const user = await Users.findOne(job.data.user.id); + const user = await Users.findOneBy({ id: job.data.user.id }); if (user == null) { done(); return; @@ -56,7 +56,7 @@ export async function exportBlocking(job: Bull.Job, done: any): P cursor = blockings[blockings.length - 1].id; for (const block of blockings) { - const u = await Users.findOne({ id: block.blockeeId }); + const u = await Users.findOneBy({ id: block.blockeeId }); if (u == null) { exportedCount++; continue; } @@ -75,7 +75,7 @@ export async function exportBlocking(job: Bull.Job, done: any): P exportedCount++; } - const total = await Blockings.count({ + const total = await Blockings.countBy({ blockerId: user.id, }); diff --git a/packages/backend/src/queue/processors/db/export-custom-emojis.ts b/packages/backend/src/queue/processors/db/export-custom-emojis.ts index a65b46cc00..c2467fb5f0 100644 --- a/packages/backend/src/queue/processors/db/export-custom-emojis.ts +++ b/packages/backend/src/queue/processors/db/export-custom-emojis.ts @@ -12,13 +12,14 @@ import { Users, Emojis } from '@/models/index.js'; import { } from '@/queue/types.js'; import { downloadUrl } from '@/misc/download-url.js'; import config from '@/config/index.js'; +import { IsNull } from 'typeorm'; const logger = queueLogger.createSubLogger('export-custom-emojis'); export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promise { logger.info(`Exporting custom emojis ...`); - const user = await Users.findOne(job.data.user.id); + const user = await Users.findOneBy({ id: job.data.user.id }); if (user == null) { done(); return; @@ -57,7 +58,7 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi const customEmojis = await Emojis.find({ where: { - host: null, + host: IsNull(), }, order: { id: 'ASC', diff --git a/packages/backend/src/queue/processors/db/export-following.ts b/packages/backend/src/queue/processors/db/export-following.ts index 162862180b..965500ac27 100644 --- a/packages/backend/src/queue/processors/db/export-following.ts +++ b/packages/backend/src/queue/processors/db/export-following.ts @@ -16,7 +16,7 @@ const logger = queueLogger.createSubLogger('export-following'); export async function exportFollowing(job: Bull.Job, done: () => void): Promise { logger.info(`Exporting following of ${job.data.user.id} ...`); - const user = await Users.findOne(job.data.user.id); + const user = await Users.findOneBy({ id: job.data.user.id }); if (user == null) { done(); return; @@ -36,7 +36,7 @@ export async function exportFollowing(job: Bull.Job, done: () => let cursor: Following['id'] | null = null; - const mutings = job.data.excludeMuting ? await Mutings.find({ + const mutings = job.data.excludeMuting ? await Mutings.findBy({ muterId: user.id, }) : []; @@ -60,7 +60,7 @@ export async function exportFollowing(job: Bull.Job, done: () => cursor = followings[followings.length - 1].id; for (const following of followings) { - const u = await Users.findOne({ id: following.followeeId }); + const u = await Users.findOneBy({ id: following.followeeId }); if (u == null) { continue; } diff --git a/packages/backend/src/queue/processors/db/export-mute.ts b/packages/backend/src/queue/processors/db/export-mute.ts index 9fb144abb2..0ef81971f1 100644 --- a/packages/backend/src/queue/processors/db/export-mute.ts +++ b/packages/backend/src/queue/processors/db/export-mute.ts @@ -15,7 +15,7 @@ const logger = queueLogger.createSubLogger('export-mute'); export async function exportMute(job: Bull.Job, done: any): Promise { logger.info(`Exporting mute of ${job.data.user.id} ...`); - const user = await Users.findOne(job.data.user.id); + const user = await Users.findOneBy({ id: job.data.user.id }); if (user == null) { done(); return; @@ -57,7 +57,7 @@ export async function exportMute(job: Bull.Job, done: any): Promi cursor = mutes[mutes.length - 1].id; for (const mute of mutes) { - const u = await Users.findOne({ id: mute.muteeId }); + const u = await Users.findOneBy({ id: mute.muteeId }); if (u == null) { exportedCount++; continue; } @@ -76,7 +76,7 @@ export async function exportMute(job: Bull.Job, done: any): Promi exportedCount++; } - const total = await Mutings.count({ + const total = await Mutings.countBy({ muterId: user.id, }); diff --git a/packages/backend/src/queue/processors/db/export-notes.ts b/packages/backend/src/queue/processors/db/export-notes.ts index c79679366c..7e12a6fac2 100644 --- a/packages/backend/src/queue/processors/db/export-notes.ts +++ b/packages/backend/src/queue/processors/db/export-notes.ts @@ -16,7 +16,7 @@ const logger = queueLogger.createSubLogger('export-notes'); export async function exportNotes(job: Bull.Job, done: any): Promise { logger.info(`Exporting notes of ${job.data.user.id} ...`); - const user = await Users.findOne(job.data.user.id); + const user = await Users.findOneBy({ id: job.data.user.id }); if (user == null) { done(); return; @@ -74,7 +74,7 @@ export async function exportNotes(job: Bull.Job, done: any): Prom for (const note of notes) { let poll: Poll | undefined; if (note.hasPoll) { - poll = await Polls.findOneOrFail({ noteId: note.id }); + poll = await Polls.findOneByOrFail({ noteId: note.id }); } const content = JSON.stringify(serialize(note, poll)); const isFirst = exportedNotesCount === 0; @@ -82,7 +82,7 @@ export async function exportNotes(job: Bull.Job, done: any): Prom exportedNotesCount++; } - const total = await Notes.count({ + const total = await Notes.countBy({ userId: user.id, }); diff --git a/packages/backend/src/queue/processors/db/export-user-lists.ts b/packages/backend/src/queue/processors/db/export-user-lists.ts index 1c04c36789..45852a6038 100644 --- a/packages/backend/src/queue/processors/db/export-user-lists.ts +++ b/packages/backend/src/queue/processors/db/export-user-lists.ts @@ -15,13 +15,13 @@ const logger = queueLogger.createSubLogger('export-user-lists'); export async function exportUserLists(job: Bull.Job, done: any): Promise { logger.info(`Exporting user lists of ${job.data.user.id} ...`); - const user = await Users.findOne(job.data.user.id); + const user = await Users.findOneBy({ id: job.data.user.id }); if (user == null) { done(); return; } - const lists = await UserLists.find({ + const lists = await UserLists.findBy({ userId: user.id, }); @@ -38,8 +38,8 @@ export async function exportUserLists(job: Bull.Job, done: any): const stream = fs.createWriteStream(path, { flags: 'a' }); for (const list of lists) { - const joinings = await UserListJoinings.find({ userListId: list.id }); - const users = await Users.find({ + const joinings = await UserListJoinings.findBy({ userListId: list.id }); + const users = await Users.findBy({ id: In(joinings.map(j => j.userId)), }); diff --git a/packages/backend/src/queue/processors/db/import-blocking.ts b/packages/backend/src/queue/processors/db/import-blocking.ts index 857c2629e3..8bddf34bc2 100644 --- a/packages/backend/src/queue/processors/db/import-blocking.ts +++ b/packages/backend/src/queue/processors/db/import-blocking.ts @@ -8,19 +8,20 @@ import { isSelfHost, toPuny } from '@/misc/convert-host.js'; import { Users, DriveFiles, Blockings } from '@/models/index.js'; import { DbUserImportJobData } from '@/queue/types.js'; import block from '@/services/blocking/create.js'; +import { IsNull } from 'typeorm'; const logger = queueLogger.createSubLogger('import-blocking'); export async function importBlocking(job: Bull.Job, done: any): Promise { logger.info(`Importing blocking of ${job.data.user.id} ...`); - const user = await Users.findOne(job.data.user.id); + const user = await Users.findOneBy({ id: job.data.user.id }); if (user == null) { done(); return; } - const file = await DriveFiles.findOne({ + const file = await DriveFiles.findOneBy({ id: job.data.fileId, }); if (file == null) { @@ -39,10 +40,10 @@ export async function importBlocking(job: Bull.Job, done: a const acct = line.split(',')[0].trim(); const { username, host } = Acct.parse(acct); - let target = isSelfHost(host!) ? await Users.findOne({ - host: null, + let target = isSelfHost(host!) ? await Users.findOneBy({ + host: IsNull(), usernameLower: username.toLowerCase(), - }) : await Users.findOne({ + }) : await Users.findOneBy({ host: toPuny(host!), usernameLower: username.toLowerCase(), }); diff --git a/packages/backend/src/queue/processors/db/import-custom-emojis.ts b/packages/backend/src/queue/processors/db/import-custom-emojis.ts index f862276b47..28e0b867a4 100644 --- a/packages/backend/src/queue/processors/db/import-custom-emojis.ts +++ b/packages/backend/src/queue/processors/db/import-custom-emojis.ts @@ -2,7 +2,6 @@ import Bull from 'bull'; import * as tmp from 'tmp'; import * as fs from 'node:fs'; import unzipper from 'unzipper'; -import { getConnection } from 'typeorm'; import { queueLogger } from '../../logger.js'; import { downloadUrl } from '@/misc/download-url.js'; @@ -10,6 +9,7 @@ import { DriveFiles, Emojis } from '@/models/index.js'; import { DbUserImportJobData } from '@/queue/types.js'; import { addFile } from '@/services/drive/add-file.js'; import { genId } from '@/misc/gen-id.js'; +import { db } from '@/db/postgre.js'; const logger = queueLogger.createSubLogger('import-custom-emojis'); @@ -17,7 +17,7 @@ const logger = queueLogger.createSubLogger('import-custom-emojis'); export async function importCustomEmojis(job: Bull.Job, done: any): Promise { logger.info(`Importing custom emojis ...`); - const file = await DriveFiles.findOne({ + const file = await DriveFiles.findOneBy({ id: job.data.fileId, }); if (file == null) { @@ -72,10 +72,10 @@ export async function importCustomEmojis(job: Bull.Job, don originalUrl: driveFile.url, publicUrl: driveFile.webpublicUrl ?? driveFile.url, type: driveFile.webpublicType ?? driveFile.type, - }).then(x => Emojis.findOneOrFail(x.identifiers[0])); + }).then(x => Emojis.findOneByOrFail(x.identifiers[0])); } - await getConnection().queryResultCache!.remove(['meta_emojis']); + await db.queryResultCache!.remove(['meta_emojis']); cleanup(); diff --git a/packages/backend/src/queue/processors/db/import-following.ts b/packages/backend/src/queue/processors/db/import-following.ts index 235fc28394..8ce2c367d6 100644 --- a/packages/backend/src/queue/processors/db/import-following.ts +++ b/packages/backend/src/queue/processors/db/import-following.ts @@ -8,19 +8,20 @@ import { downloadTextFile } from '@/misc/download-text-file.js'; import { isSelfHost, toPuny } from '@/misc/convert-host.js'; import { Users, DriveFiles } from '@/models/index.js'; import { DbUserImportJobData } from '@/queue/types.js'; +import { IsNull } from 'typeorm'; const logger = queueLogger.createSubLogger('import-following'); export async function importFollowing(job: Bull.Job, done: any): Promise { logger.info(`Importing following of ${job.data.user.id} ...`); - const user = await Users.findOne(job.data.user.id); + const user = await Users.findOneBy({ id: job.data.user.id }); if (user == null) { done(); return; } - const file = await DriveFiles.findOne({ + const file = await DriveFiles.findOneBy({ id: job.data.fileId, }); if (file == null) { @@ -39,10 +40,10 @@ export async function importFollowing(job: Bull.Job, done: const acct = line.split(',')[0].trim(); const { username, host } = Acct.parse(acct); - let target = isSelfHost(host!) ? await Users.findOne({ - host: null, + let target = isSelfHost(host!) ? await Users.findOneBy({ + host: IsNull(), usernameLower: username.toLowerCase(), - }) : await Users.findOne({ + }) : await Users.findOneBy({ host: toPuny(host!), usernameLower: username.toLowerCase(), }); diff --git a/packages/backend/src/queue/processors/db/import-muting.ts b/packages/backend/src/queue/processors/db/import-muting.ts index 32f5f6bbee..8552b797be 100644 --- a/packages/backend/src/queue/processors/db/import-muting.ts +++ b/packages/backend/src/queue/processors/db/import-muting.ts @@ -9,19 +9,20 @@ import { Users, DriveFiles, Mutings } from '@/models/index.js'; import { DbUserImportJobData } from '@/queue/types.js'; import { User } from '@/models/entities/user.js'; import { genId } from '@/misc/gen-id.js'; +import { IsNull } from 'typeorm'; const logger = queueLogger.createSubLogger('import-muting'); export async function importMuting(job: Bull.Job, done: any): Promise { logger.info(`Importing muting of ${job.data.user.id} ...`); - const user = await Users.findOne(job.data.user.id); + const user = await Users.findOneBy({ id: job.data.user.id }); if (user == null) { done(); return; } - const file = await DriveFiles.findOne({ + const file = await DriveFiles.findOneBy({ id: job.data.fileId, }); if (file == null) { @@ -40,10 +41,10 @@ export async function importMuting(job: Bull.Job, done: any const acct = line.split(',')[0].trim(); const { username, host } = Acct.parse(acct); - let target = isSelfHost(host!) ? await Users.findOne({ - host: null, + let target = isSelfHost(host!) ? await Users.findOneBy({ + host: IsNull(), usernameLower: username.toLowerCase(), - }) : await Users.findOne({ + }) : await Users.findOneBy({ host: toPuny(host!), usernameLower: username.toLowerCase(), }); diff --git a/packages/backend/src/queue/processors/db/import-user-lists.ts b/packages/backend/src/queue/processors/db/import-user-lists.ts index ae263e19b0..9919b7c53c 100644 --- a/packages/backend/src/queue/processors/db/import-user-lists.ts +++ b/packages/backend/src/queue/processors/db/import-user-lists.ts @@ -9,19 +9,20 @@ import { isSelfHost, toPuny } from '@/misc/convert-host.js'; import { DriveFiles, Users, UserLists, UserListJoinings } from '@/models/index.js'; import { genId } from '@/misc/gen-id.js'; import { DbUserImportJobData } from '@/queue/types.js'; +import { IsNull } from 'typeorm'; const logger = queueLogger.createSubLogger('import-user-lists'); export async function importUserLists(job: Bull.Job, done: any): Promise { logger.info(`Importing user lists of ${job.data.user.id} ...`); - const user = await Users.findOne(job.data.user.id); + const user = await Users.findOneBy({ id: job.data.user.id }); if (user == null) { done(); return; } - const file = await DriveFiles.findOne({ + const file = await DriveFiles.findOneBy({ id: job.data.fileId, }); if (file == null) { @@ -40,7 +41,7 @@ export async function importUserLists(job: Bull.Job, done: const listName = line.split(',')[0].trim(); const { username, host } = Acct.parse(line.split(',')[1].trim()); - let list = await UserLists.findOne({ + let list = await UserLists.findOneBy({ userId: user.id, name: listName, }); @@ -51,13 +52,13 @@ export async function importUserLists(job: Bull.Job, done: createdAt: new Date(), userId: user.id, name: listName, - }).then(x => UserLists.findOneOrFail(x.identifiers[0])); + }).then(x => UserLists.findOneByOrFail(x.identifiers[0])); } - let target = isSelfHost(host!) ? await Users.findOne({ - host: null, + let target = isSelfHost(host!) ? await Users.findOneBy({ + host: IsNull(), usernameLower: username.toLowerCase(), - }) : await Users.findOne({ + }) : await Users.findOneBy({ host: toPuny(host!), usernameLower: username.toLowerCase(), }); @@ -66,7 +67,7 @@ export async function importUserLists(job: Bull.Job, done: target = await resolveUser(username, host); } - if (await UserListJoinings.findOne({ userListId: list!.id, userId: target.id }) != null) continue; + if (await UserListJoinings.findOneBy({ userListId: list!.id, userId: target.id }) != null) continue; pushUserToUserList(target, list!); } catch (e) { diff --git a/packages/backend/src/queue/processors/ended-poll-notification.ts b/packages/backend/src/queue/processors/ended-poll-notification.ts index afac27921f..6151c96ad6 100644 --- a/packages/backend/src/queue/processors/ended-poll-notification.ts +++ b/packages/backend/src/queue/processors/ended-poll-notification.ts @@ -8,7 +8,7 @@ import { createNotification } from '@/services/create-notification.js'; const logger = queueLogger.createSubLogger('ended-poll-notification'); export async function endedPollNotification(job: Bull.Job, done: any): Promise { - const note = await Notes.findOne(job.data.noteId); + const note = await Notes.findOneBy({ id: job.data.noteId }); if (note == null || !note.hasPoll) { done(); return; diff --git a/packages/backend/src/queue/processors/inbox.ts b/packages/backend/src/queue/processors/inbox.ts index 1b3f94b700..4fbfdb234f 100644 --- a/packages/backend/src/queue/processors/inbox.ts +++ b/packages/backend/src/queue/processors/inbox.ts @@ -15,6 +15,8 @@ import DbResolver from '@/remote/activitypub/db-resolver.js'; import { resolvePerson } from '@/remote/activitypub/models/person.js'; import { LdSignature } from '@/remote/activitypub/misc/ld-signature.js'; import { StatusError } from '@/misc/fetch.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { UserPublickey } from '@/models/entities/user-publickey.js'; const logger = new Logger('inbox'); @@ -42,11 +44,13 @@ export default async (job: Bull.Job): Promise => { return `Old keyId is no longer supported. ${keyIdLower}`; } - // TDOO: キャッシュ const dbResolver = new DbResolver(); // HTTP-Signature keyIdを元にDBから取得 - let authUser = await dbResolver.getAuthUserFromKeyId(signature.keyId); + let authUser: { + user: CacheableRemoteUser; + key: UserPublickey | null; + } | null = await dbResolver.getAuthUserFromKeyId(signature.keyId); // keyIdでわからなければ、activity.actorを元にDBから取得 || activity.actorを元にリモートから取得 if (authUser == null) { diff --git a/packages/backend/src/queue/processors/object-storage/clean-remote-files.ts b/packages/backend/src/queue/processors/object-storage/clean-remote-files.ts index 7d71a20adb..77da162f6e 100644 --- a/packages/backend/src/queue/processors/object-storage/clean-remote-files.ts +++ b/packages/backend/src/queue/processors/object-storage/clean-remote-files.ts @@ -37,7 +37,7 @@ export default async function cleanRemoteFiles(job: Bull.Job) => { + try { + if (latest !== (latest = JSON.stringify(job.data.content, null, 2))) { + logger.debug(`delivering ${latest}`); + } + + const res = await getResponse({ + url: job.data.to, + method: 'POST', + headers: { + 'User-Agent': 'Misskey-Hooks', + 'X-Misskey-Host': config.host, + 'X-Misskey-Hook-Id': job.data.webhookId, + 'X-Misskey-Hook-Secret': job.data.secret, + }, + body: JSON.stringify(job.data.content), + }); + + Webhooks.update({ id: job.data.webhookId }, { + latestSentAt: new Date(), + latestStatus: res.status, + }); + + return 'Success'; + } catch (res) { + Webhooks.update({ id: job.data.webhookId }, { + latestSentAt: new Date(), + latestStatus: res instanceof StatusError ? res.statusCode : 1, + }); + + if (res instanceof StatusError) { + // 4xx + if (res.isClientError) { + return `${res.statusCode} ${res.statusMessage}`; + } + + // 5xx etc. + throw `${res.statusCode} ${res.statusMessage}`; + } else { + // DNS error, socket error, timeout ... + throw res; + } + } +}; diff --git a/packages/backend/src/queue/queues.ts b/packages/backend/src/queue/queues.ts index 02df587365..f3a267790c 100644 --- a/packages/backend/src/queue/queues.ts +++ b/packages/backend/src/queue/queues.ts @@ -1,6 +1,6 @@ import config from '@/config/index.js'; import { initialize as initializeQueue } from './initialize.js'; -import { DeliverJobData, InboxJobData, DbJobData, ObjectStorageJobData, EndedPollNotificationJobData } from './types.js'; +import { DeliverJobData, InboxJobData, DbJobData, ObjectStorageJobData, EndedPollNotificationJobData, WebhookDeliverJobData } from './types.js'; export const systemQueue = initializeQueue>('system'); export const endedPollNotificationQueue = initializeQueue('endedPollNotification'); @@ -8,3 +8,14 @@ export const deliverQueue = initializeQueue('deliver', config.de export const inboxQueue = initializeQueue('inbox', config.inboxJobPerSec || 16); export const dbQueue = initializeQueue('db'); export const objectStorageQueue = initializeQueue('objectStorage'); +export const webhookDeliverQueue = initializeQueue('webhookDeliver', 64); + +export const queues = [ + systemQueue, + endedPollNotificationQueue, + deliverQueue, + inboxQueue, + dbQueue, + objectStorageQueue, + webhookDeliverQueue, +]; diff --git a/packages/backend/src/queue/types.ts b/packages/backend/src/queue/types.ts index 5191caea4c..8aeacf4625 100644 --- a/packages/backend/src/queue/types.ts +++ b/packages/backend/src/queue/types.ts @@ -1,6 +1,7 @@ import { DriveFile } from '@/models/entities/drive-file.js'; import { Note } from '@/models/entities/note'; import { User } from '@/models/entities/user.js'; +import { Webhook } from '@/models/entities/webhook'; import { IActivity } from '@/remote/activitypub/type.js'; import httpSignature from 'http-signature'; @@ -46,6 +47,13 @@ export type EndedPollNotificationJobData = { noteId: Note['id']; }; +export type WebhookDeliverJobData = { + content: unknown; + webhookId: Webhook['id']; + to: string; + secret: string; +}; + export type ThinUser = { id: User['id']; }; diff --git a/packages/backend/src/remote/activitypub/audience.ts b/packages/backend/src/remote/activitypub/audience.ts index ba69b11e85..846ccf9c00 100644 --- a/packages/backend/src/remote/activitypub/audience.ts +++ b/packages/backend/src/remote/activitypub/audience.ts @@ -3,26 +3,26 @@ import Resolver from './resolver.js'; import { resolvePerson } from './models/person.js'; import { unique, concat } from '@/prelude/array.js'; import promiseLimit from 'promise-limit'; -import { User, IRemoteUser } from '@/models/entities/user.js'; +import { User, CacheableRemoteUser, CacheableUser } from '@/models/entities/user.js'; type Visibility = 'public' | 'home' | 'followers' | 'specified'; type AudienceInfo = { visibility: Visibility, - mentionedUsers: User[], - visibleUsers: User[], + mentionedUsers: CacheableUser[], + visibleUsers: CacheableUser[], }; -export async function parseAudience(actor: IRemoteUser, to?: ApObject, cc?: ApObject, resolver?: Resolver): Promise { +export async function parseAudience(actor: CacheableRemoteUser, to?: ApObject, cc?: ApObject, resolver?: Resolver): Promise { const toGroups = groupingAudience(getApIds(to), actor); const ccGroups = groupingAudience(getApIds(cc), actor); const others = unique(concat([toGroups.other, ccGroups.other])); - const limit = promiseLimit(2); + const limit = promiseLimit(2); const mentionedUsers = (await Promise.all( others.map(id => limit(() => resolvePerson(id, resolver).catch(() => null))) - )).filter((x): x is User => x != null); + )).filter((x): x is CacheableUser => x != null); if (toGroups.public.length > 0) { return { @@ -55,7 +55,7 @@ export async function parseAudience(actor: IRemoteUser, to?: ApObject, cc?: ApOb }; } -function groupingAudience(ids: string[], actor: IRemoteUser) { +function groupingAudience(ids: string[], actor: CacheableRemoteUser) { const groups = { public: [] as string[], followers: [] as string[], @@ -85,7 +85,7 @@ function isPublic(id: string) { ].includes(id); } -function isFollowers(id: string, actor: IRemoteUser) { +function isFollowers(id: string, actor: CacheableRemoteUser) { return ( id === (actor.followersUri || `${actor.uri}/followers`) ); diff --git a/packages/backend/src/remote/activitypub/db-resolver.ts b/packages/backend/src/remote/activitypub/db-resolver.ts index 9281e494d0..ef07966e42 100644 --- a/packages/backend/src/remote/activitypub/db-resolver.ts +++ b/packages/backend/src/remote/activitypub/db-resolver.ts @@ -1,12 +1,17 @@ +import escapeRegexp from 'escape-regexp'; import config from '@/config/index.js'; import { Note } from '@/models/entities/note.js'; -import { User, IRemoteUser } from '@/models/entities/user.js'; +import { User, IRemoteUser, CacheableRemoteUser, CacheableUser } from '@/models/entities/user.js'; import { UserPublickey } from '@/models/entities/user-publickey.js'; import { MessagingMessage } from '@/models/entities/messaging-message.js'; import { Notes, Users, UserPublickeys, MessagingMessages } from '@/models/index.js'; import { IObject, getApId } from './type.js'; import { resolvePerson } from './models/person.js'; -import escapeRegexp from 'escape-regexp'; +import { Cache } from '@/misc/cache.js'; +import { uriPersonCache, userByIdCache } from '@/services/user-cache.js'; + +const publicKeyCache = new Cache(Infinity); +const publicKeyByUserIdCache = new Cache(Infinity); export default class DbResolver { constructor() { @@ -19,15 +24,15 @@ export default class DbResolver { const parsed = this.parseUri(value); if (parsed.id) { - return (await Notes.findOne({ + return await Notes.findOneBy({ id: parsed.id, - })) || null; + }); } if (parsed.uri) { - return (await Notes.findOne({ + return await Notes.findOneBy({ uri: parsed.uri, - })) || null; + }); } return null; @@ -37,15 +42,15 @@ export default class DbResolver { const parsed = this.parseUri(value); if (parsed.id) { - return (await MessagingMessages.findOne({ + return await MessagingMessages.findOneBy({ id: parsed.id, - })) || null; + }); } if (parsed.uri) { - return (await MessagingMessages.findOne({ + return await MessagingMessages.findOneBy({ uri: parsed.uri, - })) || null; + }); } return null; @@ -54,19 +59,19 @@ export default class DbResolver { /** * AP Person => Misskey User in DB */ - public async getUserFromApId(value: string | IObject): Promise { + public async getUserFromApId(value: string | IObject): Promise { const parsed = this.parseUri(value); if (parsed.id) { - return (await Users.findOne({ + return await userByIdCache.fetchMaybe(parsed.id, () => Users.findOneBy({ id: parsed.id, - })) || null; + }).then(x => x ?? undefined)) ?? null; } if (parsed.uri) { - return (await Users.findOne({ + return await uriPersonCache.fetch(parsed.uri, () => Users.findOneBy({ uri: parsed.uri, - })) || null; + })); } return null; @@ -75,17 +80,24 @@ export default class DbResolver { /** * AP KeyId => Misskey User and Key */ - public async getAuthUserFromKeyId(keyId: string): Promise { - const key = await UserPublickeys.findOne({ - keyId, - }); + public async getAuthUserFromKeyId(keyId: string): Promise<{ + user: CacheableRemoteUser; + key: UserPublickey; + } | null> { + const key = await publicKeyCache.fetch(keyId, async () => { + const key = await UserPublickeys.findOneBy({ + keyId, + }); + + if (key == null) return null; + + return key; + }, key => key != null); if (key == null) return null; - const user = await Users.findOne(key.userId) as IRemoteUser; - return { - user, + user: await userByIdCache.fetch(key.userId, () => Users.findOneByOrFail({ id: key.userId })) as CacheableRemoteUser, key, }; } @@ -93,12 +105,15 @@ export default class DbResolver { /** * AP Actor id => Misskey User and Key */ - public async getAuthUserFromApId(uri: string): Promise { - const user = await resolvePerson(uri) as IRemoteUser; + public async getAuthUserFromApId(uri: string): Promise<{ + user: CacheableRemoteUser; + key: UserPublickey | null; + } | null> { + const user = await resolvePerson(uri) as CacheableRemoteUser; if (user == null) return null; - const key = await UserPublickeys.findOne(user.id); + const key = await publicKeyByUserIdCache.fetch(user.id, () => UserPublickeys.findOneBy({ userId: user.id }), v => v != null); return { user, @@ -125,11 +140,6 @@ export default class DbResolver { } } -export type AuthUser = { - user: IRemoteUser; - key?: UserPublickey; -}; - type UriParseResult = { /** id in DB (local object only) */ id?: string; diff --git a/packages/backend/src/remote/activitypub/deliver-manager.ts b/packages/backend/src/remote/activitypub/deliver-manager.ts index 9c4e3418ff..f95f64f77c 100644 --- a/packages/backend/src/remote/activitypub/deliver-manager.ts +++ b/packages/backend/src/remote/activitypub/deliver-manager.ts @@ -1,6 +1,7 @@ import { Users, Followings } from '@/models/index.js'; import { ILocalUser, IRemoteUser, User } from '@/models/entities/user.js'; import { deliver } from '@/queue/index.js'; +import { IsNull, Not } from 'typeorm'; //#region types interface IRecipe { @@ -78,27 +79,46 @@ export default class DeliverManager { const inboxes = new Set(); - // build inbox list - for (const recipe of this.recipes) { - if (isFollowers(recipe)) { - // followers deliver - const followers = await Followings.find({ - followeeId: this.actor.id, - }); + /* + build inbox list - for (const following of followers) { - if (Followings.isRemoteFollower(following)) { - const inbox = following.followerSharedInbox || following.followerInbox; - inboxes.add(inbox); - } - } - } else if (isDirect(recipe)) { - // direct deliver - const inbox = recipe.to.inbox; - if (inbox) inboxes.add(inbox); + Process follower recipes first to avoid duplication when processing + direct recipes later. + */ + if (this.recipes.some(r => isFollowers(r))) { + // followers deliver + // TODO: SELECT DISTINCT ON ("followerSharedInbox") "followerSharedInbox" みたいな問い合わせにすればよりパフォーマンス向上できそう + // ただ、sharedInboxがnullなリモートユーザーも稀におり、その対応ができなさそう? + const followers = await Followings.find({ + where: { + followeeId: this.actor.id, + followerHost: Not(IsNull()), + }, + select: { + followerSharedInbox: true, + followerInbox: true, + }, + }) as { + followerSharedInbox: string | null; + followerInbox: string; + }[]; + + for (const following of followers) { + const inbox = following.followerSharedInbox || following.followerInbox; + inboxes.add(inbox); } } + this.recipes.filter((recipe): recipe is IDirectRecipe => { + // followers recipes have already been processed + isDirect(recipe) + // check that shared inbox has not been added yet + && !(recipe.to.sharedInbox && inboxes.has(recipe.to.sharedInbox)) + // check that they actually have an inbox + && recipe.to.inbox + }) + .forEach(recipe => inboxes.add(recipe.to.inbox)); + // deliver for (const inbox of inboxes) { deliver(this.actor, this.activity, inbox); @@ -112,7 +132,7 @@ export default class DeliverManager { * @param activity Activity * @param from Followee */ -export async function deliverToFollowers(actor: ILocalUser, activity: any) { +export async function deliverToFollowers(actor: { id: ILocalUser['id']; host: null; }, activity: any) { const manager = new DeliverManager(actor, activity); manager.addFollowersRecipe(); await manager.execute(); @@ -123,7 +143,7 @@ export async function deliverToFollowers(actor: ILocalUser, activity: any) { * @param activity Activity * @param to Target user */ -export async function deliverToUser(actor: ILocalUser, activity: any, to: IRemoteUser) { +export async function deliverToUser(actor: { id: ILocalUser['id']; host: null; }, activity: any, to: IRemoteUser) { const manager = new DeliverManager(actor, activity); manager.addDirectRecipe(to); await manager.execute(); diff --git a/packages/backend/src/remote/activitypub/kernel/accept/follow.ts b/packages/backend/src/remote/activitypub/kernel/accept/follow.ts index 393516addf..4350ef1333 100644 --- a/packages/backend/src/remote/activitypub/kernel/accept/follow.ts +++ b/packages/backend/src/remote/activitypub/kernel/accept/follow.ts @@ -1,10 +1,10 @@ -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import accept from '@/services/following/requests/accept.js'; import { IFollow } from '../../type.js'; import DbResolver from '../../db-resolver.js'; import { relayAccepted } from '@/services/relay.js'; -export default async (actor: IRemoteUser, activity: IFollow): Promise => { +export default async (actor: CacheableRemoteUser, activity: IFollow): Promise => { // ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある const dbResolver = new DbResolver(); diff --git a/packages/backend/src/remote/activitypub/kernel/accept/index.ts b/packages/backend/src/remote/activitypub/kernel/accept/index.ts index 354bd4f6e1..78ef75ade3 100644 --- a/packages/backend/src/remote/activitypub/kernel/accept/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/accept/index.ts @@ -1,12 +1,12 @@ import Resolver from '../../resolver.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import acceptFollow from './follow.js'; import { IAccept, isFollow, getApType } from '../../type.js'; import { apLogger } from '../../logger.js'; const logger = apLogger; -export default async (actor: IRemoteUser, activity: IAccept): Promise => { +export default async (actor: CacheableRemoteUser, activity: IAccept): Promise => { const uri = activity.id || activity; logger.info(`Accept: ${uri}`); diff --git a/packages/backend/src/remote/activitypub/kernel/add/index.ts b/packages/backend/src/remote/activitypub/kernel/add/index.ts index 9a2fac1e74..c813414f93 100644 --- a/packages/backend/src/remote/activitypub/kernel/add/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/add/index.ts @@ -1,9 +1,9 @@ -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { IAdd } from '../../type.js'; import { resolveNote } from '../../models/note.js'; import { addPinned } from '@/services/i/pin.js'; -export default async (actor: IRemoteUser, activity: IAdd): Promise => { +export default async (actor: CacheableRemoteUser, activity: IAdd): Promise => { if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); } diff --git a/packages/backend/src/remote/activitypub/kernel/announce/index.ts b/packages/backend/src/remote/activitypub/kernel/announce/index.ts index 7e2e73bdd5..ae7e507c99 100644 --- a/packages/backend/src/remote/activitypub/kernel/announce/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/announce/index.ts @@ -1,12 +1,12 @@ import Resolver from '../../resolver.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import announceNote from './note.js'; import { IAnnounce, getApId } from '../../type.js'; import { apLogger } from '../../logger.js'; const logger = apLogger; -export default async (actor: IRemoteUser, activity: IAnnounce): Promise => { +export default async (actor: CacheableRemoteUser, activity: IAnnounce): Promise => { const uri = getApId(activity); logger.info(`Announce: ${uri}`); diff --git a/packages/backend/src/remote/activitypub/kernel/announce/note.ts b/packages/backend/src/remote/activitypub/kernel/announce/note.ts index f6068fac79..680749f4d8 100644 --- a/packages/backend/src/remote/activitypub/kernel/announce/note.ts +++ b/packages/backend/src/remote/activitypub/kernel/announce/note.ts @@ -1,6 +1,6 @@ import Resolver from '../../resolver.js'; import post from '@/services/note/create.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { IAnnounce, getApId } from '../../type.js'; import { fetchNote, resolveNote } from '../../models/note.js'; import { apLogger } from '../../logger.js'; @@ -15,10 +15,9 @@ const logger = apLogger; /** * アナウンスアクティビティを捌きます */ -export default async function(resolver: Resolver, actor: IRemoteUser, activity: IAnnounce, targetUri: string): Promise { +export default async function(resolver: Resolver, actor: CacheableRemoteUser, activity: IAnnounce, targetUri: string): Promise { const uri = getApId(activity); - // アナウンサーが凍結されていたらスキップ if (actor.isSuspended) { return; } diff --git a/packages/backend/src/remote/activitypub/kernel/block/index.ts b/packages/backend/src/remote/activitypub/kernel/block/index.ts index 9e4f1b316e..5e230ad7b7 100644 --- a/packages/backend/src/remote/activitypub/kernel/block/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/block/index.ts @@ -1,9 +1,10 @@ import { IBlock } from '../../type.js'; import block from '@/services/blocking/create.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import DbResolver from '../../db-resolver.js'; +import { Users } from '@/models/index.js'; -export default async (actor: IRemoteUser, activity: IBlock): Promise => { +export default async (actor: CacheableRemoteUser, activity: IBlock): Promise => { // ※ activity.objectにブロック対象があり、それは存在するローカルユーザーのはず const dbResolver = new DbResolver(); @@ -17,6 +18,6 @@ export default async (actor: IRemoteUser, activity: IBlock): Promise => return `skip: ブロックしようとしているユーザーはローカルユーザーではありません`; } - await block(actor, blockee); + await block(await Users.findOneByOrFail({ id: actor.id }), await Users.findOneByOrFail({ id: blockee.id })); return `ok`; }; diff --git a/packages/backend/src/remote/activitypub/kernel/create/index.ts b/packages/backend/src/remote/activitypub/kernel/create/index.ts index 1187b95ac6..c253f9f667 100644 --- a/packages/backend/src/remote/activitypub/kernel/create/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/create/index.ts @@ -1,5 +1,5 @@ import Resolver from '../../resolver.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import createNote from './note.js'; import { ICreate, getApId, isPost, getApType } from '../../type.js'; import { apLogger } from '../../logger.js'; @@ -7,7 +7,7 @@ import { toArray, concat, unique } from '@/prelude/array.js'; const logger = apLogger; -export default async (actor: IRemoteUser, activity: ICreate): Promise => { +export default async (actor: CacheableRemoteUser, activity: ICreate): Promise => { const uri = getApId(activity); logger.info(`Create: ${uri}`); diff --git a/packages/backend/src/remote/activitypub/kernel/create/note.ts b/packages/backend/src/remote/activitypub/kernel/create/note.ts index b5c47990aa..f8dabe06e2 100644 --- a/packages/backend/src/remote/activitypub/kernel/create/note.ts +++ b/packages/backend/src/remote/activitypub/kernel/create/note.ts @@ -1,5 +1,5 @@ import Resolver from '../../resolver.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { createNote, fetchNote } from '../../models/note.js'; import { getApId, IObject, ICreate } from '../../type.js'; import { getApLock } from '@/misc/app-lock.js'; @@ -9,7 +9,7 @@ import { StatusError } from '@/misc/fetch.js'; /** * 投稿作成アクティビティを捌きます */ -export default async function(resolver: Resolver, actor: IRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise { +export default async function(resolver: Resolver, actor: CacheableRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise { const uri = getApId(note); if (typeof note === 'object') { diff --git a/packages/backend/src/remote/activitypub/kernel/delete/actor.ts b/packages/backend/src/remote/activitypub/kernel/delete/actor.ts index 2f75841e52..1f94df033d 100644 --- a/packages/backend/src/remote/activitypub/kernel/delete/actor.ts +++ b/packages/backend/src/remote/activitypub/kernel/delete/actor.ts @@ -1,18 +1,19 @@ import { apLogger } from '../../logger.js'; import { createDeleteAccountJob } from '@/queue/index.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { Users } from '@/models/index.js'; const logger = apLogger; -export async function deleteActor(actor: IRemoteUser, uri: string): Promise { +export async function deleteActor(actor: CacheableRemoteUser, uri: string): Promise { logger.info(`Deleting the Actor: ${uri}`); if (actor.uri !== uri) { return `skip: delete actor ${actor.uri} !== ${uri}`; } - if (actor.isDeleted) { + const user = await Users.findOneByOrFail({ id: actor.id }); + if (user.isDeleted) { logger.info(`skip: already deleted`); } diff --git a/packages/backend/src/remote/activitypub/kernel/delete/index.ts b/packages/backend/src/remote/activitypub/kernel/delete/index.ts index b6d5e96d03..4c06a9de0b 100644 --- a/packages/backend/src/remote/activitypub/kernel/delete/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/delete/index.ts @@ -1,5 +1,5 @@ import deleteNote from './note.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { IDelete, getApId, isTombstone, IObject, validPost, validActor } from '../../type.js'; import { toSingle } from '@/prelude/array.js'; import { deleteActor } from './actor.js'; @@ -7,7 +7,7 @@ import { deleteActor } from './actor.js'; /** * 削除アクティビティを捌きます */ -export default async (actor: IRemoteUser, activity: IDelete): Promise => { +export default async (actor: CacheableRemoteUser, activity: IDelete): Promise => { if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); } diff --git a/packages/backend/src/remote/activitypub/kernel/delete/note.ts b/packages/backend/src/remote/activitypub/kernel/delete/note.ts index ad5e1a2edc..1f44c35562 100644 --- a/packages/backend/src/remote/activitypub/kernel/delete/note.ts +++ b/packages/backend/src/remote/activitypub/kernel/delete/note.ts @@ -1,4 +1,4 @@ -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import deleteNode from '@/services/note/delete.js'; import { apLogger } from '../../logger.js'; import DbResolver from '../../db-resolver.js'; @@ -7,7 +7,7 @@ import { deleteMessage } from '@/services/messages/delete.js'; const logger = apLogger; -export default async function(actor: IRemoteUser, uri: string): Promise { +export default async function(actor: CacheableRemoteUser, uri: string): Promise { logger.info(`Deleting the Note: ${uri}`); const unlock = await getApLock(uri); diff --git a/packages/backend/src/remote/activitypub/kernel/flag/index.ts b/packages/backend/src/remote/activitypub/kernel/flag/index.ts index e80e632786..aa2f1f5362 100644 --- a/packages/backend/src/remote/activitypub/kernel/flag/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/flag/index.ts @@ -1,17 +1,17 @@ -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import config from '@/config/index.js'; import { IFlag, getApIds } from '../../type.js'; import { AbuseUserReports, Users } from '@/models/index.js'; import { In } from 'typeorm'; import { genId } from '@/misc/gen-id.js'; -export default async (actor: IRemoteUser, activity: IFlag): Promise => { +export default async (actor: CacheableRemoteUser, activity: IFlag): Promise => { // objectは `(User|Note) | (User|Note)[]` だけど、全パターンDBスキーマと対応させられないので // 対象ユーザーは一番最初のユーザー として あとはコメントとして格納する const uris = getApIds(activity.object); const userIds = uris.filter(uri => uri.startsWith(config.url + '/users/')).map(uri => uri.split('/').pop()!); - const users = await Users.find({ + const users = await Users.findBy({ id: In(userIds), }); if (users.length < 1) return `skip`; diff --git a/packages/backend/src/remote/activitypub/kernel/follow.ts b/packages/backend/src/remote/activitypub/kernel/follow.ts index 49c1a7ee01..a9e92fa229 100644 --- a/packages/backend/src/remote/activitypub/kernel/follow.ts +++ b/packages/backend/src/remote/activitypub/kernel/follow.ts @@ -1,9 +1,9 @@ -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import follow from '@/services/following/create.js'; import { IFollow } from '../type.js'; import DbResolver from '../db-resolver.js'; -export default async (actor: IRemoteUser, activity: IFollow): Promise => { +export default async (actor: CacheableRemoteUser, activity: IFollow): Promise => { const dbResolver = new DbResolver(); const followee = await dbResolver.getUserFromApId(activity.object); diff --git a/packages/backend/src/remote/activitypub/kernel/index.ts b/packages/backend/src/remote/activitypub/kernel/index.ts index 6aea8e57cf..254a121605 100644 --- a/packages/backend/src/remote/activitypub/kernel/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/index.ts @@ -1,5 +1,5 @@ import { IObject, isCreate, isDelete, isUpdate, isRead, isFollow, isAccept, isReject, isAdd, isRemove, isAnnounce, isLike, isUndo, isBlock, isCollectionOrOrderedCollection, isCollection, isFlag } from '../type.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import create from './create/index.js'; import performDeleteActivity from './delete/index.js'; import performUpdateActivity from './update/index.js'; @@ -17,8 +17,9 @@ import flag from './flag/index.js'; import { apLogger } from '../logger.js'; import Resolver from '../resolver.js'; import { toArray } from '@/prelude/array.js'; +import { Users } from '@/models/index.js'; -export async function performActivity(actor: IRemoteUser, activity: IObject) { +export async function performActivity(actor: CacheableRemoteUser, activity: IObject) { if (isCollectionOrOrderedCollection(activity)) { const resolver = new Resolver(); for (const item of toArray(isCollection(activity) ? activity.items : activity.orderedItems)) { @@ -36,7 +37,7 @@ export async function performActivity(actor: IRemoteUser, activity: IObject) { } } -async function performOneActivity(actor: IRemoteUser, activity: IObject): Promise { +async function performOneActivity(actor: CacheableRemoteUser, activity: IObject): Promise { if (actor.isSuspended) return; if (isCreate(activity)) { diff --git a/packages/backend/src/remote/activitypub/kernel/like.ts b/packages/backend/src/remote/activitypub/kernel/like.ts index 715cc379b9..2b65ff7383 100644 --- a/packages/backend/src/remote/activitypub/kernel/like.ts +++ b/packages/backend/src/remote/activitypub/kernel/like.ts @@ -1,9 +1,9 @@ -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { ILike, getApId } from '../type.js'; import create from '@/services/note/reaction/create.js'; import { fetchNote, extractEmojis } from '../models/note.js'; -export default async (actor: IRemoteUser, activity: ILike) => { +export default async (actor: CacheableRemoteUser, activity: ILike) => { const targetUri = getApId(activity.object); const note = await fetchNote(targetUri); diff --git a/packages/backend/src/remote/activitypub/kernel/read.ts b/packages/backend/src/remote/activitypub/kernel/read.ts index 93cc36ec46..7f1519ac2e 100644 --- a/packages/backend/src/remote/activitypub/kernel/read.ts +++ b/packages/backend/src/remote/activitypub/kernel/read.ts @@ -1,10 +1,10 @@ -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { IRead, getApId } from '../type.js'; import { isSelfHost, extractDbHost } from '@/misc/convert-host.js'; import { MessagingMessages } from '@/models/index.js'; import { readUserMessagingMessage } from '../../../server/api/common/read-messaging-message.js'; -export const performReadActivity = async (actor: IRemoteUser, activity: IRead): Promise => { +export const performReadActivity = async (actor: CacheableRemoteUser, activity: IRead): Promise => { const id = await getApId(activity.object); if (!isSelfHost(extractDbHost(id))) { @@ -13,7 +13,7 @@ export const performReadActivity = async (actor: IRemoteUser, activity: IRead): const messageId = id.split('/').pop(); - const message = await MessagingMessages.findOne(messageId); + const message = await MessagingMessages.findOneBy({ id: messageId }); if (message == null) { return `skip: message not found`; } diff --git a/packages/backend/src/remote/activitypub/kernel/reject/follow.ts b/packages/backend/src/remote/activitypub/kernel/reject/follow.ts index 72751e83c0..824ac69d70 100644 --- a/packages/backend/src/remote/activitypub/kernel/reject/follow.ts +++ b/packages/backend/src/remote/activitypub/kernel/reject/follow.ts @@ -1,11 +1,11 @@ -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { remoteReject } from '@/services/following/reject.js'; import { IFollow } from '../../type.js'; import DbResolver from '../../db-resolver.js'; import { relayRejected } from '@/services/relay.js'; import { Users } from '@/models/index.js'; -export default async (actor: IRemoteUser, activity: IFollow): Promise => { +export default async (actor: CacheableRemoteUser, activity: IFollow): Promise => { // ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある const dbResolver = new DbResolver(); diff --git a/packages/backend/src/remote/activitypub/kernel/reject/index.ts b/packages/backend/src/remote/activitypub/kernel/reject/index.ts index ed86a4aa2f..00f08842f4 100644 --- a/packages/backend/src/remote/activitypub/kernel/reject/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/reject/index.ts @@ -1,12 +1,12 @@ import Resolver from '../../resolver.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import rejectFollow from './follow.js'; import { IReject, isFollow, getApType } from '../../type.js'; import { apLogger } from '../../logger.js'; const logger = apLogger; -export default async (actor: IRemoteUser, activity: IReject): Promise => { +export default async (actor: CacheableRemoteUser, activity: IReject): Promise => { const uri = activity.id || activity; logger.info(`Reject: ${uri}`); diff --git a/packages/backend/src/remote/activitypub/kernel/remove/index.ts b/packages/backend/src/remote/activitypub/kernel/remove/index.ts index 7d7b3386c0..11a994a83b 100644 --- a/packages/backend/src/remote/activitypub/kernel/remove/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/remove/index.ts @@ -1,9 +1,9 @@ -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { IRemove } from '../../type.js'; import { resolveNote } from '../../models/note.js'; import { removePinned } from '@/services/i/pin.js'; -export default async (actor: IRemoteUser, activity: IRemove): Promise => { +export default async (actor: CacheableRemoteUser, activity: IRemove): Promise => { if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); } diff --git a/packages/backend/src/remote/activitypub/kernel/undo/accept.ts b/packages/backend/src/remote/activitypub/kernel/undo/accept.ts index 2383eea5bd..8f6eab6858 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/accept.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/accept.ts @@ -1,11 +1,11 @@ import unfollow from '@/services/following/delete.js'; import cancelRequest from '@/services/following/requests/cancel.js'; import {IAccept} from '../../type.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { Followings } from '@/models/index.js'; import DbResolver from '../../db-resolver.js'; -export default async (actor: IRemoteUser, activity: IAccept): Promise => { +export default async (actor: CacheableRemoteUser, activity: IAccept): Promise => { const dbResolver = new DbResolver(); const follower = await dbResolver.getUserFromApId(activity.object); @@ -13,7 +13,7 @@ export default async (actor: IRemoteUser, activity: IAccept): Promise => return `skip: follower not found`; } - const following = await Followings.findOne({ + const following = await Followings.findOneBy({ followerId: follower.id, followeeId: actor.id, }); diff --git a/packages/backend/src/remote/activitypub/kernel/undo/announce.ts b/packages/backend/src/remote/activitypub/kernel/undo/announce.ts index 822c1e4948..c2ac31bf8d 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/announce.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/announce.ts @@ -1,12 +1,12 @@ import { Notes } from '@/models/index.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { IAnnounce, getApId } from '../../type.js'; import deleteNote from '@/services/note/delete.js'; -export const undoAnnounce = async (actor: IRemoteUser, activity: IAnnounce): Promise => { +export const undoAnnounce = async (actor: CacheableRemoteUser, activity: IAnnounce): Promise => { const uri = getApId(activity); - const note = await Notes.findOne({ + const note = await Notes.findOneBy({ uri, }); diff --git a/packages/backend/src/remote/activitypub/kernel/undo/block.ts b/packages/backend/src/remote/activitypub/kernel/undo/block.ts index 844b067e2b..4ac6698578 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/block.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/block.ts @@ -1,9 +1,10 @@ import { IBlock } from '../../type.js'; import unblock from '@/services/blocking/delete.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import DbResolver from '../../db-resolver.js'; +import { Users } from '@/models/index.js'; -export default async (actor: IRemoteUser, activity: IBlock): Promise => { +export default async (actor: CacheableRemoteUser, activity: IBlock): Promise => { const dbResolver = new DbResolver(); const blockee = await dbResolver.getUserFromApId(activity.object); @@ -15,6 +16,6 @@ export default async (actor: IRemoteUser, activity: IBlock): Promise => return `skip: ブロック解除しようとしているユーザーはローカルユーザーではありません`; } - await unblock(actor, blockee); + await unblock(await Users.findOneByOrFail({ id: actor.id }), blockee); return `ok`; }; diff --git a/packages/backend/src/remote/activitypub/kernel/undo/follow.ts b/packages/backend/src/remote/activitypub/kernel/undo/follow.ts index 6715adcf76..6a43c1444a 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/follow.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/follow.ts @@ -1,11 +1,11 @@ import unfollow from '@/services/following/delete.js'; import cancelRequest from '@/services/following/requests/cancel.js'; import { IFollow } from '../../type.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { FollowRequests, Followings } from '@/models/index.js'; import DbResolver from '../../db-resolver.js'; -export default async (actor: IRemoteUser, activity: IFollow): Promise => { +export default async (actor: CacheableRemoteUser, activity: IFollow): Promise => { const dbResolver = new DbResolver(); const followee = await dbResolver.getUserFromApId(activity.object); @@ -17,12 +17,12 @@ export default async (actor: IRemoteUser, activity: IFollow): Promise => return `skip: フォロー解除しようとしているユーザーはローカルユーザーではありません`; } - const req = await FollowRequests.findOne({ + const req = await FollowRequests.findOneBy({ followerId: actor.id, followeeId: followee.id, }); - const following = await Followings.findOne({ + const following = await Followings.findOneBy({ followerId: actor.id, followeeId: followee.id, }); diff --git a/packages/backend/src/remote/activitypub/kernel/undo/index.ts b/packages/backend/src/remote/activitypub/kernel/undo/index.ts index 05937c6855..27d433eb33 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/index.ts @@ -1,5 +1,5 @@ -import { IRemoteUser } from '@/models/entities/user.js'; -import {IUndo, isFollow, isBlock, isLike, isAnnounce, getApType, isAccept} from '../../type.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { IUndo, isFollow, isBlock, isLike, isAnnounce, getApType, isAccept } from '../../type.js'; import unfollow from './follow.js'; import unblock from './block.js'; import undoLike from './like.js'; @@ -10,7 +10,7 @@ import { apLogger } from '../../logger.js'; const logger = apLogger; -export default async (actor: IRemoteUser, activity: IUndo): Promise => { +export default async (actor: CacheableRemoteUser, activity: IUndo): Promise => { if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); } diff --git a/packages/backend/src/remote/activitypub/kernel/undo/like.ts b/packages/backend/src/remote/activitypub/kernel/undo/like.ts index 08ac630351..01aeba1fb7 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/like.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/like.ts @@ -1,4 +1,4 @@ -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { ILike, getApId } from '../../type.js'; import deleteReaction from '@/services/note/reaction/delete.js'; import { fetchNote } from '../../models/note.js'; @@ -6,7 +6,7 @@ import { fetchNote } from '../../models/note.js'; /** * Process Undo.Like activity */ -export default async (actor: IRemoteUser, activity: ILike) => { +export default async (actor: CacheableRemoteUser, activity: ILike) => { const targetUri = getApId(activity.object); const note = await fetchNote(targetUri); diff --git a/packages/backend/src/remote/activitypub/kernel/update/index.ts b/packages/backend/src/remote/activitypub/kernel/update/index.ts index 7888c698e3..9e8a81bb39 100644 --- a/packages/backend/src/remote/activitypub/kernel/update/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/update/index.ts @@ -1,4 +1,4 @@ -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { getApType, IUpdate, isActor } from '../../type.js'; import { apLogger } from '../../logger.js'; import { updateQuestion } from '../../models/question.js'; @@ -8,7 +8,7 @@ import { updatePerson } from '../../models/person.js'; /** * Updateアクティビティを捌きます */ -export default async (actor: IRemoteUser, activity: IUpdate): Promise => { +export default async (actor: CacheableRemoteUser, activity: IUpdate): Promise => { if ('actor' in activity && actor.uri !== activity.actor) { return `skip: invalid actor`; } diff --git a/packages/backend/src/remote/activitypub/models/image.ts b/packages/backend/src/remote/activitypub/models/image.ts index b5e9181d30..102b7b1346 100644 --- a/packages/backend/src/remote/activitypub/models/image.ts +++ b/packages/backend/src/remote/activitypub/models/image.ts @@ -1,10 +1,10 @@ import { uploadFromUrl } from '@/services/drive/upload-from-url.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser, IRemoteUser } from '@/models/entities/user.js'; import Resolver from '../resolver.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { apLogger } from '../logger.js'; import { DriveFile } from '@/models/entities/drive-file.js'; -import { DriveFiles } from '@/models/index.js'; +import { DriveFiles, Users } from '@/models/index.js'; import { truncate } from '@/misc/truncate.js'; import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js'; @@ -13,7 +13,7 @@ const logger = apLogger; /** * Imageを作成します。 */ -export async function createImage(actor: IRemoteUser, value: any): Promise { +export async function createImage(actor: CacheableRemoteUser, value: any): Promise { // 投稿者が凍結されていたらスキップ if (actor.isSuspended) { throw new Error('actor has been suspended'); @@ -47,7 +47,7 @@ export async function createImage(actor: IRemoteUser, value: any): Promise { +export async function resolveImage(actor: CacheableRemoteUser, value: any): Promise { // TODO // リモートサーバーからフェッチしてきて登録 diff --git a/packages/backend/src/remote/activitypub/models/mention.ts b/packages/backend/src/remote/activitypub/models/mention.ts index c5b0ea53ce..a160092969 100644 --- a/packages/backend/src/remote/activitypub/models/mention.ts +++ b/packages/backend/src/remote/activitypub/models/mention.ts @@ -3,17 +3,17 @@ import { IObject, isMention, IApMention } from '../type.js'; import { resolvePerson } from './person.js'; import promiseLimit from 'promise-limit'; import Resolver from '../resolver.js'; -import { User } from '@/models/entities/user.js'; +import { CacheableUser, User } from '@/models/entities/user.js'; export async function extractApMentions(tags: IObject | IObject[] | null | undefined) { const hrefs = unique(extractApMentionObjects(tags).map(x => x.href as string)); const resolver = new Resolver(); - const limit = promiseLimit(2); + const limit = promiseLimit(2); const mentionedUsers = (await Promise.all( hrefs.map(x => limit(() => resolvePerson(x, resolver).catch(() => null))) - )).filter((x): x is User => x != null); + )).filter((x): x is CacheableUser => x != null); return mentionedUsers; } diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts index dca64d0a60..097a716614 100644 --- a/packages/backend/src/remote/activitypub/models/note.ts +++ b/packages/backend/src/remote/activitypub/models/note.ts @@ -5,7 +5,7 @@ import Resolver from '../resolver.js'; import post from '@/services/note/create.js'; import { resolvePerson, updatePerson } from './person.js'; import { resolveImage } from './image.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser, IRemoteUser } from '@/models/entities/user.js'; import { htmlToMfm } from '../misc/html-to-mfm.js'; import { extractApHashtags } from './tag.js'; import { unique, toArray, toSingle } from '@/prelude/array.js'; @@ -15,7 +15,7 @@ import { apLogger } from '../logger.js'; import { DriveFile } from '@/models/entities/drive-file.js'; import { deliverQuestionUpdate } from '@/services/note/polls/update.js'; import { extractDbHost, toPuny } from '@/misc/convert-host.js'; -import { Emojis, Polls, MessagingMessages } from '@/models/index.js'; +import { Emojis, Polls, MessagingMessages, Users } from '@/models/index.js'; import { Note } from '@/models/entities/note.js'; import { IObject, getOneApId, getApId, getOneApHrefNullable, validPost, IPost, isEmoji, getApType } from '../type.js'; import { Emoji } from '@/models/entities/emoji.js'; @@ -90,7 +90,7 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s logger.info(`Creating the Note: ${note.id}`); // 投稿者をフェッチ - const actor = await resolvePerson(getOneApId(note.attributedTo), resolver) as IRemoteUser; + const actor = await resolvePerson(getOneApId(note.attributedTo), resolver) as CacheableRemoteUser; // 投稿者が凍結されていたらスキップ if (actor.isSuspended) { @@ -141,7 +141,7 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s const uri = getApId(note.inReplyTo); if (uri.startsWith(config.url + '/')) { const id = uri.split('/').pop(); - const talk = await MessagingMessages.findOne(id); + const talk = await MessagingMessages.findOneBy({ id }); if (talk) { isTalk = true; return null; @@ -197,11 +197,11 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s const cw = note.summary === '' ? null : note.summary; // テキストのパース - const text = note._misskey_content || (note.content ? htmlToMfm(note.content, note.tag) : null); + const text = typeof note._misskey_content !== 'undefined' ? note._misskey_content : (note.content ? htmlToMfm(note.content, note.tag) : null); // vote if (reply && reply.hasPoll) { - const poll = await Polls.findOneOrFail(reply.id); + const poll = await Polls.findOneByOrFail({ noteId: reply.id }); const tryCreateVote = async (name: string, index: number): Promise => { if (poll.expiresAt && Date.now() > new Date(poll.expiresAt).getTime()) { @@ -230,11 +230,6 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s const poll = await extractPollFromQuestion(note, resolver).catch(() => undefined); - // ユーザーの情報が古かったらついでに更新しておく - if (actor.lastFetchedAt == null || Date.now() - actor.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { - if (actor.uri) updatePerson(actor.uri); - } - if (isTalk) { for (const recipient of visibleUsers) { await createMessage(actor, recipient, undefined, text || undefined, (files && files.length > 0) ? files[0] : null, object.id); @@ -311,7 +306,7 @@ export async function extractEmojis(tags: IObject | IObject[], host: string): Pr const name = tag.name!.replace(/^:/, '').replace(/:$/, ''); tag.icon = toSingle(tag.icon); - const exists = await Emojis.findOne({ + const exists = await Emojis.findOneBy({ host, name, }); @@ -332,7 +327,7 @@ export async function extractEmojis(tags: IObject | IObject[], host: string): Pr updatedAt: new Date(), }); - return await Emojis.findOne({ + return await Emojis.findOneBy({ host, name, }) as Emoji; @@ -352,6 +347,6 @@ export async function extractEmojis(tags: IObject | IObject[], host: string): Pr publicUrl: tag.icon!.url, updatedAt: new Date(), aliases: [], - } as Partial).then(x => Emojis.findOneOrFail(x.identifiers[0])); + } as Partial).then(x => Emojis.findOneByOrFail(x.identifiers[0])); })); } diff --git a/packages/backend/src/remote/activitypub/models/person.ts b/packages/backend/src/remote/activitypub/models/person.ts index 659d3ac9a2..88661865da 100644 --- a/packages/backend/src/remote/activitypub/models/person.ts +++ b/packages/backend/src/remote/activitypub/models/person.ts @@ -15,7 +15,7 @@ import { apLogger } from '../logger.js'; import { Note } from '@/models/entities/note.js'; import { updateUsertags } from '@/services/update-hashtag.js'; import { Users, Instances, DriveFiles, Followings, UserProfiles, UserPublickeys } from '@/models/index.js'; -import { User, IRemoteUser } from '@/models/entities/user.js'; +import { User, IRemoteUser, CacheableUser } from '@/models/entities/user.js'; import { Emoji } from '@/models/entities/emoji.js'; import { UserNotePining } from '@/models/entities/user-note-pining.js'; import { genId } from '@/misc/gen-id.js'; @@ -24,12 +24,14 @@ import { UserPublickey } from '@/models/entities/user-publickey.js'; import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; import { toPuny } from '@/misc/convert-host.js'; import { UserProfile } from '@/models/entities/user-profile.js'; -import { getConnection } from 'typeorm'; import { toArray } from '@/prelude/array.js'; import { fetchInstanceMetadata } from '@/services/fetch-instance-metadata.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { truncate } from '@/misc/truncate.js'; import { StatusError } from '@/misc/fetch.js'; +import { uriPersonCache } from '@/services/user-cache.js'; +import { publishInternalEvent } from '@/services/stream.js'; +import { db } from '@/db/postgre.js'; const logger = apLogger; @@ -91,19 +93,25 @@ function validateActor(x: IObject, uri: string): IActor { * * Misskeyに対象のPersonが登録されていればそれを返します。 */ -export async function fetchPerson(uri: string, resolver?: Resolver): Promise { +export async function fetchPerson(uri: string, resolver?: Resolver): Promise { if (typeof uri !== 'string') throw new Error('uri is not string'); + const cached = uriPersonCache.get(uri); + if (cached) return cached; + // URIがこのサーバーを指しているならデータベースからフェッチ if (uri.startsWith(config.url + '/')) { const id = uri.split('/').pop(); - return await Users.findOne(id).then(x => x || null); + const u = await Users.findOneBy({ id }); + if (u) uriPersonCache.set(uri, u); + return u; } //#region このサーバーに既に登録されていたらそれを返す - const exist = await Users.findOne({ uri }); + const exist = await Users.findOneBy({ uri }); if (exist) { + uriPersonCache.set(uri, exist); return exist; } //#endregion @@ -143,7 +151,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise { + await db.transaction(async transactionalEntityManager => { user = await transactionalEntityManager.save(new User({ id: genId(), avatarId: null, @@ -189,7 +197,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise /users/:id のように入力がaliasなときにエラーになることがあるのを対応 - const u = await Users.findOne({ + const u = await Users.findOneBy({ uri: person.id, }); @@ -272,7 +280,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint } //#region このサーバーに既に登録されているか - const exist = await Users.findOne({ uri }) as IRemoteUser; + const exist = await Users.findOneBy({ uri }) as IRemoteUser; if (exist == null) { return; @@ -352,6 +360,8 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint location: person['vcard:Address'] || null, }); + publishInternalEvent('remoteUserUpdated', { id: exist.id }); + // ハッシュタグ更新 updateUsertags(exist, tags); @@ -371,7 +381,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint * Misskeyに対象のPersonが登録されていればそれを返し、そうでなければ * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 */ -export async function resolvePerson(uri: string, resolver?: Resolver): Promise { +export async function resolvePerson(uri: string, resolver?: Resolver): Promise { if (typeof uri !== 'string') throw new Error('uri is not string'); //#region このサーバーに既に登録されていたらそれを返す @@ -441,7 +451,7 @@ export function analyzeAttachments(attachments: IObject | IObject[] | undefined) } export async function updateFeatured(userId: User['id']) { - const user = await Users.findOneOrFail(userId); + const user = await Users.findOneByOrFail({ id: userId }); if (!Users.isRemoteUser(user)) return; if (!user.featured) return; @@ -464,7 +474,7 @@ export async function updateFeatured(userId: User['id']) { .slice(0, 5) .map(item => limit(() => resolveNote(item, resolver)))); - await getConnection().transaction(async transactionalEntityManager => { + await db.transaction(async transactionalEntityManager => { await transactionalEntityManager.delete(UserNotePining, { userId: user.id }); // とりあえずidを別の時間で生成して順番を維持 diff --git a/packages/backend/src/remote/activitypub/models/question.ts b/packages/backend/src/remote/activitypub/models/question.ts index 0a77465e31..9e75864c63 100644 --- a/packages/backend/src/remote/activitypub/models/question.ts +++ b/packages/backend/src/remote/activitypub/models/question.ts @@ -47,10 +47,10 @@ export async function updateQuestion(value: any) { if (uri.startsWith(config.url + '/')) throw new Error('uri points local'); //#region このサーバーに既に登録されているか - const note = await Notes.findOne({ uri }); + const note = await Notes.findOneBy({ uri }); if (note == null) throw new Error('Question is not registed'); - const poll = await Polls.findOne({ noteId: note.id }); + const poll = await Polls.findOneBy({ noteId: note.id }); if (poll == null) throw new Error('Question is not registed'); //#endregion diff --git a/packages/backend/src/remote/activitypub/perform.ts b/packages/backend/src/remote/activitypub/perform.ts index 3e18815586..a3c10ba945 100644 --- a/packages/backend/src/remote/activitypub/perform.ts +++ b/packages/backend/src/remote/activitypub/perform.ts @@ -1,7 +1,17 @@ import { IObject } from './type.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { performActivity } from './kernel/index.js'; +import { updatePerson } from './models/person.js'; -export default async (actor: IRemoteUser, activity: IObject): Promise => { +export default async (actor: CacheableRemoteUser, activity: IObject): Promise => { await performActivity(actor, activity); + + // ついでにリモートユーザーの情報が古かったら更新しておく + if (actor.uri) { + if (actor.lastFetchedAt == null || Date.now() - actor.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { + setImmediate(() => { + updatePerson(actor.uri!); + }); + } + } }; diff --git a/packages/backend/src/remote/activitypub/renderer/follow-user.ts b/packages/backend/src/remote/activitypub/renderer/follow-user.ts index ad1d63b933..9a8a16d749 100644 --- a/packages/backend/src/remote/activitypub/renderer/follow-user.ts +++ b/packages/backend/src/remote/activitypub/renderer/follow-user.ts @@ -7,6 +7,6 @@ import { User } from '@/models/entities/user.js'; * @param id Follower|Followee ID */ export default async function renderFollowUser(id: User['id']): Promise { - const user = await Users.findOneOrFail(id); + const user = await Users.findOneByOrFail({ id: id }); return Users.isLocalUser(user) ? `${config.url}/users/${user.id}` : user.uri; } diff --git a/packages/backend/src/remote/activitypub/renderer/like.ts b/packages/backend/src/remote/activitypub/renderer/like.ts index 1bf36d4708..da1bfe6e8e 100644 --- a/packages/backend/src/remote/activitypub/renderer/like.ts +++ b/packages/backend/src/remote/activitypub/renderer/like.ts @@ -2,6 +2,7 @@ import config from '@/config/index.js'; import { NoteReaction } from '@/models/entities/note-reaction.js'; import { Note } from '@/models/entities/note.js'; import { Emojis } from '@/models/index.js'; +import { IsNull } from 'typeorm'; import renderEmoji from './emoji.js'; export const renderLike = async (noteReaction: NoteReaction, note: Note) => { @@ -18,9 +19,9 @@ export const renderLike = async (noteReaction: NoteReaction, note: Note) => { if (reaction.startsWith(':')) { const name = reaction.replace(/:/g, ''); - const emoji = await Emojis.findOne({ + const emoji = await Emojis.findOneBy({ name, - host: null, + host: IsNull(), }); if (emoji) object.tag = [ renderEmoji(emoji) ]; diff --git a/packages/backend/src/remote/activitypub/renderer/note.ts b/packages/backend/src/remote/activitypub/renderer/note.ts index c3d9e120d6..679c8bbfe4 100644 --- a/packages/backend/src/remote/activitypub/renderer/note.ts +++ b/packages/backend/src/remote/activitypub/renderer/note.ts @@ -7,25 +7,25 @@ import toHtml from '../misc/get-note-html.js'; import { Note, IMentionedRemoteUsers } from '@/models/entities/note.js'; import { DriveFile } from '@/models/entities/drive-file.js'; import { DriveFiles, Notes, Users, Emojis, Polls } from '@/models/index.js'; -import { In } from 'typeorm'; +import { In, IsNull } from 'typeorm'; import { Emoji } from '@/models/entities/emoji.js'; import { Poll } from '@/models/entities/poll.js'; export default async function renderNote(note: Note, dive = true, isTalk = false): Promise> { const getPromisedFiles = async (ids: string[]) => { if (!ids || ids.length === 0) return []; - const items = await DriveFiles.find({ id: In(ids) }); + const items = await DriveFiles.findBy({ id: In(ids) }); return ids.map(id => items.find(item => item.id === id)).filter(item => item != null) as DriveFile[]; }; let inReplyTo; - let inReplyToNote: Note | undefined; + let inReplyToNote: Note | null; if (note.replyId) { - inReplyToNote = await Notes.findOne(note.replyId); + inReplyToNote = await Notes.findOneBy({ id: note.replyId }); if (inReplyToNote != null) { - const inReplyToUser = await Users.findOne(inReplyToNote.userId); + const inReplyToUser = await Users.findOneBy({ id: inReplyToNote.userId }); if (inReplyToUser != null) { if (inReplyToNote.uri) { @@ -46,16 +46,14 @@ export default async function renderNote(note: Note, dive = true, isTalk = false let quote; if (note.renoteId) { - const renote = await Notes.findOne(note.renoteId); + const renote = await Notes.findOneBy({ id: note.renoteId }); if (renote) { quote = renote.uri ? renote.uri : `${config.url}/notes/${renote.id}`; } } - const user = await Users.findOneOrFail(note.userId); - - const attributedTo = `${config.url}/users/${user.id}`; + const attributedTo = `${config.url}/users/${note.userId}`; const mentions = (JSON.parse(note.mentionedRemoteUsers) as IMentionedRemoteUsers).map(x => x.uri); @@ -75,7 +73,7 @@ export default async function renderNote(note: Note, dive = true, isTalk = false to = mentions; } - const mentionedUsers = note.mentions.length > 0 ? await Users.find({ + const mentionedUsers = note.mentions.length > 0 ? await Users.findBy({ id: In(note.mentions), }) : []; @@ -85,10 +83,10 @@ export default async function renderNote(note: Note, dive = true, isTalk = false const files = await getPromisedFiles(note.fileIds); const text = note.text; - let poll: Poll | undefined; + let poll: Poll | null; if (note.hasPoll) { - poll = await Polls.findOne({ noteId: note.id }); + poll = await Polls.findOneBy({ noteId: note.id }); } let apText = text; @@ -158,9 +156,9 @@ export async function getEmojis(names: string[]): Promise { if (names == null || names.length === 0) return []; const emojis = await Promise.all( - names.map(name => Emojis.findOne({ + names.map(name => Emojis.findOneBy({ name, - host: null, + host: IsNull(), })) ); diff --git a/packages/backend/src/remote/activitypub/renderer/person.ts b/packages/backend/src/remote/activitypub/renderer/person.ts index 3d86e37cca..cd2fd74d47 100644 --- a/packages/backend/src/remote/activitypub/renderer/person.ts +++ b/packages/backend/src/remote/activitypub/renderer/person.ts @@ -17,9 +17,9 @@ export async function renderPerson(user: ILocalUser) { const isSystem = !!user.username.match(/\./); const [avatar, banner, profile] = await Promise.all([ - user.avatarId ? DriveFiles.findOne(user.avatarId) : Promise.resolve(undefined), - user.bannerId ? DriveFiles.findOne(user.bannerId) : Promise.resolve(undefined), - UserProfiles.findOneOrFail(user.id), + user.avatarId ? DriveFiles.findOneBy({ id: user.avatarId }) : Promise.resolve(undefined), + user.bannerId ? DriveFiles.findOneBy({ id: user.bannerId }) : Promise.resolve(undefined), + UserProfiles.findOneByOrFail({ userId: user.id }), ]); const attachment: { diff --git a/packages/backend/src/remote/activitypub/renderer/undo.ts b/packages/backend/src/remote/activitypub/renderer/undo.ts index d28778e22e..46631df9ea 100644 --- a/packages/backend/src/remote/activitypub/renderer/undo.ts +++ b/packages/backend/src/remote/activitypub/renderer/undo.ts @@ -3,9 +3,11 @@ import { ILocalUser, User } from '@/models/entities/user.js'; export default (object: any, user: { id: User['id'] }) => { if (object == null) return null; + const id = typeof object.id === 'string' && object.id.startsWith(config.url) ? `${object.id}/undo` : undefined; return { type: 'Undo', + ...(id ? { id } : {}), actor: `${config.url}/users/${user.id}`, object, published: new Date().toISOString(), diff --git a/packages/backend/src/remote/resolve-user.ts b/packages/backend/src/remote/resolve-user.ts index aa37013c4a..6fc6f2c4d3 100644 --- a/packages/backend/src/remote/resolve-user.ts +++ b/packages/backend/src/remote/resolve-user.ts @@ -7,15 +7,16 @@ import chalk from 'chalk'; import { User, IRemoteUser } from '@/models/entities/user.js'; import { Users } from '@/models/index.js'; import { toPuny } from '@/misc/convert-host.js'; +import { IsNull } from 'typeorm'; const logger = remoteLogger.createSubLogger('resolve-user'); -export async function resolveUser(username: string, host: string | null, option?: any, resync = false): Promise { +export async function resolveUser(username: string, host: string | null): Promise { const usernameLower = username.toLowerCase(); if (host == null) { logger.info(`return local user: ${usernameLower}`); - return await Users.findOne({ usernameLower, host: null }).then(u => { + return await Users.findOneBy({ usernameLower, host: IsNull() }).then(u => { if (u == null) { throw new Error('user not found'); } else { @@ -28,7 +29,7 @@ export async function resolveUser(username: string, host: string | null, option? if (config.host === host) { logger.info(`return local user: ${usernameLower}`); - return await Users.findOne({ usernameLower, host: null }).then(u => { + return await Users.findOneBy({ usernameLower, host: IsNull() }).then(u => { if (u == null) { throw new Error('user not found'); } else { @@ -37,7 +38,7 @@ export async function resolveUser(username: string, host: string | null, option? }); } - const user = await Users.findOne({ usernameLower, host }, option) as IRemoteUser | null; + const user = await Users.findOneBy({ usernameLower, host }) as IRemoteUser | null; const acctLower = `${usernameLower}@${host}`; @@ -48,8 +49,8 @@ export async function resolveUser(username: string, host: string | null, option? return await createPerson(self.href); } - // resyncオプション OR ユーザー情報が古い場合は、WebFilgerからやりなおして返す - if (resync || user.lastFetchedAt == null || Date.now() - user.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { + // ユーザー情報が古い場合は、WebFilgerからやりなおして返す + if (user.lastFetchedAt == null || Date.now() - user.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { // 繋がらないインスタンスに何回も試行するのを防ぐ, 後続の同様処理の連続試行を防ぐ ため 試行前にも更新する await Users.update(user.id, { lastFetchedAt: new Date(), @@ -82,7 +83,7 @@ export async function resolveUser(username: string, host: string | null, option? await updatePerson(self.href); logger.info(`return resynced remote user: ${acctLower}`); - return await Users.findOne({ uri: self.href }).then(u => { + return await Users.findOneBy({ uri: self.href }).then(u => { if (u == null) { throw new Error('user not found'); } else { diff --git a/packages/backend/src/server/activitypub.ts b/packages/backend/src/server/activitypub.ts index 21be0a2517..133dd36066 100644 --- a/packages/backend/src/server/activitypub.ts +++ b/packages/backend/src/server/activitypub.ts @@ -15,7 +15,7 @@ import { inbox as processInbox } from '@/queue/index.js'; import { isSelfHost } from '@/misc/convert-host.js'; import { Notes, Users, Emojis, NoteReactions } from '@/models/index.js'; import { ILocalUser, User } from '@/models/entities/user.js'; -import { In } from 'typeorm'; +import { In, IsNull } from 'typeorm'; import { renderLike } from '@/remote/activitypub/renderer/like.js'; import { getUserKeypair } from '@/misc/keypair-store.js'; @@ -65,7 +65,7 @@ router.post('/users/:user/inbox', json(), inbox); router.get('/notes/:note', async (ctx, next) => { if (!isActivityPubReq(ctx)) return await next(); - const note = await Notes.findOne({ + const note = await Notes.findOneBy({ id: ctx.params.note, visibility: In(['public' as const, 'home' as const]), localOnly: false, @@ -93,9 +93,9 @@ router.get('/notes/:note', async (ctx, next) => { // note activity router.get('/notes/:note/activity', async ctx => { - const note = await Notes.findOne({ + const note = await Notes.findOneBy({ id: ctx.params.note, - userHost: null, + userHost: IsNull(), visibility: In(['public' as const, 'home' as const]), localOnly: false, }); @@ -126,9 +126,9 @@ router.get('/users/:user/collections/featured', Featured); router.get('/users/:user/publickey', async ctx => { const userId = ctx.params.user; - const user = await Users.findOne({ + const user = await Users.findOneBy({ id: userId, - host: null, + host: IsNull(), }); if (user == null) { @@ -148,7 +148,7 @@ router.get('/users/:user/publickey', async ctx => { }); // user -async function userInfo(ctx: Router.RouterContext, user: User | undefined) { +async function userInfo(ctx: Router.RouterContext, user: User | null) { if (user == null) { ctx.status = 404; return; @@ -164,9 +164,9 @@ router.get('/users/:user', async (ctx, next) => { const userId = ctx.params.user; - const user = await Users.findOne({ + const user = await Users.findOneBy({ id: userId, - host: null, + host: IsNull(), isSuspended: false, }); @@ -176,9 +176,9 @@ router.get('/users/:user', async (ctx, next) => { router.get('/@:user', async (ctx, next) => { if (!isActivityPubReq(ctx)) return await next(); - const user = await Users.findOne({ + const user = await Users.findOneBy({ usernameLower: ctx.params.user.toLowerCase(), - host: null, + host: IsNull(), isSuspended: false, }); @@ -188,8 +188,8 @@ router.get('/@:user', async (ctx, next) => { // emoji router.get('/emojis/:emoji', async ctx => { - const emoji = await Emojis.findOne({ - host: null, + const emoji = await Emojis.findOneBy({ + host: IsNull(), name: ctx.params.emoji, }); @@ -205,14 +205,14 @@ router.get('/emojis/:emoji', async ctx => { // like router.get('/likes/:like', async ctx => { - const reaction = await NoteReactions.findOne(ctx.params.like); + const reaction = await NoteReactions.findOneBy({ id: ctx.params.like }); if (reaction == null) { ctx.status = 404; return; } - const note = await Notes.findOne(reaction.noteId); + const note = await Notes.findOneBy({ id: reaction.noteId }); if (note == null) { ctx.status = 404; diff --git a/packages/backend/src/server/activitypub/featured.ts b/packages/backend/src/server/activitypub/featured.ts index 129881a718..c03fd1049f 100644 --- a/packages/backend/src/server/activitypub/featured.ts +++ b/packages/backend/src/server/activitypub/featured.ts @@ -5,14 +5,14 @@ import renderOrderedCollection from '@/remote/activitypub/renderer/ordered-colle import { setResponseType } from '../activitypub.js'; import renderNote from '@/remote/activitypub/renderer/note.js'; import { Users, Notes, UserNotePinings } from '@/models/index.js'; +import { IsNull } from 'typeorm'; export default async (ctx: Router.RouterContext) => { const userId = ctx.params.user; - // Verify user - const user = await Users.findOne({ + const user = await Users.findOneBy({ id: userId, - host: null, + host: IsNull(), }); if (user == null) { @@ -26,7 +26,7 @@ export default async (ctx: Router.RouterContext) => { }); const pinnedNotes = await Promise.all(pinings.map(pining => - Notes.findOneOrFail(pining.noteId))); + Notes.findOneByOrFail({ id: pining.noteId }))); const renderedNotes = await Promise.all(pinnedNotes.map(note => renderNote(note))); diff --git a/packages/backend/src/server/activitypub/followers.ts b/packages/backend/src/server/activitypub/followers.ts index 5d1d7c59eb..4d4f733162 100644 --- a/packages/backend/src/server/activitypub/followers.ts +++ b/packages/backend/src/server/activitypub/followers.ts @@ -9,7 +9,7 @@ import renderOrderedCollectionPage from '@/remote/activitypub/renderer/ordered-c import renderFollowUser from '@/remote/activitypub/renderer/follow-user.js'; import { setResponseType } from '../activitypub.js'; import { Users, Followings, UserProfiles } from '@/models/index.js'; -import { LessThan } from 'typeorm'; +import { IsNull, LessThan } from 'typeorm'; export default async (ctx: Router.RouterContext) => { const userId = ctx.params.user; @@ -27,10 +27,9 @@ export default async (ctx: Router.RouterContext) => { return; } - // Verify user - const user = await Users.findOne({ + const user = await Users.findOneBy({ id: userId, - host: null, + host: IsNull(), }); if (user == null) { @@ -39,7 +38,7 @@ export default async (ctx: Router.RouterContext) => { } //#region Check ff visibility - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); if (profile.ffVisibility === 'private') { ctx.status = 403; diff --git a/packages/backend/src/server/activitypub/following.ts b/packages/backend/src/server/activitypub/following.ts index 23110ce873..0af1f424f9 100644 --- a/packages/backend/src/server/activitypub/following.ts +++ b/packages/backend/src/server/activitypub/following.ts @@ -9,7 +9,7 @@ import renderOrderedCollectionPage from '@/remote/activitypub/renderer/ordered-c import renderFollowUser from '@/remote/activitypub/renderer/follow-user.js'; import { setResponseType } from '../activitypub.js'; import { Users, Followings, UserProfiles } from '@/models/index.js'; -import { LessThan, FindConditions } from 'typeorm'; +import { LessThan, IsNull, FindOptionsWhere } from 'typeorm'; import { Following } from '@/models/entities/following.js'; export default async (ctx: Router.RouterContext) => { @@ -28,10 +28,9 @@ export default async (ctx: Router.RouterContext) => { return; } - // Verify user - const user = await Users.findOne({ + const user = await Users.findOneBy({ id: userId, - host: null, + host: IsNull(), }); if (user == null) { @@ -40,7 +39,7 @@ export default async (ctx: Router.RouterContext) => { } //#region Check ff visibility - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); if (profile.ffVisibility === 'private') { ctx.status = 403; @@ -59,7 +58,7 @@ export default async (ctx: Router.RouterContext) => { if (page) { const query = { followerId: user.id, - } as FindConditions; + } as FindOptionsWhere; // カーソルが指定されている場合 if (cursor) { diff --git a/packages/backend/src/server/activitypub/outbox.ts b/packages/backend/src/server/activitypub/outbox.ts index 57c126752a..6b9592bcf3 100644 --- a/packages/backend/src/server/activitypub/outbox.ts +++ b/packages/backend/src/server/activitypub/outbox.ts @@ -13,7 +13,7 @@ import { countIf } from '@/prelude/array.js'; import * as url from '@/prelude/url.js'; import { Users, Notes } from '@/models/index.js'; import { makePaginationQuery } from '../api/common/make-pagination-query.js'; -import { Brackets } from 'typeorm'; +import { Brackets, IsNull } from 'typeorm'; import { Note } from '@/models/entities/note.js'; export default async (ctx: Router.RouterContext) => { @@ -35,10 +35,9 @@ export default async (ctx: Router.RouterContext) => { return; } - // Verify user - const user = await Users.findOne({ + const user = await Users.findOneBy({ id: userId, - host: null, + host: IsNull(), }); if (user == null) { @@ -100,7 +99,7 @@ export default async (ctx: Router.RouterContext) => { */ export async function packActivity(note: Note): Promise { if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length === 0)) { - const renote = await Notes.findOneOrFail(note.renoteId); + const renote = await Notes.findOneByOrFail({ id: note.renoteId }); return renderAnnounce(renote.uri ? renote.uri : `${config.url}/notes/${renote.id}`, note); } diff --git a/packages/backend/src/server/api/authenticate.ts b/packages/backend/src/server/api/authenticate.ts index 7fdf14666e..65ccfcf551 100644 --- a/packages/backend/src/server/api/authenticate.ts +++ b/packages/backend/src/server/api/authenticate.ts @@ -1,7 +1,12 @@ import isNativeToken from './common/is-native-token.js'; -import { User } from '@/models/entities/user.js'; +import { CacheableLocalUser, ILocalUser } from '@/models/entities/user.js'; import { Users, AccessTokens, Apps } from '@/models/index.js'; import { AccessToken } from '@/models/entities/access-token.js'; +import { Cache } from '@/misc/cache.js'; +import { App } from '@/models/entities/app.js'; +import { localUserByIdCache, localUserByNativeTokenCache } from '@/services/user-cache.js'; + +const appCache = new Cache(Infinity); export class AuthenticationError extends Error { constructor(message: string) { @@ -10,15 +15,14 @@ export class AuthenticationError extends Error { } } -export default async (token: string | null): Promise<[User | null | undefined, AccessToken | null | undefined]> => { +export default async (token: string | null): Promise<[CacheableLocalUser | null | undefined, AccessToken | null | undefined]> => { if (token == null) { return [null, null]; } if (isNativeToken(token)) { - // Fetch user - const user = await Users - .findOne({ token }); + const user = await localUserByNativeTokenCache.fetch(token, + () => Users.findOneBy({ token }) as Promise); if (user == null) { throw new AuthenticationError('user not found'); @@ -42,14 +46,14 @@ export default async (token: string | null): Promise<[User | null | undefined, A lastUsedAt: new Date(), }); - const user = await Users - .findOne({ - id: accessToken.userId, // findOne(accessToken.userId) のように書かないのは後方互換性のため - }); + const user = await localUserByIdCache.fetch(accessToken.userId, + () => Users.findOneBy({ + id: accessToken.userId, + }) as Promise); if (accessToken.appId) { - const app = await Apps - .findOneOrFail(accessToken.appId); + const app = await appCache.fetch(accessToken.appId, + () => Apps.findOneByOrFail({ id: accessToken.appId! })); return [user, { id: accessToken.id, diff --git a/packages/backend/src/server/api/call.ts b/packages/backend/src/server/api/call.ts index 5c5ef66019..9a85e4565b 100644 --- a/packages/backend/src/server/api/call.ts +++ b/packages/backend/src/server/api/call.ts @@ -1,7 +1,7 @@ import Koa from 'koa'; import { performance } from 'perf_hooks'; import { limiter } from './limiter.js'; -import { User } from '@/models/entities/user.js'; +import { CacheableLocalUser, User } from '@/models/entities/user.js'; import endpoints, { IEndpoint } from './endpoints.js'; import { ApiError } from './error.js'; import { apiLogger } from './logger.js'; @@ -13,7 +13,7 @@ const accessDenied = { id: '56f35758-7dd5-468b-8439-5d6fb8ec9b8e', }; -export default async (endpoint: string, user: User | null | undefined, token: AccessToken | null | undefined, data: any, ctx?: Koa.Context) => { +export default async (endpoint: string, user: CacheableLocalUser | null | undefined, token: AccessToken | null | undefined, data: any, ctx?: Koa.Context) => { const isSecure = user != null && token == null; const ep = endpoints.find(e => e.name === endpoint); diff --git a/packages/backend/src/server/api/common/getters.ts b/packages/backend/src/server/api/common/getters.ts index c5a47876d0..783ea9ef75 100644 --- a/packages/backend/src/server/api/common/getters.ts +++ b/packages/backend/src/server/api/common/getters.ts @@ -7,7 +7,7 @@ import { Notes, Users } from '@/models/index.js'; * Get note for API processing */ export async function getNote(noteId: Note['id']) { - const note = await Notes.findOne(noteId); + const note = await Notes.findOneBy({ id: noteId }); if (note == null) { throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.'); @@ -20,7 +20,7 @@ export async function getNote(noteId: Note['id']) { * Get user for API processing */ export async function getUser(userId: User['id']) { - const user = await Users.findOne(userId); + const user = await Users.findOneBy({ id: userId }); if (user == null) { throw new IdentifiableError('15348ddd-432d-49c2-8a5a-8069753becff', 'No such user.'); diff --git a/packages/backend/src/server/api/common/inject-featured.ts b/packages/backend/src/server/api/common/inject-featured.ts index b7dd8028b5..f7cdd365ed 100644 --- a/packages/backend/src/server/api/common/inject-featured.ts +++ b/packages/backend/src/server/api/common/inject-featured.ts @@ -11,7 +11,7 @@ export async function injectFeatured(timeline: Note[], user?: User | null) { if (timeline.length < 5) return; if (user) { - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); if (!profile.injectFeaturedNote) return; } diff --git a/packages/backend/src/server/api/common/inject-promo.ts b/packages/backend/src/server/api/common/inject-promo.ts index b467b7b70b..b0da8118b4 100644 --- a/packages/backend/src/server/api/common/inject-promo.ts +++ b/packages/backend/src/server/api/common/inject-promo.ts @@ -8,7 +8,7 @@ export async function injectPromo(timeline: Note[], user?: User | null) { // TODO: readやexpireフィルタはクエリ側でやる - const reads = user ? await PromoReads.find({ + const reads = user ? await PromoReads.findBy({ userId: user.id, }) : []; @@ -22,10 +22,10 @@ export async function injectPromo(timeline: Note[], user?: User | null) { // Pick random promo const promo = promos[Math.floor(Math.random() * promos.length)]; - const note = await Notes.findOneOrFail(promo.noteId); + const note = await Notes.findOneByOrFail({ id: promo.noteId }); // Join - note.user = await Users.findOneOrFail(note.userId); + note.user = await Users.findOneByOrFail({ id: note.userId }); (note as any)._prId_ = rndstr('a-z0-9', 8); diff --git a/packages/backend/src/server/api/common/read-messaging-message.ts b/packages/backend/src/server/api/common/read-messaging-message.ts index b0ce54d370..3638518e67 100644 --- a/packages/backend/src/server/api/common/read-messaging-message.ts +++ b/packages/backend/src/server/api/common/read-messaging-message.ts @@ -23,7 +23,7 @@ export async function readUserMessagingMessage( ) { if (messageIds.length === 0) return; - const messages = await MessagingMessages.find({ + const messages = await MessagingMessages.findBy({ id: In(messageIds), }); @@ -64,7 +64,7 @@ export async function readGroupMessagingMessage( if (messageIds.length === 0) return; // check joined - const joining = await UserGroupJoinings.findOne({ + const joining = await UserGroupJoinings.findOneBy({ userId: userId, userGroupId: groupId, }); @@ -73,7 +73,7 @@ export async function readGroupMessagingMessage( throw new IdentifiableError('930a270c-714a-46b2-b776-ad27276dc569', 'Access denied (group).'); } - const messages = await MessagingMessages.find({ + const messages = await MessagingMessages.findBy({ id: In(messageIds), }); diff --git a/packages/backend/src/server/api/common/signin.ts b/packages/backend/src/server/api/common/signin.ts index 163f132a44..f1dccee2ce 100644 --- a/packages/backend/src/server/api/common/signin.ts +++ b/packages/backend/src/server/api/common/signin.ts @@ -36,7 +36,7 @@ export default function(ctx: Koa.Context, user: ILocalUser, redirect = false) { ip: ctx.ip, headers: ctx.headers, success: true, - }).then(x => Signins.findOneOrFail(x.identifiers[0])); + }).then(x => Signins.findOneByOrFail(x.identifiers[0])); // Publish signin event publishMainStream(user.id, 'signin', await Signins.pack(record)); diff --git a/packages/backend/src/server/api/common/signup.ts b/packages/backend/src/server/api/common/signup.ts index 7689e8233f..abc142472a 100644 --- a/packages/backend/src/server/api/common/signup.ts +++ b/packages/backend/src/server/api/common/signup.ts @@ -4,12 +4,13 @@ import generateUserToken from './generate-native-user-token.js'; import { User } from '@/models/entities/user.js'; import { Users, UsedUsernames } from '@/models/index.js'; import { UserProfile } from '@/models/entities/user-profile.js'; -import { getConnection } from 'typeorm'; +import { IsNull } from 'typeorm'; import { genId } from '@/misc/gen-id.js'; import { toPunyNullable } from '@/misc/convert-host.js'; import { UserKeypair } from '@/models/entities/user-keypair.js'; import { usersChart } from '@/services/chart/index.js'; import { UsedUsername } from '@/models/entities/used-username.js'; +import { db } from '@/db/postgre.js'; export async function signup(opts: { username: User['username']; @@ -40,12 +41,12 @@ export async function signup(opts: { const secret = generateUserToken(); // Check username duplication - if (await Users.findOne({ usernameLower: username.toLowerCase(), host: null })) { + if (await Users.findOneBy({ usernameLower: username.toLowerCase(), host: IsNull() })) { throw new Error('DUPLICATED_USERNAME'); } // Check deleted username duplication - if (await UsedUsernames.findOne({ username: username.toLowerCase() })) { + if (await UsedUsernames.findOneBy({ username: username.toLowerCase() })) { throw new Error('USED_USERNAME'); } @@ -69,10 +70,10 @@ export async function signup(opts: { let account!: User; // Start transaction - await getConnection().transaction(async transactionalEntityManager => { - const exist = await transactionalEntityManager.findOne(User, { + await db.transaction(async transactionalEntityManager => { + const exist = await transactionalEntityManager.findOneBy(User, { usernameLower: username.toLowerCase(), - host: null, + host: IsNull(), }); if (exist) throw new Error(' the username is already used'); @@ -84,8 +85,8 @@ export async function signup(opts: { usernameLower: username.toLowerCase(), host: toPunyNullable(host), token: secret, - isAdmin: (await Users.count({ - host: null, + isAdmin: (await Users.countBy({ + host: IsNull(), })) === 0, })); diff --git a/packages/backend/src/server/api/define.ts b/packages/backend/src/server/api/define.ts index 4e6d041a29..1529894341 100644 --- a/packages/backend/src/server/api/define.ts +++ b/packages/backend/src/server/api/define.ts @@ -1,30 +1,16 @@ import * as fs from 'node:fs'; import Ajv from 'ajv'; -import { ILocalUser } from '@/models/entities/user.js'; +import { CacheableLocalUser, ILocalUser } from '@/models/entities/user.js'; import { IEndpointMeta } from './endpoints.js'; import { ApiError } from './error.js'; import { Schema, SchemaType } from '@/misc/schema.js'; import { AccessToken } from '@/models/entities/access-token.js'; -type SimpleUserInfo = { - id: ILocalUser['id']; - createdAt: ILocalUser['createdAt']; - host: ILocalUser['host']; - username: ILocalUser['username']; - uri: ILocalUser['uri']; - inbox: ILocalUser['inbox']; - sharedInbox: ILocalUser['sharedInbox']; - isAdmin: ILocalUser['isAdmin']; - isModerator: ILocalUser['isModerator']; - isSilenced: ILocalUser['isSilenced']; - showTimelineReplies: ILocalUser['showTimelineReplies']; -}; - export type Response = Record | void; // TODO: paramsの型をT['params']のスキーマ定義から推論する type executor = - (params: SchemaType, user: T['requireCredential'] extends true ? SimpleUserInfo : SimpleUserInfo | null, token: AccessToken | null, file?: any, cleanup?: () => any) => + (params: SchemaType, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any, cleanup?: () => any) => Promise>>; const ajv = new Ajv({ @@ -34,11 +20,11 @@ const ajv = new Ajv({ ajv.addFormat('misskey:id', /^[a-zA-Z0-9]+$/); export default function (meta: T, paramDef: Ps, cb: executor) - : (params: any, user: T['requireCredential'] extends true ? SimpleUserInfo : SimpleUserInfo | null, token: AccessToken | null, file?: any) => Promise { + : (params: any, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any) => Promise { const validate = ajv.compile(paramDef); - return (params: any, user: T['requireCredential'] extends true ? SimpleUserInfo : SimpleUserInfo | null, token: AccessToken | null, file?: any) => { + return (params: any, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any) => { function cleanup() { fs.unlink(file.path, () => {}); } diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index 6b4eff0780..e2db03f13a 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -1,5 +1,6 @@ import { Schema } from '@/misc/schema.js'; +import * as ep___admin_meta from './endpoints/admin/meta.js'; import * as ep___admin_abuseUserReports from './endpoints/admin/abuse-user-reports.js'; import * as ep___admin_accounts_create from './endpoints/admin/accounts/create.js'; import * as ep___admin_accounts_delete from './endpoints/admin/accounts/delete.js'; @@ -201,6 +202,11 @@ import * as ep___i_unpin from './endpoints/i/unpin.js'; import * as ep___i_updateEmail from './endpoints/i/update-email.js'; import * as ep___i_update from './endpoints/i/update.js'; import * as ep___i_userGroupInvites from './endpoints/i/user-group-invites.js'; +import * as ep___i_webhooks_create from './endpoints/i/webhooks/create.js'; +import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js'; +import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js'; +import * as ep___i_webhooks_update from './endpoints/i/webhooks/update.js'; +import * as ep___i_webhooks_delete from './endpoints/i/webhooks/delete.js'; import * as ep___messaging_history from './endpoints/messaging/history.js'; import * as ep___messaging_messages from './endpoints/messaging/messages.js'; import * as ep___messaging_messages_create from './endpoints/messaging/messages/create.js'; @@ -304,6 +310,7 @@ import * as ep___users_show from './endpoints/users/show.js'; import * as ep___users_stats from './endpoints/users/stats.js'; const eps = [ + ['admin/meta', ep___admin_meta], ['admin/abuse-user-reports', ep___admin_abuseUserReports], ['admin/accounts/create', ep___admin_accounts_create], ['admin/accounts/delete', ep___admin_accounts_delete], @@ -505,6 +512,11 @@ const eps = [ ['i/update-email', ep___i_updateEmail], ['i/update', ep___i_update], ['i/user-group-invites', ep___i_userGroupInvites], + ['i/webhooks/create', ep___i_webhooks_create], + ['i/webhooks/list', ep___i_webhooks_list], + ['i/webhooks/show', ep___i_webhooks_show], + ['i/webhooks/update', ep___i_webhooks_update], + ['i/webhooks/delete', ep___i_webhooks_delete], ['messaging/history', ep___messaging_history], ['messaging/messages', ep___messaging_messages], ['messaging/messages/create', ep___messaging_messages_create], diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts index 2820c7993d..5f89219991 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts @@ -1,6 +1,7 @@ import define from '../../../define.js'; import { Users } from '@/models/index.js'; import { signup } from '../../../common/signup.js'; +import { IsNull } from 'typeorm'; export const meta = { tags: ['admin'], @@ -29,9 +30,9 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, _me) => { - const me = _me ? await Users.findOneOrFail(_me.id) : null; - const noUsers = (await Users.count({ - host: null, + const me = _me ? await Users.findOneByOrFail({ id: _me.id }) : null; + const noUsers = (await Users.countBy({ + host: IsNull(), })) === 0; if (!noUsers && !me?.isAdmin) throw new Error('access denied'); diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts index 01754ec8f3..629d700582 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts @@ -21,7 +21,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOne(ps.userId); + const user = await Users.findOneBy({ id: ps.userId }); if (user == null) { throw new Error('user not found'); diff --git a/packages/backend/src/server/api/endpoints/admin/ad/delete.ts b/packages/backend/src/server/api/endpoints/admin/ad/delete.ts index 3663d974c5..0ead2be005 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/delete.ts @@ -27,7 +27,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const ad = await Ads.findOne(ps.id); + const ad = await Ads.findOneBy({ id: ps.id }); if (ad == null) throw new ApiError(meta.errors.noSuchAd); diff --git a/packages/backend/src/server/api/endpoints/admin/ad/update.ts b/packages/backend/src/server/api/endpoints/admin/ad/update.ts index 89c421db66..650f8670e3 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/update.ts @@ -34,7 +34,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const ad = await Ads.findOne(ps.id); + const ad = await Ads.findOneBy({ id: ps.id }); if (ad == null) throw new ApiError(meta.errors.noSuchAd); diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts index 41570078d4..33076b6d30 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts @@ -63,7 +63,7 @@ export default define(meta, paramDef, async (ps) => { title: ps.title, text: ps.text, imageUrl: ps.imageUrl, - }).then(x => Announcements.findOneOrFail(x.identifiers[0])); + }).then(x => Announcements.findOneByOrFail(x.identifiers[0])); return Object.assign({}, announcement, { createdAt: announcement.createdAt.toISOString(), updatedAt: null }); }); diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts b/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts index 4871dc4e12..c17765f4fc 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts @@ -27,7 +27,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const announcement = await Announcements.findOne(ps.id); + const announcement = await Announcements.findOneBy({ id: ps.id }); if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement); diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts index 0ba0a8ee08..1d8eb1d618 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts @@ -69,7 +69,7 @@ export default define(meta, paramDef, async (ps) => { const announcements = await query.take(ps.limit).getMany(); for (const announcement of announcements) { - (announcement as any).reads = await AnnouncementReads.count({ + (announcement as any).reads = await AnnouncementReads.countBy({ announcementId: announcement.id, }); } diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts index 138337ef5b..61ce106d88 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts @@ -30,7 +30,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const announcement = await Announcements.findOne(ps.id); + const announcement = await Announcements.findOneBy({ id: ps.id }); if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement); diff --git a/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts b/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts index 90e65ec4cd..dc1976624d 100644 --- a/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts @@ -19,7 +19,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const files = await DriveFiles.find({ + const files = await DriveFiles.findBy({ userId: ps.userId, }); diff --git a/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts b/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts index 3e7d43fb0b..3db942e6cd 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts @@ -18,7 +18,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const files = await DriveFiles.find({ + const files = await DriveFiles.findBy({ userId: IsNull(), }); diff --git a/packages/backend/src/server/api/endpoints/admin/drive/files.ts b/packages/backend/src/server/api/endpoints/admin/drive/files.ts index 646d85a1e0..119c4db19b 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/files.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/files.ts @@ -27,7 +27,12 @@ export const paramDef = { untilId: { type: 'string', format: 'misskey:id' }, type: { type: 'string', nullable: true, pattern: /^[a-zA-Z0-9\/\-*]+$/.toString().slice(1, -1) }, origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: "local" }, - hostname: { type: 'string', nullable: true, default: null }, + hostname: { + type: 'string', + nullable: true, + default: null, + description: 'The local host is represented with `null`.', + }, }, required: [], } as const; diff --git a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts index e821160095..039df74f1b 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts @@ -40,6 +40,7 @@ export const meta = { userHost: { type: 'string', optional: false, nullable: true, + description: 'The local host is represented with `null`.', }, md5: { type: 'string', @@ -151,16 +152,25 @@ export const meta = { export const paramDef = { type: 'object', - properties: { - fileId: { type: 'string', format: 'misskey:id' }, - url: { type: 'string' }, - }, - required: [], + anyOf: [ + { + properties: { + fileId: { type: 'string', format: 'misskey:id' }, + }, + required: ['fileId'], + }, + { + properties: { + url: { type: 'string' }, + }, + required: ['url'], + }, + ], } as const; // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const file = ps.fileId ? await DriveFiles.findOne(ps.fileId) : await DriveFiles.findOne({ + const file = ps.fileId ? await DriveFiles.findOneBy({ id: ps.fileId }) : await DriveFiles.findOne({ where: [{ url: ps.url, }, { diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts index 77a4adea61..232fbbd573 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts @@ -1,7 +1,8 @@ import define from '../../../define.js'; import { Emojis } from '@/models/index.js'; -import { getConnection, In } from 'typeorm'; +import { In } from 'typeorm'; import { ApiError } from '../../../error.js'; +import { db } from '@/db/postgre.js'; export const meta = { tags: ['admin'], @@ -25,7 +26,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps) => { - const emojis = await Emojis.find({ + const emojis = await Emojis.findBy({ id: In(ps.ids), }); @@ -36,5 +37,5 @@ export default define(meta, paramDef, async (ps) => { }); } - await getConnection().queryResultCache!.remove(['meta_emojis']); + await db.queryResultCache!.remove(['meta_emojis']); }); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts index c5787d59dc..67349c24e0 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts @@ -1,11 +1,11 @@ import define from '../../../define.js'; import { Emojis, DriveFiles } from '@/models/index.js'; import { genId } from '@/misc/gen-id.js'; -import { getConnection } from 'typeorm'; import { insertModerationLog } from '@/services/insert-moderation-log.js'; import { ApiError } from '../../../error.js'; import rndstr from 'rndstr'; import { publishBroadcastStream } from '@/services/stream.js'; +import { db } from '@/db/postgre.js'; export const meta = { tags: ['admin'], @@ -32,7 +32,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const file = await DriveFiles.findOne(ps.fileId); + const file = await DriveFiles.findOneBy({ id: ps.fileId }); if (file == null) throw new ApiError(meta.errors.noSuchFile); @@ -48,9 +48,9 @@ export default define(meta, paramDef, async (ps, me) => { originalUrl: file.url, publicUrl: file.webpublicUrl ?? file.url, type: file.webpublicType ?? file.type, - }).then(x => Emojis.findOneOrFail(x.identifiers[0])); + }).then(x => Emojis.findOneByOrFail(x.identifiers[0])); - await getConnection().queryResultCache!.remove(['meta_emojis']); + await db.queryResultCache!.remove(['meta_emojis']); publishBroadcastStream('emojiAdded', { emoji: await Emojis.pack(emoji.id), diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts index a0eaa61258..7010ade0d8 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts @@ -1,11 +1,11 @@ import define from '../../../define.js'; import { Emojis } from '@/models/index.js'; import { genId } from '@/misc/gen-id.js'; -import { getConnection } from 'typeorm'; import { ApiError } from '../../../error.js'; import { DriveFile } from '@/models/entities/drive-file.js'; import { uploadFromUrl } from '@/services/drive/upload-from-url.js'; import { publishBroadcastStream } from '@/services/stream.js'; +import { db } from '@/db/postgre.js'; export const meta = { tags: ['admin'], @@ -44,7 +44,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const emoji = await Emojis.findOne(ps.emojiId); + const emoji = await Emojis.findOneBy({ id: ps.emojiId }); if (emoji == null) { throw new ApiError(meta.errors.noSuchEmoji); @@ -68,9 +68,9 @@ export default define(meta, paramDef, async (ps, me) => { originalUrl: driveFile.url, publicUrl: driveFile.webpublicUrl ?? driveFile.url, type: driveFile.webpublicType ?? driveFile.type, - }).then(x => Emojis.findOneOrFail(x.identifiers[0])); + }).then(x => Emojis.findOneByOrFail(x.identifiers[0])); - await getConnection().queryResultCache!.remove(['meta_emojis']); + await db.queryResultCache!.remove(['meta_emojis']); publishBroadcastStream('emojiAdded', { emoji: await Emojis.pack(copied.id), diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts index 38a2d65cf6..93a6c4e4e2 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts @@ -1,8 +1,9 @@ import define from '../../../define.js'; import { Emojis } from '@/models/index.js'; -import { getConnection, In } from 'typeorm'; +import { In } from 'typeorm'; import { insertModerationLog } from '@/services/insert-moderation-log.js'; import { ApiError } from '../../../error.js'; +import { db } from '@/db/postgre.js'; export const meta = { tags: ['admin'], @@ -23,14 +24,14 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const emojis = await Emojis.find({ + const emojis = await Emojis.findBy({ id: In(ps.ids), }); for (const emoji of emojis) { await Emojis.delete(emoji.id); - await getConnection().queryResultCache!.remove(['meta_emojis']); + await db.queryResultCache!.remove(['meta_emojis']); insertModerationLog(me, 'deleteEmoji', { emoji: emoji, diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts index a0cffb47f8..67dbf28d85 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts @@ -1,8 +1,8 @@ import define from '../../../define.js'; import { Emojis } from '@/models/index.js'; -import { getConnection } from 'typeorm'; import { insertModerationLog } from '@/services/insert-moderation-log.js'; import { ApiError } from '../../../error.js'; +import { db } from '@/db/postgre.js'; export const meta = { tags: ['admin'], @@ -29,13 +29,13 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const emoji = await Emojis.findOne(ps.id); + const emoji = await Emojis.findOneBy({ id: ps.id }); if (emoji == null) throw new ApiError(meta.errors.noSuchEmoji); await Emojis.delete(emoji.id); - await getConnection().queryResultCache!.remove(['meta_emojis']); + await db.queryResultCache!.remove(['meta_emojis']); insertModerationLog(me, 'deleteEmoji', { emoji: emoji, diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts index f19c3ddbd8..d16689a280 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts @@ -40,6 +40,7 @@ export const meta = { host: { type: 'string', optional: false, nullable: true, + description: 'The local host is represented with `null`.', }, url: { type: 'string', @@ -54,7 +55,12 @@ export const paramDef = { type: 'object', properties: { query: { type: 'string', nullable: true, default: null }, - host: { type: 'string', nullable: true, default: null }, + host: { + type: 'string', + nullable: true, + default: null, + description: 'Use `null` to represent the local host.', + }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts index f488a71a00..6192978fad 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts @@ -38,8 +38,9 @@ export const meta = { optional: false, nullable: true, }, host: { - type: 'string', - optional: false, nullable: true, + type: 'null', + optional: false, + description: 'The local host is represented with `null`. The field exists for compatibility with other API endpoints that return files.', }, url: { type: 'string', diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts index dbad93d336..a4da40fffd 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts @@ -1,7 +1,8 @@ import define from '../../../define.js'; import { Emojis } from '@/models/index.js'; -import { getConnection, In } from 'typeorm'; +import { In } from 'typeorm'; import { ApiError } from '../../../error.js'; +import { db } from '@/db/postgre.js'; export const meta = { tags: ['admin'], @@ -25,7 +26,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps) => { - const emojis = await Emojis.find({ + const emojis = await Emojis.findBy({ id: In(ps.ids), }); @@ -36,5 +37,5 @@ export default define(meta, paramDef, async (ps) => { }); } - await getConnection().queryResultCache!.remove(['meta_emojis']); + await db.queryResultCache!.remove(['meta_emojis']); }); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts index 470b9bef08..ae3b190f40 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts @@ -1,7 +1,8 @@ import define from '../../../define.js'; import { Emojis } from '@/models/index.js'; -import { getConnection, In } from 'typeorm'; +import { In } from 'typeorm'; import { ApiError } from '../../../error.js'; +import { db } from '@/db/postgre.js'; export const meta = { tags: ['admin'], @@ -32,5 +33,5 @@ export default define(meta, paramDef, async (ps) => { aliases: ps.aliases, }); - await getConnection().queryResultCache!.remove(['meta_emojis']); + await db.queryResultCache!.remove(['meta_emojis']); }); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts index 40e4c0199e..cff58d6170 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts @@ -1,7 +1,8 @@ import define from '../../../define.js'; import { Emojis } from '@/models/index.js'; -import { getConnection, In } from 'typeorm'; +import { In } from 'typeorm'; import { ApiError } from '../../../error.js'; +import { db } from '@/db/postgre.js'; export const meta = { tags: ['admin'], @@ -16,7 +17,11 @@ export const paramDef = { ids: { type: 'array', items: { type: 'string', format: 'misskey:id', } }, - category: { type: 'string', nullable: true }, + category: { + type: 'string', + nullable: true, + description: 'Use `null` to reset the category.', + }, }, required: ['ids'], } as const; @@ -30,5 +35,5 @@ export default define(meta, paramDef, async (ps) => { category: ps.category, }); - await getConnection().queryResultCache!.remove(['meta_emojis']); + await db.queryResultCache!.remove(['meta_emojis']); }); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts index c6d07e16fa..5b547b3b79 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts @@ -1,7 +1,7 @@ import define from '../../../define.js'; import { Emojis } from '@/models/index.js'; -import { getConnection } from 'typeorm'; import { ApiError } from '../../../error.js'; +import { db } from '@/db/postgre.js'; export const meta = { tags: ['admin'], @@ -23,7 +23,11 @@ export const paramDef = { properties: { id: { type: 'string', format: 'misskey:id' }, name: { type: 'string' }, - category: { type: 'string', nullable: true }, + category: { + type: 'string', + nullable: true, + description: 'Use `null` to reset the category.', + }, aliases: { type: 'array', items: { type: 'string', } }, @@ -33,7 +37,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps) => { - const emoji = await Emojis.findOne(ps.id); + const emoji = await Emojis.findOneBy({ id: ps.id }); if (emoji == null) throw new ApiError(meta.errors.noSuchEmoji); @@ -44,5 +48,5 @@ export default define(meta, paramDef, async (ps) => { aliases: ps.aliases, }); - await getConnection().queryResultCache!.remove(['meta_emojis']); + await db.queryResultCache!.remove(['meta_emojis']); }); diff --git a/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts b/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts index d4251f2feb..da54201473 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts @@ -19,7 +19,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const files = await DriveFiles.find({ + const files = await DriveFiles.findBy({ userHost: ps.host, }); diff --git a/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts b/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts index 86978cc309..cb2be5ab37 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts @@ -20,7 +20,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const instance = await Instances.findOne({ host: toPuny(ps.host) }); + const instance = await Instances.findOneBy({ host: toPuny(ps.host) }); if (instance == null) { throw new Error('instance not found'); diff --git a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts index ccd07489cb..b7ee27db64 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts @@ -19,13 +19,13 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const followings = await Followings.find({ + const followings = await Followings.findBy({ followerHost: ps.host, }); const pairs = await Promise.all(followings.map(f => Promise.all([ - Users.findOneOrFail(f.followerId), - Users.findOneOrFail(f.followeeId), + Users.findOneByOrFail({ id: f.followerId }), + Users.findOneByOrFail({ id: f.followeeId }), ]))); for (const pair of pairs) { diff --git a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts index 1981082428..278131fb37 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts @@ -20,7 +20,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const instance = await Instances.findOne({ host: toPuny(ps.host) }); + const instance = await Instances.findOneBy({ host: toPuny(ps.host) }); if (instance == null) { throw new Error('instance not found'); diff --git a/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts b/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts index 37878c4143..dd16473f30 100644 --- a/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts +++ b/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts @@ -1,5 +1,5 @@ import define from '../../define.js'; -import { getConnection } from 'typeorm'; +import { db } from '@/db/postgre.js'; export const meta = { requireCredential: true, @@ -16,15 +16,13 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async () => { - const stats = await - getConnection().query(`SELECT * FROM pg_indexes;`) - .then(recs => { - const res = [] as { tablename: string; indexname: string; }[]; - for (const rec of recs) { - res.push(rec); - } - return res; - }); + const stats = await db.query(`SELECT * FROM pg_indexes;`).then(recs => { + const res = [] as { tablename: string; indexname: string; }[]; + for (const rec of recs) { + res.push(rec); + } + return res; + }); return stats; }); diff --git a/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts b/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts index 7cf2d5ffd4..aca2540fd5 100644 --- a/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts +++ b/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts @@ -1,5 +1,5 @@ +import { db } from '@/db/postgre.js'; import define from '../../define.js'; -import { getConnection } from 'typeorm'; export const meta = { requireCredential: true, @@ -28,7 +28,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async () => { const sizes = await - getConnection().query(` + db.query(` SELECT relname AS "table", reltuples as "count", pg_total_relation_size(C.oid) AS "size" FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) WHERE nspname NOT IN ('pg_catalog', 'information_schema') diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts new file mode 100644 index 0000000000..8d50486ef6 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -0,0 +1,401 @@ +import config from '@/config/index.js'; +import define from '../../define.js'; +import { fetchMeta } from '@/misc/fetch-meta.js'; +import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; + +export const meta = { + tags: ['meta'], + + requireCredential: true, + requireAdmin: true, + + res: { + type: 'object', + optional: false, nullable: false, + properties: { + driveCapacityPerLocalUserMb: { + type: 'number', + optional: false, nullable: false, + }, + driveCapacityPerRemoteUserMb: { + type: 'number', + optional: false, nullable: false, + }, + cacheRemoteFiles: { + type: 'boolean', + optional: false, nullable: false, + }, + emailRequiredForSignup: { + type: 'boolean', + optional: false, nullable: false, + }, + enableHcaptcha: { + type: 'boolean', + optional: false, nullable: false, + }, + hcaptchaSiteKey: { + type: 'string', + optional: false, nullable: true, + }, + enableRecaptcha: { + type: 'boolean', + optional: false, nullable: false, + }, + recaptchaSiteKey: { + type: 'string', + optional: false, nullable: true, + }, + swPublickey: { + type: 'string', + optional: false, nullable: true, + }, + mascotImageUrl: { + type: 'string', + optional: false, nullable: false, + default: '/assets/ai.png', + }, + bannerUrl: { + type: 'string', + optional: false, nullable: false, + }, + errorImageUrl: { + type: 'string', + optional: false, nullable: false, + default: 'https://xn--931a.moe/aiart/yubitun.png', + }, + iconUrl: { + type: 'string', + optional: false, nullable: true, + }, + maxNoteTextLength: { + type: 'number', + optional: false, nullable: false, + }, + emojis: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + aliases: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, + category: { + type: 'string', + optional: false, nullable: true, + }, + host: { + type: 'string', + optional: false, nullable: true, + }, + url: { + type: 'string', + optional: false, nullable: false, + format: 'url', + }, + }, + }, + }, + ads: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + properties: { + place: { + type: 'string', + optional: false, nullable: false, + }, + url: { + type: 'string', + optional: false, nullable: false, + format: 'url', + }, + imageUrl: { + type: 'string', + optional: false, nullable: false, + format: 'url', + }, + }, + }, + }, + enableEmail: { + type: 'boolean', + optional: false, nullable: false, + }, + enableTwitterIntegration: { + type: 'boolean', + optional: false, nullable: false, + }, + enableGithubIntegration: { + type: 'boolean', + optional: false, nullable: false, + }, + enableDiscordIntegration: { + type: 'boolean', + optional: false, nullable: false, + }, + enableServiceWorker: { + type: 'boolean', + optional: false, nullable: false, + }, + translatorAvailable: { + type: 'boolean', + optional: false, nullable: false, + }, + proxyAccountName: { + type: 'string', + optional: false, nullable: true, + }, + userStarForReactionFallback: { + type: 'boolean', + optional: true, nullable: false, + }, + pinnedUsers: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, + hiddenTags: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, + blockedHosts: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, + hcaptchaSecretKey: { + type: 'string', + optional: true, nullable: true, + }, + recaptchaSecretKey: { + type: 'string', + optional: true, nullable: true, + }, + proxyAccountId: { + type: 'string', + optional: true, nullable: true, + format: 'id', + }, + twitterConsumerKey: { + type: 'string', + optional: true, nullable: true, + }, + twitterConsumerSecret: { + type: 'string', + optional: true, nullable: true, + }, + githubClientId: { + type: 'string', + optional: true, nullable: true, + }, + githubClientSecret: { + type: 'string', + optional: true, nullable: true, + }, + discordClientId: { + type: 'string', + optional: true, nullable: true, + }, + discordClientSecret: { + type: 'string', + optional: true, nullable: true, + }, + summaryProxy: { + type: 'string', + optional: true, nullable: true, + }, + email: { + type: 'string', + optional: true, nullable: true, + }, + smtpSecure: { + type: 'boolean', + optional: true, nullable: false, + }, + smtpHost: { + type: 'string', + optional: true, nullable: true, + }, + smtpPort: { + type: 'string', + optional: true, nullable: true, + }, + smtpUser: { + type: 'string', + optional: true, nullable: true, + }, + smtpPass: { + type: 'string', + optional: true, nullable: true, + }, + swPrivateKey: { + type: 'string', + optional: true, nullable: true, + }, + useObjectStorage: { + type: 'boolean', + optional: true, nullable: false, + }, + objectStorageBaseUrl: { + type: 'string', + optional: true, nullable: true, + }, + objectStorageBucket: { + type: 'string', + optional: true, nullable: true, + }, + objectStoragePrefix: { + type: 'string', + optional: true, nullable: true, + }, + objectStorageEndpoint: { + type: 'string', + optional: true, nullable: true, + }, + objectStorageRegion: { + type: 'string', + optional: true, nullable: true, + }, + objectStoragePort: { + type: 'number', + optional: true, nullable: true, + }, + objectStorageAccessKey: { + type: 'string', + optional: true, nullable: true, + }, + objectStorageSecretKey: { + type: 'string', + optional: true, nullable: true, + }, + objectStorageUseSSL: { + type: 'boolean', + optional: true, nullable: false, + }, + objectStorageUseProxy: { + type: 'boolean', + optional: true, nullable: false, + }, + objectStorageSetPublicRead: { + type: 'boolean', + optional: true, nullable: false, + }, + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + }, + required: [], +} as const; + +// eslint-disable-next-line import/no-default-export +export default define(meta, paramDef, async (ps, me) => { + const instance = await fetchMeta(true); + + return { + maintainerName: instance.maintainerName, + maintainerEmail: instance.maintainerEmail, + version: config.version, + name: instance.name, + uri: config.url, + description: instance.description, + langs: instance.langs, + tosUrl: instance.ToSUrl, + repositoryUrl: instance.repositoryUrl, + feedbackUrl: instance.feedbackUrl, + disableRegistration: instance.disableRegistration, + disableLocalTimeline: instance.disableLocalTimeline, + disableGlobalTimeline: instance.disableGlobalTimeline, + driveCapacityPerLocalUserMb: instance.localDriveCapacityMb, + driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb, + emailRequiredForSignup: instance.emailRequiredForSignup, + enableHcaptcha: instance.enableHcaptcha, + hcaptchaSiteKey: instance.hcaptchaSiteKey, + enableRecaptcha: instance.enableRecaptcha, + recaptchaSiteKey: instance.recaptchaSiteKey, + swPublickey: instance.swPublicKey, + themeColor: instance.themeColor, + mascotImageUrl: instance.mascotImageUrl, + bannerUrl: instance.bannerUrl, + errorImageUrl: instance.errorImageUrl, + iconUrl: instance.iconUrl, + backgroundImageUrl: instance.backgroundImageUrl, + logoImageUrl: instance.logoImageUrl, + maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため + defaultLightTheme: instance.defaultLightTheme, + defaultDarkTheme: instance.defaultDarkTheme, + enableEmail: instance.enableEmail, + enableTwitterIntegration: instance.enableTwitterIntegration, + enableGithubIntegration: instance.enableGithubIntegration, + enableDiscordIntegration: instance.enableDiscordIntegration, + enableServiceWorker: instance.enableServiceWorker, + translatorAvailable: instance.deeplAuthKey != null, + pinnedPages: instance.pinnedPages, + pinnedClipId: instance.pinnedClipId, + cacheRemoteFiles: instance.cacheRemoteFiles, + + useStarForReactionFallback: instance.useStarForReactionFallback, + pinnedUsers: instance.pinnedUsers, + hiddenTags: instance.hiddenTags, + blockedHosts: instance.blockedHosts, + hcaptchaSecretKey: instance.hcaptchaSecretKey, + recaptchaSecretKey: instance.recaptchaSecretKey, + proxyAccountId: instance.proxyAccountId, + twitterConsumerKey: instance.twitterConsumerKey, + twitterConsumerSecret: instance.twitterConsumerSecret, + githubClientId: instance.githubClientId, + githubClientSecret: instance.githubClientSecret, + discordClientId: instance.discordClientId, + discordClientSecret: instance.discordClientSecret, + summalyProxy: instance.summalyProxy, + email: instance.email, + smtpSecure: instance.smtpSecure, + smtpHost: instance.smtpHost, + smtpPort: instance.smtpPort, + smtpUser: instance.smtpUser, + smtpPass: instance.smtpPass, + swPrivateKey: instance.swPrivateKey, + useObjectStorage: instance.useObjectStorage, + objectStorageBaseUrl: instance.objectStorageBaseUrl, + objectStorageBucket: instance.objectStorageBucket, + objectStoragePrefix: instance.objectStoragePrefix, + objectStorageEndpoint: instance.objectStorageEndpoint, + objectStorageRegion: instance.objectStorageRegion, + objectStoragePort: instance.objectStoragePort, + objectStorageAccessKey: instance.objectStorageAccessKey, + objectStorageSecretKey: instance.objectStorageSecretKey, + objectStorageUseSSL: instance.objectStorageUseSSL, + objectStorageUseProxy: instance.objectStorageUseProxy, + objectStorageSetPublicRead: instance.objectStorageSetPublicRead, + objectStorageS3ForcePathStyle: instance.objectStorageS3ForcePathStyle, + deeplAuthKey: instance.deeplAuthKey, + deeplIsPro: instance.deeplIsPro, + }; +}); diff --git a/packages/backend/src/server/api/endpoints/admin/moderators/add.ts b/packages/backend/src/server/api/endpoints/admin/moderators/add.ts index 4206e3a3c2..7b209c2d99 100644 --- a/packages/backend/src/server/api/endpoints/admin/moderators/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/moderators/add.ts @@ -1,5 +1,6 @@ import define from '../../../define.js'; import { Users } from '@/models/index.js'; +import { publishInternalEvent } from '@/services/stream.js'; export const meta = { tags: ['admin'], @@ -18,7 +19,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps) => { - const user = await Users.findOne(ps.userId as string); + const user = await Users.findOneBy({ id: ps.userId }); if (user == null) { throw new Error('user not found'); @@ -31,4 +32,6 @@ export default define(meta, paramDef, async (ps) => { await Users.update(user.id, { isModerator: true, }); + + publishInternalEvent('userChangeModeratorState', { id: user.id, isModerator: true }); }); diff --git a/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts b/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts index 143119bfe4..a01e9f3c69 100644 --- a/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts +++ b/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts @@ -1,5 +1,6 @@ import define from '../../../define.js'; import { Users } from '@/models/index.js'; +import { publishInternalEvent } from '@/services/stream.js'; export const meta = { tags: ['admin'], @@ -18,7 +19,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps) => { - const user = await Users.findOne(ps.userId as string); + const user = await Users.findOneBy({ id: ps.userId }); if (user == null) { throw new Error('user not found'); @@ -27,4 +28,6 @@ export default define(meta, paramDef, async (ps) => { await Users.update(user.id, { isModerator: false, }); + + publishInternalEvent('userChangeModeratorState', { id: user.id, isModerator: false }); }); diff --git a/packages/backend/src/server/api/endpoints/admin/promo/create.ts b/packages/backend/src/server/api/endpoints/admin/promo/create.ts index 2eec5bf0db..68a17867b2 100644 --- a/packages/backend/src/server/api/endpoints/admin/promo/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/promo/create.ts @@ -40,7 +40,7 @@ export default define(meta, paramDef, async (ps, user) => { throw e; }); - const exist = await PromoNotes.findOne(note.id); + const exist = await PromoNotes.findOneBy({ noteId: note.id }); if (exist != null) { throw new ApiError(meta.errors.alreadyPromoted); diff --git a/packages/backend/src/server/api/endpoints/admin/reset-password.ts b/packages/backend/src/server/api/endpoints/admin/reset-password.ts index 1fd5c8d5a5..be4c2dceed 100644 --- a/packages/backend/src/server/api/endpoints/admin/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/admin/reset-password.ts @@ -33,7 +33,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps) => { - const user = await Users.findOne(ps.userId as string); + const user = await Users.findOneBy({ id: ps.userId }); if (user == null) { throw new Error('user not found'); diff --git a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts index a9e5658413..3edae4a85f 100644 --- a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts +++ b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts @@ -23,7 +23,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const report = await AbuseUserReports.findOne(ps.reportId); + const report = await AbuseUserReports.findOneByOrFail({ id: ps.reportId }); if (report == null) { throw new Error('report not found'); @@ -31,7 +31,7 @@ export default define(meta, paramDef, async (ps, me) => { if (ps.forward && report.targetUserHost != null) { const actor = await getInstanceActor(); - const targetUser = await Users.findOneOrFail(report.targetUserId); + const targetUser = await Users.findOneByOrFail({ id: report.targetUserId }); deliver(actor, renderActivity(renderFlag(actor, [targetUser.uri!], report.comment)), targetUser.inbox); } diff --git a/packages/backend/src/server/api/endpoints/admin/server-info.ts b/packages/backend/src/server/api/endpoints/admin/server-info.ts index 8bf1c4341c..9c150420b1 100644 --- a/packages/backend/src/server/api/endpoints/admin/server-info.ts +++ b/packages/backend/src/server/api/endpoints/admin/server-info.ts @@ -1,8 +1,8 @@ import * as os from 'node:os'; import si from 'systeminformation'; -import { getConnection } from 'typeorm'; import define from '../../define.js'; import { redisClient } from '../../../../db/redis.js'; +import { db } from '@/db/postgre.js'; export const meta = { requireCredential: true, @@ -103,7 +103,7 @@ export default define(meta, paramDef, async () => { machine: os.hostname(), os: os.platform(), node: process.version, - psql: await getConnection().query('SHOW server_version').then(x => x[0].server_version), + psql: await db.query('SHOW server_version').then(x => x[0].server_version), redis: redisClient.server_info.redis_version, cpu: { model: os.cpus()[0].model, diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts index a435dcc288..bf6cc16532 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts @@ -23,13 +23,14 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOne(ps.userId as string); + const user = await Users.findOneBy({ id: ps.userId }); if (user == null) { throw new Error('user not found'); } - if ((me.isModerator && !me.isAdmin) && user.isAdmin) { + const _me = await Users.findOneByOrFail({ id: me.id }); + if ((_me.isModerator && !_me.isAdmin) && user.isAdmin) { throw new Error('cannot show info of admin'); } diff --git a/packages/backend/src/server/api/endpoints/admin/show-users.ts b/packages/backend/src/server/api/endpoints/admin/show-users.ts index 1ec86fef2e..2703b4b9db 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-users.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-users.ts @@ -26,8 +26,13 @@ export const paramDef = { sort: { type: 'string', enum: ['+follower', '-follower', '+createdAt', '-createdAt', '+updatedAt', '-updatedAt'] }, state: { type: 'string', enum: ['all', 'available', 'admin', 'moderator', 'adminOrModerator', 'silenced', 'suspended'], default: "all" }, origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: "local" }, - username: { type: 'string', default: null }, - hostname: { type: 'string', default: null }, + username: { type: 'string', nullable: true, default: null }, + hostname: { + type: 'string', + nullable: true, + default: null, + description: 'The local host is represented with `null`.', + }, }, required: [], } as const; diff --git a/packages/backend/src/server/api/endpoints/admin/silence-user.ts b/packages/backend/src/server/api/endpoints/admin/silence-user.ts index 4a74c3fb00..17b9f3b5a0 100644 --- a/packages/backend/src/server/api/endpoints/admin/silence-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/silence-user.ts @@ -1,6 +1,7 @@ import define from '../../define.js'; import { Users } from '@/models/index.js'; import { insertModerationLog } from '@/services/insert-moderation-log.js'; +import { publishInternalEvent } from '@/services/stream.js'; export const meta = { tags: ['admin'], @@ -19,7 +20,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOne(ps.userId as string); + const user = await Users.findOneBy({ id: ps.userId }); if (user == null) { throw new Error('user not found'); @@ -33,6 +34,8 @@ export default define(meta, paramDef, async (ps, me) => { isSilenced: true, }); + publishInternalEvent('userChangeSilencedState', { id: user.id, isSilenced: true }); + insertModerationLog(me, 'silence', { targetId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts index adaa7b86ce..ed513eda08 100644 --- a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts @@ -23,7 +23,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOne(ps.userId as string); + const user = await Users.findOneBy({ id: ps.userId }); if (user == null) { throw new Error('user not found'); @@ -58,12 +58,12 @@ export default define(meta, paramDef, async (ps, me) => { }); async function unFollowAll(follower: User) { - const followings = await Followings.find({ + const followings = await Followings.findBy({ followerId: follower.id, }); for (const following of followings) { - const followee = await Users.findOne({ + const followee = await Users.findOneBy({ id: following.followeeId, }); diff --git a/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts b/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts index 4e6366aa18..a4b373f5c7 100644 --- a/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts @@ -1,6 +1,7 @@ import define from '../../define.js'; import { Users } from '@/models/index.js'; import { insertModerationLog } from '@/services/insert-moderation-log.js'; +import { publishInternalEvent } from '@/services/stream.js'; export const meta = { tags: ['admin'], @@ -19,7 +20,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOne(ps.userId as string); + const user = await Users.findOneBy({ id: ps.userId }); if (user == null) { throw new Error('user not found'); @@ -29,6 +30,8 @@ export default define(meta, paramDef, async (ps, me) => { isSilenced: false, }); + publishInternalEvent('userChangeSilencedState', { id: user.id, isSilenced: false }); + insertModerationLog(me, 'unsilence', { targetId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts index 3b9e0a94e0..5cf26251be 100644 --- a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts @@ -20,7 +20,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOne(ps.userId as string); + const user = await Users.findOneBy({ id: ps.userId }); if (user == null) { throw new Error('user not found'); diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index 66b634c877..3c39bf0f30 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -1,8 +1,8 @@ import define from '../../define.js'; -import { getConnection } from 'typeorm'; import { Meta } from '@/models/entities/meta.js'; import { insertModerationLog } from '@/services/insert-moderation-log.js'; import { DB_MAX_NOTE_TEXT_LENGTH } from '@/misc/hard-limits.js'; +import { db } from '@/db/postgre.js'; export const meta = { tags: ['admin'], @@ -396,7 +396,7 @@ export default define(meta, paramDef, async (ps, me) => { set.deeplIsPro = ps.deeplIsPro; } - await getConnection().transaction(async transactionalEntityManager => { + await db.transaction(async transactionalEntityManager => { const meta = await transactionalEntityManager.findOne(Meta, { order: { id: 'DESC', diff --git a/packages/backend/src/server/api/endpoints/admin/vacuum.ts b/packages/backend/src/server/api/endpoints/admin/vacuum.ts index 4c04e019da..0546acfacb 100644 --- a/packages/backend/src/server/api/endpoints/admin/vacuum.ts +++ b/packages/backend/src/server/api/endpoints/admin/vacuum.ts @@ -1,6 +1,6 @@ import define from '../../define.js'; -import { getConnection } from 'typeorm'; import { insertModerationLog } from '@/services/insert-moderation-log.js'; +import { db } from '@/db/postgre.js'; export const meta = { tags: ['admin'], @@ -30,7 +30,7 @@ export default define(meta, paramDef, async (ps, me) => { params.push('ANALYZE'); } - getConnection().query('VACUUM ' + params.join(' ')); + db.query('VACUUM ' + params.join(' ')); insertModerationLog(me, 'vacuum', ps); }); diff --git a/packages/backend/src/server/api/endpoints/announcements.ts b/packages/backend/src/server/api/endpoints/announcements.ts index bba66e98cf..222efdcef0 100644 --- a/packages/backend/src/server/api/endpoints/announcements.ts +++ b/packages/backend/src/server/api/endpoints/announcements.ts @@ -69,7 +69,7 @@ export default define(meta, paramDef, async (ps, user) => { const announcements = await query.take(ps.limit).getMany(); if (user) { - const reads = (await AnnouncementReads.find({ + const reads = (await AnnouncementReads.findBy({ userId: user.id, })).map(x => x.announcementId); diff --git a/packages/backend/src/server/api/endpoints/antennas/create.ts b/packages/backend/src/server/api/endpoints/antennas/create.ts index 92cbba817e..7a4923b944 100644 --- a/packages/backend/src/server/api/endpoints/antennas/create.ts +++ b/packages/backend/src/server/api/endpoints/antennas/create.ts @@ -66,7 +66,7 @@ export default define(meta, paramDef, async (ps, user) => { let userGroupJoining; if (ps.src === 'list' && ps.userListId) { - userList = await UserLists.findOne({ + userList = await UserLists.findOneBy({ id: ps.userListId, userId: user.id, }); @@ -75,7 +75,7 @@ export default define(meta, paramDef, async (ps, user) => { throw new ApiError(meta.errors.noSuchUserList); } } else if (ps.src === 'group' && ps.userGroupId) { - userGroupJoining = await UserGroupJoinings.findOne({ + userGroupJoining = await UserGroupJoinings.findOneBy({ userGroupId: ps.userGroupId, userId: user.id, }); @@ -100,7 +100,7 @@ export default define(meta, paramDef, async (ps, user) => { withReplies: ps.withReplies, withFile: ps.withFile, notify: ps.notify, - }).then(x => Antennas.findOneOrFail(x.identifiers[0])); + }).then(x => Antennas.findOneByOrFail(x.identifiers[0])); publishInternalEvent('antennaCreated', antenna); diff --git a/packages/backend/src/server/api/endpoints/antennas/delete.ts b/packages/backend/src/server/api/endpoints/antennas/delete.ts index 4e6b8b3d2e..ced34ba313 100644 --- a/packages/backend/src/server/api/endpoints/antennas/delete.ts +++ b/packages/backend/src/server/api/endpoints/antennas/delete.ts @@ -29,7 +29,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const antenna = await Antennas.findOne({ + const antenna = await Antennas.findOneBy({ id: ps.antennaId, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/antennas/list.ts b/packages/backend/src/server/api/endpoints/antennas/list.ts index accca5de76..c519b452ef 100644 --- a/packages/backend/src/server/api/endpoints/antennas/list.ts +++ b/packages/backend/src/server/api/endpoints/antennas/list.ts @@ -27,7 +27,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const antennas = await Antennas.find({ + const antennas = await Antennas.findBy({ userId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts index f0cb2ba3c0..004e4c131d 100644 --- a/packages/backend/src/server/api/endpoints/antennas/notes.ts +++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts @@ -48,7 +48,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const antenna = await Antennas.findOne({ + const antenna = await Antennas.findOneBy({ id: ps.antennaId, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/antennas/show.ts b/packages/backend/src/server/api/endpoints/antennas/show.ts index 36c4da81b7..dd693789cb 100644 --- a/packages/backend/src/server/api/endpoints/antennas/show.ts +++ b/packages/backend/src/server/api/endpoints/antennas/show.ts @@ -35,7 +35,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { // Fetch the antenna - const antenna = await Antennas.findOne({ + const antenna = await Antennas.findOneBy({ id: ps.antennaId, userId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/antennas/update.ts b/packages/backend/src/server/api/endpoints/antennas/update.ts index a99964555b..edfedc1752 100644 --- a/packages/backend/src/server/api/endpoints/antennas/update.ts +++ b/packages/backend/src/server/api/endpoints/antennas/update.ts @@ -69,7 +69,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { // Fetch the antenna - const antenna = await Antennas.findOne({ + const antenna = await Antennas.findOneBy({ id: ps.antennaId, userId: user.id, }); @@ -82,7 +82,7 @@ export default define(meta, paramDef, async (ps, user) => { let userGroupJoining; if (ps.src === 'list' && ps.userListId) { - userList = await UserLists.findOne({ + userList = await UserLists.findOneBy({ id: ps.userListId, userId: user.id, }); @@ -91,7 +91,7 @@ export default define(meta, paramDef, async (ps, user) => { throw new ApiError(meta.errors.noSuchUserList); } } else if (ps.src === 'group' && ps.userGroupId) { - userGroupJoining = await UserGroupJoinings.findOne({ + userGroupJoining = await UserGroupJoinings.findOneBy({ userGroupId: ps.userGroupId, userId: user.id, }); @@ -115,7 +115,7 @@ export default define(meta, paramDef, async (ps, user) => { notify: ps.notify, }); - publishInternalEvent('antennaUpdated', await Antennas.findOneOrFail(antenna.id)); + publishInternalEvent('antennaUpdated', await Antennas.findOneByOrFail({ id: antenna.id })); return await Antennas.pack(antenna.id); }); diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index 7595c38e8a..3c0c0642e3 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -97,7 +97,7 @@ async function fetchAny(uri: string): Promise | n const type = parts.pop(); if (type === 'notes') { - const note = await Notes.findOne(id); + const note = await Notes.findOneBy({ id }); if (note) { return { @@ -106,7 +106,7 @@ async function fetchAny(uri: string): Promise | n }; } } else if (type === 'users') { - const user = await Users.findOne(id); + const user = await Users.findOneBy({ id }); if (user) { return { @@ -124,8 +124,8 @@ async function fetchAny(uri: string): Promise | n // URI(AP Object id)としてDB検索 { const [user, note] = await Promise.all([ - Users.findOne({ uri: uri }), - Notes.findOne({ uri: uri }), + Users.findOneBy({ uri: uri }), + Notes.findOneBy({ uri: uri }), ]); const packed = await mergePack(user, note); @@ -145,7 +145,7 @@ async function fetchAny(uri: string): Promise | n const type = parts.pop(); if (type === 'notes') { - const note = await Notes.findOne(id); + const note = await Notes.findOneBy({ id }); if (note) { return { @@ -154,7 +154,7 @@ async function fetchAny(uri: string): Promise | n }; } } else if (type === 'users') { - const user = await Users.findOne(id); + const user = await Users.findOneBy({ id }); if (user) { return { @@ -166,8 +166,8 @@ async function fetchAny(uri: string): Promise | n } const [user, note] = await Promise.all([ - Users.findOne({ uri: object.id }), - Notes.findOne({ uri: object.id }), + Users.findOneBy({ uri: object.id }), + Notes.findOneBy({ uri: object.id }), ]); const packed = await mergePack(user, note); diff --git a/packages/backend/src/server/api/endpoints/app/create.ts b/packages/backend/src/server/api/endpoints/app/create.ts index e0cf8632fb..a0a7350822 100644 --- a/packages/backend/src/server/api/endpoints/app/create.ts +++ b/packages/backend/src/server/api/endpoints/app/create.ts @@ -47,7 +47,7 @@ export default define(meta, paramDef, async (ps, user) => { permission, callbackUrl: ps.callbackUrl, secret: secret, - }).then(x => Apps.findOneOrFail(x.identifiers[0])); + }).then(x => Apps.findOneByOrFail(x.identifiers[0])); return await Apps.pack(app, null, { detail: true, diff --git a/packages/backend/src/server/api/endpoints/app/show.ts b/packages/backend/src/server/api/endpoints/app/show.ts index 54e714e193..451969d971 100644 --- a/packages/backend/src/server/api/endpoints/app/show.ts +++ b/packages/backend/src/server/api/endpoints/app/show.ts @@ -33,7 +33,7 @@ export default define(meta, paramDef, async (ps, user, token) => { const isSecure = user != null && token == null; // Lookup app - const ap = await Apps.findOne(ps.appId); + const ap = await Apps.findOneBy({ id: ps.appId }); if (ap == null) { throw new ApiError(meta.errors.noSuchApp); diff --git a/packages/backend/src/server/api/endpoints/auth/accept.ts b/packages/backend/src/server/api/endpoints/auth/accept.ts index 0760eef52b..b5c06792bb 100644 --- a/packages/backend/src/server/api/endpoints/auth/accept.ts +++ b/packages/backend/src/server/api/endpoints/auth/accept.ts @@ -33,7 +33,7 @@ export const paramDef = { export default define(meta, paramDef, async (ps, user) => { // Fetch token const session = await AuthSessions - .findOne({ token: ps.token }); + .findOneBy({ token: ps.token }); if (session == null) { throw new ApiError(meta.errors.noSuchSession); @@ -43,14 +43,14 @@ export default define(meta, paramDef, async (ps, user) => { const accessToken = secureRndstr(32, true); // Fetch exist access token - const exist = await AccessTokens.findOne({ + const exist = await AccessTokens.findOneBy({ appId: session.appId, userId: user.id, }); if (exist == null) { // Lookup app - const app = await Apps.findOneOrFail(session.appId); + const app = await Apps.findOneByOrFail({ id: session.appId }); // Generate Hash const sha256 = crypto.createHash('sha256'); diff --git a/packages/backend/src/server/api/endpoints/auth/session/generate.ts b/packages/backend/src/server/api/endpoints/auth/session/generate.ts index bd571327d2..717c3e5086 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/generate.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/generate.ts @@ -46,7 +46,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps) => { // Lookup app - const app = await Apps.findOne({ + const app = await Apps.findOneBy({ secret: ps.appSecret, }); @@ -63,7 +63,7 @@ export default define(meta, paramDef, async (ps) => { createdAt: new Date(), appId: app.id, token: token, - }).then(x => AuthSessions.findOneOrFail(x.identifiers[0])); + }).then(x => AuthSessions.findOneByOrFail(x.identifiers[0])); return { token: doc.token, diff --git a/packages/backend/src/server/api/endpoints/auth/session/show.ts b/packages/backend/src/server/api/endpoints/auth/session/show.ts index d40c9363c6..3f3a4d1427 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/show.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/show.ts @@ -48,7 +48,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { // Lookup session - const session = await AuthSessions.findOne({ + const session = await AuthSessions.findOneBy({ token: ps.token, }); diff --git a/packages/backend/src/server/api/endpoints/auth/session/userkey.ts b/packages/backend/src/server/api/endpoints/auth/session/userkey.ts index b699c6fa25..89884ed38a 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/userkey.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/userkey.ts @@ -57,7 +57,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps) => { // Lookup app - const app = await Apps.findOne({ + const app = await Apps.findOneBy({ secret: ps.appSecret, }); @@ -66,7 +66,7 @@ export default define(meta, paramDef, async (ps) => { } // Fetch token - const session = await AuthSessions.findOne({ + const session = await AuthSessions.findOneBy({ token: ps.token, appId: app.id, }); @@ -80,7 +80,7 @@ export default define(meta, paramDef, async (ps) => { } // Lookup access token - const accessToken = await AccessTokens.findOneOrFail({ + const accessToken = await AccessTokens.findOneByOrFail({ appId: app.id, userId: session.userId, }); diff --git a/packages/backend/src/server/api/endpoints/blocking/create.ts b/packages/backend/src/server/api/endpoints/blocking/create.ts index c5e73c0131..0540e6ab0f 100644 --- a/packages/backend/src/server/api/endpoints/blocking/create.ts +++ b/packages/backend/src/server/api/endpoints/blocking/create.ts @@ -54,7 +54,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const blocker = await Users.findOneOrFail(user.id); + const blocker = await Users.findOneByOrFail({ id: user.id }); // 自分自身 if (user.id === ps.userId) { @@ -68,7 +68,7 @@ export default define(meta, paramDef, async (ps, user) => { }); // Check if already blocking - const exist = await Blockings.findOne({ + const exist = await Blockings.findOneBy({ blockerId: blocker.id, blockeeId: blockee.id, }); diff --git a/packages/backend/src/server/api/endpoints/blocking/delete.ts b/packages/backend/src/server/api/endpoints/blocking/delete.ts index a45547290c..77e17b3ba9 100644 --- a/packages/backend/src/server/api/endpoints/blocking/delete.ts +++ b/packages/backend/src/server/api/endpoints/blocking/delete.ts @@ -54,7 +54,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const blocker = await Users.findOneOrFail(user.id); + const blocker = await Users.findOneByOrFail({ id: user.id }); // Check if the blockee is yourself if (user.id === ps.userId) { @@ -68,7 +68,7 @@ export default define(meta, paramDef, async (ps, user) => { }); // Check not blocking - const exist = await Blockings.findOne({ + const exist = await Blockings.findOneBy({ blockerId: blocker.id, blockeeId: blockee.id, }); diff --git a/packages/backend/src/server/api/endpoints/channels/create.ts b/packages/backend/src/server/api/endpoints/channels/create.ts index 16456b9c01..94dcfe5023 100644 --- a/packages/backend/src/server/api/endpoints/channels/create.ts +++ b/packages/backend/src/server/api/endpoints/channels/create.ts @@ -40,7 +40,7 @@ export const paramDef = { export default define(meta, paramDef, async (ps, user) => { let banner = null; if (ps.bannerId != null) { - banner = await DriveFiles.findOne({ + banner = await DriveFiles.findOneBy({ id: ps.bannerId, userId: user.id, }); @@ -57,7 +57,7 @@ export default define(meta, paramDef, async (ps, user) => { name: ps.name, description: ps.description || null, bannerId: banner ? banner.id : null, - } as Channel).then(x => Channels.findOneOrFail(x.identifiers[0])); + } as Channel).then(x => Channels.findOneByOrFail(x.identifiers[0])); return await Channels.pack(channel, user); }); diff --git a/packages/backend/src/server/api/endpoints/channels/follow.ts b/packages/backend/src/server/api/endpoints/channels/follow.ts index 4372c283cb..895ffed0bd 100644 --- a/packages/backend/src/server/api/endpoints/channels/follow.ts +++ b/packages/backend/src/server/api/endpoints/channels/follow.ts @@ -30,7 +30,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const channel = await Channels.findOne({ + const channel = await Channels.findOneBy({ id: ps.channelId, }); diff --git a/packages/backend/src/server/api/endpoints/channels/pin-note.ts b/packages/backend/src/server/api/endpoints/channels/pin-note.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/backend/src/server/api/endpoints/channels/show.ts b/packages/backend/src/server/api/endpoints/channels/show.ts index ea4e013073..87665a9865 100644 --- a/packages/backend/src/server/api/endpoints/channels/show.ts +++ b/packages/backend/src/server/api/endpoints/channels/show.ts @@ -32,7 +32,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const channel = await Channels.findOne({ + const channel = await Channels.findOneBy({ id: ps.channelId, }); diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts index 57a9fa44b8..deaa299013 100644 --- a/packages/backend/src/server/api/endpoints/channels/timeline.ts +++ b/packages/backend/src/server/api/endpoints/channels/timeline.ts @@ -43,7 +43,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const channel = await Channels.findOne({ + const channel = await Channels.findOneBy({ id: ps.channelId, }); diff --git a/packages/backend/src/server/api/endpoints/channels/unfollow.ts b/packages/backend/src/server/api/endpoints/channels/unfollow.ts index 32beb24d6f..e065d897a5 100644 --- a/packages/backend/src/server/api/endpoints/channels/unfollow.ts +++ b/packages/backend/src/server/api/endpoints/channels/unfollow.ts @@ -29,7 +29,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const channel = await Channels.findOne({ + const channel = await Channels.findOneBy({ id: ps.channelId, }); diff --git a/packages/backend/src/server/api/endpoints/channels/update.ts b/packages/backend/src/server/api/endpoints/channels/update.ts index 2f2b4aeeb2..13104f324f 100644 --- a/packages/backend/src/server/api/endpoints/channels/update.ts +++ b/packages/backend/src/server/api/endpoints/channels/update.ts @@ -49,7 +49,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const channel = await Channels.findOne({ + const channel = await Channels.findOneBy({ id: ps.channelId, }); @@ -64,7 +64,7 @@ export default define(meta, paramDef, async (ps, me) => { // eslint:disable-next-line:no-unnecessary-initializer let banner = undefined; if (ps.bannerId != null) { - banner = await DriveFiles.findOne({ + banner = await DriveFiles.findOneBy({ id: ps.bannerId, userId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/clips/add-note.ts b/packages/backend/src/server/api/endpoints/clips/add-note.ts index c630302b98..5d72f5c1bf 100644 --- a/packages/backend/src/server/api/endpoints/clips/add-note.ts +++ b/packages/backend/src/server/api/endpoints/clips/add-note.ts @@ -43,7 +43,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const clip = await Clips.findOne({ + const clip = await Clips.findOneBy({ id: ps.clipId, userId: user.id, }); @@ -57,7 +57,7 @@ export default define(meta, paramDef, async (ps, user) => { throw e; }); - const exist = await ClipNotes.findOne({ + const exist = await ClipNotes.findOneBy({ noteId: note.id, clipId: clip.id, }); diff --git a/packages/backend/src/server/api/endpoints/clips/create.ts b/packages/backend/src/server/api/endpoints/clips/create.ts index 531847d15c..4afe4222a1 100644 --- a/packages/backend/src/server/api/endpoints/clips/create.ts +++ b/packages/backend/src/server/api/endpoints/clips/create.ts @@ -20,7 +20,7 @@ export const paramDef = { type: 'object', properties: { name: { type: 'string', minLength: 1, maxLength: 100 }, - isPublic: { type: 'boolean' }, + isPublic: { type: 'boolean', default: false }, description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 }, }, required: ['name'], @@ -35,7 +35,7 @@ export default define(meta, paramDef, async (ps, user) => { name: ps.name, isPublic: ps.isPublic, description: ps.description, - }).then(x => Clips.findOneOrFail(x.identifiers[0])); + }).then(x => Clips.findOneByOrFail(x.identifiers[0])); return await Clips.pack(clip); }); diff --git a/packages/backend/src/server/api/endpoints/clips/delete.ts b/packages/backend/src/server/api/endpoints/clips/delete.ts index 675db1d57f..b6c0eb702a 100644 --- a/packages/backend/src/server/api/endpoints/clips/delete.ts +++ b/packages/backend/src/server/api/endpoints/clips/delete.ts @@ -28,7 +28,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const clip = await Clips.findOne({ + const clip = await Clips.findOneBy({ id: ps.clipId, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/clips/list.ts b/packages/backend/src/server/api/endpoints/clips/list.ts index 1c955d64fc..378811eba0 100644 --- a/packages/backend/src/server/api/endpoints/clips/list.ts +++ b/packages/backend/src/server/api/endpoints/clips/list.ts @@ -27,7 +27,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const clips = await Clips.find({ + const clips = await Clips.findBy({ userId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/clips/notes.ts b/packages/backend/src/server/api/endpoints/clips/notes.ts index 2627884ee1..4b6782fca0 100644 --- a/packages/backend/src/server/api/endpoints/clips/notes.ts +++ b/packages/backend/src/server/api/endpoints/clips/notes.ts @@ -45,7 +45,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const clip = await Clips.findOne({ + const clip = await Clips.findOneBy({ id: ps.clipId, }); diff --git a/packages/backend/src/server/api/endpoints/clips/show.ts b/packages/backend/src/server/api/endpoints/clips/show.ts index 0a3b25c94e..c3d73c168d 100644 --- a/packages/backend/src/server/api/endpoints/clips/show.ts +++ b/packages/backend/src/server/api/endpoints/clips/show.ts @@ -35,7 +35,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { // Fetch the clip - const clip = await Clips.findOne({ + const clip = await Clips.findOneBy({ id: ps.clipId, }); diff --git a/packages/backend/src/server/api/endpoints/clips/update.ts b/packages/backend/src/server/api/endpoints/clips/update.ts index 0ac5ccd047..b67d844f6e 100644 --- a/packages/backend/src/server/api/endpoints/clips/update.ts +++ b/packages/backend/src/server/api/endpoints/clips/update.ts @@ -38,7 +38,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { // Fetch the clip - const clip = await Clips.findOne({ + const clip = await Clips.findOneBy({ id: ps.clipId, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts index 3c68beee17..7ffe89a1e5 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts @@ -39,7 +39,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { // Fetch file - const file = await DriveFiles.findOne({ + const file = await DriveFiles.findOneBy({ id: ps.fileId, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts index 7e5cb2498e..80293df5d9 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts @@ -24,7 +24,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const file = await DriveFiles.findOne({ + const file = await DriveFiles.findOneBy({ md5: ps.md5, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/drive/files/delete.ts b/packages/backend/src/server/api/endpoints/drive/files/delete.ts index 5f565a63fb..61c56e6314 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/delete.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/delete.ts @@ -2,7 +2,7 @@ import { deleteFile } from '@/services/drive/delete-file.js'; import { publishDriveStream } from '@/services/stream.js'; import define from '../../../define.js'; import { ApiError } from '../../../error.js'; -import { DriveFiles } from '@/models/index.js'; +import { DriveFiles, Users } from '@/models/index.js'; export const meta = { tags: ['drive'], @@ -36,13 +36,13 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const file = await DriveFiles.findOne(ps.fileId); + const file = await DriveFiles.findOneBy({ id: ps.fileId }); if (file == null) { throw new ApiError(meta.errors.noSuchFile); } - if (!user.isAdmin && !user.isModerator && (file.userId !== user.id)) { + if ((!user.isAdmin && !user.isModerator) && (file.userId !== user.id)) { throw new ApiError(meta.errors.accessDenied); } diff --git a/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts index e45ec633d3..f9b4ea89ea 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts @@ -29,7 +29,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const files = await DriveFiles.find({ + const files = await DriveFiles.findBy({ md5: ps.md5, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/drive/files/find.ts b/packages/backend/src/server/api/endpoints/drive/files/find.ts index 974fc9fbad..4938a69d11 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/find.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/find.ts @@ -1,5 +1,6 @@ import define from '../../../define.js'; import { DriveFiles } from '@/models/index.js'; +import { IsNull } from 'typeorm'; export const meta = { requireCredential: true, @@ -30,10 +31,10 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const files = await DriveFiles.find({ + const files = await DriveFiles.findBy({ name: ps.name, userId: user.id, - folderId: ps.folderId, + folderId: ps.folderId ?? IsNull(), }); return await Promise.all(files.map(file => DriveFiles.pack(file, { self: true }))); diff --git a/packages/backend/src/server/api/endpoints/drive/files/show.ts b/packages/backend/src/server/api/endpoints/drive/files/show.ts index 181365c7e6..a2bc0c7aa4 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/show.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/show.ts @@ -1,7 +1,7 @@ import define from '../../../define.js'; import { ApiError } from '../../../error.js'; import { DriveFile } from '@/models/entities/drive-file.js'; -import { DriveFiles } from '@/models/index.js'; +import { DriveFiles, Users } from '@/models/index.js'; export const meta = { tags: ['drive'], @@ -28,22 +28,25 @@ export const meta = { code: 'ACCESS_DENIED', id: '25b73c73-68b1-41d0-bad1-381cfdf6579f', }, - - fileIdOrUrlRequired: { - message: 'fileId or url required.', - code: 'INVALID_PARAM', - id: '89674805-722c-440c-8d88-5641830dc3e4', - }, }, } as const; export const paramDef = { type: 'object', - properties: { - fileId: { type: 'string', format: 'misskey:id' }, - url: { type: 'string' }, - }, - required: [], + anyOf: [ + { + properties: { + fileId: { type: 'string', format: 'misskey:id' }, + }, + required: ['fileId'], + }, + { + properties: { + url: { type: 'string' }, + }, + required: ['url'], + }, + ], } as const; // eslint-disable-next-line import/no-default-export @@ -51,7 +54,7 @@ export default define(meta, paramDef, async (ps, user) => { let file: DriveFile | undefined; if (ps.fileId) { - file = await DriveFiles.findOne(ps.fileId); + file = await DriveFiles.findOneBy({ id: ps.fileId }); } else if (ps.url) { file = await DriveFiles.findOne({ where: [{ @@ -62,15 +65,13 @@ export default define(meta, paramDef, async (ps, user) => { thumbnailUrl: ps.url, }], }); - } else { - throw new ApiError(meta.errors.fileIdOrUrlRequired); } if (file == null) { throw new ApiError(meta.errors.noSuchFile); } - if (!user.isAdmin && !user.isModerator && (file.userId !== user.id)) { + if ((!user.isAdmin && !user.isModerator) && (file.userId !== user.id)) { throw new ApiError(meta.errors.accessDenied); } diff --git a/packages/backend/src/server/api/endpoints/drive/files/update.ts b/packages/backend/src/server/api/endpoints/drive/files/update.ts index ab8e4aeeb2..4b3f5f2dc9 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/update.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/update.ts @@ -1,7 +1,7 @@ import { publishDriveStream } from '@/services/stream.js'; import define from '../../../define.js'; import { ApiError } from '../../../error.js'; -import { DriveFiles, DriveFolders } from '@/models/index.js'; +import { DriveFiles, DriveFolders, Users } from '@/models/index.js'; import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js'; export const meta = { @@ -58,13 +58,13 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const file = await DriveFiles.findOne(ps.fileId); + const file = await DriveFiles.findOneBy({ id: ps.fileId }); if (file == null) { throw new ApiError(meta.errors.noSuchFile); } - if (!user.isAdmin && !user.isModerator && (file.userId !== user.id)) { + if ((!user.isAdmin && !user.isModerator) && (file.userId !== user.id)) { throw new ApiError(meta.errors.accessDenied); } @@ -81,7 +81,7 @@ export default define(meta, paramDef, async (ps, user) => { if (ps.folderId === null) { file.folderId = null; } else { - const folder = await DriveFolders.findOne({ + const folder = await DriveFolders.findOneBy({ id: ps.folderId, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/drive/folders/create.ts b/packages/backend/src/server/api/endpoints/drive/folders/create.ts index 4ae10f0621..3d7f514c85 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/create.ts @@ -41,7 +41,7 @@ export default define(meta, paramDef, async (ps, user) => { let parent = null; if (ps.parentId) { // Fetch parent folder - parent = await DriveFolders.findOne({ + parent = await DriveFolders.findOneBy({ id: ps.parentId, userId: user.id, }); @@ -58,7 +58,7 @@ export default define(meta, paramDef, async (ps, user) => { name: ps.name, parentId: parent !== null ? parent.id : null, userId: user.id, - }).then(x => DriveFolders.findOneOrFail(x.identifiers[0])); + }).then(x => DriveFolders.findOneByOrFail(x.identifiers[0])); const folderObj = await DriveFolders.pack(folder); diff --git a/packages/backend/src/server/api/endpoints/drive/folders/delete.ts b/packages/backend/src/server/api/endpoints/drive/folders/delete.ts index 4994615cc6..ab9d411ec0 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/delete.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/delete.ts @@ -36,7 +36,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { // Get folder - const folder = await DriveFolders.findOne({ + const folder = await DriveFolders.findOneBy({ id: ps.folderId, userId: user.id, }); @@ -46,8 +46,8 @@ export default define(meta, paramDef, async (ps, user) => { } const [childFoldersCount, childFilesCount] = await Promise.all([ - DriveFolders.count({ parentId: folder.id }), - DriveFiles.count({ folderId: folder.id }), + DriveFolders.countBy({ parentId: folder.id }), + DriveFiles.countBy({ folderId: folder.id }), ]); if (childFoldersCount !== 0 || childFilesCount !== 0) { diff --git a/packages/backend/src/server/api/endpoints/drive/folders/find.ts b/packages/backend/src/server/api/endpoints/drive/folders/find.ts index 9bf0e3d61b..1feab273a1 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/find.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/find.ts @@ -1,5 +1,6 @@ import define from '../../../define.js'; import { DriveFolders } from '@/models/index.js'; +import { IsNull } from 'typeorm'; export const meta = { tags: ['drive'], @@ -30,10 +31,10 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const folders = await DriveFolders.find({ + const folders = await DriveFolders.findBy({ name: ps.name, userId: user.id, - parentId: ps.parentId, + parentId: ps.parentId ?? IsNull(), }); return await Promise.all(folders.map(folder => DriveFolders.pack(folder))); diff --git a/packages/backend/src/server/api/endpoints/drive/folders/show.ts b/packages/backend/src/server/api/endpoints/drive/folders/show.ts index f09816d57a..1e7aa2b16c 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/show.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/show.ts @@ -35,7 +35,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { // Get folder - const folder = await DriveFolders.findOne({ + const folder = await DriveFolders.findOneBy({ id: ps.folderId, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/drive/folders/update.ts b/packages/backend/src/server/api/endpoints/drive/folders/update.ts index c020b243ef..1aa2e84292 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/update.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/update.ts @@ -50,7 +50,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { // Fetch folder - const folder = await DriveFolders.findOne({ + const folder = await DriveFolders.findOneBy({ id: ps.folderId, userId: user.id, }); @@ -68,7 +68,7 @@ export default define(meta, paramDef, async (ps, user) => { folder.parentId = null; } else { // Get parent folder - const parent = await DriveFolders.findOne({ + const parent = await DriveFolders.findOneBy({ id: ps.parentId, userId: user.id, }); @@ -78,9 +78,9 @@ export default define(meta, paramDef, async (ps, user) => { } // Check if the circular reference will occur - async function checkCircle(folderId: any): Promise { + async function checkCircle(folderId: string): Promise { // Fetch folder - const folder2 = await DriveFolders.findOne({ + const folder2 = await DriveFolders.findOneBy({ id: folderId, }); diff --git a/packages/backend/src/server/api/endpoints/endpoint.ts b/packages/backend/src/server/api/endpoints/endpoint.ts index 9db140183c..c174126779 100644 --- a/packages/backend/src/server/api/endpoints/endpoint.ts +++ b/packages/backend/src/server/api/endpoints/endpoint.ts @@ -20,9 +20,9 @@ export default define(meta, paramDef, async (ps) => { const ep = endpoints.find(x => x.name === ps.endpoint); if (ep == null) return null; return { - params: Object.entries(ep.meta.params || {}).map(([k, v]) => ({ + params: Object.entries(ep.params.properties || {}).map(([k, v]) => ({ name: k, - type: v.validator.name === 'ID' ? 'String' : v.validator.name, + type: v.type.charAt(0).toUpperCase() + v.type.slice(1), })), }; }); diff --git a/packages/backend/src/server/api/endpoints/federation/instances.ts b/packages/backend/src/server/api/endpoints/federation/instances.ts index e272971763..07e5c07c6a 100644 --- a/packages/backend/src/server/api/endpoints/federation/instances.ts +++ b/packages/backend/src/server/api/endpoints/federation/instances.ts @@ -22,7 +22,7 @@ export const meta = { export const paramDef = { type: 'object', properties: { - host: { type: 'string', nullable: true }, + host: { type: 'string', nullable: true, description: 'Omit or use `null` to not filter by host.' }, blocked: { type: 'boolean', nullable: true }, notResponding: { type: 'boolean', nullable: true }, suspended: { type: 'boolean', nullable: true }, diff --git a/packages/backend/src/server/api/endpoints/federation/show-instance.ts b/packages/backend/src/server/api/endpoints/federation/show-instance.ts index 5bfe43fc9c..2fbb8a15cb 100644 --- a/packages/backend/src/server/api/endpoints/federation/show-instance.ts +++ b/packages/backend/src/server/api/endpoints/federation/show-instance.ts @@ -28,7 +28,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { const instance = await Instances - .findOne({ host: toPuny(ps.host) }); + .findOneBy({ host: toPuny(ps.host) }); return instance ? await Instances.pack(instance) : null; }); diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts index 8758a64a39..02a030cd5e 100644 --- a/packages/backend/src/server/api/endpoints/following/create.ts +++ b/packages/backend/src/server/api/endpoints/following/create.ts @@ -81,7 +81,7 @@ export default define(meta, paramDef, async (ps, user) => { }); // Check if already following - const exist = await Followings.findOne({ + const exist = await Followings.findOneBy({ followerId: follower.id, followeeId: followee.id, }); diff --git a/packages/backend/src/server/api/endpoints/following/delete.ts b/packages/backend/src/server/api/endpoints/following/delete.ts index 47efc59b81..2f41b16e9a 100644 --- a/packages/backend/src/server/api/endpoints/following/delete.ts +++ b/packages/backend/src/server/api/endpoints/following/delete.ts @@ -68,7 +68,7 @@ export default define(meta, paramDef, async (ps, user) => { }); // Check not following - const exist = await Followings.findOne({ + const exist = await Followings.findOneBy({ followerId: follower.id, followeeId: followee.id, }); diff --git a/packages/backend/src/server/api/endpoints/following/invalidate.ts b/packages/backend/src/server/api/endpoints/following/invalidate.ts index 24d8256ca6..18ec5affe8 100644 --- a/packages/backend/src/server/api/endpoints/following/invalidate.ts +++ b/packages/backend/src/server/api/endpoints/following/invalidate.ts @@ -68,7 +68,7 @@ export default define(meta, paramDef, async (ps, user) => { }); // Check not following - const exist = await Followings.findOne({ + const exist = await Followings.findOneBy({ followerId: follower.id, followeeId: followee.id, }); diff --git a/packages/backend/src/server/api/endpoints/following/requests/list.ts b/packages/backend/src/server/api/endpoints/following/requests/list.ts index 3b60b89b3c..a8f42c481d 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/list.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/list.ts @@ -43,7 +43,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const reqs = await FollowRequests.find({ + const reqs = await FollowRequests.findBy({ followeeId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts index eb6c0f3eb1..8074a3b34f 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts @@ -45,7 +45,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { const files = (await Promise.all(ps.fileIds.map(fileId => - DriveFiles.findOne({ + DriveFiles.findOneBy({ id: fileId, userId: user.id, }) @@ -64,7 +64,7 @@ export default define(meta, paramDef, async (ps, user) => { userId: user.id, isSensitive: ps.isSensitive, fileIds: files.map(file => file.id), - })).then(x => GalleryPosts.findOneOrFail(x.identifiers[0])); + })).then(x => GalleryPosts.findOneByOrFail(x.identifiers[0])); return await GalleryPosts.pack(post, user); }); diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts b/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts index f8bf785ee6..b00ee0e2ae 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts @@ -28,7 +28,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const post = await GalleryPosts.findOne({ + const post = await GalleryPosts.findOneBy({ id: ps.postId, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts index d154bfc3c6..b858114aec 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts @@ -41,7 +41,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const post = await GalleryPosts.findOne(ps.postId); + const post = await GalleryPosts.findOneBy({ id: ps.postId }); if (post == null) { throw new ApiError(meta.errors.noSuchPost); } @@ -51,7 +51,7 @@ export default define(meta, paramDef, async (ps, user) => { } // if already liked - const exist = await GalleryLikes.findOne({ + const exist = await GalleryLikes.findOneBy({ postId: post.id, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/show.ts b/packages/backend/src/server/api/endpoints/gallery/posts/show.ts index 5b4594070c..4f6dafd7cb 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/show.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/show.ts @@ -32,7 +32,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const post = await GalleryPosts.findOne({ + const post = await GalleryPosts.findOneBy({ id: ps.postId, }); diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts b/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts index b00008a864..d136239e5e 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts @@ -34,12 +34,12 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const post = await GalleryPosts.findOne(ps.postId); + const post = await GalleryPosts.findOneBy({ id: ps.postId }); if (post == null) { throw new ApiError(meta.errors.noSuchPost); } - const exist = await GalleryLikes.findOne({ + const exist = await GalleryLikes.findOneBy({ postId: post.id, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts index 123794d08c..82fe38078e 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts @@ -45,7 +45,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { const files = (await Promise.all(ps.fileIds.map(fileId => - DriveFiles.findOne({ + DriveFiles.findOneBy({ id: fileId, userId: user.id, }) @@ -66,7 +66,7 @@ export default define(meta, paramDef, async (ps, user) => { fileIds: files.map(file => file.id), }); - const post = await GalleryPosts.findOneOrFail(ps.postId); + const post = await GalleryPosts.findOneByOrFail({ id: ps.postId }); return await GalleryPosts.pack(post, user); }); diff --git a/packages/backend/src/server/api/endpoints/get-online-users-count.ts b/packages/backend/src/server/api/endpoints/get-online-users-count.ts index 80a2334cfa..b0c1225bee 100644 --- a/packages/backend/src/server/api/endpoints/get-online-users-count.ts +++ b/packages/backend/src/server/api/endpoints/get-online-users-count.ts @@ -17,7 +17,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async () => { - const count = await Users.count({ + const count = await Users.countBy({ lastActiveDate: MoreThan(new Date(Date.now() - USER_ONLINE_THRESHOLD)), }); diff --git a/packages/backend/src/server/api/endpoints/hashtags/show.ts b/packages/backend/src/server/api/endpoints/hashtags/show.ts index 6e6afa4f13..5b78f6ac7f 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/show.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/show.ts @@ -33,7 +33,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const hashtag = await Hashtags.findOne({ name: normalizeForSearch(ps.tag) }); + const hashtag = await Hashtags.findOneBy({ name: normalizeForSearch(ps.tag) }); if (hashtag == null) { throw new ApiError(meta.errors.noSuchHashtag); } diff --git a/packages/backend/src/server/api/endpoints/i/2fa/done.ts b/packages/backend/src/server/api/endpoints/i/2fa/done.ts index 70478430db..35806b2bc3 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/done.ts @@ -20,7 +20,7 @@ export const paramDef = { export default define(meta, paramDef, async (ps, user) => { const token = ps.token.replace(/\s/g, ''); - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); if (profile.twoFactorTempSecret == null) { throw new Error('二段階認証の設定が開始されていません'); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts index f33237c8bf..0116a55fb7 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts @@ -35,7 +35,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); // Compare password const same = await bcrypt.compare(ps.password, profile.password!); @@ -96,7 +96,7 @@ export default define(meta, paramDef, async (ps, user) => { }); if (!verificationData.valid) throw new Error('signature invalid'); - const attestationChallenge = await AttestationChallenges.findOne({ + const attestationChallenge = await AttestationChallenges.findOneBy({ userId: user.id, id: ps.challengeId, registrationChallenge: true, diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts index 0c4c99271e..e906b82043 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts @@ -24,7 +24,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); // Compare password const same = await bcrypt.compare(ps.password, profile.password!); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register.ts b/packages/backend/src/server/api/endpoints/i/2fa/register.ts index 7951e393b8..d5e1b19e54 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register.ts @@ -21,7 +21,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); // Compare password const same = await bcrypt.compare(ps.password, profile.password!); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts index 2b69b1f8c3..eb2f75308d 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts @@ -20,7 +20,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); // Compare password const same = await bcrypt.compare(ps.password, profile.password!); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts index c5633f68b1..45e7a98639 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts @@ -18,7 +18,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); // Compare password const same = await bcrypt.compare(ps.password, profile.password!); diff --git a/packages/backend/src/server/api/endpoints/i/change-password.ts b/packages/backend/src/server/api/endpoints/i/change-password.ts index 16509d2dcf..f9f6a33a80 100644 --- a/packages/backend/src/server/api/endpoints/i/change-password.ts +++ b/packages/backend/src/server/api/endpoints/i/change-password.ts @@ -19,7 +19,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); // Compare password const same = await bcrypt.compare(ps.currentPassword, profile.password!); diff --git a/packages/backend/src/server/api/endpoints/i/delete-account.ts b/packages/backend/src/server/api/endpoints/i/delete-account.ts index 8cb6b6a631..184005eb53 100644 --- a/packages/backend/src/server/api/endpoints/i/delete-account.ts +++ b/packages/backend/src/server/api/endpoints/i/delete-account.ts @@ -21,8 +21,8 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const profile = await UserProfiles.findOneOrFail(user.id); - const userDetailed = await Users.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); + const userDetailed = await Users.findOneByOrFail({ id: user.id }); if (userDetailed.isDeleted) { return; } diff --git a/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts b/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts index bc3e0aff47..e7d7518c5b 100644 --- a/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts +++ b/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts @@ -29,7 +29,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { return { - count: await MutedNotes.count({ + count: await MutedNotes.countBy({ userId: user.id, reason: 'word', }), diff --git a/packages/backend/src/server/api/endpoints/i/import-blocking.ts b/packages/backend/src/server/api/endpoints/i/import-blocking.ts index c70704f9a8..0bcbf37ddd 100644 --- a/packages/backend/src/server/api/endpoints/i/import-blocking.ts +++ b/packages/backend/src/server/api/endpoints/i/import-blocking.ts @@ -50,7 +50,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const file = await DriveFiles.findOne(ps.fileId); + const file = await DriveFiles.findOneBy({ id: ps.fileId }); if (file == null) throw new ApiError(meta.errors.noSuchFile); //if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType); diff --git a/packages/backend/src/server/api/endpoints/i/import-following.ts b/packages/backend/src/server/api/endpoints/i/import-following.ts index 7e9175cbf8..ee2abbea19 100644 --- a/packages/backend/src/server/api/endpoints/i/import-following.ts +++ b/packages/backend/src/server/api/endpoints/i/import-following.ts @@ -49,7 +49,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const file = await DriveFiles.findOne(ps.fileId); + const file = await DriveFiles.findOneBy({ id: ps.fileId }); if (file == null) throw new ApiError(meta.errors.noSuchFile); //if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType); diff --git a/packages/backend/src/server/api/endpoints/i/import-muting.ts b/packages/backend/src/server/api/endpoints/i/import-muting.ts index abbf07212e..b3b3b39238 100644 --- a/packages/backend/src/server/api/endpoints/i/import-muting.ts +++ b/packages/backend/src/server/api/endpoints/i/import-muting.ts @@ -50,7 +50,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const file = await DriveFiles.findOne(ps.fileId); + const file = await DriveFiles.findOneBy({ id: ps.fileId }); if (file == null) throw new ApiError(meta.errors.noSuchFile); //if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType); diff --git a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts index be162817f1..64f5ec05fd 100644 --- a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts +++ b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts @@ -49,7 +49,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const file = await DriveFiles.findOne(ps.fileId); + const file = await DriveFiles.findOneBy({ id: ps.fileId }); if (file == null) throw new ApiError(meta.errors.noSuchFile); //if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType); diff --git a/packages/backend/src/server/api/endpoints/i/notifications.ts b/packages/backend/src/server/api/endpoints/i/notifications.ts index 7d9bd44d1d..6ea8cb3574 100644 --- a/packages/backend/src/server/api/endpoints/i/notifications.ts +++ b/packages/backend/src/server/api/endpoints/i/notifications.ts @@ -70,6 +70,8 @@ export default define(meta, paramDef, async (ps, user) => { .andWhere(`notification.notifieeId = :meId`, { meId: user.id }) .leftJoinAndSelect('notification.notifier', 'notifier') .leftJoinAndSelect('notification.note', 'note') + .leftJoinAndSelect('notifier.avatar', 'notifierAvatar') + .leftJoinAndSelect('notifier.banner', 'notifierBanner') .leftJoinAndSelect('note.user', 'user') .leftJoinAndSelect('user.avatar', 'avatar') .leftJoinAndSelect('user.banner', 'banner') diff --git a/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts b/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts index 2e291a34a0..7ff6409caf 100644 --- a/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts +++ b/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts @@ -26,7 +26,7 @@ export default define(meta, paramDef, async (ps, user) => { isRead: true, }); - const joinings = await UserGroupJoinings.find({ userId: user.id }); + const joinings = await UserGroupJoinings.findBy({ userId: user.id }); await Promise.all(joinings.map(j => MessagingMessages.createQueryBuilder().update() .set({ diff --git a/packages/backend/src/server/api/endpoints/i/read-announcement.ts b/packages/backend/src/server/api/endpoints/i/read-announcement.ts index 647fa77fa4..45b6e98c86 100644 --- a/packages/backend/src/server/api/endpoints/i/read-announcement.ts +++ b/packages/backend/src/server/api/endpoints/i/read-announcement.ts @@ -31,14 +31,14 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { // Check if announcement exists - const announcement = await Announcements.findOne(ps.announcementId); + const announcement = await Announcements.findOneBy({ id: ps.announcementId }); if (announcement == null) { throw new ApiError(meta.errors.noSuchAnnouncement); } // Check if already read - const read = await AnnouncementReads.findOne({ + const read = await AnnouncementReads.findOneBy({ announcementId: ps.announcementId, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts index 771c98b212..af929b04e8 100644 --- a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts +++ b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts @@ -1,5 +1,5 @@ import bcrypt from 'bcryptjs'; -import { publishMainStream, publishUserEvent } from '@/services/stream.js'; +import { publishInternalEvent, publishMainStream, publishUserEvent } from '@/services/stream.js'; import generateUserToken from '../../common/generate-native-user-token.js'; import define from '../../define.js'; import { Users, UserProfiles } from '@/models/index.js'; @@ -20,7 +20,10 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const profile = await UserProfiles.findOneOrFail(user.id); + const freshUser = await Users.findOneByOrFail({ id: user.id }); + const oldToken = freshUser.token; + + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); // Compare password const same = await bcrypt.compare(ps.password, profile.password!); @@ -29,14 +32,14 @@ export default define(meta, paramDef, async (ps, user) => { throw new Error('incorrect password'); } - // Generate secret - const secret = generateUserToken(); + const newToken = generateUserToken(); await Users.update(user.id, { - token: secret, + token: newToken, }); // Publish event + publishInternalEvent('userTokenRegenerated', { id: user.id, oldToken, newToken }); publishMainStream(user.id, 'myTokenRegenerated'); // Terminate streaming diff --git a/packages/backend/src/server/api/endpoints/i/revoke-token.ts b/packages/backend/src/server/api/endpoints/i/revoke-token.ts index b957fd0796..c692453794 100644 --- a/packages/backend/src/server/api/endpoints/i/revoke-token.ts +++ b/packages/backend/src/server/api/endpoints/i/revoke-token.ts @@ -18,7 +18,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const token = await AccessTokens.findOne(ps.tokenId); + const token = await AccessTokens.findOneBy({ id: ps.tokenId }); if (token) { await AccessTokens.delete({ diff --git a/packages/backend/src/server/api/endpoints/i/update-email.ts b/packages/backend/src/server/api/endpoints/i/update-email.ts index 389ff1b81d..3318078523 100644 --- a/packages/backend/src/server/api/endpoints/i/update-email.ts +++ b/packages/backend/src/server/api/endpoints/i/update-email.ts @@ -45,7 +45,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); // Compare password const same = await bcrypt.compare(ps.password, profile.password!); diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index 85d0a62548..b2964e68c7 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -121,13 +121,13 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, _user, token) => { - const user = await Users.findOneOrFail(_user.id); + const user = await Users.findOneByOrFail({ id: _user.id }); const isSecure = token == null; const updates = {} as Partial; const profileUpdates = {} as Partial; - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); if (ps.name !== undefined) updates.name = ps.name; if (ps.description !== undefined) profileUpdates.description = ps.description; @@ -171,21 +171,21 @@ export default define(meta, paramDef, async (ps, _user, token) => { if (ps.emailNotificationTypes !== undefined) profileUpdates.emailNotificationTypes = ps.emailNotificationTypes; if (ps.avatarId) { - const avatar = await DriveFiles.findOne(ps.avatarId); + const avatar = await DriveFiles.findOneBy({ id: ps.avatarId }); if (avatar == null || avatar.userId !== user.id) throw new ApiError(meta.errors.noSuchAvatar); if (!avatar.type.startsWith('image/')) throw new ApiError(meta.errors.avatarNotAnImage); } if (ps.bannerId) { - const banner = await DriveFiles.findOne(ps.bannerId); + const banner = await DriveFiles.findOneBy({ id: ps.bannerId }); if (banner == null || banner.userId !== user.id) throw new ApiError(meta.errors.noSuchBanner); if (!banner.type.startsWith('image/')) throw new ApiError(meta.errors.bannerNotAnImage); } if (ps.pinnedPageId) { - const page = await Pages.findOne(ps.pinnedPageId); + const page = await Pages.findOneBy({ id: ps.pinnedPageId }); if (page == null || page.userId !== user.id) throw new ApiError(meta.errors.noSuchPage); @@ -238,7 +238,7 @@ export default define(meta, paramDef, async (ps, _user, token) => { // Publish meUpdated event publishMainStream(user.id, 'meUpdated', iObj); - publishUserEvent(user.id, 'updateUserProfile', await UserProfiles.findOne(user.id)); + publishUserEvent(user.id, 'updateUserProfile', await UserProfiles.findOneBy({ userId: user.id })); // 鍵垢を解除したとき、溜まっていたフォローリクエストがあるならすべて承認 if (user.isLocked && ps.isLocked === false) { diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts new file mode 100644 index 0000000000..2e2fd00b8c --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts @@ -0,0 +1,43 @@ +import define from '../../../define.js'; +import { genId } from '@/misc/gen-id.js'; +import { Webhooks } from '@/models/index.js'; +import { publishInternalEvent } from '@/services/stream.js'; +import { webhookEventTypes } from '@/models/entities/webhook.js'; + +export const meta = { + tags: ['webhooks'], + + requireCredential: true, + + kind: 'write:account', +} as const; + +export const paramDef = { + type: 'object', + properties: { + name: { type: 'string', minLength: 1, maxLength: 100 }, + url: { type: 'string', minLength: 1, maxLength: 1024 }, + secret: { type: 'string', minLength: 1, maxLength: 1024 }, + on: { type: 'array', items: { + type: 'string', enum: webhookEventTypes, + } }, + }, + required: ['name', 'url', 'secret', 'on'], +} as const; + +// eslint-disable-next-line import/no-default-export +export default define(meta, paramDef, async (ps, user) => { + const webhook = await Webhooks.insert({ + id: genId(), + createdAt: new Date(), + userId: user.id, + name: ps.name, + url: ps.url, + secret: ps.secret, + on: ps.on, + }).then(x => Webhooks.findOneByOrFail(x.identifiers[0])); + + publishInternalEvent('webhookCreated', webhook); + + return webhook; +}); diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts b/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts new file mode 100644 index 0000000000..2821eaa5f1 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts @@ -0,0 +1,44 @@ +import define from '../../../define.js'; +import { ApiError } from '../../../error.js'; +import { Webhooks } from '@/models/index.js'; +import { publishInternalEvent } from '@/services/stream.js'; + +export const meta = { + tags: ['webhooks'], + + requireCredential: true, + + kind: 'write:account', + + errors: { + noSuchWebhook: { + message: 'No such webhook.', + code: 'NO_SUCH_WEBHOOK', + id: 'bae73e5a-5522-4965-ae19-3a8688e71d82', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + webhookId: { type: 'string', format: 'misskey:id' }, + }, + required: ['webhookId'], +} as const; + +// eslint-disable-next-line import/no-default-export +export default define(meta, paramDef, async (ps, user) => { + const webhook = await Webhooks.findOneBy({ + id: ps.webhookId, + userId: user.id, + }); + + if (webhook == null) { + throw new ApiError(meta.errors.noSuchWebhook); + } + + await Webhooks.delete(webhook.id); + + publishInternalEvent('webhookDeleted', webhook); +}); diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts new file mode 100644 index 0000000000..54e4563732 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts @@ -0,0 +1,25 @@ +import define from '../../../define.js'; +import { Webhooks } from '@/models/index.js'; + +export const meta = { + tags: ['webhooks', 'account'], + + requireCredential: true, + + kind: 'read:account', +} as const; + +export const paramDef = { + type: 'object', + properties: {}, + required: [], +} as const; + +// eslint-disable-next-line import/no-default-export +export default define(meta, paramDef, async (ps, me) => { + const webhooks = await Webhooks.findBy({ + userId: me.id, + }); + + return webhooks; +}); diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts new file mode 100644 index 0000000000..02fa1edb5e --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts @@ -0,0 +1,41 @@ +import define from '../../../define.js'; +import { ApiError } from '../../../error.js'; +import { Webhooks } from '@/models/index.js'; + +export const meta = { + tags: ['webhooks'], + + requireCredential: true, + + kind: 'read:account', + + errors: { + noSuchWebhook: { + message: 'No such webhook.', + code: 'NO_SUCH_WEBHOOK', + id: '50f614d9-3047-4f7e-90d8-ad6b2d5fb098', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + webhookId: { type: 'string', format: 'misskey:id' }, + }, + required: ['webhookId'], +} as const; + +// eslint-disable-next-line import/no-default-export +export default define(meta, paramDef, async (ps, user) => { + const webhook = await Webhooks.findOneBy({ + id: ps.webhookId, + userId: user.id, + }); + + if (webhook == null) { + throw new ApiError(meta.errors.noSuchWebhook); + } + + return webhook; +}); diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/update.ts b/packages/backend/src/server/api/endpoints/i/webhooks/update.ts new file mode 100644 index 0000000000..f87b9753fb --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/webhooks/update.ts @@ -0,0 +1,59 @@ +import define from '../../../define.js'; +import { ApiError } from '../../../error.js'; +import { Webhooks } from '@/models/index.js'; +import { publishInternalEvent } from '@/services/stream.js'; +import { webhookEventTypes } from '@/models/entities/webhook.js'; + +export const meta = { + tags: ['webhooks'], + + requireCredential: true, + + kind: 'write:account', + + errors: { + noSuchWebhook: { + message: 'No such webhook.', + code: 'NO_SUCH_WEBHOOK', + id: 'fb0fea69-da18-45b1-828d-bd4fd1612518', + }, + }, + +} as const; + +export const paramDef = { + type: 'object', + properties: { + webhookId: { type: 'string', format: 'misskey:id' }, + name: { type: 'string', minLength: 1, maxLength: 100 }, + url: { type: 'string', minLength: 1, maxLength: 1024 }, + secret: { type: 'string', minLength: 1, maxLength: 1024 }, + on: { type: 'array', items: { + type: 'string', enum: webhookEventTypes, + } }, + active: { type: 'boolean' }, + }, + required: ['webhookId', 'name', 'url', 'secret', 'on', 'active'], +} as const; + +// eslint-disable-next-line import/no-default-export +export default define(meta, paramDef, async (ps, user) => { + const webhook = await Webhooks.findOneBy({ + id: ps.webhookId, + userId: user.id, + }); + + if (webhook == null) { + throw new ApiError(meta.errors.noSuchWebhook); + } + + await Webhooks.update(webhook.id, { + name: ps.name, + url: ps.url, + secret: ps.secret, + on: ps.on, + active: ps.active, + }); + + publishInternalEvent('webhookUpdated', webhook); +}); diff --git a/packages/backend/src/server/api/endpoints/messaging/history.ts b/packages/backend/src/server/api/endpoints/messaging/history.ts index 14de4e1028..ea0600d0e4 100644 --- a/packages/backend/src/server/api/endpoints/messaging/history.ts +++ b/packages/backend/src/server/api/endpoints/messaging/history.ts @@ -32,11 +32,11 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const mute = await Mutings.find({ + const mute = await Mutings.findBy({ muterId: user.id, }); - const groups = ps.group ? await UserGroupJoinings.find({ + const groups = ps.group ? await UserGroupJoinings.findBy({ userId: user.id, }).then(xs => xs.map(x => x.userGroupId)) : []; diff --git a/packages/backend/src/server/api/endpoints/messaging/messages.ts b/packages/backend/src/server/api/endpoints/messaging/messages.ts index 49ace21600..dbf1f6c868 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages.ts @@ -47,14 +47,25 @@ export const meta = { export const paramDef = { type: 'object', properties: { - userId: { type: 'string', format: 'misskey:id' }, - groupId: { type: 'string', format: 'misskey:id' }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, markAsRead: { type: 'boolean', default: true }, }, - required: [], + anyOf: [ + { + properties: { + userId: { type: 'string', format: 'misskey:id' }, + }, + required: ['userId'], + }, + { + properties: { + groupId: { type: 'string', format: 'misskey:id' }, + }, + required: ['groupId'], + }, + ], } as const; // eslint-disable-next-line import/no-default-export @@ -97,14 +108,14 @@ export default define(meta, paramDef, async (ps, user) => { }))); } else if (ps.groupId != null) { // Fetch recipient (group) - const recipientGroup = await UserGroups.findOne(ps.groupId); + const recipientGroup = await UserGroups.findOneBy({ id: ps.groupId }); if (recipientGroup == null) { throw new ApiError(meta.errors.noSuchGroup); } // check joined - const joining = await UserGroupJoinings.findOne({ + const joining = await UserGroupJoinings.findOneBy({ userId: user.id, userGroupId: recipientGroup.id, }); @@ -126,7 +137,5 @@ export default define(meta, paramDef, async (ps, user) => { return await Promise.all(messages.map(message => MessagingMessages.pack(message, user, { populateGroup: false, }))); - } else { - throw new Error(); } }); diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/create.ts b/packages/backend/src/server/api/endpoints/messaging/messages/create.ts index a9b926c4fb..405af5ec17 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/create.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/create.ts @@ -67,18 +67,29 @@ export const meta = { export const paramDef = { type: 'object', properties: { - userId: { type: 'string', format: 'misskey:id' }, - groupId: { type: 'string', format: 'misskey:id' }, text: { type: 'string', nullable: true, maxLength: 3000 }, fileId: { type: 'string', format: 'misskey:id' }, }, - required: [], + anyOf: [ + { + properties: { + userId: { type: 'string', format: 'misskey:id' }, + }, + required: ['userId'], + }, + { + properties: { + groupId: { type: 'string', format: 'misskey:id' }, + }, + required: ['groupId'], + }, + ], } as const; // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - let recipientUser: User | undefined; - let recipientGroup: UserGroup | undefined; + let recipientUser: User | null; + let recipientGroup: UserGroup | null; if (ps.userId != null) { // Myself @@ -93,7 +104,7 @@ export default define(meta, paramDef, async (ps, user) => { }); // Check blocking - const block = await Blockings.findOne({ + const block = await Blockings.findOneBy({ blockerId: recipientUser.id, blockeeId: user.id, }); @@ -102,14 +113,14 @@ export default define(meta, paramDef, async (ps, user) => { } } else if (ps.groupId != null) { // Fetch recipient (group) - recipientGroup = await UserGroups.findOne(ps.groupId); + recipientGroup = await UserGroups.findOneBy({ id: ps.groupId! }); if (recipientGroup == null) { throw new ApiError(meta.errors.noSuchGroup); } // check joined - const joining = await UserGroupJoinings.findOne({ + const joining = await UserGroupJoinings.findOneBy({ userId: user.id, userGroupId: recipientGroup.id, }); @@ -121,7 +132,7 @@ export default define(meta, paramDef, async (ps, user) => { let file = null; if (ps.fileId != null) { - file = await DriveFiles.findOne({ + file = await DriveFiles.findOneBy({ id: ps.fileId, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts b/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts index a0945af510..f66d75873c 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts @@ -36,7 +36,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const message = await MessagingMessages.findOne({ + const message = await MessagingMessages.findOneBy({ id: ps.messageId, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/read.ts b/packages/backend/src/server/api/endpoints/messaging/messages/read.ts index 8d38e509ac..db12ae922c 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/read.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/read.ts @@ -29,7 +29,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const message = await MessagingMessages.findOne(ps.messageId); + const message = await MessagingMessages.findOneBy({ id: ps.messageId }); if (message == null) { throw new ApiError(meta.errors.noSuchMessage); diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index 1aff1f63fb..e1ae282a97 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -3,7 +3,7 @@ import define from '../define.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { Ads, Emojis, Users } from '@/models/index.js'; import { DB_MAX_NOTE_TEXT_LENGTH } from '@/misc/hard-limits.js'; -import { MoreThan } from 'typeorm'; +import { IsNull, MoreThan } from 'typeorm'; import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; export const meta = { @@ -169,6 +169,7 @@ export const meta = { host: { type: 'string', optional: false, nullable: true, + description: 'The local host is represented with `null`.', }, url: { type: 'string', @@ -290,151 +291,6 @@ export const meta = { }, }, }, - userStarForReactionFallback: { - type: 'boolean', - optional: true, nullable: false, - }, - pinnedUsers: { - type: 'array', - optional: true, nullable: false, - items: { - type: 'string', - optional: false, nullable: false, - }, - }, - hiddenTags: { - type: 'array', - optional: true, nullable: false, - items: { - type: 'string', - optional: false, nullable: false, - }, - }, - blockedHosts: { - type: 'array', - optional: true, nullable: false, - items: { - type: 'string', - optional: false, nullable: false, - }, - }, - hcaptchaSecretKey: { - type: 'string', - optional: true, nullable: true, - }, - recaptchaSecretKey: { - type: 'string', - optional: true, nullable: true, - }, - proxyAccountId: { - type: 'string', - optional: true, nullable: true, - format: 'id', - }, - twitterConsumerKey: { - type: 'string', - optional: true, nullable: true, - }, - twitterConsumerSecret: { - type: 'string', - optional: true, nullable: true, - }, - githubClientId: { - type: 'string', - optional: true, nullable: true, - }, - githubClientSecret: { - type: 'string', - optional: true, nullable: true, - }, - discordClientId: { - type: 'string', - optional: true, nullable: true, - }, - discordClientSecret: { - type: 'string', - optional: true, nullable: true, - }, - summaryProxy: { - type: 'string', - optional: true, nullable: true, - }, - email: { - type: 'string', - optional: true, nullable: true, - }, - smtpSecure: { - type: 'boolean', - optional: true, nullable: false, - }, - smtpHost: { - type: 'string', - optional: true, nullable: true, - }, - smtpPort: { - type: 'string', - optional: true, nullable: true, - }, - smtpUser: { - type: 'string', - optional: true, nullable: true, - }, - smtpPass: { - type: 'string', - optional: true, nullable: true, - }, - swPrivateKey: { - type: 'string', - optional: true, nullable: true, - }, - useObjectStorage: { - type: 'boolean', - optional: true, nullable: false, - }, - objectStorageBaseUrl: { - type: 'string', - optional: true, nullable: true, - }, - objectStorageBucket: { - type: 'string', - optional: true, nullable: true, - }, - objectStoragePrefix: { - type: 'string', - optional: true, nullable: true, - }, - objectStorageEndpoint: { - type: 'string', - optional: true, nullable: true, - }, - objectStorageRegion: { - type: 'string', - optional: true, nullable: true, - }, - objectStoragePort: { - type: 'number', - optional: true, nullable: true, - }, - objectStorageAccessKey: { - type: 'string', - optional: true, nullable: true, - }, - objectStorageSecretKey: { - type: 'string', - optional: true, nullable: true, - }, - objectStorageUseSSL: { - type: 'boolean', - optional: true, nullable: false, - }, - objectStorageUseProxy: { - type: 'boolean', - optional: true, nullable: false, - }, - objectStorageSetPublicRead: { - type: 'boolean', - optional: true, nullable: false, - }, }, }, } as const; @@ -453,7 +309,7 @@ export default define(meta, paramDef, async (ps, me) => { const emojis = await Emojis.find({ where: { - host: null, + host: IsNull(), }, order: { category: 'ASC', @@ -527,8 +383,8 @@ export default define(meta, paramDef, async (ps, me) => { pinnedPages: instance.pinnedPages, pinnedClipId: instance.pinnedClipId, cacheRemoteFiles: instance.cacheRemoteFiles, - requireSetup: (await Users.count({ - host: null, + requireSetup: (await Users.countBy({ + host: IsNull(), })) === 0, } : {}), }; @@ -552,45 +408,6 @@ export default define(meta, paramDef, async (ps, me) => { serviceWorker: instance.enableServiceWorker, miauth: true, }; - - if (me && me.isAdmin) { - response.useStarForReactionFallback = instance.useStarForReactionFallback; - response.pinnedUsers = instance.pinnedUsers; - response.hiddenTags = instance.hiddenTags; - response.blockedHosts = instance.blockedHosts; - response.hcaptchaSecretKey = instance.hcaptchaSecretKey; - response.recaptchaSecretKey = instance.recaptchaSecretKey; - response.proxyAccountId = instance.proxyAccountId; - response.twitterConsumerKey = instance.twitterConsumerKey; - response.twitterConsumerSecret = instance.twitterConsumerSecret; - response.githubClientId = instance.githubClientId; - response.githubClientSecret = instance.githubClientSecret; - response.discordClientId = instance.discordClientId; - response.discordClientSecret = instance.discordClientSecret; - response.summalyProxy = instance.summalyProxy; - response.email = instance.email; - response.smtpSecure = instance.smtpSecure; - response.smtpHost = instance.smtpHost; - response.smtpPort = instance.smtpPort; - response.smtpUser = instance.smtpUser; - response.smtpPass = instance.smtpPass; - response.swPrivateKey = instance.swPrivateKey; - response.useObjectStorage = instance.useObjectStorage; - response.objectStorageBaseUrl = instance.objectStorageBaseUrl; - response.objectStorageBucket = instance.objectStorageBucket; - response.objectStoragePrefix = instance.objectStoragePrefix; - response.objectStorageEndpoint = instance.objectStorageEndpoint; - response.objectStorageRegion = instance.objectStorageRegion; - response.objectStoragePort = instance.objectStoragePort; - response.objectStorageAccessKey = instance.objectStorageAccessKey; - response.objectStorageSecretKey = instance.objectStorageSecretKey; - response.objectStorageUseSSL = instance.objectStorageUseSSL; - response.objectStorageUseProxy = instance.objectStorageUseProxy; - response.objectStorageSetPublicRead = instance.objectStorageSetPublicRead; - response.objectStorageS3ForcePathStyle = instance.objectStorageS3ForcePathStyle; - response.deeplAuthKey = instance.deeplAuthKey; - response.deeplIsPro = instance.deeplIsPro; - } } return response; diff --git a/packages/backend/src/server/api/endpoints/mute/create.ts b/packages/backend/src/server/api/endpoints/mute/create.ts index dacee40d01..7e857e6731 100644 --- a/packages/backend/src/server/api/endpoints/mute/create.ts +++ b/packages/backend/src/server/api/endpoints/mute/create.ts @@ -38,7 +38,11 @@ export const paramDef = { type: 'object', properties: { userId: { type: 'string', format: 'misskey:id' }, - expiresAt: { type: 'integer', nullable: true }, + expiresAt: { + type: 'integer', + nullable: true, + description: 'A Unix Epoch timestamp that must lie in the future. `null` means an indefinite mute.', + }, }, required: ['userId'], } as const; @@ -59,7 +63,7 @@ export default define(meta, paramDef, async (ps, user) => { }); // Check if already muting - const exist = await Mutings.findOne({ + const exist = await Mutings.findOneBy({ muterId: muter.id, muteeId: mutee.id, }); diff --git a/packages/backend/src/server/api/endpoints/mute/delete.ts b/packages/backend/src/server/api/endpoints/mute/delete.ts index a8cf2a6667..0b173dbe24 100644 --- a/packages/backend/src/server/api/endpoints/mute/delete.ts +++ b/packages/backend/src/server/api/endpoints/mute/delete.ts @@ -56,7 +56,7 @@ export default define(meta, paramDef, async (ps, user) => { }); // Check not muting - const exist = await Mutings.findOne({ + const exist = await Mutings.findOneBy({ muterId: muter.id, muteeId: mutee.id, }); diff --git a/packages/backend/src/server/api/endpoints/notes.ts b/packages/backend/src/server/api/endpoints/notes.ts index 96657f8d3f..99c8b973f6 100644 --- a/packages/backend/src/server/api/endpoints/notes.ts +++ b/packages/backend/src/server/api/endpoints/notes.ts @@ -19,7 +19,7 @@ export const meta = { export const paramDef = { type: 'object', properties: { - local: { type: 'boolean' }, + local: { type: 'boolean', default: false }, reply: { type: 'boolean' }, renote: { type: 'boolean' }, withFiles: { type: 'boolean' }, diff --git a/packages/backend/src/server/api/endpoints/notes/clips.ts b/packages/backend/src/server/api/endpoints/notes/clips.ts index 9a863b7148..8683a7f75a 100644 --- a/packages/backend/src/server/api/endpoints/notes/clips.ts +++ b/packages/backend/src/server/api/endpoints/notes/clips.ts @@ -43,11 +43,11 @@ export default define(meta, paramDef, async (ps, me) => { throw e; }); - const clipNotes = await ClipNotes.find({ + const clipNotes = await ClipNotes.findBy({ noteId: note.id, }); - const clips = await Clips.find({ + const clips = await Clips.findBy({ id: In(clipNotes.map(x => x.clipId)), isPublic: true, }); diff --git a/packages/backend/src/server/api/endpoints/notes/conversation.ts b/packages/backend/src/server/api/endpoints/notes/conversation.ts index 2552c0f99d..8f5d21db60 100644 --- a/packages/backend/src/server/api/endpoints/notes/conversation.ts +++ b/packages/backend/src/server/api/endpoints/notes/conversation.ts @@ -50,7 +50,7 @@ export default define(meta, paramDef, async (ps, user) => { async function get(id: any) { i++; - const p = await Notes.findOne(id); + const p = await Notes.findOneBy({ id }); if (p == null) return; if (i > ps.offset!) { diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index e4a9b28891..4b18ab6023 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -59,12 +59,6 @@ export const meta = { id: '3ac74a84-8fd5-4bb0-870f-01804f82ce15', }, - contentRequired: { - message: 'Content required. You need to set text, fileIds, renoteId or poll.', - code: 'CONTENT_REQUIRED', - id: '6f57e42b-c348-439b-bc45-993995cc515a', - }, - cannotCreateAlreadyExpiredPoll: { message: 'Poll is already expired.', code: 'CANNOT_CREATE_ALREADY_EXPIRED_POLL', @@ -92,29 +86,41 @@ export const paramDef = { visibleUserIds: { type: 'array', uniqueItems: true, items: { type: 'string', format: 'misskey:id', } }, - text: { type: 'string', nullable: true, maxLength: MAX_NOTE_TEXT_LENGTH, default: null }, + text: { type: 'string', maxLength: MAX_NOTE_TEXT_LENGTH, nullable: true }, cw: { type: 'string', nullable: true, maxLength: 100 }, localOnly: { type: 'boolean', default: false }, noExtractMentions: { type: 'boolean', default: false }, noExtractHashtags: { type: 'boolean', default: false }, noExtractEmojis: { type: 'boolean', default: false }, - fileIds: { type: 'array', uniqueItems: true, minItems: 1, maxItems: 16, items: { - type: 'string', format: 'misskey:id', - } }, - mediaIds: { type: 'array', uniqueItems: true, minItems: 1, maxItems: 16, items: { - type: 'string', format: 'misskey:id', - } }, + fileIds: { + type: 'array', + uniqueItems: true, + minItems: 1, + maxItems: 16, + items: { type: 'string', format: 'misskey:id' }, + }, + mediaIds: { + deprecated: true, + description: 'Use `fileIds` instead. If both are specified, this property is discarded.', + type: 'array', + uniqueItems: true, + minItems: 1, + maxItems: 16, + items: { type: 'string', format: 'misskey:id' }, + }, replyId: { type: 'string', format: 'misskey:id', nullable: true }, renoteId: { type: 'string', format: 'misskey:id', nullable: true }, channelId: { type: 'string', format: 'misskey:id', nullable: true }, poll: { - type: 'object', nullable: true, + type: 'object', + nullable: true, properties: { choices: { - type: 'array', uniqueItems: true, minItems: 2, maxItems: 10, - items: { - type: 'string', minLength: 1, maxLength: 50, - }, + type: 'array', + uniqueItems: true, + minItems: 2, + maxItems: 10, + items: { type: 'string', minLength: 1, maxLength: 50 }, }, multiple: { type: 'boolean', default: false }, expiresAt: { type: 'integer', nullable: true }, @@ -123,14 +129,37 @@ export const paramDef = { required: ['choices'], }, }, - required: [], + anyOf: [ + { + // (re)note with text, files and poll are optional + properties: { + text: { type: 'string', maxLength: MAX_NOTE_TEXT_LENGTH, nullable: false }, + }, + required: ['text'], + }, + { + // (re)note with files, text and poll are optional + required: ['fileIds'], + }, + { + // (re)note with files, text and poll are optional + required: ['mediaIds'], + }, + { + // (re)note with poll, text and files are optional + properties: { + poll: { type: 'object', nullable: false, }, + }, + required: ['poll'], + }, + ], } as const; // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { let visibleUsers: User[] = []; if (ps.visibleUserIds) { - visibleUsers = (await Promise.all(ps.visibleUserIds.map(id => Users.findOne(id)))) + visibleUsers = (await Promise.all(ps.visibleUserIds.map(id => Users.findOneBy({ id })))) .filter(x => x != null) as User[]; } @@ -138,17 +167,17 @@ export default define(meta, paramDef, async (ps, user) => { const fileIds = ps.fileIds != null ? ps.fileIds : ps.mediaIds != null ? ps.mediaIds : null; if (fileIds != null) { files = (await Promise.all(fileIds.map(fileId => - DriveFiles.findOne({ + DriveFiles.findOneBy({ id: fileId, userId: user.id, }) ))).filter(file => file != null) as DriveFile[]; } - let renote: Note | undefined; + let renote: Note | null; if (ps.renoteId != null) { // Fetch renote to note - renote = await Notes.findOne(ps.renoteId); + renote = await Notes.findOneBy({ id: ps.renoteId }); if (renote == null) { throw new ApiError(meta.errors.noSuchRenoteTarget); @@ -158,7 +187,7 @@ export default define(meta, paramDef, async (ps, user) => { // Check blocking if (renote.userId !== user.id) { - const block = await Blockings.findOne({ + const block = await Blockings.findOneBy({ blockerId: renote.userId, blockeeId: user.id, }); @@ -168,10 +197,10 @@ export default define(meta, paramDef, async (ps, user) => { } } - let reply: Note | undefined; + let reply: Note | null; if (ps.replyId != null) { // Fetch reply - reply = await Notes.findOne(ps.replyId); + reply = await Notes.findOneBy({ id: ps.replyId }); if (reply == null) { throw new ApiError(meta.errors.noSuchReplyTarget); @@ -184,7 +213,7 @@ export default define(meta, paramDef, async (ps, user) => { // Check blocking if (reply.userId !== user.id) { - const block = await Blockings.findOne({ + const block = await Blockings.findOneBy({ blockerId: reply.userId, blockeeId: user.id, }); @@ -204,14 +233,9 @@ export default define(meta, paramDef, async (ps, user) => { } } - // テキストが無いかつ添付ファイルが無いかつRenoteも無いかつ投票も無かったらエラー - if (!(ps.text || files.length || renote || ps.poll)) { - throw new ApiError(meta.errors.contentRequired); - } - let channel: Channel | undefined; if (ps.channelId != null) { - channel = await Channels.findOne(ps.channelId); + channel = await Channels.findOneBy({ id: ps.channelId }); if (channel == null) { throw new ApiError(meta.errors.noSuchChannel); diff --git a/packages/backend/src/server/api/endpoints/notes/delete.ts b/packages/backend/src/server/api/endpoints/notes/delete.ts index 22ff2275ca..804e146fa4 100644 --- a/packages/backend/src/server/api/endpoints/notes/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/delete.ts @@ -48,10 +48,10 @@ export default define(meta, paramDef, async (ps, user) => { throw e; }); - if (!user.isAdmin && !user.isModerator && (note.userId !== user.id)) { + if ((!user.isAdmin && !user.isModerator) && (note.userId !== user.id)) { throw new ApiError(meta.errors.accessDenied); } // この操作を行うのが投稿者とは限らない(例えばモデレーター)ため - await deleteNote(await Users.findOneOrFail(note.userId), note); + await deleteNote(await Users.findOneByOrFail({ id: note.userId }), note); }); diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts index bcc2c44c02..41dc5ac8e1 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts @@ -43,7 +43,7 @@ export default define(meta, paramDef, async (ps, user) => { }); // if already favorited - const exist = await NoteFavorites.findOne({ + const exist = await NoteFavorites.findOneBy({ noteId: note.id, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts index d41fab22d3..a48f7a0aa8 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts @@ -42,7 +42,7 @@ export default define(meta, paramDef, async (ps, user) => { }); // if already favorited - const exist = await NoteFavorites.findOne({ + const exist = await NoteFavorites.findOneBy({ noteId: note.id, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts index 26aaa0919c..cb402ecaa1 100644 --- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts @@ -2,7 +2,7 @@ import define from '../../define.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { ApiError } from '../../error.js'; import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { Notes } from '@/models/index.js'; +import { Notes, Users } from '@/models/index.js'; import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; import { generateMutedInstanceQuery } from '../../common/generate-muted-instance-query.js'; import { activeUsersChart } from '@/services/chart/index.js'; @@ -35,7 +35,11 @@ export const meta = { export const paramDef = { type: 'object', properties: { - withFiles: { type: 'boolean' }, + withFiles: { + type: 'boolean', + default: false, + description: 'Only show notes that have attached files.', + }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts index 9bcb64b656..f9893527e0 100644 --- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -2,7 +2,7 @@ import define from '../../define.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { ApiError } from '../../error.js'; import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { Followings, Notes } from '@/models/index.js'; +import { Followings, Notes, Users } from '@/models/index.js'; import { Brackets } from 'typeorm'; import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; @@ -48,7 +48,11 @@ export const paramDef = { includeMyRenotes: { type: 'boolean', default: true }, includeRenotedMyNotes: { type: 'boolean', default: true }, includeLocalRenotes: { type: 'boolean', default: true }, - withFiles: { type: 'boolean' }, + withFiles: { + type: 'boolean', + default: false, + description: 'Only show notes that have attached files.', + }, }, required: [], } as const; @@ -56,7 +60,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { const m = await fetchMeta(); - if (m.disableLocalTimeline && !user.isAdmin && !user.isModerator) { + if (m.disableLocalTimeline && (!user.isAdmin && !user.isModerator)) { throw new ApiError(meta.errors.stlDisabled); } diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts index 12fc88b1fd..03edf30b31 100644 --- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts @@ -1,7 +1,7 @@ import define from '../../define.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { ApiError } from '../../error.js'; -import { Notes } from '@/models/index.js'; +import { Notes, Users } from '@/models/index.js'; import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; import { makePaginationQuery } from '../../common/make-pagination-query.js'; import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; @@ -37,7 +37,11 @@ export const meta = { export const paramDef = { type: 'object', properties: { - withFiles: { type: 'boolean' }, + withFiles: { + type: 'boolean', + default: false, + description: 'Only show notes that have attached files.', + }, fileType: { type: 'array', items: { type: 'string', } }, diff --git a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts index bdd1aeecd4..28bfade2f0 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts @@ -64,7 +64,7 @@ export default define(meta, paramDef, async (ps, user) => { if (polls.length === 0) return []; - const notes = await Notes.find({ + const notes = await Notes.findBy({ id: In(polls.map(poll => poll.noteId)), }); diff --git a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts index ef52d03664..6380b331f2 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts @@ -83,7 +83,7 @@ export default define(meta, paramDef, async (ps, user) => { // Check blocking if (note.userId !== user.id) { - const block = await Blockings.findOne({ + const block = await Blockings.findOneBy({ blockerId: note.userId, blockeeId: user.id, }); @@ -92,7 +92,7 @@ export default define(meta, paramDef, async (ps, user) => { } } - const poll = await Polls.findOneOrFail({ noteId: note.id }); + const poll = await Polls.findOneByOrFail({ noteId: note.id }); if (poll.expiresAt && poll.expiresAt < createdAt) { throw new ApiError(meta.errors.alreadyExpired); @@ -103,7 +103,7 @@ export default define(meta, paramDef, async (ps, user) => { } // if already voted - const exist = await PollVotes.find({ + const exist = await PollVotes.findBy({ noteId: note.id, userId: user.id, }); @@ -125,7 +125,7 @@ export default define(meta, paramDef, async (ps, user) => { noteId: note.id, userId: user.id, choice: ps.choice, - }).then(x => PollVotes.findOneOrFail(x.identifiers[0])); + }).then(x => PollVotes.findOneByOrFail(x.identifiers[0])); // Increment votes count const index = ps.choice + 1; // In SQL, array index is 1 based @@ -144,7 +144,7 @@ export default define(meta, paramDef, async (ps, user) => { }); // Fetch watchers - NoteWatchings.find({ + NoteWatchings.findBy({ noteId: note.id, userId: Not(user.id), }).then(watchers => { @@ -159,7 +159,7 @@ export default define(meta, paramDef, async (ps, user) => { // リモート投票の場合リプライ送信 if (note.userHost != null) { - const pollOwner = await Users.findOneOrFail(note.userId) as IRemoteUser; + const pollOwner = await Users.findOneByOrFail({ id: note.userId }) as IRemoteUser; deliver(user, renderActivity(await renderVote(user, vote, note, poll, pollOwner)), pollOwner.inbox); } diff --git a/packages/backend/src/server/api/endpoints/notes/reactions.ts b/packages/backend/src/server/api/endpoints/notes/reactions.ts index 43e5d1ef6f..3555424fa6 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions.ts @@ -1,5 +1,4 @@ import define from '../../define.js'; -import { getNote } from '../../common/getters.js'; import { ApiError } from '../../error.js'; import { NoteReactions } from '@/models/index.js'; import { DeepPartial } from 'typeorm'; @@ -44,13 +43,8 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); - const query = { - noteId: note.id, + noteId: ps.noteId, } as DeepPartial; if (ps.type) { diff --git a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts index c6503eb057..bb85c92008 100644 --- a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts +++ b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts @@ -25,21 +25,44 @@ export const meta = { export const paramDef = { type: 'object', properties: { - tag: { type: 'string' }, - query: { type: 'array', items: { - type: 'array', items: { - type: 'string', - }, - } }, reply: { type: 'boolean', nullable: true, default: null }, renote: { type: 'boolean', nullable: true, default: null }, - withFiles: { type: 'boolean' }, + withFiles: { + type: 'boolean', + default: false, + description: 'Only show notes that have attached files.', + }, poll: { type: 'boolean', nullable: true, default: null }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, }, - required: [], + anyOf: [ + { + properties: { + tag: { type: 'string', minLength: 1 }, + }, + required: ['tag'], + }, + { + properties: { + query: { + type: 'array', + description: 'The outer arrays are chained with OR, the inner arrays are chained with AND.', + items: { + type: 'array', + items: { + type: 'string', + minLength: 1, + }, + minItems: 1, + }, + minItems: 1, + }, + }, + required: ['query'], + }, + ], } as const; // eslint-disable-next-line import/no-default-export diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts index e77892b150..af9b5f0a10 100644 --- a/packages/backend/src/server/api/endpoints/notes/search.ts +++ b/packages/backend/src/server/api/endpoints/notes/search.ts @@ -35,7 +35,11 @@ export const paramDef = { untilId: { type: 'string', format: 'misskey:id' }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, offset: { type: 'integer', default: 0 }, - host: { type: 'string', nullable: true }, + host: { + type: 'string', + nullable: true, + description: 'The local host is represented with `null`.', + }, userId: { type: 'string', format: 'misskey:id', nullable: true, default: null }, channelId: { type: 'string', format: 'misskey:id', nullable: true, default: null }, }, diff --git a/packages/backend/src/server/api/endpoints/notes/state.ts b/packages/backend/src/server/api/endpoints/notes/state.ts index 6fdb8e88fb..069f11fa4a 100644 --- a/packages/backend/src/server/api/endpoints/notes/state.ts +++ b/packages/backend/src/server/api/endpoints/notes/state.ts @@ -36,7 +36,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const note = await Notes.findOneOrFail(ps.noteId); + const note = await Notes.findOneByOrFail({ id: ps.noteId }); const [favorite, watching, threadMuting] = await Promise.all([ NoteFavorites.count({ diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts index fde66b241b..0f976d18be 100644 --- a/packages/backend/src/server/api/endpoints/notes/timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts @@ -38,7 +38,11 @@ export const paramDef = { includeMyRenotes: { type: 'boolean', default: true }, includeRenotedMyNotes: { type: 'boolean', default: true }, includeLocalRenotes: { type: 'boolean', default: true }, - withFiles: { type: 'boolean' }, + withFiles: { + type: 'boolean', + default: false, + description: 'Only show notes that have attached files.', + }, }, required: [], } as const; diff --git a/packages/backend/src/server/api/endpoints/notes/unrenote.ts b/packages/backend/src/server/api/endpoints/notes/unrenote.ts index a9aadba338..5e8c31eaf8 100644 --- a/packages/backend/src/server/api/endpoints/notes/unrenote.ts +++ b/packages/backend/src/server/api/endpoints/notes/unrenote.ts @@ -42,12 +42,12 @@ export default define(meta, paramDef, async (ps, user) => { throw e; }); - const renotes = await Notes.find({ + const renotes = await Notes.findBy({ userId: user.id, renoteId: note.id, }); for (const note of renotes) { - deleteNote(await Users.findOneOrFail(user.id), note); + deleteNote(await Users.findOneByOrFail({ id: user.id }), note); } }); diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts index 0829d0e4c1..6c6402603a 100644 --- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts @@ -42,14 +42,18 @@ export const paramDef = { includeMyRenotes: { type: 'boolean', default: true }, includeRenotedMyNotes: { type: 'boolean', default: true }, includeLocalRenotes: { type: 'boolean', default: true }, - withFiles: { type: 'boolean' }, + withFiles: { + type: 'boolean', + default: false, + description: 'Only show notes that have attached files.', + }, }, required: ['listId'], } as const; // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const list = await UserLists.findOne({ + const list = await UserLists.findOneBy({ id: ps.listId, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/notifications/read.ts b/packages/backend/src/server/api/endpoints/notifications/read.ts index 34f4c155fa..c7bc5dc0a5 100644 --- a/packages/backend/src/server/api/endpoints/notifications/read.ts +++ b/packages/backend/src/server/api/endpoints/notifications/read.ts @@ -30,7 +30,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const notification = await Notifications.findOne({ + const notification = await Notifications.findOneBy({ notifieeId: user.id, id: ps.notificationId, }); diff --git a/packages/backend/src/server/api/endpoints/page-push.ts b/packages/backend/src/server/api/endpoints/page-push.ts index acaa118470..7096aaa3d3 100644 --- a/packages/backend/src/server/api/endpoints/page-push.ts +++ b/packages/backend/src/server/api/endpoints/page-push.ts @@ -28,7 +28,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const page = await Pages.findOne(ps.pageId); + const page = await Pages.findOneBy({ id: ps.pageId }); if (page == null) { throw new ApiError(meta.errors.noSuchPage); } diff --git a/packages/backend/src/server/api/endpoints/pages/create.ts b/packages/backend/src/server/api/endpoints/pages/create.ts index 7cac530606..c171cd39f5 100644 --- a/packages/backend/src/server/api/endpoints/pages/create.ts +++ b/packages/backend/src/server/api/endpoints/pages/create.ts @@ -62,7 +62,7 @@ export const paramDef = { export default define(meta, paramDef, async (ps, user) => { let eyeCatchingImage = null; if (ps.eyeCatchingImageId != null) { - eyeCatchingImage = await DriveFiles.findOne({ + eyeCatchingImage = await DriveFiles.findOneBy({ id: ps.eyeCatchingImageId, userId: user.id, }); @@ -72,7 +72,7 @@ export default define(meta, paramDef, async (ps, user) => { } } - await Pages.find({ + await Pages.findBy({ userId: user.id, name: ps.name, }).then(result => { @@ -97,7 +97,7 @@ export default define(meta, paramDef, async (ps, user) => { alignCenter: ps.alignCenter, hideTitleWhenPinned: ps.hideTitleWhenPinned, font: ps.font, - })).then(x => Pages.findOneOrFail(x.identifiers[0])); + })).then(x => Pages.findOneByOrFail(x.identifiers[0])); return await Pages.pack(page); }); diff --git a/packages/backend/src/server/api/endpoints/pages/delete.ts b/packages/backend/src/server/api/endpoints/pages/delete.ts index ddf691f53c..e35ad9ebf2 100644 --- a/packages/backend/src/server/api/endpoints/pages/delete.ts +++ b/packages/backend/src/server/api/endpoints/pages/delete.ts @@ -34,7 +34,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const page = await Pages.findOne(ps.pageId); + const page = await Pages.findOneBy({ id: ps.pageId }); if (page == null) { throw new ApiError(meta.errors.noSuchPage); } diff --git a/packages/backend/src/server/api/endpoints/pages/like.ts b/packages/backend/src/server/api/endpoints/pages/like.ts index cab78e576c..20793db988 100644 --- a/packages/backend/src/server/api/endpoints/pages/like.ts +++ b/packages/backend/src/server/api/endpoints/pages/like.ts @@ -41,7 +41,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const page = await Pages.findOne(ps.pageId); + const page = await Pages.findOneBy({ id: ps.pageId }); if (page == null) { throw new ApiError(meta.errors.noSuchPage); } @@ -51,7 +51,7 @@ export default define(meta, paramDef, async (ps, user) => { } // if already liked - const exist = await PageLikes.findOne({ + const exist = await PageLikes.findOneBy({ pageId: page.id, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/pages/show.ts b/packages/backend/src/server/api/endpoints/pages/show.ts index 4e3facae5b..3dcce8550f 100644 --- a/packages/backend/src/server/api/endpoints/pages/show.ts +++ b/packages/backend/src/server/api/endpoints/pages/show.ts @@ -2,6 +2,7 @@ import define from '../../define.js'; import { ApiError } from '../../error.js'; import { Pages, Users } from '@/models/index.js'; import { Page } from '@/models/entities/page.js'; +import { IsNull } from 'typeorm'; export const meta = { tags: ['pages'], @@ -25,12 +26,21 @@ export const meta = { export const paramDef = { type: 'object', - properties: { - pageId: { type: 'string', format: 'misskey:id' }, - name: { type: 'string' }, - username: { type: 'string' }, - }, - required: [], + anyOf: [ + { + properties: { + pageId: { type: 'string', format: 'misskey:id' }, + }, + required: ['pageId'], + }, + { + properties: { + name: { type: 'string' }, + username: { type: 'string' }, + }, + required: ['name', 'username'], + }, + ], } as const; // eslint-disable-next-line import/no-default-export @@ -38,14 +48,14 @@ export default define(meta, paramDef, async (ps, user) => { let page: Page | undefined; if (ps.pageId) { - page = await Pages.findOne(ps.pageId); + page = await Pages.findOneBy({ id: ps.pageId }); } else if (ps.name && ps.username) { - const author = await Users.findOne({ - host: null, + const author = await Users.findOneBy({ + host: IsNull(), usernameLower: ps.username.toLowerCase(), }); if (author) { - page = await Pages.findOne({ + page = await Pages.findOneBy({ name: ps.name, userId: author.id, }); diff --git a/packages/backend/src/server/api/endpoints/pages/unlike.ts b/packages/backend/src/server/api/endpoints/pages/unlike.ts index 31cd1a3359..636f3c7149 100644 --- a/packages/backend/src/server/api/endpoints/pages/unlike.ts +++ b/packages/backend/src/server/api/endpoints/pages/unlike.ts @@ -34,12 +34,12 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const page = await Pages.findOne(ps.pageId); + const page = await Pages.findOneBy({ id: ps.pageId }); if (page == null) { throw new ApiError(meta.errors.noSuchPage); } - const exist = await PageLikes.findOne({ + const exist = await PageLikes.findOneBy({ pageId: page.id, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/pages/update.ts b/packages/backend/src/server/api/endpoints/pages/update.ts index 24c8f467e6..bf95ab36f2 100644 --- a/packages/backend/src/server/api/endpoints/pages/update.ts +++ b/packages/backend/src/server/api/endpoints/pages/update.ts @@ -66,7 +66,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const page = await Pages.findOne(ps.pageId); + const page = await Pages.findOneBy({ id: ps.pageId }); if (page == null) { throw new ApiError(meta.errors.noSuchPage); } @@ -76,7 +76,7 @@ export default define(meta, paramDef, async (ps, user) => { let eyeCatchingImage = null; if (ps.eyeCatchingImageId != null) { - eyeCatchingImage = await DriveFiles.findOne({ + eyeCatchingImage = await DriveFiles.findOneBy({ id: ps.eyeCatchingImageId, userId: user.id, }); @@ -86,7 +86,7 @@ export default define(meta, paramDef, async (ps, user) => { } } - await Pages.find({ + await Pages.findBy({ id: Not(ps.pageId), userId: user.id, name: ps.name, diff --git a/packages/backend/src/server/api/endpoints/pinned-users.ts b/packages/backend/src/server/api/endpoints/pinned-users.ts index 1d26ab266e..8d253c1f33 100644 --- a/packages/backend/src/server/api/endpoints/pinned-users.ts +++ b/packages/backend/src/server/api/endpoints/pinned-users.ts @@ -3,6 +3,7 @@ import { Users } from '@/models/index.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import * as Acct from '@/misc/acct.js'; import { User } from '@/models/entities/user.js'; +import { IsNull } from 'typeorm'; export const meta = { tags: ['users'], @@ -30,7 +31,10 @@ export const paramDef = { export default define(meta, paramDef, async (ps, me) => { const meta = await fetchMeta(); - const users = await Promise.all(meta.pinnedUsers.map(acct => Users.findOne(Acct.parse(acct)))); + const users = await Promise.all(meta.pinnedUsers.map(acct => Acct.parse(acct)).map(acct => Users.findOneBy({ + usernameLower: acct.username.toLowerCase(), + host: acct.host ?? IsNull(), + }))); return await Users.packMany(users.filter(x => x !== undefined) as User[], me, { detail: true }); }); diff --git a/packages/backend/src/server/api/endpoints/promo/read.ts b/packages/backend/src/server/api/endpoints/promo/read.ts index ea34ca3aad..cc602857de 100644 --- a/packages/backend/src/server/api/endpoints/promo/read.ts +++ b/packages/backend/src/server/api/endpoints/promo/read.ts @@ -33,7 +33,7 @@ export default define(meta, paramDef, async (ps, user) => { throw e; }); - const exist = await PromoReads.findOne({ + const exist = await PromoReads.findOneBy({ noteId: note.id, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/request-reset-password.ts b/packages/backend/src/server/api/endpoints/request-reset-password.ts index 18cd98b164..046337f040 100644 --- a/packages/backend/src/server/api/endpoints/request-reset-password.ts +++ b/packages/backend/src/server/api/endpoints/request-reset-password.ts @@ -33,7 +33,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps) => { - const user = await Users.findOne({ + const user = await Users.findOneBy({ usernameLower: ps.username.toLowerCase(), host: IsNull(), }); @@ -43,7 +43,7 @@ export default define(meta, paramDef, async (ps) => { return; } - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); // 合致するメアドが登録されていなかったら無視 if (profile.email !== ps.email) { diff --git a/packages/backend/src/server/api/endpoints/reset-password.ts b/packages/backend/src/server/api/endpoints/reset-password.ts index 3abf232af0..7acc545c40 100644 --- a/packages/backend/src/server/api/endpoints/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/reset-password.ts @@ -23,7 +23,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const req = await PasswordResetRequests.findOneOrFail({ + const req = await PasswordResetRequests.findOneByOrFail({ token: ps.token, }); diff --git a/packages/backend/src/server/api/endpoints/stats.ts b/packages/backend/src/server/api/endpoints/stats.ts index 92fea4de6a..f8a1ee29de 100644 --- a/packages/backend/src/server/api/endpoints/stats.ts +++ b/packages/backend/src/server/api/endpoints/stats.ts @@ -1,6 +1,7 @@ import define from '../define.js'; import { Instances, NoteReactions, Notes, Users } from '@/models/index.js'; import { } from '@/services/chart/index.js'; +import { IsNull } from 'typeorm'; export const meta = { requireCredential: false, @@ -61,11 +62,11 @@ export default define(meta, paramDef, async () => { instances, ] = await Promise.all([ Notes.count({ cache: 3600000 }), // 1 hour - Notes.count({ where: { userHost: null }, cache: 3600000 }), + Notes.count({ where: { userHost: IsNull() }, cache: 3600000 }), Users.count({ cache: 3600000 }), - Users.count({ where: { host: null }, cache: 3600000 }), + Users.count({ where: { host: IsNull() }, cache: 3600000 }), NoteReactions.count({ cache: 3600000 }), // 1 hour - //NoteReactions.count({ where: { userHost: null }, cache: 3600000 }), + //NoteReactions.count({ where: { userHost: IsNull() }, cache: 3600000 }), Instances.count({ cache: 3600000 }), ]); diff --git a/packages/backend/src/server/api/endpoints/sw/register.ts b/packages/backend/src/server/api/endpoints/sw/register.ts index 6c7714e19b..a48973a0df 100644 --- a/packages/backend/src/server/api/endpoints/sw/register.ts +++ b/packages/backend/src/server/api/endpoints/sw/register.ts @@ -38,7 +38,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { // if already subscribed - const exist = await SwSubscriptions.findOne({ + const exist = await SwSubscriptions.findOneBy({ userId: user.id, endpoint: ps.endpoint, auth: ps.auth, diff --git a/packages/backend/src/server/api/endpoints/username/available.ts b/packages/backend/src/server/api/endpoints/username/available.ts index 5a1c4128ab..04b754f4ad 100644 --- a/packages/backend/src/server/api/endpoints/username/available.ts +++ b/packages/backend/src/server/api/endpoints/username/available.ts @@ -1,5 +1,6 @@ import define from '../../define.js'; import { Users, UsedUsernames } from '@/models/index.js'; +import { IsNull } from 'typeorm'; export const meta = { tags: ['users'], @@ -29,12 +30,12 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps) => { // Get exist - const exist = await Users.count({ - host: null, + const exist = await Users.countBy({ + host: IsNull(), usernameLower: ps.username.toLowerCase(), }); - const exist2 = await UsedUsernames.count({ username: ps.username.toLowerCase() }); + const exist2 = await UsedUsernames.countBy({ username: ps.username.toLowerCase() }); return { available: exist === 0 && exist2 === 0, diff --git a/packages/backend/src/server/api/endpoints/users/followers.ts b/packages/backend/src/server/api/endpoints/users/followers.ts index 1e104b6bcc..26b1f20df0 100644 --- a/packages/backend/src/server/api/endpoints/users/followers.ts +++ b/packages/backend/src/server/api/endpoints/users/followers.ts @@ -3,6 +3,7 @@ import { ApiError } from '../../error.js'; import { Users, Followings, UserProfiles } from '@/models/index.js'; import { makePaginationQuery } from '../../common/make-pagination-query.js'; import { toPunyNullable } from '@/misc/convert-host.js'; +import { IsNull } from 'typeorm'; export const meta = { tags: ['users'], @@ -37,27 +38,42 @@ export const meta = { export const paramDef = { type: 'object', properties: { - userId: { type: 'string', format: 'misskey:id' }, - username: { type: 'string' }, - host: { type: 'string', nullable: true }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, }, - required: [], + anyOf: [ + { + properties: { + userId: { type: 'string', format: 'misskey:id' }, + }, + required: ['userId'], + }, + { + properties: { + username: { type: 'string' }, + host: { + type: 'string', + nullable: true, + description: 'The local host is represented with `null`.', + }, + }, + required: ['username', 'host'], + }, + ], } as const; // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOne(ps.userId != null + const user = await Users.findOneBy(ps.userId != null ? { id: ps.userId } - : { usernameLower: ps.username!.toLowerCase(), host: toPunyNullable(ps.host) }); + : { usernameLower: ps.username!.toLowerCase(), host: toPunyNullable(ps.host) ?? IsNull() }); if (user == null) { throw new ApiError(meta.errors.noSuchUser); } - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); if (profile.ffVisibility === 'private') { if (me == null || (me.id !== user.id)) { @@ -67,7 +83,7 @@ export default define(meta, paramDef, async (ps, me) => { if (me == null) { throw new ApiError(meta.errors.forbidden); } else if (me.id !== user.id) { - const following = await Followings.findOne({ + const following = await Followings.findOneBy({ followeeId: user.id, followerId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/users/following.ts b/packages/backend/src/server/api/endpoints/users/following.ts index b0a1036c76..42cf5216e8 100644 --- a/packages/backend/src/server/api/endpoints/users/following.ts +++ b/packages/backend/src/server/api/endpoints/users/following.ts @@ -3,6 +3,7 @@ import { ApiError } from '../../error.js'; import { Users, Followings, UserProfiles } from '@/models/index.js'; import { makePaginationQuery } from '../../common/make-pagination-query.js'; import { toPunyNullable } from '@/misc/convert-host.js'; +import { IsNull } from 'typeorm'; export const meta = { tags: ['users'], @@ -37,27 +38,42 @@ export const meta = { export const paramDef = { type: 'object', properties: { - userId: { type: 'string', format: 'misskey:id' }, - username: { type: 'string' }, - host: { type: 'string', nullable: true }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, }, - required: [], + anyOf: [ + { + properties: { + userId: { type: 'string', format: 'misskey:id' }, + }, + required: ['userId'], + }, + { + properties: { + username: { type: 'string' }, + host: { + type: 'string', + nullable: true, + description: 'The local host is represented with `null`.', + }, + }, + required: ['username', 'host'], + }, + ], } as const; // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOne(ps.userId != null + const user = await Users.findOneBy(ps.userId != null ? { id: ps.userId } - : { usernameLower: ps.username!.toLowerCase(), host: toPunyNullable(ps.host) }); + : { usernameLower: ps.username!.toLowerCase(), host: toPunyNullable(ps.host) ?? IsNull() }); if (user == null) { throw new ApiError(meta.errors.noSuchUser); } - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); if (profile.ffVisibility === 'private') { if (me == null || (me.id !== user.id)) { @@ -67,7 +83,7 @@ export default define(meta, paramDef, async (ps, me) => { if (me == null) { throw new ApiError(meta.errors.forbidden); } else if (me.id !== user.id) { - const following = await Followings.findOne({ + const following = await Followings.findOneBy({ followeeId: user.id, followerId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/users/groups/create.ts b/packages/backend/src/server/api/endpoints/users/groups/create.ts index 9f6d8464d8..fc775d7cc1 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/create.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/create.ts @@ -33,7 +33,7 @@ export default define(meta, paramDef, async (ps, user) => { createdAt: new Date(), userId: user.id, name: ps.name, - } as UserGroup).then(x => UserGroups.findOneOrFail(x.identifiers[0])); + } as UserGroup).then(x => UserGroups.findOneByOrFail(x.identifiers[0])); // Push the owner await UserGroupJoinings.insert({ diff --git a/packages/backend/src/server/api/endpoints/users/groups/delete.ts b/packages/backend/src/server/api/endpoints/users/groups/delete.ts index f4898a3c7c..f68006994c 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/delete.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/delete.ts @@ -28,7 +28,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const userGroup = await UserGroups.findOne({ + const userGroup = await UserGroups.findOneBy({ id: ps.groupId, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts index efbdf968f6..75c1acc302 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts @@ -31,7 +31,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { // Fetch the invitation - const invitation = await UserGroupInvitations.findOne({ + const invitation = await UserGroupInvitations.findOneBy({ id: ps.invitationId, }); diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts index fe5d431eab..46bc780ab0 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts @@ -29,7 +29,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { // Fetch the invitation - const invitation = await UserGroupInvitations.findOne({ + const invitation = await UserGroupInvitations.findOneBy({ id: ps.invitationId, }); diff --git a/packages/backend/src/server/api/endpoints/users/groups/invite.ts b/packages/backend/src/server/api/endpoints/users/groups/invite.ts index 10bfb7eca1..30a5beb1d9 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invite.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invite.ts @@ -52,7 +52,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { // Fetch the group - const userGroup = await UserGroups.findOne({ + const userGroup = await UserGroups.findOneBy({ id: ps.groupId, userId: me.id, }); @@ -67,7 +67,7 @@ export default define(meta, paramDef, async (ps, me) => { throw e; }); - const joining = await UserGroupJoinings.findOne({ + const joining = await UserGroupJoinings.findOneBy({ userGroupId: userGroup.id, userId: user.id, }); @@ -76,7 +76,7 @@ export default define(meta, paramDef, async (ps, me) => { throw new ApiError(meta.errors.alreadyAdded); } - const existInvitation = await UserGroupInvitations.findOne({ + const existInvitation = await UserGroupInvitations.findOneBy({ userGroupId: userGroup.id, userId: user.id, }); @@ -90,7 +90,7 @@ export default define(meta, paramDef, async (ps, me) => { createdAt: new Date(), userId: user.id, userGroupId: userGroup.id, - } as UserGroupInvitation).then(x => UserGroupInvitations.findOneOrFail(x.identifiers[0])); + } as UserGroupInvitation).then(x => UserGroupInvitations.findOneByOrFail(x.identifiers[0])); // 通知を作成 createNotification(user.id, 'groupInvited', { diff --git a/packages/backend/src/server/api/endpoints/users/groups/joined.ts b/packages/backend/src/server/api/endpoints/users/groups/joined.ts index e52de78595..77dc59d3e5 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/joined.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/joined.ts @@ -28,11 +28,11 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const ownedGroups = await UserGroups.find({ + const ownedGroups = await UserGroups.findBy({ userId: me.id, }); - const joinings = await UserGroupJoinings.find({ + const joinings = await UserGroupJoinings.findBy({ userId: me.id, ...(ownedGroups.length > 0 ? { userGroupId: Not(In(ownedGroups.map(x => x.id))), diff --git a/packages/backend/src/server/api/endpoints/users/groups/leave.ts b/packages/backend/src/server/api/endpoints/users/groups/leave.ts index c1a8c2c024..33abd5439f 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/leave.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/leave.ts @@ -35,7 +35,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { // Fetch the group - const userGroup = await UserGroups.findOne({ + const userGroup = await UserGroups.findOneBy({ id: ps.groupId, }); diff --git a/packages/backend/src/server/api/endpoints/users/groups/owned.ts b/packages/backend/src/server/api/endpoints/users/groups/owned.ts index 11aad0f73c..b1289e601f 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/owned.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/owned.ts @@ -27,7 +27,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const userGroups = await UserGroups.find({ + const userGroups = await UserGroups.findBy({ userId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/users/groups/pull.ts b/packages/backend/src/server/api/endpoints/users/groups/pull.ts index 55ec9f915b..b31990b2e3 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/pull.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/pull.ts @@ -43,7 +43,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { // Fetch the group - const userGroup = await UserGroups.findOne({ + const userGroup = await UserGroups.findOneBy({ id: ps.groupId, userId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/users/groups/show.ts b/packages/backend/src/server/api/endpoints/users/groups/show.ts index 28ca1162c8..3ffb0f5ba9 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/show.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/show.ts @@ -35,7 +35,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { // Fetch the group - const userGroup = await UserGroups.findOne({ + const userGroup = await UserGroups.findOneBy({ id: ps.groupId, }); @@ -43,7 +43,7 @@ export default define(meta, paramDef, async (ps, me) => { throw new ApiError(meta.errors.noSuchGroup); } - const joining = await UserGroupJoinings.findOne({ + const joining = await UserGroupJoinings.findOneBy({ userId: me.id, userGroupId: userGroup.id, }); diff --git a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts index f48e1ddbf0..41ceee3b2e 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts @@ -49,7 +49,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { // Fetch the group - const userGroup = await UserGroups.findOne({ + const userGroup = await UserGroups.findOneBy({ id: ps.groupId, userId: me.id, }); @@ -64,7 +64,7 @@ export default define(meta, paramDef, async (ps, me) => { throw e; }); - const joining = await UserGroupJoinings.findOne({ + const joining = await UserGroupJoinings.findOneBy({ userGroupId: userGroup.id, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/users/groups/update.ts b/packages/backend/src/server/api/endpoints/users/groups/update.ts index b3e17dfd9e..1016aa8926 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/update.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/update.ts @@ -36,7 +36,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { // Fetch the group - const userGroup = await UserGroups.findOne({ + const userGroup = await UserGroups.findOneBy({ id: ps.groupId, userId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/users/lists/create.ts b/packages/backend/src/server/api/endpoints/users/lists/create.ts index 1a0599f9e7..d5260256d5 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/create.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/create.ts @@ -32,7 +32,7 @@ export default define(meta, paramDef, async (ps, user) => { createdAt: new Date(), userId: user.id, name: ps.name, - } as UserList).then(x => UserLists.findOneOrFail(x.identifiers[0])); + } as UserList).then(x => UserLists.findOneByOrFail(x.identifiers[0])); return await UserLists.pack(userList); }); diff --git a/packages/backend/src/server/api/endpoints/users/lists/delete.ts b/packages/backend/src/server/api/endpoints/users/lists/delete.ts index aeefb98c83..b7ad96eef0 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/delete.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/delete.ts @@ -28,7 +28,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const userList = await UserLists.findOne({ + const userList = await UserLists.findOneBy({ id: ps.listId, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/users/lists/list.ts b/packages/backend/src/server/api/endpoints/users/lists/list.ts index a8663ada8a..78311292cb 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/list.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/list.ts @@ -27,7 +27,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const userLists = await UserLists.find({ + const userLists = await UserLists.findBy({ userId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/users/lists/pull.ts b/packages/backend/src/server/api/endpoints/users/lists/pull.ts index 2c4c61d51e..76863f07d1 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/pull.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/pull.ts @@ -38,7 +38,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { // Fetch the list - const userList = await UserLists.findOne({ + const userList = await UserLists.findOneBy({ id: ps.listId, userId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/users/lists/push.ts b/packages/backend/src/server/api/endpoints/users/lists/push.ts index 034a9d2db6..260665c63a 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/push.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts @@ -50,7 +50,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { // Fetch the list - const userList = await UserLists.findOne({ + const userList = await UserLists.findOneBy({ id: ps.listId, userId: me.id, }); @@ -67,7 +67,7 @@ export default define(meta, paramDef, async (ps, me) => { // Check blocking if (user.id !== me.id) { - const block = await Blockings.findOne({ + const block = await Blockings.findOneBy({ blockerId: user.id, blockeeId: me.id, }); @@ -76,7 +76,7 @@ export default define(meta, paramDef, async (ps, me) => { } } - const exist = await UserListJoinings.findOne({ + const exist = await UserListJoinings.findOneBy({ userListId: userList.id, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/users/lists/show.ts b/packages/backend/src/server/api/endpoints/users/lists/show.ts index fadb94c90e..5f51980e95 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/show.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/show.ts @@ -35,7 +35,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { // Fetch the list - const userList = await UserLists.findOne({ + const userList = await UserLists.findOneBy({ id: ps.listId, userId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/users/lists/update.ts b/packages/backend/src/server/api/endpoints/users/lists/update.ts index 5ec99031e1..52353a14cc 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/update.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/update.ts @@ -36,7 +36,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { // Fetch the list - const userList = await UserLists.findOne({ + const userList = await UserLists.findOneBy({ id: ps.listId, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/users/reactions.ts b/packages/backend/src/server/api/endpoints/users/reactions.ts index 7b55a16711..c2d1994343 100644 --- a/packages/backend/src/server/api/endpoints/users/reactions.ts +++ b/packages/backend/src/server/api/endpoints/users/reactions.ts @@ -43,7 +43,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const profile = await UserProfiles.findOneOrFail(ps.userId); + const profile = await UserProfiles.findOneByOrFail({ userId: ps.userId }); if (me == null || (me.id !== ps.userId && !profile.publicReactions)) { throw new ApiError(meta.errors.reactionsNotPublic); diff --git a/packages/backend/src/server/api/endpoints/users/report-abuse.ts b/packages/backend/src/server/api/endpoints/users/report-abuse.ts index e091b8e1b1..0be385dbbf 100644 --- a/packages/backend/src/server/api/endpoints/users/report-abuse.ts +++ b/packages/backend/src/server/api/endpoints/users/report-abuse.ts @@ -67,10 +67,10 @@ export default define(meta, paramDef, async (ps, me) => { reporterId: me.id, reporterHost: null, comment: ps.comment, - }).then(x => AbuseUserReports.findOneOrFail(x.identifiers[0])); + }).then(x => AbuseUserReports.findOneByOrFail(x.identifiers[0])); // Publish event to moderators - setTimeout(async () => { + setImmediate(async () => { const moderators = await Users.find({ where: [{ isAdmin: true, @@ -94,5 +94,5 @@ export default define(meta, paramDef, async (ps, me) => { sanitizeHtml(ps.comment), sanitizeHtml(ps.comment)); } - }, 1); + }); }); diff --git a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts index 897b5de3fe..f74d80e2ae 100644 --- a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts +++ b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts @@ -28,7 +28,10 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, detail: { type: 'boolean', default: true }, }, - required: [], + anyOf: [ + { required: ['username'] }, + { required: ['host'] }, + ], } as const; // TODO: avatar,bannerをJOINしたいけどエラーになる diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts index 263c102a7a..b1a568145a 100644 --- a/packages/backend/src/server/api/endpoints/users/show.ts +++ b/packages/backend/src/server/api/endpoints/users/show.ts @@ -3,7 +3,7 @@ import define from '../../define.js'; import { apiLogger } from '../../logger.js'; import { ApiError } from '../../error.js'; import { Users } from '@/models/index.js'; -import { In } from 'typeorm'; +import { FindOptionsWhere, In, IsNull } from 'typeorm'; import { User } from '@/models/entities/user.js'; export const meta = { @@ -46,15 +46,33 @@ export const meta = { export const paramDef = { type: 'object', - properties: { - userId: { type: 'string', format: 'misskey:id' }, - userIds: { type: 'array', uniqueItems: true, items: { - type: 'string', format: 'misskey:id', - } }, - username: { type: 'string' }, - host: { type: 'string', nullable: true }, - }, - required: [], + anyOf: [ + { + properties: { + userId: { type: 'string', format: 'misskey:id' }, + }, + required: ['userId'], + }, + { + properties: { + userIds: { type: 'array', uniqueItems: true, items: { + type: 'string', format: 'misskey:id', + } }, + }, + required: ['userIds'], + }, + { + properties: { + username: { type: 'string' }, + host: { + type: 'string', + nullable: true, + description: 'The local host is represented with `null`.', + }, + }, + required: ['username', 'host'], + }, + ], } as const; // eslint-disable-next-line import/no-default-export @@ -68,7 +86,7 @@ export default define(meta, paramDef, async (ps, me) => { return []; } - const users = await Users.find(isAdminOrModerator ? { + const users = await Users.findBy(isAdminOrModerator ? { id: In(ps.userIds), } : { id: In(ps.userIds), @@ -92,11 +110,11 @@ export default define(meta, paramDef, async (ps, me) => { throw new ApiError(meta.errors.failedToResolveRemoteUser); }); } else { - const q: any = ps.userId != null + const q: FindOptionsWhere = ps.userId != null ? { id: ps.userId } - : { usernameLower: ps.username!.toLowerCase(), host: null }; + : { usernameLower: ps.username!.toLowerCase(), host: IsNull() }; - user = await Users.findOne(q); + user = await Users.findOneBy(q); } if (user == null || (!isAdminOrModerator && user.isSuspended)) { diff --git a/packages/backend/src/server/api/endpoints/users/stats.ts b/packages/backend/src/server/api/endpoints/users/stats.ts index 180a9386d3..d138019a72 100644 --- a/packages/backend/src/server/api/endpoints/users/stats.ts +++ b/packages/backend/src/server/api/endpoints/users/stats.ts @@ -26,7 +26,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOne(ps.userId); + const user = await Users.findOneBy({ id: ps.userId }); if (user == null) { throw new ApiError(meta.errors.noSuchUser); } diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts index ba2a71951c..02bec31b17 100644 --- a/packages/backend/src/server/api/index.ts +++ b/packages/backend/src/server/api/index.ts @@ -81,7 +81,7 @@ router.get('/v1/instance/peers', async ctx => { }); router.post('/miauth/:session/check', async ctx => { - const token = await AccessTokens.findOne({ + const token = await AccessTokens.findOneBy({ session: ctx.params.session, }); diff --git a/packages/backend/src/server/api/limiter.ts b/packages/backend/src/server/api/limiter.ts index 7e6b93b39f..e74db8466e 100644 --- a/packages/backend/src/server/api/limiter.ts +++ b/packages/backend/src/server/api/limiter.ts @@ -2,12 +2,12 @@ import Limiter from 'ratelimiter'; import { redisClient } from '../../db/redis.js'; import { IEndpoint } from './endpoints.js'; import * as Acct from '@/misc/acct.js'; -import { User } from '@/models/entities/user.js'; +import { CacheableLocalUser, User } from '@/models/entities/user.js'; import Logger from '@/services/logger.js'; const logger = new Logger('limiter'); -export const limiter = (endpoint: IEndpoint & { meta: { limit: NonNullable } }, user: User) => new Promise((ok, reject) => { +export const limiter = (endpoint: IEndpoint & { meta: { limit: NonNullable } }, user: CacheableLocalUser) => new Promise((ok, reject) => { const limitation = endpoint.meta.limit; const key = Object.prototype.hasOwnProperty.call(limitation, 'key') diff --git a/packages/backend/src/server/api/private/signin.ts b/packages/backend/src/server/api/private/signin.ts index b0f88948a0..3f7118ad22 100644 --- a/packages/backend/src/server/api/private/signin.ts +++ b/packages/backend/src/server/api/private/signin.ts @@ -8,6 +8,7 @@ import { ILocalUser } from '@/models/entities/user.js'; import { genId } from '@/misc/gen-id.js'; import { verifyLogin, hash } from '../2fa.js'; import { randomBytes } from 'node:crypto'; +import { IsNull } from 'typeorm'; export default async (ctx: Koa.Context) => { ctx.set('Access-Control-Allow-Origin', config.url); @@ -39,9 +40,9 @@ export default async (ctx: Koa.Context) => { } // Fetch user - const user = await Users.findOne({ + const user = await Users.findOneBy({ usernameLower: username.toLowerCase(), - host: null, + host: IsNull(), }) as ILocalUser; if (user == null) { @@ -58,7 +59,7 @@ export default async (ctx: Koa.Context) => { return; } - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); // Compare password const same = await bcrypt.compare(password, profile.password!); @@ -123,7 +124,7 @@ export default async (ctx: Koa.Context) => { const clientDataJSON = Buffer.from(body.clientDataJSON, 'hex'); const clientData = JSON.parse(clientDataJSON.toString('utf-8')); - const challenge = await AttestationChallenges.findOne({ + const challenge = await AttestationChallenges.findOneBy({ userId: user.id, id: body.challengeId, registrationChallenge: false, @@ -149,7 +150,7 @@ export default async (ctx: Koa.Context) => { return; } - const securityKey = await UserSecurityKeys.findOne({ + const securityKey = await UserSecurityKeys.findOneBy({ id: Buffer.from( body.credentialId .replace(/-/g, '+') @@ -191,7 +192,7 @@ export default async (ctx: Koa.Context) => { return; } - const keys = await UserSecurityKeys.find({ + const keys = await UserSecurityKeys.findBy({ userId: user.id, }); diff --git a/packages/backend/src/server/api/private/signup-pending.ts b/packages/backend/src/server/api/private/signup-pending.ts index 1a667ddb43..e5e39ba00d 100644 --- a/packages/backend/src/server/api/private/signup-pending.ts +++ b/packages/backend/src/server/api/private/signup-pending.ts @@ -9,7 +9,7 @@ export default async (ctx: Koa.Context) => { const code = body['code']; try { - const pendingUser = await UserPendings.findOneOrFail({ code }); + const pendingUser = await UserPendings.findOneByOrFail({ code }); const { account, secret } = await signup({ username: pendingUser.username, @@ -20,7 +20,7 @@ export default async (ctx: Koa.Context) => { id: pendingUser.id, }); - const profile = await UserProfiles.findOneOrFail(account.id); + const profile = await UserProfiles.findOneByOrFail({ userId: account.id }); await UserProfiles.update({ userId: profile.userId }, { email: pendingUser.email, diff --git a/packages/backend/src/server/api/private/signup.ts b/packages/backend/src/server/api/private/signup.ts index 01f284a57f..26f172637c 100644 --- a/packages/backend/src/server/api/private/signup.ts +++ b/packages/backend/src/server/api/private/signup.ts @@ -56,7 +56,7 @@ export default async (ctx: Koa.Context) => { return; } - const ticket = await RegistrationTickets.findOne({ + const ticket = await RegistrationTickets.findOneBy({ code: invitationCode, }); diff --git a/packages/backend/src/server/api/service/discord.ts b/packages/backend/src/server/api/service/discord.ts index 089f7de0cd..04197574c2 100644 --- a/packages/backend/src/server/api/service/discord.ts +++ b/packages/backend/src/server/api/service/discord.ts @@ -10,6 +10,7 @@ import signin from '../common/signin.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { Users, UserProfiles } from '@/models/index.js'; import { ILocalUser } from '@/models/entities/user.js'; +import { IsNull } from 'typeorm'; function getUserToken(ctx: Koa.BaseContext): string | null { return ((ctx.headers['cookie'] || '').match(/igi=(\w+)/) || [null, null])[1]; @@ -40,12 +41,12 @@ router.get('/disconnect/discord', async ctx => { return; } - const user = await Users.findOneOrFail({ - host: null, + const user = await Users.findOneByOrFail({ + host: IsNull(), token: userToken, }); - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); delete profile.integrations.discord; @@ -206,7 +207,7 @@ router.get('/dc/cb', async ctx => { }, }); - signin(ctx, await Users.findOne(profile.userId) as ILocalUser, true); + signin(ctx, await Users.findOneBy({ id: profile.userId }) as ILocalUser, true); } else { const code = ctx.query.code; @@ -252,12 +253,12 @@ router.get('/dc/cb', async ctx => { return; } - const user = await Users.findOneOrFail({ - host: null, + const user = await Users.findOneByOrFail({ + host: IsNull(), token: userToken, }); - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); await UserProfiles.update(user.id, { integrations: { diff --git a/packages/backend/src/server/api/service/github.ts b/packages/backend/src/server/api/service/github.ts index ce032db181..61bb768a63 100644 --- a/packages/backend/src/server/api/service/github.ts +++ b/packages/backend/src/server/api/service/github.ts @@ -10,6 +10,7 @@ import signin from '../common/signin.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { Users, UserProfiles } from '@/models/index.js'; import { ILocalUser } from '@/models/entities/user.js'; +import { IsNull } from 'typeorm'; function getUserToken(ctx: Koa.BaseContext): string | null { return ((ctx.headers['cookie'] || '').match(/igi=(\w+)/) || [null, null])[1]; @@ -40,12 +41,12 @@ router.get('/disconnect/github', async ctx => { return; } - const user = await Users.findOneOrFail({ - host: null, + const user = await Users.findOneByOrFail({ + host: IsNull(), token: userToken, }); - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); delete profile.integrations.github; @@ -184,7 +185,7 @@ router.get('/gh/cb', async ctx => { return; } - signin(ctx, await Users.findOne(link.userId) as ILocalUser, true); + signin(ctx, await Users.findOneBy({ id: link.userId }) as ILocalUser, true); } else { const code = ctx.query.code; @@ -227,12 +228,12 @@ router.get('/gh/cb', async ctx => { return; } - const user = await Users.findOneOrFail({ - host: null, + const user = await Users.findOneByOrFail({ + host: IsNull(), token: userToken, }); - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); await UserProfiles.update(user.id, { integrations: { diff --git a/packages/backend/src/server/api/service/twitter.ts b/packages/backend/src/server/api/service/twitter.ts index e6e4398fa2..e72b71e2f7 100644 --- a/packages/backend/src/server/api/service/twitter.ts +++ b/packages/backend/src/server/api/service/twitter.ts @@ -9,6 +9,7 @@ import signin from '../common/signin.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { Users, UserProfiles } from '@/models/index.js'; import { ILocalUser } from '@/models/entities/user.js'; +import { IsNull } from 'typeorm'; function getUserToken(ctx: Koa.BaseContext): string | null { return ((ctx.headers['cookie'] || '').match(/igi=(\w+)/) || [null, null])[1]; @@ -39,12 +40,12 @@ router.get('/disconnect/twitter', async ctx => { return; } - const user = await Users.findOneOrFail({ - host: null, + const user = await Users.findOneByOrFail({ + host: IsNull(), token: userToken, }); - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); delete profile.integrations.twitter; @@ -143,7 +144,7 @@ router.get('/tw/cb', async ctx => { return; } - signin(ctx, await Users.findOne(link.userId) as ILocalUser, true); + signin(ctx, await Users.findOneBy({ id: link.userId }) as ILocalUser, true); } else { const verifier = ctx.query.oauth_verifier; @@ -162,12 +163,12 @@ router.get('/tw/cb', async ctx => { const result = await twAuth!.done(JSON.parse(twCtx), verifier); - const user = await Users.findOneOrFail({ - host: null, + const user = await Users.findOneByOrFail({ + host: IsNull(), token: userToken, }); - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); await UserProfiles.update(user.id, { integrations: { diff --git a/packages/backend/src/server/api/stream/channels/messaging.ts b/packages/backend/src/server/api/stream/channels/messaging.ts index 94bbdeca52..877d44c38e 100644 --- a/packages/backend/src/server/api/stream/channels/messaging.ts +++ b/packages/backend/src/server/api/stream/channels/messaging.ts @@ -26,12 +26,12 @@ export default class extends Channel { public async init(params: any) { this.otherpartyId = params.otherparty; - this.otherparty = this.otherpartyId ? await Users.findOneOrFail({ id: this.otherpartyId }) : null; + this.otherparty = this.otherpartyId ? await Users.findOneByOrFail({ id: this.otherpartyId }) : null; this.groupId = params.group; // Check joining if (this.groupId) { - const joining = await UserGroupJoinings.findOne({ + const joining = await UserGroupJoinings.findOneBy({ userId: this.user!.id, userGroupId: this.groupId, }); @@ -72,7 +72,7 @@ export default class extends Channel { // リモートユーザーからのメッセージだったら既読配信 if (Users.isLocalUser(this.user!) && Users.isRemoteUser(this.otherparty!)) { - MessagingMessages.findOne(body.id).then(message => { + MessagingMessages.findOneBy({ id: body.id }).then(message => { if (message) deliverReadActivity(this.user as ILocalUser, this.otherparty as IRemoteUser, message); }); } diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts index 57523c8488..d8034e83fe 100644 --- a/packages/backend/src/server/api/stream/channels/user-list.ts +++ b/packages/backend/src/server/api/stream/channels/user-list.ts @@ -23,7 +23,7 @@ export default class extends Channel { this.listId = params.listId as string; // Check existence and owner - const list = await UserLists.findOne({ + const list = await UserLists.findOneBy({ id: this.listId, userId: this.user!.id, }); diff --git a/packages/backend/src/server/api/stream/index.ts b/packages/backend/src/server/api/stream/index.ts index 0cb38e2a99..b803478281 100644 --- a/packages/backend/src/server/api/stream/index.ts +++ b/packages/backend/src/server/api/stream/index.ts @@ -188,7 +188,7 @@ export default class Connection { */ private async onApiRequest(payload: any) { // 新鮮なデータを利用するためにユーザーをフェッチ - const user = this.user ? await Users.findOne(this.user.id) : null; + const user = this.user ? await Users.findOneBy({ id: this.user.id }) : null; const endpoint = payload.endpoint || payload.ep; // alias @@ -386,7 +386,7 @@ export default class Connection { } private async updateUserProfile() { - this.userProfile = await UserProfiles.findOne({ + this.userProfile = await UserProfiles.findOneBy({ userId: this.user!.id, }); } diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts index 90cf59038d..3b0a75d793 100644 --- a/packages/backend/src/server/api/stream/types.ts +++ b/packages/backend/src/server/api/stream/types.ts @@ -15,9 +15,18 @@ import { AbuseUserReport } from '@/models/entities/abuse-user-report.js'; import { Signin } from '@/models/entities/signin.js'; import { Page } from '@/models/entities/page.js'; import { Packed } from '@/misc/schema.js'; +import { Webhook } from '@/models/entities/webhook'; //#region Stream type-body definitions export interface InternalStreamTypes { + userChangeSuspendedState: { id: User['id']; isSuspended: User['isSuspended']; }; + userChangeSilencedState: { id: User['id']; isSilenced: User['isSilenced']; }; + userChangeModeratorState: { id: User['id']; isModerator: User['isModerator']; }; + userTokenRegenerated: { id: User['id']; oldToken: User['token']; newToken: User['token']; }; + remoteUserUpdated: { id: User['id']; }; + webhookCreated: Webhook; + webhookDeleted: Webhook; + webhookUpdated: Webhook; antennaCreated: Antenna; antennaDeleted: Antenna; antennaUpdated: Antenna; diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts index 95a9ec6a00..a68cebfeb2 100644 --- a/packages/backend/src/server/index.ts +++ b/packages/backend/src/server/index.ts @@ -26,6 +26,7 @@ import { createTemp } from '@/misc/create-temp.js'; import { publishMainStream } from '@/services/stream.js'; import * as Acct from '@/misc/acct.js'; import { initializeStreamingServer } from './api/streaming.js'; +import { IsNull } from 'typeorm'; export const serverLogger = new Logger('server', 'gray', false); @@ -71,10 +72,11 @@ router.use(wellKnown.routes()); router.get('/avatar/@:acct', async ctx => { const { username, host } = Acct.parse(ctx.params.acct); const user = await Users.findOne({ - usernameLower: username.toLowerCase(), - host: host === config.host ? null : host, - isSuspended: false, - }, { + where: { + usernameLower: username.toLowerCase(), + host: (host == null) || (host === config.host) ? IsNull() : host, + isSuspended: false, + }, relations: ['avatar'], }); @@ -93,7 +95,7 @@ router.get('/identicon/:x', async ctx => { }); router.get('/verify-email/:code', async ctx => { - const profile = await UserProfiles.findOne({ + const profile = await UserProfiles.findOneBy({ emailVerifyCode: ctx.params.code, }); diff --git a/packages/backend/src/server/nodeinfo.ts b/packages/backend/src/server/nodeinfo.ts index f4b56fc8a5..13a362a75f 100644 --- a/packages/backend/src/server/nodeinfo.ts +++ b/packages/backend/src/server/nodeinfo.ts @@ -2,8 +2,9 @@ import Router from '@koa/router'; import config from '@/config/index.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { Users, Notes } from '@/models/index.js'; -import { MoreThan } from 'typeorm'; +import { IsNull, MoreThan } from 'typeorm'; import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; +import { Cache } from '@/misc/cache.js'; const router = new Router(); @@ -28,10 +29,10 @@ const nodeinfo2 = async () => { localPosts, ] = await Promise.all([ fetchMeta(true), - Users.count({ where: { host: null } }), - Users.count({ where: { host: null, lastActiveDate: MoreThan(new Date(now - 15552000000)) } }), - Users.count({ where: { host: null, lastActiveDate: MoreThan(new Date(now - 2592000000)) } }), - Notes.count({ where: { userHost: null } }), + Users.count({ where: { host: IsNull() } }), + Users.count({ where: { host: IsNull(), lastActiveDate: MoreThan(new Date(now - 15552000000)) } }), + Users.count({ where: { host: IsNull(), lastActiveDate: MoreThan(new Date(now - 2592000000)) } }), + Notes.count({ where: { userHost: IsNull() } }), ]); const proxyAccount = meta.proxyAccountId ? await Users.pack(meta.proxyAccountId).catch(() => null) : null; @@ -81,15 +82,17 @@ const nodeinfo2 = async () => { }; }; +const cache = new Cache>>(1000 * 60 * 10); + router.get(nodeinfo2_1path, async ctx => { - const base = await nodeinfo2(); + const base = await cache.fetch(null, () => nodeinfo2()); ctx.body = { version: '2.1', ...base }; ctx.set('Cache-Control', 'public, max-age=600'); }); router.get(nodeinfo2_0path, async ctx => { - const base = await nodeinfo2(); + const base = await cache.fetch(null, () => nodeinfo2()); delete base.software.repository; diff --git a/packages/backend/src/server/web/feed.ts b/packages/backend/src/server/web/feed.ts index b98e3f8bf6..eba8dc58d4 100644 --- a/packages/backend/src/server/web/feed.ts +++ b/packages/backend/src/server/web/feed.ts @@ -2,7 +2,7 @@ import { Feed } from 'feed'; import config from '@/config/index.js'; import { User } from '@/models/entities/user.js'; import { Notes, DriveFiles, UserProfiles } from '@/models/index.js'; -import { In } from 'typeorm'; +import { In, IsNull } from 'typeorm'; export default async function(user: User) { const author = { @@ -10,12 +10,12 @@ export default async function(user: User) { name: user.name || user.username, }; - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); const notes = await Notes.find({ where: { userId: user.id, - renoteId: null, + renoteId: IsNull(), visibility: In(['public', 'home']), }, order: { createdAt: -1 }, @@ -39,7 +39,7 @@ export default async function(user: User) { }); for (const note of notes) { - const files = note.fileIds.length > 0 ? await DriveFiles.find({ + const files = note.fileIds.length > 0 ? await DriveFiles.findBy({ id: In(note.fileIds), }) : []; const file = files.find(file => file.type.startsWith('image/')); diff --git a/packages/backend/src/server/web/index.ts b/packages/backend/src/server/web/index.ts index cc4c2cc9ca..48bf6f7338 100644 --- a/packages/backend/src/server/web/index.ts +++ b/packages/backend/src/server/web/index.ts @@ -10,6 +10,9 @@ import Router from '@koa/router'; import send from 'koa-send'; import favicon from 'koa-favicon'; import views from 'koa-views'; +import { createBullBoard } from '@bull-board/api'; +import { BullAdapter } from '@bull-board/api/bullAdapter.js'; +import { KoaAdapter } from '@bull-board/koa'; import packFeed from './feed.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; @@ -20,6 +23,8 @@ import * as Acct from '@/misc/acct.js'; import { getNoteSummary } from '@/misc/get-note-summary.js'; import { urlPreviewHandler } from './url-preview.js'; import { manifestHandler } from './manifest.js'; +import { queues } from '@/queue/queues.js'; +import { IsNull } from 'typeorm'; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); @@ -31,6 +36,37 @@ const assets = `${_dirname}/../../../../../built/_client_dist_/`; // Init app const app = new Koa(); +//#region Bull Dashboard +const bullBoardPath = '/queue'; + +// Authenticate +app.use(async (ctx, next) => { + if (ctx.path === bullBoardPath || ctx.path.startsWith(bullBoardPath + '/')) { + const token = ctx.cookies.get('token'); + if (token == null) { + ctx.status = 401; + return; + } + const user = await Users.findOneBy({ token }); + if (user == null || !(user.isAdmin || user.isModerator)) { + ctx.status = 403; + return; + } + } + await next(); +}); + +const serverAdapter = new KoaAdapter(); + +createBullBoard({ + queues: queues.map(q => new BullAdapter(q)), + serverAdapter, +}); + +serverAdapter.setBasePath(bullBoardPath); +app.use(serverAdapter.registerPlugin()); +//#endregion + // Init renderer app.use(views(_dirname + '/views', { extension: 'pug', @@ -133,9 +169,9 @@ router.get('/api.json', async ctx => { const getFeed = async (acct: string) => { const { username, host } = Acct.parse(acct); - const user = await Users.findOne({ + const user = await Users.findOneBy({ usernameLower: username.toLowerCase(), - host, + host: host ?? IsNull(), isSuspended: false, }); @@ -182,14 +218,14 @@ router.get('/@:user.json', async ctx => { // User router.get(['/@:user', '/@:user/:sub'], async (ctx, next) => { const { username, host } = Acct.parse(ctx.params.user); - const user = await Users.findOne({ + const user = await Users.findOneBy({ usernameLower: username.toLowerCase(), - host, + host: host ?? IsNull(), isSuspended: false, }); if (user != null) { - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); const meta = await fetchMeta(); const me = profile.fields ? profile.fields @@ -213,9 +249,9 @@ router.get(['/@:user', '/@:user/:sub'], async (ctx, next) => { }); router.get('/users/:user', async ctx => { - const user = await Users.findOne({ + const user = await Users.findOneBy({ id: ctx.params.user, - host: null, + host: IsNull(), isSuspended: false, }); @@ -229,11 +265,11 @@ router.get('/users/:user', async ctx => { // Note router.get('/notes/:note', async (ctx, next) => { - const note = await Notes.findOne(ctx.params.note); + const note = await Notes.findOneBy({ id: ctx.params.note }); if (note) { const _note = await Notes.pack(note); - const profile = await UserProfiles.findOneOrFail(note.userId); + const profile = await UserProfiles.findOneByOrFail({ userId: note.userId }); const meta = await fetchMeta(); await ctx.render('note', { note: _note, @@ -260,21 +296,21 @@ router.get('/notes/:note', async (ctx, next) => { // Page router.get('/@:user/pages/:page', async (ctx, next) => { const { username, host } = Acct.parse(ctx.params.user); - const user = await Users.findOne({ + const user = await Users.findOneBy({ usernameLower: username.toLowerCase(), - host, + host: host ?? IsNull(), }); if (user == null) return; - const page = await Pages.findOne({ + const page = await Pages.findOneBy({ name: ctx.params.page, userId: user.id, }); if (page) { const _page = await Pages.pack(page); - const profile = await UserProfiles.findOneOrFail(page.userId); + const profile = await UserProfiles.findOneByOrFail({ userId: page.userId }); const meta = await fetchMeta(); await ctx.render('page', { page: _page, @@ -299,13 +335,13 @@ router.get('/@:user/pages/:page', async (ctx, next) => { // Clip // TODO: 非publicなclipのハンドリング router.get('/clips/:clip', async (ctx, next) => { - const clip = await Clips.findOne({ + const clip = await Clips.findOneBy({ id: ctx.params.clip, }); if (clip) { const _clip = await Clips.pack(clip); - const profile = await UserProfiles.findOneOrFail(clip.userId); + const profile = await UserProfiles.findOneByOrFail({ userId: clip.userId }); const meta = await fetchMeta(); await ctx.render('clip', { clip: _clip, @@ -325,11 +361,11 @@ router.get('/clips/:clip', async (ctx, next) => { // Gallery post router.get('/gallery/:post', async (ctx, next) => { - const post = await GalleryPosts.findOne(ctx.params.post); + const post = await GalleryPosts.findOneBy({ id: ctx.params.post }); if (post) { const _post = await GalleryPosts.pack(post); - const profile = await UserProfiles.findOneOrFail(post.userId); + const profile = await UserProfiles.findOneByOrFail({ userId: post.userId }); const meta = await fetchMeta(); await ctx.render('gallery-post', { post: _post, @@ -349,7 +385,7 @@ router.get('/gallery/:post', async (ctx, next) => { // Channel router.get('/channels/:channel', async (ctx, next) => { - const channel = await Channels.findOne({ + const channel = await Channels.findOneBy({ id: ctx.params.channel, }); @@ -381,8 +417,8 @@ router.get('/_info_card_', async ctx => { version: config.version, host: config.host, meta: meta, - originalUsersCount: await Users.count({ host: null }), - originalNotesCount: await Notes.count({ userHost: null }), + originalUsersCount: await Users.countBy({ host: IsNull() }), + originalNotesCount: await Notes.countBy({ userHost: IsNull() }), }); }); diff --git a/packages/backend/src/server/well-known.ts b/packages/backend/src/server/well-known.ts index 7a5d085413..7530b4e0ba 100644 --- a/packages/backend/src/server/well-known.ts +++ b/packages/backend/src/server/well-known.ts @@ -6,6 +6,7 @@ import { links } from './nodeinfo.js'; import { escapeAttribute, escapeValue } from '@/prelude/xml.js'; import { Users } from '@/models/index.js'; import { User } from '@/models/entities/user.js'; +import { FindOptionsWhere, IsNull } from 'typeorm'; // Init router const router = new Router(); @@ -66,13 +67,13 @@ router.get('/.well-known/change-password', async ctx => { */ router.get(webFingerPath, async ctx => { - const fromId = (id: User['id']): Record => ({ + const fromId = (id: User['id']): FindOptionsWhere => ({ id, - host: null, + host: IsNull(), isSuspended: false, }); - const generateQuery = (resource: string) => + const generateQuery = (resource: string): FindOptionsWhere | number => resource.startsWith(`${config.url.toLowerCase()}/users/`) ? fromId(resource.split('/').pop()!) : fromAcct(Acct.parse( @@ -80,10 +81,10 @@ router.get(webFingerPath, async ctx => { resource.startsWith('acct:') ? resource.slice('acct:'.length) : resource)); - const fromAcct = (acct: Acct.Acct): Record | number => + const fromAcct = (acct: Acct.Acct): FindOptionsWhere | number => !acct.host || acct.host === config.host.toLowerCase() ? { usernameLower: acct.username, - host: null, + host: IsNull(), isSuspended: false, } : 422; @@ -99,7 +100,7 @@ router.get(webFingerPath, async ctx => { return; } - const user = await Users.findOne(query); + const user = await Users.findOneBy(query); if (user == null) { ctx.status = 404; diff --git a/packages/backend/src/services/add-note-to-antenna.ts b/packages/backend/src/services/add-note-to-antenna.ts index e88c387234..f86f394f80 100644 --- a/packages/backend/src/services/add-note-to-antenna.ts +++ b/packages/backend/src/services/add-note-to-antenna.ts @@ -33,10 +33,10 @@ export async function addNoteToAntenna(antenna: Antenna, note: Note, noteUser: { }; if (note.replyId != null) { - _note.reply = await Notes.findOneOrFail(note.replyId); + _note.reply = await Notes.findOneByOrFail({ id: note.replyId }); } if (note.renoteId != null) { - _note.renote = await Notes.findOneOrFail(note.renoteId); + _note.renote = await Notes.findOneByOrFail({ id: note.renoteId }); } if (isMutedUserRelated(_note, new Set(mutings.map(x => x.muteeId)))) { @@ -45,7 +45,7 @@ export async function addNoteToAntenna(antenna: Antenna, note: Note, noteUser: { // 2秒経っても既読にならなかったら通知 setTimeout(async () => { - const unread = await AntennaNotes.findOne({ antennaId: antenna.id, read: false }); + const unread = await AntennaNotes.findOneBy({ antennaId: antenna.id, read: false }); if (unread) { publishMainStream(antenna.userId, 'unreadAntenna', antenna); } diff --git a/packages/backend/src/services/blocking/create.ts b/packages/backend/src/services/blocking/create.ts index 198d28705e..5c67190079 100644 --- a/packages/backend/src/services/blocking/create.ts +++ b/packages/backend/src/services/blocking/create.ts @@ -10,6 +10,8 @@ import { Blockings, Users, FollowRequests, Followings, UserListJoinings, UserLis import { perUserFollowingChart } from '@/services/chart/index.js'; import { genId } from '@/misc/gen-id.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; +import { getActiveWebhooks } from '@/misc/webhook-cache.js'; +import { webhookDeliver } from '@/queue/index.js'; export default async function(blocker: User, blockee: User) { await Promise.all([ @@ -34,7 +36,7 @@ export default async function(blocker: User, blockee: User) { } async function cancelRequest(follower: User, followee: User) { - const request = await FollowRequests.findOne({ + const request = await FollowRequests.findOneBy({ followeeId: followee.id, followerId: follower.id, }); @@ -57,9 +59,17 @@ async function cancelRequest(follower: User, followee: User) { if (Users.isLocalUser(follower)) { Users.pack(followee, follower, { detail: true, - }).then(packed => { + }).then(async packed => { publishUserEvent(follower.id, 'unfollow', packed); publishMainStream(follower.id, 'unfollow', packed); + + const webhooks = (await getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('unfollow')); + for (const webhook of webhooks) { + webhookDeliver(webhook, { + type: 'unfollow', + user: packed, + }); + } }); } @@ -77,7 +87,7 @@ async function cancelRequest(follower: User, followee: User) { } async function unFollow(follower: User, followee: User) { - const following = await Followings.findOne({ + const following = await Followings.findOneBy({ followerId: follower.id, followeeId: followee.id, }); @@ -102,9 +112,17 @@ async function unFollow(follower: User, followee: User) { if (Users.isLocalUser(follower)) { Users.pack(followee, follower, { detail: true, - }).then(packed => { + }).then(async packed => { publishUserEvent(follower.id, 'unfollow', packed); publishMainStream(follower.id, 'unfollow', packed); + + const webhooks = (await getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('unfollow')); + for (const webhook of webhooks) { + webhookDeliver(webhook, { + type: 'unfollow', + user: packed, + }); + } }); } @@ -116,7 +134,7 @@ async function unFollow(follower: User, followee: User) { } async function removeFromList(listOwner: User, user: User) { - const userLists = await UserLists.find({ + const userLists = await UserLists.findBy({ userId: listOwner.id, }); diff --git a/packages/backend/src/services/blocking/delete.ts b/packages/backend/src/services/blocking/delete.ts index c4f3784b05..d7b5ddd5ff 100644 --- a/packages/backend/src/services/blocking/delete.ts +++ b/packages/backend/src/services/blocking/delete.ts @@ -3,13 +3,13 @@ import renderBlock from '@/remote/activitypub/renderer/block.js'; import renderUndo from '@/remote/activitypub/renderer/undo.js'; import { deliver } from '@/queue/index.js'; import Logger from '../logger.js'; -import { User } from '@/models/entities/user.js'; +import { CacheableUser, User } from '@/models/entities/user.js'; import { Blockings, Users } from '@/models/index.js'; const logger = new Logger('blocking/delete'); -export default async function(blocker: User, blockee: User) { - const blocking = await Blockings.findOne({ +export default async function(blocker: CacheableUser, blockee: CacheableUser) { + const blocking = await Blockings.findOneBy({ blockerId: blocker.id, blockeeId: blockee.id, }); diff --git a/packages/backend/src/services/chart/charts/instance.ts b/packages/backend/src/services/chart/charts/instance.ts index f1257fdf14..fe29ba5228 100644 --- a/packages/backend/src/services/chart/charts/instance.ts +++ b/packages/backend/src/services/chart/charts/instance.ts @@ -22,11 +22,11 @@ export default class InstanceChart extends Chart { followersCount, driveFiles, ] = await Promise.all([ - Notes.count({ userHost: group }), - Users.count({ host: group }), - Followings.count({ followerHost: group }), - Followings.count({ followeeHost: group }), - DriveFiles.count({ userHost: group }), + Notes.countBy({ userHost: group }), + Users.countBy({ host: group }), + Followings.countBy({ followerHost: group }), + Followings.countBy({ followeeHost: group }), + DriveFiles.countBy({ userHost: group }), ]); return { diff --git a/packages/backend/src/services/chart/charts/notes.ts b/packages/backend/src/services/chart/charts/notes.ts index ab6a37e3c1..bb14b62f3c 100644 --- a/packages/backend/src/services/chart/charts/notes.ts +++ b/packages/backend/src/services/chart/charts/notes.ts @@ -15,8 +15,8 @@ export default class NotesChart extends Chart { protected async tickMajor(): Promise>> { const [localCount, remoteCount] = await Promise.all([ - Notes.count({ userHost: null }), - Notes.count({ userHost: Not(IsNull()) }), + Notes.countBy({ userHost: IsNull() }), + Notes.countBy({ userHost: Not(IsNull()) }), ]); return { diff --git a/packages/backend/src/services/chart/charts/per-user-drive.ts b/packages/backend/src/services/chart/charts/per-user-drive.ts index 131befa396..5f75dc6887 100644 --- a/packages/backend/src/services/chart/charts/per-user-drive.ts +++ b/packages/backend/src/services/chart/charts/per-user-drive.ts @@ -14,7 +14,7 @@ export default class PerUserDriveChart extends Chart { protected async tickMajor(group: string): Promise>> { const [count, size] = await Promise.all([ - DriveFiles.count({ userId: group }), + DriveFiles.countBy({ userId: group }), DriveFiles.calcDriveUsageOf(group), ]); diff --git a/packages/backend/src/services/chart/charts/per-user-following.ts b/packages/backend/src/services/chart/charts/per-user-following.ts index 5d5dd1fa1e..02b149f52a 100644 --- a/packages/backend/src/services/chart/charts/per-user-following.ts +++ b/packages/backend/src/services/chart/charts/per-user-following.ts @@ -20,10 +20,10 @@ export default class PerUserFollowingChart extends Chart { remoteFollowingsCount, remoteFollowersCount, ] = await Promise.all([ - Followings.count({ followerId: group, followeeHost: null }), - Followings.count({ followeeId: group, followerHost: null }), - Followings.count({ followerId: group, followeeHost: Not(IsNull()) }), - Followings.count({ followeeId: group, followerHost: Not(IsNull()) }), + Followings.countBy({ followerId: group, followeeHost: IsNull() }), + Followings.countBy({ followeeId: group, followerHost: IsNull() }), + Followings.countBy({ followerId: group, followeeHost: Not(IsNull()) }), + Followings.countBy({ followeeId: group, followerHost: Not(IsNull()) }), ]); return { diff --git a/packages/backend/src/services/chart/charts/per-user-notes.ts b/packages/backend/src/services/chart/charts/per-user-notes.ts index 9c5dea1aa9..b9191dd088 100644 --- a/packages/backend/src/services/chart/charts/per-user-notes.ts +++ b/packages/backend/src/services/chart/charts/per-user-notes.ts @@ -15,7 +15,7 @@ export default class PerUserNotesChart extends Chart { protected async tickMajor(group: string): Promise>> { const [count] = await Promise.all([ - Notes.count({ userId: group }), + Notes.countBy({ userId: group }), ]); return { diff --git a/packages/backend/src/services/chart/charts/users.ts b/packages/backend/src/services/chart/charts/users.ts index fb9d5e15fb..acb16ead87 100644 --- a/packages/backend/src/services/chart/charts/users.ts +++ b/packages/backend/src/services/chart/charts/users.ts @@ -15,8 +15,8 @@ export default class UsersChart extends Chart { protected async tickMajor(): Promise>> { const [localCount, remoteCount] = await Promise.all([ - Users.count({ host: null }), - Users.count({ host: Not(IsNull()) }), + Users.countBy({ host: IsNull() }), + Users.countBy({ host: Not(IsNull()) }), ]); return { diff --git a/packages/backend/src/services/chart/core.ts b/packages/backend/src/services/chart/core.ts index 39fad71dd7..cf69e2194d 100644 --- a/packages/backend/src/services/chart/core.ts +++ b/packages/backend/src/services/chart/core.ts @@ -6,9 +6,10 @@ import * as nestedProperty from 'nested-property'; import Logger from '../logger.js'; -import { EntitySchema, getRepository, Repository, LessThan, Between } from 'typeorm'; +import { EntitySchema, Repository, LessThan, Between } from 'typeorm'; import { dateUTC, isTimeSame, isTimeBefore, subtractTime, addTime } from '@/prelude/time.js'; import { getChartInsertLock } from '@/misc/app-lock.js'; +import { db } from '@/db/postgre.js'; const logger = new Logger('chart', 'white', process.env.NODE_ENV !== 'test'); @@ -241,8 +242,8 @@ export default abstract class Chart { this.schema = schema; const { hour, day } = Chart.schemaToEntity(name, schema, grouped); - this.repositoryForHour = getRepository<{ id: number; group?: string | null; date: number; }>(hour); - this.repositoryForDay = getRepository<{ id: number; group?: string | null; date: number; }>(day); + this.repositoryForHour = db.getRepository<{ id: number; group?: string | null; date: number; }>(hour); + this.repositoryForDay = db.getRepository<{ id: number; group?: string | null; date: number; }>(day); } private convertRawRecord(x: RawRecord): KVs { @@ -271,9 +272,10 @@ export default abstract class Chart { span === 'day' ? this.repositoryForDay : new Error('not happen') as never; - return repository.findOne(group ? { - group: group, - } : {}, { + return repository.findOne({ + where: group ? { + group: group, + } : {}, order: { date: -1, }, @@ -297,7 +299,7 @@ export default abstract class Chart { new Error('not happen') as never; // 現在(=今のHour or Day)のログ - const currentLog = await repository.findOne({ + const currentLog = await repository.findOneBy({ date: Chart.dateToTimestamp(current), ...(group ? { group: group } : {}), }) as RawRecord | undefined; @@ -337,7 +339,7 @@ export default abstract class Chart { const unlock = await getChartInsertLock(lockKey); try { // ロック内でもう1回チェックする - const currentLog = await repository.findOne({ + const currentLog = await repository.findOneBy({ date: date, ...(group ? { group: group } : {}), }) as RawRecord | undefined; @@ -356,7 +358,7 @@ export default abstract class Chart { date: date, ...(group ? { group: group } : {}), ...columns, - }).then(x => repository.findOneOrFail(x.identifiers[0])) as RawRecord; + }).then(x => repository.findOneByOrFail(x.identifiers[0])) as RawRecord; logger.info(`${this.name + (group ? `:${group}` : '')}(${span}): New commit created`); @@ -598,9 +600,10 @@ export default abstract class Chart { if (logs.length === 0) { // もっとも新しいログを持ってくる // (すくなくともひとつログが無いと隙間埋めできないため) - const recentLog = await repository.findOne(group ? { - group: group, - } : {}, { + const recentLog = await repository.findOne({ + where: group ? { + group: group, + } : {}, order: { date: -1, }, @@ -615,9 +618,10 @@ export default abstract class Chart { // 要求された範囲の最も古い箇所時点での最も新しいログを持ってきて末尾に追加する // (隙間埋めできないため) const outdatedLog = await repository.findOne({ - date: LessThan(Chart.dateToTimestamp(gt)), - ...(group ? { group: group } : {}), - }, { + where: { + date: LessThan(Chart.dateToTimestamp(gt)), + ...(group ? { group: group } : {}), + }, order: { date: -1, }, diff --git a/packages/backend/src/services/create-notification.ts b/packages/backend/src/services/create-notification.ts index d78e707ecf..9a53db1f38 100644 --- a/packages/backend/src/services/create-notification.ts +++ b/packages/backend/src/services/create-notification.ts @@ -15,7 +15,7 @@ export async function createNotification( return null; } - const profile = await UserProfiles.findOne({ userId: notifieeId }); + const profile = await UserProfiles.findOneBy({ userId: notifieeId }); const isMuted = profile?.mutingNotificationTypes.includes(type); @@ -29,7 +29,7 @@ export async function createNotification( isRead: isMuted, ...data, } as Partial) - .then(x => Notifications.findOneOrFail(x.identifiers[0])); + .then(x => Notifications.findOneByOrFail(x.identifiers[0])); const packed = await Notifications.pack(notification, {}); @@ -38,12 +38,12 @@ export async function createNotification( // 2秒経っても(今回作成した)通知が既読にならなかったら「未読の通知がありますよ」イベントを発行する setTimeout(async () => { - const fresh = await Notifications.findOne(notification.id); + const fresh = await Notifications.findOneBy({ id: notification.id }); if (fresh == null) return; // 既に削除されているかもしれない if (fresh.isRead) return; //#region ただしミュートしているユーザーからの通知なら無視 - const mutings = await Mutings.find({ + const mutings = await Mutings.findBy({ muterId: notifieeId, }); if (data.notifierId && mutings.map(m => m.muteeId).includes(data.notifierId)) { @@ -54,8 +54,8 @@ export async function createNotification( publishMainStream(notifieeId, 'unreadNotification', packed); pushSw(notifieeId, 'notification', packed); - if (type === 'follow') sendEmailNotification.follow(notifieeId, await Users.findOneOrFail(data.notifierId!)); - if (type === 'receiveFollowRequest') sendEmailNotification.receiveFollowRequest(notifieeId, await Users.findOneOrFail(data.notifierId!)); + if (type === 'follow') sendEmailNotification.follow(notifieeId, await Users.findOneByOrFail({ id: data.notifierId! })); + if (type === 'receiveFollowRequest') sendEmailNotification.receiveFollowRequest(notifieeId, await Users.findOneByOrFail({ id: data.notifierId! })); }, 2000); return notification; diff --git a/packages/backend/src/services/create-system-user.ts b/packages/backend/src/services/create-system-user.ts index 781e0560d1..bae91ec4c3 100644 --- a/packages/backend/src/services/create-system-user.ts +++ b/packages/backend/src/services/create-system-user.ts @@ -4,10 +4,11 @@ import generateNativeUserToken from '../server/api/common/generate-native-user-t import { genRsaKeyPair } from '@/misc/gen-key-pair.js'; import { User } from '@/models/entities/user.js'; import { UserProfile } from '@/models/entities/user-profile.js'; -import { getConnection, ObjectLiteral } from 'typeorm'; +import { IsNull } from 'typeorm'; import { genId } from '@/misc/gen-id.js'; import { UserKeypair } from '@/models/entities/user-keypair.js'; import { UsedUsername } from '@/models/entities/used-username.js'; +import { db } from '@/db/postgre.js'; export async function createSystemUser(username: string) { const password = uuid(); @@ -21,13 +22,13 @@ export async function createSystemUser(username: string) { const keyPair = await genRsaKeyPair(4096); - let account!: User | ObjectLiteral; + let account!: User; // Start transaction - await getConnection().transaction(async transactionalEntityManager => { - const exist = await transactionalEntityManager.findOne(User, { + await db.transaction(async transactionalEntityManager => { + const exist = await transactionalEntityManager.findOneBy(User, { usernameLower: username.toLowerCase(), - host: null, + host: IsNull(), }); if (exist) throw new Error('the user is already exists'); @@ -43,7 +44,7 @@ export async function createSystemUser(username: string) { isLocked: true, isExplorable: false, isBot: true, - }).then(x => transactionalEntityManager.findOneOrFail(User, x.identifiers[0])); + }).then(x => transactionalEntityManager.findOneByOrFail(User, x.identifiers[0])); await transactionalEntityManager.insert(UserKeypair, { publicKey: keyPair.publicKey, diff --git a/packages/backend/src/services/drive/add-file.ts b/packages/backend/src/services/drive/add-file.ts index 8397945668..549b11c9fe 100644 --- a/packages/backend/src/services/drive/add-file.ts +++ b/packages/backend/src/services/drive/add-file.ts @@ -21,6 +21,7 @@ import S3 from 'aws-sdk/clients/s3.js'; import { getS3 } from './s3.js'; import sharp from 'sharp'; import { FILE_TYPE_BROWSERSAFE } from '@/const.js'; +import { IsNull } from 'typeorm'; const logger = driveLogger.createSubLogger('register', 'yellow'); @@ -108,7 +109,7 @@ async function save(file: DriveFile, path: string, name: string, type: string, h file.size = size; file.storedInternal = false; - return await DriveFiles.insert(file).then(x => DriveFiles.findOneOrFail(x.identifiers[0])); + return await DriveFiles.insert(file).then(x => DriveFiles.findOneByOrFail(x.identifiers[0])); } else { // use internal storage const accessKey = uuid(); const thumbnailAccessKey = 'thumbnail-' + uuid(); @@ -142,7 +143,7 @@ async function save(file: DriveFile, path: string, name: string, type: string, h file.md5 = hash; file.size = size; - return await DriveFiles.insert(file).then(x => DriveFiles.findOneOrFail(x.identifiers[0])); + return await DriveFiles.insert(file).then(x => DriveFiles.findOneByOrFail(x.identifiers[0])); } } @@ -344,7 +345,7 @@ export async function addFile({ if (user && !force) { // Check if there is a file with the same hash - const much = await DriveFiles.findOne({ + const much = await DriveFiles.findOneBy({ md5: info.md5, userId: user.id, }); @@ -370,7 +371,7 @@ export async function addFile({ throw new Error('no-free-space'); } else { // (アバターまたはバナーを含まず)最も古いファイルを削除する - deleteOldFile(await Users.findOneOrFail(user.id) as IRemoteUser); + deleteOldFile(await Users.findOneByOrFail({ id: user.id }) as IRemoteUser); } } } @@ -381,9 +382,9 @@ export async function addFile({ return null; } - const driveFolder = await DriveFolders.findOne({ + const driveFolder = await DriveFolders.findOneBy({ id: folderId, - userId: user ? user.id : null, + userId: user ? user.id : IsNull(), }); if (driveFolder == null) throw new Error('folder-not-found'); @@ -405,7 +406,7 @@ export async function addFile({ properties['orientation'] = info.orientation; } - const profile = user ? await UserProfiles.findOne(user.id) : null; + const profile = user ? await UserProfiles.findOneBy({ userId: user.id }) : null; const folder = await fetchFolder(); @@ -450,15 +451,15 @@ export async function addFile({ file.type = info.type.mime; file.storedInternal = false; - file = await DriveFiles.insert(file).then(x => DriveFiles.findOneOrFail(x.identifiers[0])); + file = await DriveFiles.insert(file).then(x => DriveFiles.findOneByOrFail(x.identifiers[0])); } catch (err) { // duplicate key error (when already registered) if (isDuplicateKeyValueError(err)) { logger.info(`already registered ${file.uri}`); - file = await DriveFiles.findOne({ - uri: file.uri, - userId: user ? user.id : null, + file = await DriveFiles.findOneBy({ + uri: file.uri!, + userId: user ? user.id : IsNull(), }) as DriveFile; } else { logger.error(err as Error); diff --git a/packages/backend/src/services/fetch-instance-metadata.ts b/packages/backend/src/services/fetch-instance-metadata.ts index f3a0424abd..2b6f82a910 100644 --- a/packages/backend/src/services/fetch-instance-metadata.ts +++ b/packages/backend/src/services/fetch-instance-metadata.ts @@ -13,7 +13,7 @@ export async function fetchInstanceMetadata(instance: Instance, force = false): const unlock = await getFetchInstanceMetadataLock(instance.host); if (!force) { - const _instance = await Instances.findOne({ host: instance.host }); + const _instance = await Instances.findOneBy({ host: instance.host }); const now = Date.now(); if (_instance && _instance.infoUpdatedAt && (now - _instance.infoUpdatedAt.getTime() < 1000 * 60 * 60 * 24)) { unlock(); diff --git a/packages/backend/src/services/following/create.ts b/packages/backend/src/services/following/create.ts index a416412131..d243317d97 100644 --- a/packages/backend/src/services/following/create.ts +++ b/packages/backend/src/services/following/create.ts @@ -15,6 +15,8 @@ import { genId } from '@/misc/gen-id.js'; import { createNotification } from '../create-notification.js'; import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; import { Packed } from '@/misc/schema.js'; +import { getActiveWebhooks } from '@/misc/webhook-cache.js'; +import { webhookDeliver } from '@/queue/index.js'; const logger = new Logger('following/create'); @@ -45,7 +47,7 @@ export async function insertFollowingDoc(followee: { id: User['id']; host: User[ } }); - const req = await FollowRequests.findOne({ + const req = await FollowRequests.findOneBy({ followeeId: followee.id, followerId: follower.id, }); @@ -89,15 +91,33 @@ export async function insertFollowingDoc(followee: { id: User['id']; host: User[ if (Users.isLocalUser(follower)) { Users.pack(followee.id, follower, { detail: true, - }).then(packed => { + }).then(async packed => { publishUserEvent(follower.id, 'follow', packed as Packed<"UserDetailedNotMe">); publishMainStream(follower.id, 'follow', packed as Packed<"UserDetailedNotMe">); + + const webhooks = (await getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('follow')); + for (const webhook of webhooks) { + webhookDeliver(webhook, { + type: 'follow', + user: packed, + }); + } }); } // Publish followed event if (Users.isLocalUser(followee)) { - Users.pack(follower.id, followee).then(packed => publishMainStream(followee.id, 'followed', packed)); + Users.pack(follower.id, followee).then(async packed => { + publishMainStream(followee.id, 'followed', packed) + + const webhooks = (await getActiveWebhooks()).filter(x => x.userId === followee.id && x.on.includes('followed')); + for (const webhook of webhooks) { + webhookDeliver(webhook, { + type: 'followed', + user: packed, + }); + } + }); // 通知を作成 createNotification(followee.id, 'follow', { @@ -108,17 +128,17 @@ export async function insertFollowingDoc(followee: { id: User['id']; host: User[ export default async function(_follower: { id: User['id'] }, _followee: { id: User['id'] }, requestId?: string) { const [follower, followee] = await Promise.all([ - Users.findOneOrFail(_follower.id), - Users.findOneOrFail(_followee.id), + Users.findOneByOrFail({ id: _follower.id }), + Users.findOneByOrFail({ id: _followee.id }), ]); // check blocking const [blocking, blocked] = await Promise.all([ - Blockings.findOne({ + Blockings.findOneBy({ blockerId: follower.id, blockeeId: followee.id, }), - Blockings.findOne({ + Blockings.findOneBy({ blockerId: followee.id, blockeeId: follower.id, }), @@ -138,7 +158,7 @@ export default async function(_follower: { id: User['id'] }, _followee: { id: Us if (blocked != null) throw new IdentifiableError('3338392a-f764-498d-8855-db939dcf8c48', 'blocked'); } - const followeeProfile = await UserProfiles.findOneOrFail(followee.id); + const followeeProfile = await UserProfiles.findOneByOrFail({ userId: followee.id }); // フォロー対象が鍵アカウントである or // フォロワーがBotであり、フォロー対象がBotからのフォローに慎重である or @@ -148,7 +168,7 @@ export default async function(_follower: { id: User['id'] }, _followee: { id: Us let autoAccept = false; // 鍵アカウントであっても、既にフォローされていた場合はスルー - const following = await Followings.findOne({ + const following = await Followings.findOneBy({ followerId: follower.id, followeeId: followee.id, }); @@ -158,7 +178,7 @@ export default async function(_follower: { id: User['id'] }, _followee: { id: Us // フォローしているユーザーは自動承認オプション if (!autoAccept && (Users.isLocalUser(followee) && followeeProfile.autoAcceptFollowed)) { - const followed = await Followings.findOne({ + const followed = await Followings.findOneBy({ followerId: followee.id, followeeId: follower.id, }); diff --git a/packages/backend/src/services/following/delete.ts b/packages/backend/src/services/following/delete.ts index d82c0be52d..85e40f1365 100644 --- a/packages/backend/src/services/following/delete.ts +++ b/packages/backend/src/services/following/delete.ts @@ -3,17 +3,18 @@ import { renderActivity } from '@/remote/activitypub/renderer/index.js'; import renderFollow from '@/remote/activitypub/renderer/follow.js'; import renderUndo from '@/remote/activitypub/renderer/undo.js'; import renderReject from '@/remote/activitypub/renderer/reject.js'; -import { deliver } from '@/queue/index.js'; +import { deliver, webhookDeliver } from '@/queue/index.js'; import Logger from '../logger.js'; import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc.js'; import { User } from '@/models/entities/user.js'; import { Followings, Users, Instances } from '@/models/index.js'; import { instanceChart, perUserFollowingChart } from '@/services/chart/index.js'; +import { getActiveWebhooks } from '@/misc/webhook-cache.js'; const logger = new Logger('following/delete'); export default async function(follower: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']; }, followee: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']; }, silent = false) { - const following = await Followings.findOne({ + const following = await Followings.findOneBy({ followerId: follower.id, followeeId: followee.id, }); @@ -31,9 +32,17 @@ export default async function(follower: { id: User['id']; host: User['host']; ur if (!silent && Users.isLocalUser(follower)) { Users.pack(followee.id, follower, { detail: true, - }).then(packed => { + }).then(async packed => { publishUserEvent(follower.id, 'unfollow', packed); publishMainStream(follower.id, 'unfollow', packed); + + const webhooks = (await getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('unfollow')); + for (const webhook of webhooks) { + webhookDeliver(webhook, { + type: 'unfollow', + user: packed, + }); + } }); } diff --git a/packages/backend/src/services/following/reject.ts b/packages/backend/src/services/following/reject.ts index 3b0cb2ba88..e1744e05be 100644 --- a/packages/backend/src/services/following/reject.ts +++ b/packages/backend/src/services/following/reject.ts @@ -1,14 +1,24 @@ import { renderActivity } from '@/remote/activitypub/renderer/index.js'; import renderFollow from '@/remote/activitypub/renderer/follow.js'; import renderReject from '@/remote/activitypub/renderer/reject.js'; -import { deliver } from '@/queue/index.js'; +import { deliver, webhookDeliver } from '@/queue/index.js'; import { publishMainStream, publishUserEvent } from '@/services/stream.js'; import { User, ILocalUser, IRemoteUser } from '@/models/entities/user.js'; import { Users, FollowRequests, Followings } from '@/models/index.js'; import { decrementFollowing } from './delete.js'; +import { getActiveWebhooks } from '@/misc/webhook-cache.js'; -type Local = ILocalUser | { id: User['id']; host: User['host']; uri: User['host'] }; -type Remote = IRemoteUser; +type Local = ILocalUser | { + id: ILocalUser['id']; + host: ILocalUser['host']; + uri: ILocalUser['uri'] +}; +type Remote = IRemoteUser | { + id: IRemoteUser['id']; + host: IRemoteUser['host']; + uri: IRemoteUser['uri']; + inbox: IRemoteUser['inbox']; +}; type Both = Local | Remote; /** @@ -54,7 +64,7 @@ export async function remoteReject(actor: Remote, follower: Local) { * Remove follow request record */ async function removeFollowRequest(followee: Both, follower: Both) { - const request = await FollowRequests.findOne({ + const request = await FollowRequests.findOneBy({ followeeId: followee.id, followerId: follower.id, }); @@ -68,7 +78,7 @@ async function removeFollowRequest(followee: Both, follower: Both) { * Remove follow record */ async function removeFollow(followee: Both, follower: Both) { - const following = await Followings.findOne({ + const following = await Followings.findOneBy({ followeeId: followee.id, followerId: follower.id, }); @@ -83,7 +93,7 @@ async function removeFollow(followee: Both, follower: Both) { * Deliver Reject to remote */ async function deliverReject(followee: Local, follower: Remote) { - const request = await FollowRequests.findOne({ + const request = await FollowRequests.findOneBy({ followeeId: followee.id, followerId: follower.id, }); @@ -102,4 +112,12 @@ async function publishUnfollow(followee: Both, follower: Local) { publishUserEvent(follower.id, 'unfollow', packedFollowee); publishMainStream(follower.id, 'unfollow', packedFollowee); + + const webhooks = (await getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('unfollow')); + for (const webhook of webhooks) { + webhookDeliver(webhook, { + type: 'unfollow', + user: packedFollowee, + }); + } } diff --git a/packages/backend/src/services/following/requests/accept-all.ts b/packages/backend/src/services/following/requests/accept-all.ts index a240bec8f4..5fbb549e01 100644 --- a/packages/backend/src/services/following/requests/accept-all.ts +++ b/packages/backend/src/services/following/requests/accept-all.ts @@ -7,12 +7,12 @@ import { FollowRequests, Users } from '@/models/index.js'; * @param user ユーザー */ export default async function(user: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']; }) { - const requests = await FollowRequests.find({ + const requests = await FollowRequests.findBy({ followeeId: user.id, }); for (const request of requests) { - const follower = await Users.findOneOrFail(request.followerId); + const follower = await Users.findOneByOrFail({ id: request.followerId }); accept(user, follower); } } diff --git a/packages/backend/src/services/following/requests/accept.ts b/packages/backend/src/services/following/requests/accept.ts index b8113cd1b1..20829f70c7 100644 --- a/packages/backend/src/services/following/requests/accept.ts +++ b/packages/backend/src/services/following/requests/accept.ts @@ -4,12 +4,12 @@ import renderAccept from '@/remote/activitypub/renderer/accept.js'; import { deliver } from '@/queue/index.js'; import { publishMainStream } from '@/services/stream.js'; import { insertFollowingDoc } from '../create.js'; -import { User, ILocalUser } from '@/models/entities/user.js'; +import { User, ILocalUser, CacheableUser } from '@/models/entities/user.js'; import { FollowRequests, Users } from '@/models/index.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; -export default async function(followee: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']; }, follower: User) { - const request = await FollowRequests.findOne({ +export default async function(followee: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']; }, follower: CacheableUser) { + const request = await FollowRequests.findOneBy({ followeeId: followee.id, followerId: follower.id, }); diff --git a/packages/backend/src/services/following/requests/cancel.ts b/packages/backend/src/services/following/requests/cancel.ts index ca9777d38b..56531fa1fd 100644 --- a/packages/backend/src/services/following/requests/cancel.ts +++ b/packages/backend/src/services/following/requests/cancel.ts @@ -16,7 +16,7 @@ export default async function(followee: { id: User['id']; host: User['host']; ur } } - const request = await FollowRequests.findOne({ + const request = await FollowRequests.findOneBy({ followeeId: followee.id, followerId: follower.id, }); diff --git a/packages/backend/src/services/following/requests/create.ts b/packages/backend/src/services/following/requests/create.ts index bca607d7e4..bda2f8f92d 100644 --- a/packages/backend/src/services/following/requests/create.ts +++ b/packages/backend/src/services/following/requests/create.ts @@ -12,11 +12,11 @@ export default async function(follower: { id: User['id']; host: User['host']; ur // check blocking const [blocking, blocked] = await Promise.all([ - Blockings.findOne({ + Blockings.findOneBy({ blockerId: follower.id, blockeeId: followee.id, }), - Blockings.findOne({ + Blockings.findOneBy({ blockerId: followee.id, blockeeId: follower.id, }), @@ -39,7 +39,7 @@ export default async function(follower: { id: User['id']; host: User['host']; ur followeeHost: followee.host, followeeInbox: Users.isRemoteUser(followee) ? followee.inbox : undefined, followeeSharedInbox: Users.isRemoteUser(followee) ? followee.sharedInbox : undefined, - }).then(x => FollowRequests.findOneOrFail(x.identifiers[0])); + }).then(x => FollowRequests.findOneByOrFail(x.identifiers[0])); // Publish receiveRequest event if (Users.isLocalUser(followee)) { diff --git a/packages/backend/src/services/i/pin.ts b/packages/backend/src/services/i/pin.ts index 06d7e79e89..f35392a34b 100644 --- a/packages/backend/src/services/i/pin.ts +++ b/packages/backend/src/services/i/pin.ts @@ -18,7 +18,7 @@ import { deliverToRelays } from '../relay.js'; */ export async function addPinned(user: { id: User['id']; host: User['host']; }, noteId: Note['id']) { // Fetch pinee - const note = await Notes.findOne({ + const note = await Notes.findOneBy({ id: noteId, userId: user.id, }); @@ -27,7 +27,7 @@ export async function addPinned(user: { id: User['id']; host: User['host']; }, n throw new IdentifiableError('70c4e51f-5bea-449c-a030-53bee3cce202', 'No such note.'); } - const pinings = await UserNotePinings.find({ userId: user.id }); + const pinings = await UserNotePinings.findBy({ userId: user.id }); if (pinings.length >= 5) { throw new IdentifiableError('15a018eb-58e5-4da1-93be-330fcc5e4e1a', 'You can not pin notes any more.'); @@ -57,7 +57,7 @@ export async function addPinned(user: { id: User['id']; host: User['host']; }, n */ export async function removePinned(user: { id: User['id']; host: User['host']; }, noteId: Note['id']) { // Fetch unpinee - const note = await Notes.findOne({ + const note = await Notes.findOneBy({ id: noteId, userId: user.id, }); @@ -78,7 +78,7 @@ export async function removePinned(user: { id: User['id']; host: User['host']; } } export async function deliverPinnedChange(userId: User['id'], noteId: Note['id'], isAddition: boolean) { - const user = await Users.findOne(userId); + const user = await Users.findOneBy({ id: userId }); if (user == null) throw new Error('user not found'); if (!Users.isLocalUser(user)) return; diff --git a/packages/backend/src/services/i/update.ts b/packages/backend/src/services/i/update.ts index 1fbaf40df1..27bd38bd39 100644 --- a/packages/backend/src/services/i/update.ts +++ b/packages/backend/src/services/i/update.ts @@ -7,7 +7,7 @@ import { deliverToFollowers } from '@/remote/activitypub/deliver-manager.js'; import { deliverToRelays } from '../relay.js'; export async function publishToFollowers(userId: User['id']) { - const user = await Users.findOne(userId); + const user = await Users.findOneBy({ id: userId }); if (user == null) throw new Error('user not found'); // フォロワーがリモートユーザーかつ投稿者がローカルユーザーならUpdateを配信 diff --git a/packages/backend/src/services/instance-actor.ts b/packages/backend/src/services/instance-actor.ts index e271710488..bddd0355a2 100644 --- a/packages/backend/src/services/instance-actor.ts +++ b/packages/backend/src/services/instance-actor.ts @@ -2,6 +2,7 @@ import { createSystemUser } from './create-system-user.js'; import { ILocalUser } from '@/models/entities/user.js'; import { Users } from '@/models/index.js'; import { Cache } from '@/misc/cache.js'; +import { IsNull } from 'typeorm'; const ACTOR_USERNAME = 'instance.actor' as const; @@ -11,8 +12,8 @@ export async function getInstanceActor(): Promise { const cached = cache.get(null); if (cached) return cached; - const user = await Users.findOne({ - host: null, + const user = await Users.findOneBy({ + host: IsNull(), username: ACTOR_USERNAME, }) as ILocalUser | undefined; diff --git a/packages/backend/src/services/messages/create.ts b/packages/backend/src/services/messages/create.ts index c3908b2552..e5cd5a30d2 100644 --- a/packages/backend/src/services/messages/create.ts +++ b/packages/backend/src/services/messages/create.ts @@ -1,4 +1,4 @@ -import { User } from '@/models/entities/user.js'; +import { CacheableUser, User } from '@/models/entities/user.js'; import { UserGroup } from '@/models/entities/user-group.js'; import { DriveFile } from '@/models/entities/drive-file.js'; import { MessagingMessages, UserGroupJoinings, Mutings, Users } from '@/models/index.js'; @@ -13,7 +13,7 @@ import renderCreate from '@/remote/activitypub/renderer/create.js'; import { renderActivity } from '@/remote/activitypub/renderer/index.js'; import { deliver } from '@/queue/index.js'; -export async function createMessage(user: { id: User['id']; host: User['host']; }, recipientUser: User | undefined, recipientGroup: UserGroup | undefined, text: string | null | undefined, file: DriveFile | null, uri?: string) { +export async function createMessage(user: { id: User['id']; host: User['host']; }, recipientUser: CacheableUser | undefined, recipientGroup: UserGroup | undefined, text: string | null | undefined, file: DriveFile | null, uri?: string) { const message = { id: genId(), createdAt: new Date(), @@ -50,7 +50,7 @@ export async function createMessage(user: { id: User['id']; host: User['host']; publishGroupMessagingStream(recipientGroup.id, 'message', messageObj); // メンバーのストリーム - const joinings = await UserGroupJoinings.find({ userGroupId: recipientGroup.id }); + const joinings = await UserGroupJoinings.findBy({ userGroupId: recipientGroup.id }); for (const joining of joinings) { publishMessagingIndexStream(joining.userId, 'message', messageObj); publishMainStream(joining.userId, 'messagingMessage', messageObj); @@ -59,14 +59,14 @@ export async function createMessage(user: { id: User['id']; host: User['host']; // 2秒経っても(今回作成した)メッセージが既読にならなかったら「未読のメッセージがありますよ」イベントを発行する setTimeout(async () => { - const freshMessage = await MessagingMessages.findOne(message.id); + const freshMessage = await MessagingMessages.findOneBy({ id: message.id }); if (freshMessage == null) return; // メッセージが削除されている場合もある if (recipientUser && Users.isLocalUser(recipientUser)) { if (freshMessage.isRead) return; // 既読 //#region ただしミュートされているなら発行しない - const mute = await Mutings.find({ + const mute = await Mutings.findBy({ muterId: recipientUser.id, }); if (mute.map(m => m.muteeId).includes(user.id)) return; @@ -75,7 +75,7 @@ export async function createMessage(user: { id: User['id']; host: User['host']; publishMainStream(recipientUser.id, 'unreadMessagingMessage', messageObj); pushNotification(recipientUser.id, 'unreadMessagingMessage', messageObj); } else if (recipientGroup) { - const joinings = await UserGroupJoinings.find({ userGroupId: recipientGroup.id, userId: Not(user.id) }); + const joinings = await UserGroupJoinings.findBy({ userGroupId: recipientGroup.id, userId: Not(user.id) }); for (const joining of joinings) { if (freshMessage.reads.includes(joining.userId)) return; // 既読 publishMainStream(joining.userId, 'unreadMessagingMessage', messageObj); diff --git a/packages/backend/src/services/messages/delete.ts b/packages/backend/src/services/messages/delete.ts index 82eb6cb21c..1e7ce1981c 100644 --- a/packages/backend/src/services/messages/delete.ts +++ b/packages/backend/src/services/messages/delete.ts @@ -14,8 +14,8 @@ export async function deleteMessage(message: MessagingMessage) { async function postDeleteMessage(message: MessagingMessage) { if (message.recipientId) { - const user = await Users.findOneOrFail(message.userId); - const recipient = await Users.findOneOrFail(message.recipientId); + const user = await Users.findOneByOrFail({ id: message.userId }); + const recipient = await Users.findOneByOrFail({ id: message.recipientId }); if (Users.isLocalUser(user)) publishMessagingStream(message.userId, message.recipientId, 'deleted', message.id); if (Users.isLocalUser(recipient)) publishMessagingStream(message.recipientId, message.userId, 'deleted', message.id); diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts index 8c5f133628..6f373aaf45 100644 --- a/packages/backend/src/services/note/create.ts +++ b/packages/backend/src/services/note/create.ts @@ -19,7 +19,7 @@ import { Note, IMentionedRemoteUsers } from '@/models/entities/note.js'; import { Mutings, Users, NoteWatchings, Notes, Instances, UserProfiles, Antennas, Followings, MutedNotes, Channels, ChannelFollowings, Blockings, NoteThreadMutings } from '@/models/index.js'; import { DriveFile } from '@/models/entities/drive-file.js'; import { App } from '@/models/entities/app.js'; -import { Not, getConnection, In } from 'typeorm'; +import { Not, In } from 'typeorm'; import { User, ILocalUser, IRemoteUser } from '@/models/entities/user.js'; import { genId } from '@/misc/gen-id.js'; import { notesChart, perUserNotesChart, activeUsersChart, instanceChart } from '@/services/chart/index.js'; @@ -35,6 +35,13 @@ import { Channel } from '@/models/entities/channel.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { getAntennas } from '@/misc/antenna-cache.js'; import { endedPollNotificationQueue } from '@/queue/queues.js'; +import { webhookDeliver } from '@/queue/index.js'; +import { Cache } from '@/misc/cache.js'; +import { UserProfile } from '@/models/entities/user-profile.js'; +import { db } from '@/db/postgre.js'; +import { getActiveWebhooks } from '@/misc/webhook-cache.js'; + +const mutedWordsCache = new Cache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(1000 * 60 * 5); type NotificationType = 'reply' | 'renote' | 'quote' | 'mention'; @@ -74,7 +81,7 @@ class NotificationManager { public async deliver() { for (const x of this.queue) { // ミュート情報を取得 - const mentioneeMutes = await Mutings.find({ + const mentioneeMutes = await Mutings.findBy({ muterId: x.target, }); @@ -91,6 +98,13 @@ class NotificationManager { } } +type MinimumUser = { + id: User['id']; + host: User['host']; + username: User['username']; + uri: User['uri']; +}; + type Option = { createdAt?: Date | null; name?: string | null; @@ -102,9 +116,9 @@ type Option = { localOnly?: boolean | null; cw?: string | null; visibility?: string; - visibleUsers?: User[] | null; + visibleUsers?: MinimumUser[] | null; channel?: Channel | null; - apMentions?: User[] | null; + apMentions?: MinimumUser[] | null; apHashtags?: string[] | null; apEmojis?: string[] | null; uri?: string | null; @@ -117,7 +131,7 @@ export default async (user: { id: User['id']; username: User['username']; host: // (クライアントサイドでやっても良い処理だと思うけどとりあえずサーバーサイドで) if (data.reply && data.channel && data.reply.channelId !== data.channel.id) { if (data.reply.channelId) { - data.channel = await Channels.findOne(data.reply.channelId); + data.channel = await Channels.findOneBy({ id: data.reply.channelId }); } else { data.channel = null; } @@ -126,7 +140,7 @@ export default async (user: { id: User['id']; username: User['username']; host: // チャンネル内にリプライしたら対象のスコープに合わせる // (クライアントサイドでやっても良い処理だと思うけどとりあえずサーバーサイドで) if (data.reply && (data.channel == null) && data.reply.channelId) { - data.channel = await Channels.findOne(data.reply.channelId); + data.channel = await Channels.findOneBy({ id: data.reply.channelId }); } if (data.createdAt == null) data.createdAt = new Date(); @@ -199,7 +213,7 @@ export default async (user: { id: User['id']; username: User['username']; host: tags = tags.filter(tag => Array.from(tag || '').length <= 128).splice(0, 32); if (data.reply && (user.id !== data.reply.userId) && !mentionedUsers.some(u => u.id === data.reply!.userId)) { - mentionedUsers.push(await Users.findOneOrFail(data.reply.userId)); + mentionedUsers.push(await Users.findOneByOrFail({ id: data.reply!.userId })); } if (data.visibility === 'specified') { @@ -212,7 +226,7 @@ export default async (user: { id: User['id']; username: User['username']; host: } if (data.reply && !data.visibleUsers.some(x => x.id === data.reply!.userId)) { - data.visibleUsers.push(await Users.findOneOrFail(data.reply.userId)); + data.visibleUsers.push(await Users.findOneByOrFail({ id: data.reply!.userId })); } } @@ -241,10 +255,12 @@ export default async (user: { id: User['id']; username: User['username']; host: incNotesCountOfUser(user); // Word mute - // TODO: cache - UserProfiles.find({ - enableWordMute: true, - }).then(us => { + mutedWordsCache.fetch(null, () => UserProfiles.find({ + where: { + enableWordMute: true, + }, + select: ['userId', 'mutedWords'], + })).then(us => { for (const u of us) { checkWordMute(note, { id: u.userId }, u.mutedWords).then(shouldMute => { if (shouldMute) { @@ -260,25 +276,17 @@ export default async (user: { id: User['id']; username: User['username']; host: }); // Antenna - Followings.createQueryBuilder('following') - .andWhere(`following.followeeId = :userId`, { userId: note.userId }) - .getMany() - .then(async followings => { - const blockings = await Blockings.find({ blockerId: user.id }); // TODO: キャッシュしたい - const followers = followings.map(f => f.followerId); - for (const antenna of (await getAntennas())) { - if (blockings.some(blocking => blocking.blockeeId === antenna.userId)) continue; // この処理は checkHitAntenna 内でやるようにしてもいいかも - checkHitAntenna(antenna, note, user, followers).then(hit => { - if (hit) { - addNoteToAntenna(antenna, note, user); - } - }); + for (const antenna of (await getAntennas())) { + checkHitAntenna(antenna, note, user).then(hit => { + if (hit) { + addNoteToAntenna(antenna, note, user); } }); + } // Channel if (note.channelId) { - ChannelFollowings.find({ followeeId: note.channelId }).then(followings => { + ChannelFollowings.findBy({ followeeId: note.channelId }).then(followings => { for (const following of followings) { insertNoteUnread(following.followerId, note, { isSpecified: false, @@ -339,6 +347,16 @@ export default async (user: { id: User['id']; username: User['username']; host: publishNotesStream(noteObj); + getActiveWebhooks().then(webhooks => { + webhooks = webhooks.filter(x => x.userId === user.id && x.on.includes('note')); + for (const webhook of webhooks) { + webhookDeliver(webhook, { + type: 'note', + note: noteObj, + }); + } + }); + const nm = new NotificationManager(user, note); const nmRelatedPromises = []; @@ -351,7 +369,7 @@ export default async (user: { id: User['id']; username: User['username']; host: // 通知 if (data.reply.userHost === null) { - const threadMuted = await NoteThreadMutings.findOne({ + const threadMuted = await NoteThreadMutings.findOneBy({ userId: data.reply.userId, threadId: data.reply.threadId || data.reply.id, }); @@ -359,6 +377,14 @@ export default async (user: { id: User['id']; username: User['username']; host: if (!threadMuted) { nm.push(data.reply.userId, 'reply'); publishMainStream(data.reply.userId, 'reply', noteObj); + + const webhooks = (await getActiveWebhooks()).filter(x => x.userId === data.reply!.userId && x.on.includes('reply')); + for (const webhook of webhooks) { + webhookDeliver(webhook, { + type: 'reply', + note: noteObj, + }); + } } } } @@ -378,6 +404,14 @@ export default async (user: { id: User['id']; username: User['username']; host: // Publish event if ((user.id !== data.renote.userId) && data.renote.userHost === null) { publishMainStream(data.renote.userId, 'renote', noteObj); + + const webhooks = (await getActiveWebhooks()).filter(x => x.userId === data.renote!.userId && x.on.includes('renote')); + for (const webhook of webhooks) { + webhookDeliver(webhook, { + type: 'renote', + note: noteObj, + }); + } } } @@ -398,13 +432,13 @@ export default async (user: { id: User['id']; username: User['username']; host: // 投稿がリプライかつ投稿者がローカルユーザーかつリプライ先の投稿の投稿者がリモートユーザーなら配送 if (data.reply && data.reply.userHost !== null) { - const u = await Users.findOne(data.reply.userId); + const u = await Users.findOneBy({ id: data.reply.userId }); if (u && Users.isRemoteUser(u)) dm.addDirectRecipe(u); } // 投稿がRenoteかつ投稿者がローカルユーザーかつRenote元の投稿の投稿者がリモートユーザーなら配送 if (data.renote && data.renote.userHost !== null) { - const u = await Users.findOne(data.renote.userId); + const u = await Users.findOneBy({ id: data.renote.userId }); if (u && Users.isRemoteUser(u)) dm.addDirectRecipe(u); } @@ -429,7 +463,7 @@ export default async (user: { id: User['id']; username: User['username']; host: lastNotedAt: new Date(), }); - Notes.count({ + Notes.countBy({ userId: user.id, channelId: data.channel.id, }).then(count => { @@ -465,7 +499,7 @@ function incRenoteCount(renote: Note) { .execute(); } -async function insertNote(user: { id: User['id']; host: User['host']; }, data: Option, tags: string[], emojis: string[], mentionedUsers: User[]) { +async function insertNote(user: { id: User['id']; host: User['host']; }, data: Option, tags: string[], emojis: string[], mentionedUsers: MinimumUser[]) { const insert = new Note({ id: genId(data.createdAt!), createdAt: data.createdAt!, @@ -509,7 +543,7 @@ async function insertNote(user: { id: User['id']; host: User['host']; }, data: O // Append mentions data if (mentionedUsers.length > 0) { insert.mentions = mentionedUsers.map(u => u.id); - const profiles = await UserProfiles.find({ userId: In(insert.mentions) }); + const profiles = await UserProfiles.findBy({ userId: In(insert.mentions) }); insert.mentionedRemoteUsers = JSON.stringify(mentionedUsers.filter(u => Users.isRemoteUser(u)).map(u => { const profile = profiles.find(p => p.userId === u.id); const url = profile != null ? profile.url : null; @@ -526,7 +560,7 @@ async function insertNote(user: { id: User['id']; host: User['host']; }, data: O try { if (insert.hasPoll) { // Start transaction - await getConnection().transaction(async transactionalEntityManager => { + await db.transaction(async transactionalEntityManager => { await transactionalEntityManager.insert(Note, insert); const poll = new Poll({ @@ -576,7 +610,7 @@ function index(note: Note) { } async function notifyToWatchersOfRenotee(renote: Note, user: { id: User['id']; }, nm: NotificationManager, type: NotificationType) { - const watchers = await NoteWatchings.find({ + const watchers = await NoteWatchings.findBy({ noteId: renote.id, userId: Not(user.id), }); @@ -587,7 +621,7 @@ async function notifyToWatchersOfRenotee(renote: Note, user: { id: User['id']; } } async function notifyToWatchersOfReplyee(reply: Note, user: { id: User['id']; }, nm: NotificationManager) { - const watchers = await NoteWatchings.find({ + const watchers = await NoteWatchings.findBy({ noteId: reply.id, userId: Not(user.id), }); @@ -597,9 +631,9 @@ async function notifyToWatchersOfReplyee(reply: Note, user: { id: User['id']; }, } } -async function createMentionedEvents(mentionedUsers: User[], note: Note, nm: NotificationManager) { +async function createMentionedEvents(mentionedUsers: MinimumUser[], note: Note, nm: NotificationManager) { for (const u of mentionedUsers.filter(u => Users.isLocalUser(u))) { - const threadMuted = await NoteThreadMutings.findOne({ + const threadMuted = await NoteThreadMutings.findOneBy({ userId: u.id, threadId: note.threadId || note.id, }); @@ -614,6 +648,14 @@ async function createMentionedEvents(mentionedUsers: User[], note: Note, nm: Not publishMainStream(u.id, 'mention', detailPackedNote); + const webhooks = (await getActiveWebhooks()).filter(x => x.userId === u.id && x.on.includes('mention')); + for (const webhook of webhooks) { + webhookDeliver(webhook, { + type: 'mention', + note: detailPackedNote, + }); + } + // Create notification nm.push(u.id, 'mention'); } diff --git a/packages/backend/src/services/note/delete.ts b/packages/backend/src/services/note/delete.ts index cf23656f8f..ffd609dd84 100644 --- a/packages/backend/src/services/note/delete.ts +++ b/packages/backend/src/services/note/delete.ts @@ -20,7 +20,7 @@ import { Brackets, In } from 'typeorm'; * @param user 投稿者 * @param note 投稿 */ -export default async function(user: User, note: Note, quiet = false) { +export default async function(user: { id: User['id']; uri: User['uri']; host: User['host']; }, note: Note, quiet = false) { const deletedAt = new Date(); // この投稿を除く指定したユーザーによる指定したノートのリノートが存在しないとき @@ -29,6 +29,10 @@ export default async function(user: User, note: Note, quiet = false) { Notes.decrement({ id: note.renoteId }, 'score', 1); } + if (note.replyId) { + await Notes.decrement({ id: note.replyId }, 'repliesCount', 1); + } + if (!quiet) { publishNoteStream(note.id, 'deleted', { deletedAt: deletedAt, @@ -36,11 +40,11 @@ export default async function(user: User, note: Note, quiet = false) { //#region ローカルの投稿なら削除アクティビティを配送 if (Users.isLocalUser(user) && !note.localOnly) { - let renote: Note | undefined; + let renote: Note | null; // if deletd note is renote if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length === 0)) { - renote = await Notes.findOne({ + renote = await Notes.findOneBy({ id: note.renoteId, }); } @@ -127,7 +131,7 @@ async function getMentionedRemoteUsers(note: Note) { }) as IRemoteUser[]; } -async function deliverToConcerned(user: ILocalUser, note: Note, content: any) { +async function deliverToConcerned(user: { id: ILocalUser['id']; host: null; }, note: Note, content: any) { deliverToFollowers(user, content); deliverToRelays(user, content); const remoteUsers = await getMentionedRemoteUsers(note); diff --git a/packages/backend/src/services/note/polls/update.ts b/packages/backend/src/services/note/polls/update.ts index 88baf16b64..43ca3eff4d 100644 --- a/packages/backend/src/services/note/polls/update.ts +++ b/packages/backend/src/services/note/polls/update.ts @@ -7,10 +7,10 @@ import { deliverToFollowers } from '@/remote/activitypub/deliver-manager.js'; import { deliverToRelays } from '../../relay.js'; export async function deliverQuestionUpdate(noteId: Note['id']) { - const note = await Notes.findOne(noteId); + const note = await Notes.findOneBy({ id: noteId }); if (note == null) throw new Error('note not found'); - const user = await Users.findOne(note.userId); + const user = await Users.findOneBy({ id: note.userId }); if (user == null) throw new Error('note not found'); if (Users.isLocalUser(user)) { diff --git a/packages/backend/src/services/note/polls/vote.ts b/packages/backend/src/services/note/polls/vote.ts index 9b83b1953f..84d98769d9 100644 --- a/packages/backend/src/services/note/polls/vote.ts +++ b/packages/backend/src/services/note/polls/vote.ts @@ -1,13 +1,13 @@ import { publishNoteStream } from '@/services/stream.js'; -import { User } from '@/models/entities/user.js'; +import { CacheableUser, User } from '@/models/entities/user.js'; import { Note } from '@/models/entities/note.js'; import { PollVotes, NoteWatchings, Polls, Blockings } from '@/models/index.js'; import { Not } from 'typeorm'; import { genId } from '@/misc/gen-id.js'; import { createNotification } from '../../create-notification.js'; -export default async function(user: User, note: Note, choice: number) { - const poll = await Polls.findOne(note.id); +export default async function(user: CacheableUser, note: Note, choice: number) { + const poll = await Polls.findOneBy({ noteId: note.id }); if (poll == null) throw new Error('poll not found'); @@ -16,7 +16,7 @@ export default async function(user: User, note: Note, choice: number) { // Check blocking if (note.userId !== user.id) { - const block = await Blockings.findOne({ + const block = await Blockings.findOneBy({ blockerId: note.userId, blockeeId: user.id, }); @@ -26,7 +26,7 @@ export default async function(user: User, note: Note, choice: number) { } // if already voted - const exist = await PollVotes.find({ + const exist = await PollVotes.findBy({ noteId: note.id, userId: user.id, }); @@ -65,7 +65,7 @@ export default async function(user: User, note: Note, choice: number) { }); // Fetch watchers - NoteWatchings.find({ + NoteWatchings.findBy({ noteId: note.id, userId: Not(user.id), }) diff --git a/packages/backend/src/services/note/reaction/create.ts b/packages/backend/src/services/note/reaction/create.ts index 236aa79938..5a0948bca9 100644 --- a/packages/backend/src/services/note/reaction/create.ts +++ b/packages/backend/src/services/note/reaction/create.ts @@ -6,7 +6,7 @@ import { toDbReaction, decodeReaction } from '@/misc/reaction-lib.js'; import { User, IRemoteUser } from '@/models/entities/user.js'; import { Note } from '@/models/entities/note.js'; import { NoteReactions, Users, NoteWatchings, Notes, Emojis, Blockings } from '@/models/index.js'; -import { Not } from 'typeorm'; +import { IsNull, Not } from 'typeorm'; import { perUserReactionsChart } from '@/services/chart/index.js'; import { genId } from '@/misc/gen-id.js'; import { createNotification } from '../../create-notification.js'; @@ -18,7 +18,7 @@ import { IdentifiableError } from '@/misc/identifiable-error.js'; export default async (user: { id: User['id']; host: User['host']; }, note: Note, reaction?: string) => { // Check blocking if (note.userId !== user.id) { - const block = await Blockings.findOne({ + const block = await Blockings.findOneBy({ blockerId: note.userId, blockeeId: user.id, }); @@ -43,7 +43,7 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note, await NoteReactions.insert(record); } catch (e) { if (isDuplicateKeyValueError(e)) { - const exists = await NoteReactions.findOneOrFail({ + const exists = await NoteReactions.findOneByOrFail({ noteId: note.id, userId: user.id, }); @@ -79,7 +79,7 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note, const emoji = await Emojis.findOne({ where: { name: decodedReaction.name, - host: decodedReaction.host, + host: decodedReaction.host ?? IsNull(), }, select: ['name', 'host', 'originalUrl', 'publicUrl'], }); @@ -103,7 +103,7 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note, } // Fetch watchers - NoteWatchings.find({ + NoteWatchings.findBy({ noteId: note.id, userId: Not(user.id), }).then(watchers => { @@ -121,10 +121,19 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note, const content = renderActivity(await renderLike(record, note)); const dm = new DeliverManager(user, content); if (note.userHost !== null) { - const reactee = await Users.findOne(note.userId); + const reactee = await Users.findOneBy({ id: note.userId }); dm.addDirectRecipe(reactee as IRemoteUser); } - dm.addFollowersRecipe(); + + if (['public', 'home', 'followers'].includes(note.visibility)) { + dm.addFollowersRecipe(); + } else if (note.visibility === 'specified') { + const visibleUsers = await Promise.all(note.visibleUserIds.map(id => Users.findOneBy({ id }))); + for (const u of visibleUsers.filter(u => u && Users.isRemoteUser(u))) { + dm.addDirectRecipe(u as IRemoteUser); + } + } + dm.execute(); } //#endregion diff --git a/packages/backend/src/services/note/reaction/delete.ts b/packages/backend/src/services/note/reaction/delete.ts index 62b00f56fd..a7cbcb1c17 100644 --- a/packages/backend/src/services/note/reaction/delete.ts +++ b/packages/backend/src/services/note/reaction/delete.ts @@ -11,7 +11,7 @@ import { decodeReaction } from '@/misc/reaction-lib.js'; export default async (user: { id: User['id']; host: User['host']; }, note: Note) => { // if already unreacted - const exist = await NoteReactions.findOne({ + const exist = await NoteReactions.findOneBy({ noteId: note.id, userId: user.id, }); @@ -48,7 +48,7 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note) const content = renderActivity(renderUndo(await renderLike(exist, note), user)); const dm = new DeliverManager(user, content); if (note.userHost !== null) { - const reactee = await Users.findOne(note.userId); + const reactee = await Users.findOneBy({ id: note.userId }); dm.addDirectRecipe(reactee as IRemoteUser); } dm.addFollowersRecipe(); diff --git a/packages/backend/src/services/note/read.ts b/packages/backend/src/services/note/read.ts index 28827c5965..915a9e9eef 100644 --- a/packages/backend/src/services/note/read.ts +++ b/packages/backend/src/services/note/read.ts @@ -68,7 +68,7 @@ export default async function( // TODO: ↓まとめてクエリしたい - NoteUnreads.count({ + NoteUnreads.countBy({ userId: userId, isMentioned: true, }).then(mentionsCount => { @@ -78,7 +78,7 @@ export default async function( } }); - NoteUnreads.count({ + NoteUnreads.countBy({ userId: userId, isSpecified: true, }).then(specifiedCount => { @@ -88,7 +88,7 @@ export default async function( } }); - NoteUnreads.count({ + NoteUnreads.countBy({ userId: userId, noteChannelId: Not(IsNull()), }).then(channelNoteCount => { @@ -113,7 +113,7 @@ export default async function( // TODO: まとめてクエリしたい for (const antenna of myAntennas) { - const count = await AntennaNotes.count({ + const count = await AntennaNotes.countBy({ antennaId: antenna.id, read: false, }); diff --git a/packages/backend/src/services/note/unread.ts b/packages/backend/src/services/note/unread.ts index ef95dc7e8c..d9ed711e03 100644 --- a/packages/backend/src/services/note/unread.ts +++ b/packages/backend/src/services/note/unread.ts @@ -11,14 +11,14 @@ export async function insertNoteUnread(userId: User['id'], note: Note, params: { }) { //#region ミュートしているなら無視 // TODO: 現在の仕様ではChannelにミュートは適用されないのでよしなにケアする - const mute = await Mutings.find({ + const mute = await Mutings.findBy({ muterId: userId, }); if (mute.map(m => m.muteeId).includes(note.userId)) return; //#endregion // スレッドミュート - const threadMute = await NoteThreadMutings.findOne({ + const threadMute = await NoteThreadMutings.findOneBy({ userId: userId, threadId: note.threadId || note.id, }); @@ -38,7 +38,7 @@ export async function insertNoteUnread(userId: User['id'], note: Note, params: { // 2秒経っても既読にならなかったら「未読の投稿がありますよ」イベントを発行する setTimeout(async () => { - const exist = await NoteUnreads.findOne(unread.id); + const exist = await NoteUnreads.findOneBy({ id: unread.id }); if (exist == null) return; diff --git a/packages/backend/src/services/push-notification.ts b/packages/backend/src/services/push-notification.ts index 8d5c09e6ae..41122c92e8 100644 --- a/packages/backend/src/services/push-notification.ts +++ b/packages/backend/src/services/push-notification.ts @@ -41,7 +41,7 @@ export default async function(userId: string, type: notificationType, body: noti meta.swPrivateKey); // Fetch - const subscriptions = await SwSubscriptions.find({ + const subscriptions = await SwSubscriptions.findBy({ userId: userId, }); diff --git a/packages/backend/src/services/register-or-fetch-instance-doc.ts b/packages/backend/src/services/register-or-fetch-instance-doc.ts index 152930dbd9..df7d125d0b 100644 --- a/packages/backend/src/services/register-or-fetch-instance-doc.ts +++ b/packages/backend/src/services/register-or-fetch-instance-doc.ts @@ -12,7 +12,7 @@ export async function registerOrFetchInstanceDoc(host: string): Promise Instances.findOneOrFail(x.identifiers[0])); + }).then(x => Instances.findOneByOrFail(x.identifiers[0])); cache.set(host, i); return i; diff --git a/packages/backend/src/services/relay.ts b/packages/backend/src/services/relay.ts index 6f0da503fc..1ab45588da 100644 --- a/packages/backend/src/services/relay.ts +++ b/packages/backend/src/services/relay.ts @@ -6,12 +6,17 @@ import { deliver } from '@/queue/index.js'; import { ILocalUser, User } from '@/models/entities/user.js'; import { Users, Relays } from '@/models/index.js'; import { genId } from '@/misc/gen-id.js'; +import { Cache } from '@/misc/cache.js'; +import { Relay } from '@/models/entities/relay.js'; +import { IsNull } from 'typeorm'; const ACTOR_USERNAME = 'relay.actor' as const; +const relaysCache = new Cache(1000 * 60 * 10); + export async function getRelayActor(): Promise { - const user = await Users.findOne({ - host: null, + const user = await Users.findOneBy({ + host: IsNull(), username: ACTOR_USERNAME, }); @@ -26,7 +31,7 @@ export async function addRelay(inbox: string) { id: genId(), inbox, status: 'requesting', - }).then(x => Relays.findOneOrFail(x.identifiers[0])); + }).then(x => Relays.findOneByOrFail(x.identifiers[0])); const relayActor = await getRelayActor(); const follow = await renderFollowRelay(relay, relayActor); @@ -37,7 +42,7 @@ export async function addRelay(inbox: string) { } export async function removeRelay(inbox: string) { - const relay = await Relays.findOne({ + const relay = await Relays.findOneBy({ inbox, }); @@ -78,9 +83,9 @@ export async function relayRejected(id: string) { export async function deliverToRelays(user: { id: User['id']; host: null; }, activity: any) { if (activity == null) return; - const relays = await Relays.find({ + const relays = await relaysCache.fetch(null, () => Relays.findBy({ status: 'accepted', - }); + })); if (relays.length === 0) return; const copy = JSON.parse(JSON.stringify(activity)); diff --git a/packages/backend/src/services/send-email-notification.ts b/packages/backend/src/services/send-email-notification.ts index debaf3476d..4a2f94b425 100644 --- a/packages/backend/src/services/send-email-notification.ts +++ b/packages/backend/src/services/send-email-notification.ts @@ -10,7 +10,7 @@ import * as Acct from '@/misc/acct.js'; async function follow(userId: User['id'], follower: User) { /* - const userProfile = await UserProfiles.findOneOrFail({ userId: userId }); + const userProfile = await UserProfiles.findOneByOrFail({ userId: userId }); if (!userProfile.email || !userProfile.emailNotificationTypes.includes('follow')) return; const locale = locales[userProfile.lang || 'ja-JP']; const i18n = new I18n(locale); @@ -21,7 +21,7 @@ async function follow(userId: User['id'], follower: User) { async function receiveFollowRequest(userId: User['id'], follower: User) { /* - const userProfile = await UserProfiles.findOneOrFail({ userId: userId }); + const userProfile = await UserProfiles.findOneByOrFail({ userId: userId }); if (!userProfile.email || !userProfile.emailNotificationTypes.includes('receiveFollowRequest')) return; const locale = locales[userProfile.lang || 'ja-JP']; const i18n = new I18n(locale); diff --git a/packages/backend/src/services/suspend-user.ts b/packages/backend/src/services/suspend-user.ts index 033311a3cc..e96b06a351 100644 --- a/packages/backend/src/services/suspend-user.ts +++ b/packages/backend/src/services/suspend-user.ts @@ -5,8 +5,11 @@ import config from '@/config/index.js'; import { User } from '@/models/entities/user.js'; import { Users, Followings } from '@/models/index.js'; import { Not, IsNull } from 'typeorm'; +import { publishInternalEvent } from '@/services/stream.js'; export async function doPostSuspend(user: { id: User['id']; host: User['host'] }) { + publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: true }); + if (Users.isLocalUser(user)) { // 知り得る全SharedInboxにDelete配信 const content = renderActivity(renderDelete(`${config.url}/users/${user.id}`, user)); diff --git a/packages/backend/src/services/unsuspend-user.ts b/packages/backend/src/services/unsuspend-user.ts index 3be081d0ed..44a0d01ca2 100644 --- a/packages/backend/src/services/unsuspend-user.ts +++ b/packages/backend/src/services/unsuspend-user.ts @@ -6,8 +6,11 @@ import config from '@/config/index.js'; import { User } from '@/models/entities/user.js'; import { Users, Followings } from '@/models/index.js'; import { Not, IsNull } from 'typeorm'; +import { publishInternalEvent } from '@/services/stream.js'; export async function doPostUnsuspend(user: User) { + publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: false }); + if (Users.isLocalUser(user)) { // 知り得る全SharedInboxにUndo Delete配信 const content = renderActivity(renderUndo(renderDelete(`${config.url}/users/${user.id}`, user), user)); diff --git a/packages/backend/src/services/update-hashtag.ts b/packages/backend/src/services/update-hashtag.ts index b6fb38bc5a..23b210b7a9 100644 --- a/packages/backend/src/services/update-hashtag.ts +++ b/packages/backend/src/services/update-hashtag.ts @@ -24,7 +24,7 @@ export async function updateUsertags(user: User, tags: string[]) { export async function updateHashtag(user: { id: User['id']; host: User['host']; }, tag: string, isUserAttached = false, inc = true) { tag = normalizeForSearch(tag); - const index = await Hashtags.findOne({ name: tag }); + const index = await Hashtags.findOneBy({ name: tag }); if (index == null && !inc) return; diff --git a/packages/backend/src/services/user-cache.ts b/packages/backend/src/services/user-cache.ts new file mode 100644 index 0000000000..407301f2fd --- /dev/null +++ b/packages/backend/src/services/user-cache.ts @@ -0,0 +1,44 @@ +import { CacheableLocalUser, CacheableUser, ILocalUser, User } from '@/models/entities/user.js'; +import { Users } from '@/models/index.js'; +import { Cache } from '@/misc/cache.js'; +import { subsdcriber } from '@/db/redis.js'; + +export const userByIdCache = new Cache(Infinity); +export const localUserByNativeTokenCache = new Cache(Infinity); +export const localUserByIdCache = new Cache(Infinity); +export const uriPersonCache = new Cache(Infinity); + +subsdcriber.on('message', async (_, data) => { + const obj = JSON.parse(data); + + if (obj.channel === 'internal') { + const { type, body } = obj.message; + switch (type) { + case 'userChangeSuspendedState': + case 'userChangeSilencedState': + case 'userChangeModeratorState': + case 'remoteUserUpdated': { + const user = await Users.findOneByOrFail({ id: body.id }); + userByIdCache.set(user.id, user); + for (const [k, v] of uriPersonCache.cache.entries()) { + if (v.value?.id === user.id) { + uriPersonCache.set(k, user); + } + } + if (Users.isLocalUser(user)) { + localUserByNativeTokenCache.set(user.token, user); + localUserByIdCache.set(user.id, user); + } + break; + } + case 'userTokenRegenerated': { + const user = await Users.findOneByOrFail({ id: body.id }) as ILocalUser; + localUserByNativeTokenCache.delete(body.oldToken); + localUserByNativeTokenCache.set(body.newToken, user); + break; + } + default: + break; + } + } +}); diff --git a/packages/backend/src/services/validate-email-for-account.ts b/packages/backend/src/services/validate-email-for-account.ts index 3c49d37eef..132168fb31 100644 --- a/packages/backend/src/services/validate-email-for-account.ts +++ b/packages/backend/src/services/validate-email-for-account.ts @@ -1,11 +1,11 @@ -import validateEmail from 'deep-email-validator'; +import { validate as validateEmail } from 'deep-email-validator'; import { UserProfiles } from '@/models/index.js'; export async function validateEmailForAccount(emailAddress: string): Promise<{ available: boolean; reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp'; }> { - const exist = await UserProfiles.count({ + const exist = await UserProfiles.countBy({ emailVerified: true, email: emailAddress, }); diff --git a/packages/backend/src/tools/accept-migration.ts b/packages/backend/src/tools/accept-migration.ts deleted file mode 100644 index adbfcdadf7..0000000000 --- a/packages/backend/src/tools/accept-migration.ts +++ /dev/null @@ -1,25 +0,0 @@ -// ex) node built/tools/accept-migration Yo 1000000000001 - -import { createConnection } from 'typeorm'; -import config from '@/config/index.js'; - -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/packages/backend/src/tools/demote-admin.ts b/packages/backend/src/tools/demote-admin.ts deleted file mode 100644 index 7f67222473..0000000000 --- a/packages/backend/src/tools/demote-admin.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { initDb } from '../db/postgre.js'; - -async function main(username: string) { - if (!username) throw `username required`; - username = username.replace(/^@/, ''); - - await initDb(); - const { Users } = await import('@/models/index'); - - const res = await Users.update({ - usernameLower: username.toLowerCase(), - host: null, - }, { - isAdmin: false, - }); - - if (res.affected !== 1) { - throw 'Failed'; - } -} - -const args = process.argv.slice(2); - -main(args[0]).then(() => { - console.log('Success'); - process.exit(0); -}).catch(e => { - console.error(`Error: ${e.message || e}`); - process.exit(1); -}); diff --git a/packages/backend/src/tools/mark-admin.ts b/packages/backend/src/tools/mark-admin.ts deleted file mode 100644 index 630179e7ab..0000000000 --- a/packages/backend/src/tools/mark-admin.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { initDb } from '../db/postgre.js'; - -async function main(username: string) { - if (!username) throw `username required`; - username = username.replace(/^@/, ''); - - await initDb(); - const { Users } = await import('@/models/index'); - - const res = await Users.update({ - usernameLower: username.toLowerCase(), - host: null, - }, { - isAdmin: true, - }); - - if (res.affected !== 1) { - throw 'Failed'; - } -} - -const args = process.argv.slice(2); - -main(args[0]).then(() => { - console.log('Success'); - process.exit(0); -}).catch(e => { - console.error(`Error: ${e.message || e}`); - process.exit(1); -}); diff --git a/packages/backend/src/tools/refresh-question.ts b/packages/backend/src/tools/refresh-question.ts deleted file mode 100644 index 0111a2257a..0000000000 --- a/packages/backend/src/tools/refresh-question.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { initDb } from '@/db/postgre.js'; - -async function main(uri: string): Promise { - await initDb(); - const { updateQuestion } = await import('@/remote/activitypub/models/question'); - - return await updateQuestion(uri); -} - -const args = process.argv.slice(2); -const uri = args[0]; - -main(uri).then(result => { - console.log(`Done: ${result}`); -}).catch(e => { - console.warn(e); -}); diff --git a/packages/backend/src/tools/resync-remote-user.ts b/packages/backend/src/tools/resync-remote-user.ts deleted file mode 100644 index 8c02ef7efc..0000000000 --- a/packages/backend/src/tools/resync-remote-user.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { initDb } from '@/db/postgre.js'; -import * as Acct from '@/misc/acct.js'; - -async function main(acct: string): Promise { - await initDb(); - const { resolveUser } = await import('@/remote/resolve-user'); - - const { username, host } = Acct.parse(acct); - await resolveUser(username, host, {}, true); -} - -// get args -const args = process.argv.slice(2); -let acct = args[0]; - -// normalize args -acct = acct.replace(/^@/, ''); - -// check args -if (!acct.match(/^\w+@\w/)) { - throw `Invalid acct format. Valid format are user@host`; -} - -console.log(`resync ${acct}`); - -main(acct).then(() => { - console.log('Done'); -}).catch(e => { - console.warn(e); -}); diff --git a/packages/backend/src/tools/show-signin-history.ts b/packages/backend/src/tools/show-signin-history.ts deleted file mode 100644 index c3388fd1b6..0000000000 --- a/packages/backend/src/tools/show-signin-history.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { initDb } from '@/db/postgre.js'; - -// node built/tools/show-signin-history username -// => {Success} {Date} {IPAddrsss} - -// node built/tools/show-signin-history username user-agent,x-forwarded-for -// with user-agent and x-forwarded-for - -// node built/tools/show-signin-history username all -// with full request headers - -async function main(username: string, headers?: string[]) { - await initDb(); - const { Users, Signins } = await import('@/models/index'); - - const user = await Users.findOne({ - host: null, - usernameLower: username.toLowerCase(), - }); - - if (user == null) throw new Error('User not found'); - - const history = await Signins.find({ - userId: user.id, - }); - - for (const signin of history) { - console.log(`${signin.success ? 'OK' : 'NG'} ${signin.createdAt ? signin.createdAt.toISOString() : 'Unknown'} ${signin.ip}`); - - // headers - if (headers != null) { - for (const key of Object.keys(signin.headers)) { - if (headers.includes('all') || headers.includes(key)) { - console.log(` ${key}: ${signin.headers[key]}`); - } - } - } - } -} - -// get args -const args = process.argv.slice(2); - -let username = args[0]; -let headers: string[] | undefined; - -if (args[1] != null) { - headers = args[1].split(/,/).map(header => header.toLowerCase()); -} - -// normalize args -username = username.replace(/^@/, ''); - -main(username, headers).then(() => { - process.exit(0); -}).catch(e => { - console.warn(e); - process.exit(1); -}); diff --git a/packages/backend/test/note.ts b/packages/backend/test/note.ts index 62cea5208b..942b2709df 100644 --- a/packages/backend/test/note.ts +++ b/packages/backend/test/note.ts @@ -333,4 +333,36 @@ describe('Note', () => { assert.strictEqual(res.status, 400); })); }); + + describe('notes/delete', () => { + it('delete a reply', async(async () => { + const mainNoteRes = await request('/notes/create', { + text: 'main post', + }, alice); + const replyOneRes = await request('/notes/create', { + text: 'reply one', + replyId: mainNoteRes.body.createdNote.id + }, alice); + const replyTwoRes = await request('/notes/create', { + text: 'reply two', + replyId: mainNoteRes.body.createdNote.id + }, alice); + + const deleteOneRes = await request('/notes/delete', { + noteId: replyOneRes.body.createdNote.id, + }, alice); + + assert.strictEqual(deleteOneRes.status, 204); + let mainNote = await Notes.findOne({id: mainNoteRes.body.createdNote.id}); + assert.strictEqual(mainNote.repliesCount, 1); + + const deleteTwoRes = await request('/notes/delete', { + noteId: replyTwoRes.body.createdNote.id, + }, alice); + + assert.strictEqual(deleteTwoRes.status, 204); + mainNote = await Notes.findOne({id: mainNoteRes.body.createdNote.id}); + assert.strictEqual(mainNote.repliesCount, 0); + })); + }); }); diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts index 994c098b7b..32a030f933 100644 --- a/packages/backend/test/utils.ts +++ b/packages/backend/test/utils.ts @@ -7,7 +7,6 @@ import * as childProcess from 'child_process'; import * as http from 'http'; import loadConfig from '../src/config/load.js'; import { SIGKILL } from 'constants'; -import { createConnection, getConnection } from 'typeorm'; import { entities } from '../src/db/postgre.js'; const config = loadConfig(); diff --git a/packages/backend/yarn.lock b/packages/backend/yarn.lock index 67a02742d4..970a3f8b5c 100644 --- a/packages/backend/yarn.lock +++ b/packages/backend/yarn.lock @@ -35,6 +35,34 @@ lodash "^4.17.19" to-fast-properties "^2.0.0" +"@bull-board/api@3.10.2": + version "3.10.2" + resolved "https://registry.yarnpkg.com/@bull-board/api/-/api-3.10.2.tgz#382450b703c671bb64eeb4d76f139b5e172d1fde" + integrity sha512-jufgsRvAZpUoq/IbmNhwRPQKav6oFUTMjgq0Z200cvNgyFkVDexPhNKNrXdhxaKhBOass4CWvgyQQntDlvCaoQ== + dependencies: + redis-info "^3.0.8" + +"@bull-board/koa@3.10.2": + version "3.10.2" + resolved "https://registry.yarnpkg.com/@bull-board/koa/-/koa-3.10.2.tgz#b50049355913eb049471169faec278d30bb44559" + integrity sha512-SJu+yoE/823sjif003X7030Cj8FmbQ+shUN3LPcUlQ9+0tIQ6ao0+FifJ4uhFnp1CN6FWpn+DCAf4vlC771PNQ== + dependencies: + "@bull-board/api" "3.10.2" + "@bull-board/ui" "3.10.2" + ejs "^3.1.6" + koa "^2.13.1" + koa-mount "^4.0.0" + koa-router "^10.0.0" + koa-static "^5.0.0" + koa-views "^7.0.1" + +"@bull-board/ui@3.10.2": + version "3.10.2" + resolved "https://registry.yarnpkg.com/@bull-board/ui/-/ui-3.10.2.tgz#ab6400b1cbd459604b9e8afeaef9e3cc235d1dd9" + integrity sha512-XFFbnJjZZDoMxntNdmgJoyTlEvMcCfNqeC/QPiqTJU0X/k0cxWDx36tw83PKjN+lKxPjzN/WNpTebYZPKV78Yg== + dependencies: + "@bull-board/api" "3.10.2" + "@cspotcode/source-map-consumer@0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b" @@ -61,10 +89,10 @@ ky "^0.25.1" ky-universal "^0.8.2" -"@discordapp/twemoji@13.1.0": - version "13.1.0" - resolved "https://registry.yarnpkg.com/@discordapp/twemoji/-/twemoji-13.1.0.tgz#6b25f3958fa8fd68692248c87776bc737fd009a9" - integrity sha512-KEw/te+ylD2MHutzigafyptv0kdTU05Dbgxr9Y5J9IAQw8PbFz16nKtlPnJtA23BLp9fZQeNXzUmegkRi7fpDA== +"@discordapp/twemoji@13.1.1": + version "13.1.1" + resolved "https://registry.yarnpkg.com/@discordapp/twemoji/-/twemoji-13.1.1.tgz#f750d491ffb740eca619fac0c63650c1de7fff91" + integrity sha512-WDnPjWq/trfCcZk7dzQ2cYH5v5XaIfPzyixJ//O9XKilYYZRVS3p61vFvax5qMwanMMbnNG1iOzeqHKtivO32A== dependencies: fs-extra "^8.0.1" jsonfile "^5.0.0" @@ -82,16 +110,16 @@ pump "^3.0.0" secure-json-parse "^2.1.0" -"@eslint/eslintrc@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.0.tgz#7ce1547a5c46dfe56e1e45c3c9ed18038c721c6a" - integrity sha512-igm9SjJHNEJRiUnecP/1R5T3wKLEJ7pL6e2P+GUSfCd0dGjPYYZve08uzw8L2J8foVHFz+NGu12JxRcU2gGo6w== +"@eslint/eslintrc@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.1.tgz#8b5e1c49f4077235516bc9ec7d41378c0f69b8c6" + integrity sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ== dependencies: ajv "^6.12.4" debug "^4.3.2" espree "^9.3.1" globals "^13.9.0" - ignore "^4.0.6" + ignore "^5.2.0" import-fresh "^3.2.1" js-yaml "^4.1.0" minimatch "^3.0.4" @@ -216,10 +244,10 @@ require-from-string "^2.0.2" uri-js "^4.2.2" -"@redocly/openapi-core@1.0.0-beta.83": - version "1.0.0-beta.83" - resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.0.0-beta.83.tgz#df1324cc6f1874ecf3046e503192cf872f134a2f" - integrity sha512-XwlxMAmNEQeyBfODXVg2iBpSUqzCwT2zI+7o5iKxjUwJ+5ZugNOYjZGGM3Q9rJGqzFVwLKdElM5a1MlhPvlu4Q== +"@redocly/openapi-core@1.0.0-beta.91": + version "1.0.0-beta.91" + resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.0.0-beta.91.tgz#58dbd8c3cad9ef82f2437c6bbeb6a14dd1bc537d" + integrity sha512-8RhZGn5jSoy3oZE0sAdXxhPPHrqKgy2JVJzLqjgX9LDjNf7cXOTYOXkXIkjv1tfZHFBV/H7c08rRLEdxnzn0dg== dependencies: "@redocly/ajv" "^8.6.4" "@types/node" "^14.11.8" @@ -237,7 +265,7 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-3.1.2.tgz#548650de521b344e3781fbdb0ece4aa6f729afb8" integrity sha512-JiX9vxoKMmu8Y3Zr2RVathBL1Cdu4Nt4MuNWemt1Nc06A0RAin9c5FArkhGsyMBWfCu4zj+9b+GxtjAnE4qqLQ== -"@sindresorhus/is@^4.2.0": +"@sindresorhus/is@^4.6.0": version "4.6.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== @@ -511,10 +539,10 @@ dependencies: "@types/node" "*" -"@types/koa-bodyparser@4.3.6": - version "4.3.6" - resolved "https://registry.yarnpkg.com/@types/koa-bodyparser/-/koa-bodyparser-4.3.6.tgz#99a7d215560fdc168334ebb6a259c6cec9381a56" - integrity sha512-keCpj2kmoooL2oHC9YIVvciN66uDT21uMp4rvrosyjLsHD1aAipn6cg3xSxav9tR2Ly/NMvs8jdlNPTTQvn8SA== +"@types/koa-bodyparser@4.3.7": + version "4.3.7" + resolved "https://registry.yarnpkg.com/@types/koa-bodyparser/-/koa-bodyparser-4.3.7.tgz#3ac41f2dec9d97db7a6f798bbb2e2368be762714" + integrity sha512-21NhEp7LjZm4zbNV5alHHmrNY4J+S7B8lYTO6CzRL8ShTMnl20Gd14dRgVhAxraLaW5iZMofox+BycbuiDvj2Q== dependencies: "@types/koa" "*" @@ -638,10 +666,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.6.2.tgz#331b7b9f8621c638284787c5559423822fdffc50" integrity sha512-LSw8TZt12ZudbpHc6EkIyDM3nHVWKYrAvGy6EAJfNfjusbwnThqjqxUKKRwuV3iWYeW/LYMzNgaq3MaLffQ2xA== -"@types/node@17.0.21": - version "17.0.21" - resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.21.tgz#864b987c0c68d07b4345845c3e63b75edd143644" - integrity sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ== +"@types/node@17.0.23": + version "17.0.23" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.23.tgz#3b41a6e643589ac6442bdbd7a4a3ded62f33f7da" + integrity sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw== "@types/node@^14.11.8": version "14.17.9" @@ -757,17 +785,17 @@ "@types/express-serve-static-core" "*" "@types/mime" "*" -"@types/sharp@0.29.5": - version "0.29.5" - resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.29.5.tgz#9c7032d30d138ad16dde6326beaff2af757b91b3" - integrity sha512-3TC+S3H5RwnJmLYMHrcdfNjz/CaApKmujjY9b6PU/pE6n0qfooi99YqXGWoW8frU9EWYj/XTI35Pzxa+ThAZ5Q== +"@types/sharp@0.30.0": + version "0.30.0" + resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.30.0.tgz#58cb016c8fdc558b4c5771ad1f3668336685c843" + integrity sha512-bZ0Y/JVlrOyqwlBMJ2taEgnwFavjLnyZmLOLecmOesuG5kR2Lx9b2fM4osgfVjLJi8UlE+t3R1JzRVMxF6MbfA== dependencies: "@types/node" "*" -"@types/sinonjs__fake-timers@8.1.1": - version "8.1.1" - resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz#b49c2c70150141a15e0fa7e79cf1f92a72934ce3" - integrity sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g== +"@types/sinonjs__fake-timers@8.1.2": + version "8.1.2" + resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz#bf2e02a3dbd4aecaf95942ecd99b7402e03fad5e" + integrity sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA== "@types/speakeasy@2.0.7": version "2.0.7" @@ -815,26 +843,21 @@ dependencies: "@types/node" "*" -"@types/ws@8.5.2": - version "8.5.2" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.2.tgz#77e0c2e360e9579da930ffcfa53c5975ea3bdd26" - integrity sha512-VXI82ykONr5tacHEojnErTQk+KQSoYbW1NB6iz6wUwrNd+BqfkfggQNoNdCqhJSzbNumShPERbM+Pc5zpfhlbw== +"@types/ws@8.5.3": + version "8.5.3" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d" + integrity sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w== dependencies: "@types/node" "*" -"@types/zen-observable@^0.8.2": - version "0.8.2" - resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.2.tgz#808c9fa7e4517274ed555fa158f2de4b4f468e71" - integrity sha512-HrCIVMLjE1MOozVoD86622S7aunluLb2PJdPfb3nYiEtohm8mIB/vyv0Fd37AdeMFrTUQXEunw78YloMA3Qilg== - -"@typescript-eslint/eslint-plugin@5.14.0": - version "5.14.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.14.0.tgz#5119b67152356231a0e24b998035288a9cd21335" - integrity sha512-ir0wYI4FfFUDfLcuwKzIH7sMVA+db7WYen47iRSaCGl+HMAZI9fpBwfDo45ZALD3A45ZGyHWDNLhbg8tZrMX4w== +"@typescript-eslint/eslint-plugin@5.17.0": + version "5.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.17.0.tgz#704eb4e75039000531255672bf1c85ee85cf1d67" + integrity sha512-qVstvQilEd89HJk3qcbKt/zZrfBZ+9h2ynpAGlWjWiizA7m/MtLT9RoX6gjtpE500vfIg8jogAkDzdCxbsFASQ== dependencies: - "@typescript-eslint/scope-manager" "5.14.0" - "@typescript-eslint/type-utils" "5.14.0" - "@typescript-eslint/utils" "5.14.0" + "@typescript-eslint/scope-manager" "5.17.0" + "@typescript-eslint/type-utils" "5.17.0" + "@typescript-eslint/utils" "5.17.0" debug "^4.3.2" functional-red-black-tree "^1.0.1" ignore "^5.1.8" @@ -842,69 +865,69 @@ semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/parser@5.14.0": - version "5.14.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.14.0.tgz#7c79f898aa3cff0ceee6f1d34eeed0f034fb9ef3" - integrity sha512-aHJN8/FuIy1Zvqk4U/gcO/fxeMKyoSv/rS46UXMXOJKVsLQ+iYPuXNbpbH7cBLcpSbmyyFbwrniLx5+kutu1pw== +"@typescript-eslint/parser@5.17.0": + version "5.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.17.0.tgz#7def77d5bcd8458d12d52909118cf3f0a45f89d5" + integrity sha512-aRzW9Jg5Rlj2t2/crzhA2f23SIYFlF9mchGudyP0uiD6SenIxzKoLjwzHbafgHn39dNV/TV7xwQkLfFTZlJ4ig== dependencies: - "@typescript-eslint/scope-manager" "5.14.0" - "@typescript-eslint/types" "5.14.0" - "@typescript-eslint/typescript-estree" "5.14.0" + "@typescript-eslint/scope-manager" "5.17.0" + "@typescript-eslint/types" "5.17.0" + "@typescript-eslint/typescript-estree" "5.17.0" debug "^4.3.2" -"@typescript-eslint/scope-manager@5.14.0": - version "5.14.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.14.0.tgz#ea518962b42db8ed0a55152ea959c218cb53ca7b" - integrity sha512-LazdcMlGnv+xUc5R4qIlqH0OWARyl2kaP8pVCS39qSL3Pd1F7mI10DbdXeARcE62sVQE4fHNvEqMWsypWO+yEw== +"@typescript-eslint/scope-manager@5.17.0": + version "5.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.17.0.tgz#4cea7d0e0bc0e79eb60cad431c89120987c3f952" + integrity sha512-062iCYQF/doQ9T2WWfJohQKKN1zmmXVfAcS3xaiialiw8ZUGy05Em6QVNYJGO34/sU1a7a+90U3dUNfqUDHr3w== dependencies: - "@typescript-eslint/types" "5.14.0" - "@typescript-eslint/visitor-keys" "5.14.0" + "@typescript-eslint/types" "5.17.0" + "@typescript-eslint/visitor-keys" "5.17.0" -"@typescript-eslint/type-utils@5.14.0": - version "5.14.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.14.0.tgz#711f08105860b12988454e91df433567205a8f0b" - integrity sha512-d4PTJxsqaUpv8iERTDSQBKUCV7Q5yyXjqXUl3XF7Sd9ogNLuKLkxz82qxokqQ4jXdTPZudWpmNtr/JjbbvUixw== +"@typescript-eslint/type-utils@5.17.0": + version "5.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.17.0.tgz#1c4549d68c89877662224aabb29fbbebf5fc9672" + integrity sha512-3hU0RynUIlEuqMJA7dragb0/75gZmwNwFf/QJokWzPehTZousP/MNifVSgjxNcDCkM5HI2K22TjQWUmmHUINSg== dependencies: - "@typescript-eslint/utils" "5.14.0" + "@typescript-eslint/utils" "5.17.0" debug "^4.3.2" tsutils "^3.21.0" -"@typescript-eslint/types@5.14.0": - version "5.14.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.14.0.tgz#96317cf116cea4befabc0defef371a1013f8ab11" - integrity sha512-BR6Y9eE9360LNnW3eEUqAg6HxS9Q35kSIs4rp4vNHRdfg0s+/PgHgskvu5DFTM7G5VKAVjuyaN476LCPrdA7Mw== +"@typescript-eslint/types@5.17.0": + version "5.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.17.0.tgz#861ec9e669ffa2aa9b873dd4d28d9b1ce26d216f" + integrity sha512-AgQ4rWzmCxOZLioFEjlzOI3Ch8giDWx8aUDxyNw9iOeCvD3GEYAB7dxWGQy4T/rPVe8iPmu73jPHuaSqcjKvxw== -"@typescript-eslint/typescript-estree@5.14.0": - version "5.14.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.14.0.tgz#78b7f7385d5b6f2748aacea5c9b7f6ae62058314" - integrity sha512-QGnxvROrCVtLQ1724GLTHBTR0lZVu13izOp9njRvMkCBgWX26PKvmMP8k82nmXBRD3DQcFFq2oj3cKDwr0FaUA== +"@typescript-eslint/typescript-estree@5.17.0": + version "5.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.17.0.tgz#a7cba7dfc8f9cc2ac78c18584e684507df4f2488" + integrity sha512-X1gtjEcmM7Je+qJRhq7ZAAaNXYhTgqMkR10euC4Si6PIjb+kwEQHSxGazXUQXFyqfEXdkGf6JijUu5R0uceQzg== dependencies: - "@typescript-eslint/types" "5.14.0" - "@typescript-eslint/visitor-keys" "5.14.0" + "@typescript-eslint/types" "5.17.0" + "@typescript-eslint/visitor-keys" "5.17.0" debug "^4.3.2" globby "^11.0.4" is-glob "^4.0.3" semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/utils@5.14.0": - version "5.14.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.14.0.tgz#6c8bc4f384298cbbb32b3629ba7415f9f80dc8c4" - integrity sha512-EHwlII5mvUA0UsKYnVzySb/5EE/t03duUTweVy8Zqt3UQXBrpEVY144OTceFKaOe4xQXZJrkptCf7PjEBeGK4w== +"@typescript-eslint/utils@5.17.0": + version "5.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.17.0.tgz#549a9e1d491c6ccd3624bc3c1b098f5cfb45f306" + integrity sha512-DVvndq1QoxQH+hFv+MUQHrrWZ7gQ5KcJzyjhzcqB1Y2Xes1UQQkTRPUfRpqhS8mhTWsSb2+iyvDW1Lef5DD7vA== dependencies: "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.14.0" - "@typescript-eslint/types" "5.14.0" - "@typescript-eslint/typescript-estree" "5.14.0" + "@typescript-eslint/scope-manager" "5.17.0" + "@typescript-eslint/types" "5.17.0" + "@typescript-eslint/typescript-estree" "5.17.0" eslint-scope "^5.1.1" eslint-utils "^3.0.0" -"@typescript-eslint/visitor-keys@5.14.0": - version "5.14.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.14.0.tgz#1927005b3434ccd0d3ae1b2ecf60e65943c36986" - integrity sha512-yL0XxfzR94UEkjBqyymMLgCBdojzEuy/eim7N9/RIcTNxpJudAcqsU8eRyfzBbcEzGoPWfdM3AGak3cN08WOIw== +"@typescript-eslint/visitor-keys@5.17.0": + version "5.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.17.0.tgz#52daae45c61b0211b4c81b53a71841911e479128" + integrity sha512-6K/zlc4OfCagUu7Am/BD5k8PSWQOgh34Nrv9Rxe2tBzlJ7uOeJ/h7ugCGDCeEZHT6k2CJBhbk9IsbkPI0uvUkA== dependencies: - "@typescript-eslint/types" "5.14.0" + "@typescript-eslint/types" "5.17.0" eslint-visitor-keys "^3.0.0" "@ungap/promise-all-settled@1.1.2": @@ -1004,10 +1027,10 @@ ajv-keywords@^3.5.2: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== -ajv@8.10.0: - version "8.10.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.10.0.tgz#e573f719bd3af069017e3b66538ab968d040e54d" - integrity sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw== +ajv@8.11.0: + version "8.11.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f" + integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg== dependencies: fast-deep-equal "^3.1.1" json-schema-traverse "^1.0.0" @@ -1209,6 +1232,11 @@ assert-plus@1.0.0, assert-plus@^1.0.0: resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= +async@0.9.x: + version "0.9.2" + resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" + integrity sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0= + async@>=0.2.9: version "3.2.0" resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720" @@ -1243,10 +1271,10 @@ autwh@0.1.0: dependencies: oauth "0.9.15" -aws-sdk@2.1079.0: - version "2.1079.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1079.0.tgz#41ede54aa4ba5ce77d4ffe202f9a1ee7869da2a8" - integrity sha512-WHYWiye9f2XYQ33Rj/uVw4VF/Qq/xrB9NDnGlRhgK8Ga7T20+8/iZD5/Z8wICVNZTsfUZ3g6LfkeZ1l+LZhHKw== +aws-sdk@2.1105.0: + version "2.1105.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1105.0.tgz#3e63129f2aca254f1d6d5a1580b988bb786e98fa" + integrity sha512-YZ6IbKvtiw8noD/Iuyp3hXNX5NmhJ2xSU4598pZr55CfnIQ0oU5ZwtQqLPG8E07ouA363/moCYddIAVGYSkQ+A== dependencies: buffer "4.9.2" events "1.1.1" @@ -1467,10 +1495,10 @@ bufferutil@^4.0.1: dependencies: node-gyp-build "~3.7.0" -bull@4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/bull/-/bull-4.7.0.tgz#89442d4676117edd9f9a1359bb0edfb489595e70" - integrity sha512-rnJIsuXrDjDlz3HMHz6xobiRZAWe3o4MJBkzx7FdUjO+K2nSYrhR2KpcL+ZCNUMPKtONxL4DqmRjat5SBHFlAw== +bull@4.8.1: + version "4.8.1" + resolved "https://registry.yarnpkg.com/bull/-/bull-4.8.1.tgz#83daaefc3118876450b21d7a02bc11ea28a2440e" + integrity sha512-ojH5AfOchKQsQwwE+thViS1pMpvREGC+Ov1+3HXsQqn5Q27ZSGkgMriMqc6c9J9rvQ/+D732pZE+TN1+2LRWVg== dependencies: cron-parser "^4.2.1" debuglog "^1.0.0" @@ -2075,7 +2103,7 @@ data-urls@^3.0.1: whatwg-mimetype "^3.0.0" whatwg-url "^10.0.0" -date-fns@2.28.0: +date-fns@2.28.0, date-fns@^2.28.0: version "2.28.0" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.28.0.tgz#9570d656f5fc13143e50c975a3b6bbeb46cd08b2" integrity sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw== @@ -2101,6 +2129,13 @@ debug@4.3.3: dependencies: ms "2.1.2" +debug@^3.1.0, debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + debug@^3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" @@ -2108,13 +2143,6 @@ debug@^3.2.6: dependencies: ms "^2.1.1" -debug@^3.2.7: - version "3.2.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" - integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== - dependencies: - ms "^2.1.1" - debug@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" @@ -2122,6 +2150,13 @@ debug@^4.3.2: dependencies: ms "2.1.2" +debug@^4.3.3: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + debuglog@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" @@ -2398,10 +2433,10 @@ domutils@^2.5.2: domelementtype "^2.2.0" domhandler "^4.2.0" -dotenv@^8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" - integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== +dotenv@^16.0.0: + version "16.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.0.tgz#c619001253be89ebb638d027b609c75c26e47411" + integrity sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q== duplexer2@~0.1.4: version "0.1.4" @@ -2440,6 +2475,13 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= +ejs@^3.1.6: + version "3.1.6" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.6.tgz#5bfd0a0689743bb5268b3550cceeebbc1702822a" + integrity sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw== + dependencies: + jake "^10.6.1" + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -2673,12 +2715,12 @@ eslint-visitor-keys@^3.3.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== -eslint@8.10.0: - version "8.10.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.10.0.tgz#931be395eb60f900c01658b278e05b6dae47199d" - integrity sha512-tcI1D9lfVec+R4LE1mNDnzoJ/f71Kl/9Cv4nG47jOueCMBrCCKYXr4AUVS7go6mWYGFD4+EoN6+eXSrEbRzXVw== +eslint@8.12.0: + version "8.12.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.12.0.tgz#c7a5bd1cfa09079aae64c9076c07eada66a46e8e" + integrity sha512-it1oBL9alZg1S8UycLm5YDMAkIhtH6FtAzuZs6YvoGVldWjbS08BkAdb/ymP9LlAyq8koANu32U7Ib/w+UNh8Q== dependencies: - "@eslint/eslintrc" "^1.2.0" + "@eslint/eslintrc" "^1.2.1" "@humanwhocodes/config-array" "^0.9.2" ajv "^6.10.0" chalk "^4.0.0" @@ -2918,6 +2960,13 @@ file-type@17.1.1: strtok3 "^7.0.0-alpha.7" token-types "^5.0.0-alpha.2" +filelist@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.2.tgz#80202f21462d4d1c2e214119b1807c1bc0380e5b" + integrity sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ== + dependencies: + minimatch "^3.0.4" + fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -2993,9 +3042,9 @@ fluent-ffmpeg@2.1.2: which "^1.1.1" follow-redirects@^1.14.4: - version "1.14.7" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685" - integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ== + version "1.14.8" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.8.tgz#016996fb9a11a100566398b1c6839337d7bfa8fc" + integrity sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA== form-data-encoder@1.7.1: version "1.7.1" @@ -3180,7 +3229,7 @@ glob-parent@^6.0.1: dependencies: is-glob "^4.0.3" -glob@7.2.0: +glob@7.2.0, glob@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== @@ -3192,7 +3241,7 @@ glob@7.2.0: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: +glob@^7.1.3, glob@^7.1.4: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -3267,12 +3316,12 @@ got@11.5.1: p-cancelable "^2.0.0" responselike "^2.0.0" -got@12.0.1: - version "12.0.1" - resolved "https://registry.yarnpkg.com/got/-/got-12.0.1.tgz#78747f1c5bc7069bbd739636ed8b70c7f2140a39" - integrity sha512-1Zhoh+lDej3t7Ks1BP/Jufn+rNqdiHQgUOcTxHzg2Dao1LQfp5S4Iq0T3iBxN4Zdo7QqCJL+WJUNzDX6rCP2Ew== +got@12.0.3: + version "12.0.3" + resolved "https://registry.yarnpkg.com/got/-/got-12.0.3.tgz#c7314daab26d42039e624adbf98f6d442e5de749" + integrity sha512-hmdcXi/S0gcAtDg4P8j/rM7+j3o1Aq6bXhjxkDhRY2ipe7PHpvx/14DgTY2czHOLaGeU8VRvRecidwfu9qdFug== dependencies: - "@sindresorhus/is" "^4.2.0" + "@sindresorhus/is" "^4.6.0" "@szmarczak/http-timer" "^5.0.1" "@types/cacheable-request" "^6.0.2" "@types/responselike" "^1.0.0" @@ -3281,7 +3330,7 @@ got@12.0.1: decompress-response "^6.0.0" form-data-encoder "1.7.1" get-stream "^6.0.1" - http2-wrapper "^2.1.9" + http2-wrapper "^2.1.10" lowercase-keys "^3.0.0" p-cancelable "^3.0.0" responselike "^2.0.0" @@ -3480,7 +3529,7 @@ http2-wrapper@^1.0.0-beta.5.0: quick-lru "^5.1.1" resolve-alpn "^1.0.0" -http2-wrapper@^2.1.9: +http2-wrapper@^2.1.10: version "2.1.10" resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-2.1.10.tgz#307cd0cee2564723692ad34c2d570d12f10e83be" integrity sha512-QHgsdYkieKp+6JbXP25P+tepqiHYd+FVnDwXpxi/BlUcoIB0nsmTOymTNvETuTO+pDuwcSklPE72VR3DqV+Haw== @@ -3551,11 +3600,6 @@ ieee754@^1.2.1: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -ignore@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" - integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== - ignore@^5.1.4: version "5.1.8" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" @@ -3943,6 +3987,16 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= +jake@^10.6.1: + version "10.8.2" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.2.tgz#ebc9de8558160a66d82d0eadc6a2e58fbc500a7b" + integrity sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A== + dependencies: + async "0.9.x" + chalk "^2.4.2" + filelist "^1.0.1" + minimatch "^3.0.4" + jmespath@0.16.0: version "0.16.0" resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.16.0.tgz#b15b0a85dfd4d930d43e69ed605943c802785076" @@ -3974,7 +4028,7 @@ js-stringify@^1.0.2: resolved "https://registry.yarnpkg.com/js-stringify/-/js-stringify-1.0.2.tgz#1736fddfd9724f28a3682adc6230ae7e4e9679db" integrity sha1-Fzb939lyTyijaCrcYjCufk6Weds= -js-yaml@4.1.0, js-yaml@^4.0.0, js-yaml@^4.1.0: +js-yaml@4.1.0, js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== @@ -4068,12 +4122,10 @@ json5-loader@4.0.1: loader-utils "^2.0.0" schema-utils "^3.0.0" -json5@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" - integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== - dependencies: - minimist "^1.2.5" +json5@2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" + integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== json5@^1.0.1: version "1.0.1" @@ -4221,7 +4273,7 @@ koa-logger@3.2.1: humanize-number "0.0.2" passthrough-counter "^1.0.0" -koa-mount@4.0.0: +koa-mount@4.0.0, koa-mount@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/koa-mount/-/koa-mount-4.0.0.tgz#e0265e58198e1a14ef889514c607254ff386329c" integrity sha512-rm71jaA/P+6HeCpoRhmCv8KVBIi0tfGuO/dMKicbQnQW/YJntJ6MnnspkodoA4QstMVEZArsCphmd0bJEtoMjQ== @@ -4229,6 +4281,17 @@ koa-mount@4.0.0: debug "^4.0.1" koa-compose "^4.1.0" +koa-router@^10.0.0: + version "10.1.1" + resolved "https://registry.yarnpkg.com/koa-router/-/koa-router-10.1.1.tgz#20809f82648518b84726cd445037813cd99f17ff" + integrity sha512-z/OzxVjf5NyuNO3t9nJpx7e1oR3FSBAauiwXtMQu4ppcnuNZzTaQ4p21P8A6r2Es8uJJM339oc4oVW+qX7SqnQ== + dependencies: + debug "^4.1.1" + http-errors "^1.7.3" + koa-compose "^4.1.0" + methods "^1.1.2" + path-to-regexp "^6.1.0" + koa-send@5.0.1, koa-send@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/koa-send/-/koa-send-5.0.1.tgz#39dceebfafb395d0d60beaffba3a70b4f543fe79" @@ -4246,6 +4309,14 @@ koa-slow@2.1.0: lodash.isregexp "3.0.5" q "1.4.1" +koa-static@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/koa-static/-/koa-static-5.0.0.tgz#5e92fc96b537ad5219f425319c95b64772776943" + integrity sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ== + dependencies: + debug "^3.1.0" + koa-send "^5.0.0" + koa-views@*: version "7.0.1" resolved "https://registry.yarnpkg.com/koa-views/-/koa-views-7.0.1.tgz#0c8f8e65d5cd2e08249430cb83dc361e49a17a5a" @@ -4260,7 +4331,7 @@ koa-views@*: pretty "^2.0.0" resolve-path "^1.4.0" -koa-views@7.0.2: +koa-views@7.0.2, koa-views@^7.0.1: version "7.0.2" resolved "https://registry.yarnpkg.com/koa-views/-/koa-views-7.0.2.tgz#c96fd9e2143ef00c29dc5160c5ed639891aa723d" integrity sha512-dvx3mdVeSVuIPEaKAoGbxLcenudvhl821xxyuRbcoA+bOJ2dvN8wlGjkLu0ZFMlkCscXZV6lzxy28rafeazI/w== @@ -4273,7 +4344,7 @@ koa-views@7.0.2: pretty "^2.0.0" resolve-path "^1.4.0" -koa@2.13.4: +koa@2.13.4, koa@^2.13.1: version "2.13.4" resolved "https://registry.yarnpkg.com/koa/-/koa-2.13.4.tgz#ee5b0cb39e0b8069c38d115139c774833d32462e" integrity sha512-43zkIKubNbnrULWlHdN5h1g3SEKXOEzoAlRsHOTFpnlDu8JlAOZSMJBLULusuXRequboiwJcj5vtYXKB3k7+2g== @@ -4469,7 +4540,7 @@ lodash.union@^4.6.0: resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg= -lodash@^4.17.14, lodash@^4.17.19, lodash@^4.17.21: +lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.19, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -4594,17 +4665,17 @@ mime-db@1.44.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== -mime-db@1.51.0: - version "1.51.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c" - integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g== +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@2.1.34: - version "2.1.34" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24" - integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A== +mime-types@2.1.35: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: - mime-db "1.51.0" + mime-db "1.52.0" mime-types@^2.1.12, mime-types@^2.1.18, mime-types@~2.1.24: version "2.1.27" @@ -4633,17 +4704,24 @@ minimalistic-assert@^1.0.0: resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimatch@3.0.4, minimatch@^3.0.4: +minimatch@4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-4.2.1.tgz#40d9d511a46bdc4e563c22c3080cde9c0d8299b4" + integrity sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== dependencies: brace-expansion "^1.1.7" -minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== +minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== minipass-collect@^1.0.2: version "1.0.2" @@ -4730,10 +4808,10 @@ mkdirp@^1.0.3, mkdirp@^1.0.4, mkdirp@~1.0.3: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -mocha@9.2.1: - version "9.2.1" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.2.1.tgz#a1abb675aa9a8490798503af57e8782a78f1338e" - integrity sha512-T7uscqjJVS46Pq1XDXyo9Uvey9gd3huT/DD9cYBb4K2Xc/vbKRPUWK067bxDQRK0yIz6Jxk73IrnimvASzBNAQ== +mocha@9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.2.2.tgz#d70db46bdb93ca57402c809333e5a84977a88fb9" + integrity sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g== dependencies: "@ungap/promise-all-settled" "1.1.2" ansi-colors "4.1.1" @@ -4748,9 +4826,9 @@ mocha@9.2.1: he "1.2.0" js-yaml "4.1.0" log-symbols "4.1.0" - minimatch "3.0.4" + minimatch "4.2.1" ms "2.1.3" - nanoid "3.2.0" + nanoid "3.3.1" serialize-javascript "6.0.0" strip-json-comments "3.1.1" supports-color "8.1.1" @@ -4840,15 +4918,10 @@ nano-time@1.0.0: dependencies: big-integer "^1.6.16" -nanoid@3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c" - integrity sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA== - -nanoid@^3.1.30: - version "3.1.30" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.30.tgz#63f93cc548d2a113dc5dfbc63bfa09e2b9b64362" - integrity sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ== +nanoid@3.3.1, nanoid@^3.1.30: + version "3.3.1" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35" + integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw== napi-build-utils@^1.0.1: version "1.0.2" @@ -4923,10 +4996,10 @@ node-fetch@3.0.0-beta.9: data-uri-to-buffer "^3.0.1" fetch-blob "^2.1.1" -node-fetch@3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.2.tgz#16d33fbe32ca7c6ca1ca8ba5dfea1dd885c59f04" - integrity sha512-Cwhq1JFIoon15wcIkFzubVNFE5GvXGV82pKf4knXXjvGmn7RJKcypeuqcVNZMGDZsAFWyIRya/anwAJr7TWJ7w== +node-fetch@3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.3.tgz#a03c9cc2044d21d1a021566bd52f080f333719a6" + integrity sha512-AXP18u4pidSZ1xYXRDPY/8jdv3RAozIt/WLNR/MBGZAz+xjtlr90RvCnsvHQRiXyWliZF/CpytExp32UU67/SA== dependencies: data-uri-to-buffer "^4.0.0" fetch-blob "^3.1.4" @@ -4965,10 +5038,10 @@ node-gyp@^8.4.1: tar "^6.1.2" which "^2.0.2" -nodemailer@6.7.2: - version "6.7.2" - resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.7.2.tgz#44b2ad5f7ed71b7067f7a21c4fedabaec62b85e0" - integrity sha512-Dz7zVwlef4k5R71fdmxwR8Q39fiboGbu3xgswkzGwczUfjp873rVxt1O46+Fh0j1ORnAC6L9+heI8uUpO6DT7Q== +nodemailer@6.7.3: + version "6.7.3" + resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.7.3.tgz#b73f9a81b9c8fa8acb4ea14b608f5e725ea8e018" + integrity sha512-KUdDsspqx89sD4UUyUKzdlUOper3hRkDVkrKh/89G+d9WKsU5ox51NWS4tB1XR5dPUdR4SP0E3molyEfOvSa3g== nofilter@^2.0.3: version "2.0.3" @@ -5881,6 +5954,13 @@ redis-errors@^1.0.0, redis-errors@^1.2.0: resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" integrity sha1-62LSrbFeTq9GEMBK/hUpOEJQq60= +redis-info@^3.0.8: + version "3.1.0" + resolved "https://registry.yarnpkg.com/redis-info/-/redis-info-3.1.0.tgz#5e349c8720e82d27ac84c73136dce0931e10469a" + integrity sha512-ER4L9Sh/vm63DkIE0bkSjxluQlioBiBgf5w1UuldaW/3vPcecdljVDisZhmnCMvsxHNiARTTDDHGg9cGwTfrKg== + dependencies: + lodash "^4.17.11" + redis-lock@0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/redis-lock/-/redis-lock-0.1.4.tgz#e83590bee22b5f01cdb65bfbd88d988045356272" @@ -6117,6 +6197,13 @@ seedrandom@3.0.5: resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7" integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg== +semver@7.3.5, semver@^7.3.5: + version "7.3.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" + semver@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" @@ -6129,13 +6216,6 @@ semver@^7.3.2, semver@^7.3.4: dependencies: lru-cache "^6.0.0" -semver@^7.3.5: - version "7.3.5" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" - integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== - dependencies: - lru-cache "^6.0.0" - serialize-javascript@6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" @@ -6171,10 +6251,10 @@ sha.js@^2.4.11: inherits "^2.0.1" safe-buffer "^5.0.1" -sharp@0.30.2: - version "0.30.2" - resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.30.2.tgz#95b309b2740424702dc19b62a62595dd34a458b1" - integrity sha512-mrMeKI5ECTdYhslPlA2TbBtU3nZXMEBcQwI6qYXjPlu1LpW4HBZLFm6xshMI1HpIdEEJ3UcYp5AKifLT/fEHZQ== +sharp@0.30.3: + version "0.30.3" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.30.3.tgz#315a1817423a4d1cde5119a21c99c234a7a6fb37" + integrity sha512-rjpfJFK58ZOFSG8sxYSo3/JQb4ej095HjXp9X7gVu7gEn1aqSG8TCW29h/Rr31+PXrFADo1H/vKfw0uhMQWFtg== dependencies: color "^4.2.1" detect-libc "^2.0.1" @@ -6534,10 +6614,10 @@ syslog-pro@1.0.0: dependencies: moment "^2.22.2" -systeminformation@5.11.6: - version "5.11.6" - resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.11.6.tgz#8624cbb2e95e6fa98a4ebb0d10759427c0e88144" - integrity sha512-7KBXgdnIDxABQ93w+GrPSrK/pup73+fM09VGka4A/+FhgzdlRY0JNGGDFmV8BHnFuzP9zwlI3n64yDbp7emasQ== +systeminformation@5.11.9: + version "5.11.9" + resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.11.9.tgz#95f2334e739dd224178948a2afaced7d9abfdf9d" + integrity sha512-eeMtL9UJFR/LYG+2rpeAgZ0Va4ojlNQTkYiQH/xbbPwDjDMsaetj3Pkc+C1aH5G8mav6HvDY8kI4Vl4noksSkA== tapable@^2.2.0: version "2.2.0" @@ -6700,10 +6780,10 @@ trace-redirect@1.0.6: resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk= -ts-loader@9.2.7: - version "9.2.7" - resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.2.7.tgz#948654099ca96992b62ec47bd9cee5632006e101" - integrity sha512-Fxh44mKli9QezgbdCXkEJWxnedQ0ead7DXTH+lfXEPedu+Y9EtMJ2aQ9G3Dj1j7Q612E8931rww8NDZha4Tibg== +ts-loader@9.2.8: + version "9.2.8" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.2.8.tgz#e89aa32fa829c5cad0a1d023d6b3adecd51d5a48" + integrity sha512-gxSak7IHUuRtwKf3FIPSW1VpZcqF9+MBrHOvBp9cjHh+525SjtCIJKVGjRKIAfxBwDGDGCFF00rTfzB1quxdSw== dependencies: chalk "^4.1.0" enhanced-resolve "^5.0.0" @@ -6741,14 +6821,14 @@ tsc-alias@1.4.1: mylas "^2.1.4" normalize-path "^3.0.0" -tsconfig-paths@3.13.0: - version "3.13.0" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.13.0.tgz#f3e9b8f6876698581d94470c03c95b3a48c0e3d7" - integrity sha512-nWuffZppoaYK0vQ1SQmkSsQzJoHA4s6uzdb2waRpD806x9yfq153AdVsWz4je2qZcW+pENrMQXbGQ3sMCkXuhw== +tsconfig-paths@3.14.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a" + integrity sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ== dependencies: "@types/json5" "^0.0.29" json5 "^1.0.1" - minimist "^1.2.0" + minimist "^1.2.6" strip-bom "^3.0.0" tsconfig-paths@^3.12.0: @@ -6766,10 +6846,10 @@ tslib@^1.8.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== -tslib@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c" - integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w== +tslib@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" + integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== tsscmp@1.0.6: version "1.0.6" @@ -6800,6 +6880,11 @@ twemoji-parser@13.1.0, twemoji-parser@13.1.x: resolved "https://registry.yarnpkg.com/twemoji-parser/-/twemoji-parser-13.1.0.tgz#65e7e449c59258791b22ac0b37077349127e3ea4" integrity sha512-AQOzLJpYlpWMy8n+0ATyKKZzWlZBJN+G0C+5lhX7Ftc2PeEVdUU/7ns2Pn2vVje26AIZ/OHwFoUbdv6YYD/wGg== +twemoji-parser@14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/twemoji-parser/-/twemoji-parser-14.0.0.tgz#13dabcb6d3a261d9efbf58a1666b182033bf2b62" + integrity sha512-9DUOTGLOWs0pFWnh1p6NF+C3CkQ96PWmEFwhOVmT3WbecRC+68AIqpsnJXygfkFcp4aXbOp8Dwbhh/HQgvoRxA== + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -6854,33 +6939,33 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typeorm@0.2.45: - version "0.2.45" - resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.2.45.tgz#e5bbb3af822dc4646bad96cfa48cd22fa4687cea" - integrity sha512-c0rCO8VMJ3ER7JQ73xfk0zDnVv0WDjpsP6Q1m6CVKul7DB9iVdWLRjPzc8v2eaeBuomsbZ2+gTaYr8k1gm3bYA== +typeorm@0.3.4: + version "0.3.4" + resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.3.4.tgz#6608f7efb15c40f3fa2863cefb45ff78a208c40c" + integrity sha512-6v3HH12viDhIQwQDod/B0Plt1o7IYIVDxP7zwatD6fzN+IDdqTTinW/sWNw84Edpbhh2t7XILTaQEqj0NXFP/Q== dependencies: "@sqltools/formatter" "^1.2.2" app-root-path "^3.0.0" buffer "^6.0.3" chalk "^4.1.0" cli-highlight "^2.1.11" - debug "^4.3.1" - dotenv "^8.2.0" - glob "^7.1.6" - js-yaml "^4.0.0" + date-fns "^2.28.0" + debug "^4.3.3" + dotenv "^16.0.0" + glob "^7.2.0" + js-yaml "^4.1.0" mkdirp "^1.0.4" reflect-metadata "^0.1.13" sha.js "^2.4.11" - tslib "^2.1.0" + tslib "^2.3.1" uuid "^8.3.2" xml2js "^0.4.23" - yargs "^17.0.1" - zen-observable-ts "^1.0.0" + yargs "^17.3.1" -typescript@4.6.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.2.tgz#fe12d2727b708f4eef40f51598b3398baa9611d4" - integrity sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg== +typescript@4.6.3: + version "4.6.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.3.tgz#eefeafa6afdd31d725584c67a0eaba80f6fc6c6c" + integrity sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw== ulid@2.3.0: version "2.3.0" @@ -7295,6 +7380,11 @@ yargs-parser@^18.1.2: camelcase "^5.0.0" decamelize "^1.2.0" +yargs-parser@^21.0.0: + version "21.0.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.0.1.tgz#0267f286c877a4f0f728fceb6f8a3e4cb95c6e35" + integrity sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg== + yargs-unparser@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" @@ -7335,18 +7425,18 @@ yargs@^15.3.1: y18n "^4.0.0" yargs-parser "^18.1.2" -yargs@^17.0.1: - version "17.1.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.1.1.tgz#c2a8091564bdb196f7c0a67c1d12e5b85b8067ba" - integrity sha512-c2k48R0PwKIqKhPMWjeiF6y2xY/gPMUlro0sgxqXpbOIohWiLNXWslsootttv7E1e73QPAMQSg5FeySbVcpsPQ== +yargs@^17.3.1: + version "17.4.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.4.0.tgz#9fc9efc96bd3aa2c1240446af28499f0e7593d00" + integrity sha512-WJudfrk81yWFSOkZYpAZx4Nt7V4xp7S/uJkX0CnxovMCt1wCE8LNftPpNuF9X/u9gN5nsD7ycYtRcDf2pL3UiA== dependencies: cliui "^7.0.2" escalade "^3.1.1" get-caller-file "^2.0.5" require-directory "^2.1.1" - string-width "^4.2.0" + string-width "^4.2.3" y18n "^5.0.5" - yargs-parser "^20.2.2" + yargs-parser "^21.0.0" ylru@^1.2.0: version "1.2.1" @@ -7358,19 +7448,6 @@ yn@3.1.1: resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== -zen-observable-ts@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-1.0.0.tgz#30d1202b81d8ba4c489e3781e8ca09abf0075e70" - integrity sha512-KmWcbz+9kKUeAQ8btY8m1SsEFgBcp7h/Uf3V5quhan7ZWdjGsf0JcGLULQiwOZibbFWnHkYq8Nn2AZbJabovQg== - dependencies: - "@types/zen-observable" "^0.8.2" - zen-observable "^0.8.15" - -zen-observable@^0.8.15: - version "0.8.15" - resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15" - integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ== - zip-stream@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-4.1.0.tgz#51dd326571544e36aa3f756430b313576dc8fc79" diff --git a/packages/client/package.json b/packages/client/package.json index 7a1ae47c05..c6b0363adb 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -10,8 +10,8 @@ "lodash": "^4.17.21" }, "dependencies": { - "@discordapp/twemoji": "13.1.0", - "@fortawesome/fontawesome-free": "6.0.0", + "@discordapp/twemoji": "13.1.1", + "@fortawesome/fontawesome-free": "6.1.1", "@syuilo/aiscript": "0.11.1", "@types/escape-regexp": "0.0.1", "@types/glob": "7.2.0", @@ -33,8 +33,8 @@ "@types/webpack": "5.28.0", "@types/webpack-stream": "3.2.12", "@types/websocket": "1.0.5", - "@types/ws": "8.5.2", - "@typescript-eslint/parser": "5.14.0", + "@types/ws": "8.5.3", + "@typescript-eslint/parser": "5.17.0", "@vue/compiler-sfc": "3.2.31", "abort-controller": "3.0.0", "autobind-decorator": "2.4.0", @@ -44,15 +44,15 @@ "broadcast-channel": "4.10.0", "chart.js": "3.7.1", "chartjs-adapter-date-fns": "2.0.0", - "chartjs-plugin-gradient": "0.2.1", - "chartjs-plugin-zoom": "1.2.0", + "chartjs-plugin-gradient": "0.2.2", + "chartjs-plugin-zoom": "1.2.1", "compare-versions": "4.1.3", "content-disposition": "0.5.4", "css-loader": "6.7.1", - "cssnano": "5.1.1", + "cssnano": "5.1.6", "date-fns": "2.28.0", "escape-regexp": "0.0.1", - "eslint": "8.10.0", + "eslint": "8.12.0", "eslint-plugin-vue": "8.5.0", "eventemitter3": "4.0.7", "feed": "4.2.2", @@ -60,19 +60,19 @@ "idb-keyval": "6.1.0", "insert-text-at-cursor": "0.3.0", "ip-cidr": "3.0.4", - "json5": "2.2.0", + "json5": "2.2.1", "json5-loader": "4.0.1", - "katex": "0.15.2", + "katex": "0.15.3", "matter-js": "0.18.0", "mfm-js": "0.21.0", "misskey-js": "0.0.14", - "mocha": "9.2.1", + "mocha": "9.2.2", "ms": "2.1.3", "nested-property": "4.0.0", "parse5": "6.0.1", - "photoswipe": "git+https://github.com/dimsemenov/photoswipe#v5-beta", + "photoswipe": "5.2.2", "portscanner": "2.2.0", - "postcss": "8.4.8", + "postcss": "8.4.12", "postcss-loader": "6.2.1", "prismjs": "1.27.0", "private-ip": "2.3.3", @@ -85,7 +85,7 @@ "reflect-metadata": "0.1.13", "rndstr": "1.0.0", "s-age": "1.1.2", - "sass": "1.49.9", + "sass": "1.49.10", "sass-loader": "12.6.0", "seedrandom": "3.0.5", "strict-event-emitter-types": "2.0.0", @@ -93,21 +93,21 @@ "style-loader": "3.3.1", "syuilo-password-strength": "0.0.1", "textarea-caret": "3.1.0", - "three": "0.138.3", + "three": "0.139.0", "throttle-debounce": "3.0.1", "tinycolor2": "1.4.2", - "ts-loader": "9.2.7", + "ts-loader": "9.2.8", "tsc-alias": "1.5.0", - "tsconfig-paths": "3.13.0", - "twemoji-parser": "13.1.0", - "typescript": "4.6.2", + "tsconfig-paths": "3.14.1", + "twemoji-parser": "14.0.0", + "typescript": "4.6.3", "uuid": "8.3.2", "v-debounce": "0.1.2", "vanilla-tilt": "1.7.2", "vue": "3.2.31", "vue-loader": "17.0.0", "vue-prism-editor": "2.0.0-alpha.2", - "vue-router": "4.0.13", + "vue-router": "4.0.14", "vue-style-loader": "4.1.3", "vue-svg-loader": "0.17.0-beta.2", "vuedraggable": "4.0.1", @@ -117,9 +117,9 @@ "ws": "8.5.0" }, "devDependencies": { - "@typescript-eslint/eslint-plugin": "5.12.1", + "@typescript-eslint/eslint-plugin": "5.17.0", "cross-env": "7.0.3", - "cypress": "9.5.1", + "cypress": "9.5.3", "eslint-plugin-import": "2.25.4", "start-server-and-test": "1.14.0" } diff --git a/packages/client/src/account.ts b/packages/client/src/account.ts index 4aeceeccab..4772c0baa5 100644 --- a/packages/client/src/account.ts +++ b/packages/client/src/account.ts @@ -2,7 +2,7 @@ import { del, get, set } from '@/scripts/idb-proxy'; import { reactive } from 'vue'; import * as misskey from 'misskey-js'; import { apiUrl } from '@/config'; -import { waiting, api, popup, popupMenu, success } from '@/os'; +import { waiting, api, popup, popupMenu, success, alert } from '@/os'; import { unisonReload, reloadChannel } from '@/scripts/unison-reload'; import { showSuspendedDialog } from './scripts/show-suspended-dialog'; import { i18n } from './i18n'; @@ -89,7 +89,11 @@ function fetchAccount(token): Promise { signout(); }); } else { - signout(); + alert({ + type: 'error', + title: i18n.ts.failedToFetchAccountInformation, + text: JSON.stringify(res.error), + }); } } else { res.token = token; @@ -116,6 +120,7 @@ export async function login(token: Account['token'], redirect?: string) { if (_DEV_) console.log('logging as token ', token); const me = await fetchAccount(token); localStorage.setItem('account', JSON.stringify(me)); + document.cookie = `token=${token}; path=/; max-age=31536000`; // bull dashboardの認証とかで使う await addAccount(me.id, token); if (redirect) { diff --git a/packages/client/src/components/form/link.vue b/packages/client/src/components/form/link.vue index 3eb74425b0..b74e9bd684 100644 --- a/packages/client/src/components/form/link.vue +++ b/packages/client/src/components/form/link.vue @@ -80,7 +80,7 @@ export default defineComponent({ margin-right: 0.75em; flex-shrink: 0; text-align: center; - opacity: 0.8; + color: var(--fgTransparentWeak); &:empty { display: none; diff --git a/packages/client/src/components/global/url.vue b/packages/client/src/components/global/url.vue index 56a8c3453a..55f6c5d5f9 100644 --- a/packages/client/src/components/global/url.vue +++ b/packages/client/src/components/global/url.vue @@ -24,6 +24,14 @@ import { url as local } from '@/config'; import * as os from '@/os'; import { useTooltip } from '@/scripts/use-tooltip'; +function safeURIDecode(str: string) { + try { + return decodeURIComponent(str); + } catch { + return str; + } +} + export default defineComponent({ props: { url: { @@ -54,9 +62,9 @@ export default defineComponent({ schema: url.protocol, hostname: decodePunycode(url.hostname), port: url.port, - pathname: decodeURIComponent(url.pathname), - query: decodeURIComponent(url.search), - hash: decodeURIComponent(url.hash), + pathname: safeURIDecode(url.pathname), + query: safeURIDecode(url.search), + hash: safeURIDecode(url.hash), self: self, attr: self ? 'to' : 'href', target: self ? null : '_blank', diff --git a/packages/client/src/components/launch-pad.vue b/packages/client/src/components/launch-pad.vue index 4fe36bfefc..ffefc1b085 100644 --- a/packages/client/src/components/launch-pad.vue +++ b/packages/client/src/components/launch-pad.vue @@ -1,5 +1,5 @@ -