diff --git a/CHANGELOG.md b/CHANGELOG.md index 188405268..f538a4106 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 6e0f500be..a696bc5ce 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 000000000..3ccc098d3 --- /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 def791f9d..69d932783 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 e70249da1..1f558787a 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 5ec97f05f..99fe05375 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 934ffd2d4..72d81b4ac 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 5ccf1b2b6..1fe74fa9a 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 cf8915867..11dff184c 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 e2cf11b49..6326094dd 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 a00716fe0..f4e4a6218 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 0f12155e3..104e4ceb7 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 7f8ed82b8..8909a72ec 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 db45365a5..c6f2f59bd 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 483faba0d..f64458583 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 65aa436cf..77c822bf2 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 000000000..aea369a5c --- /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 000000000..2feb68d61 --- /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 b8150f701..a4e903aba 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 87712faf7..d6490f1cb 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 1c909dff1..09d20f936 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 066a3c673..f7638a53d 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 76835b44b..01bbe98a8 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 ceb74d690..d9cedee7d 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 9f85d3d1d..5417c1096 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 ed8a4c794..b61bba264 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 3d505c0ab..1183b9a78 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 4953c6890..86f1356c3 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 086944ccf..fefc2781f 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 000000000..4bd233366 --- /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 9d5db10eb..c76824c97 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 000000000..56b411f87 --- /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 e7b685488..814b37d44 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 348f88b3a..36d7ab90c 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 3440ca187..70180e2de 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 4c3c488da..e08dd6f0e 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 7a7bd3a1e..3f1f6f489 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 b155bf944..1d569fb87 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 cc13d7c1e..213ac3671 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 9e1979729..b4a342905 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 6452632db..69dc1721c 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 b0e09eedf..ab5f3dab6 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 3b13832a3..a0d390d79 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 1da1f875e..c4a7203aa 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 f25289d19..46109244f 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 545186fa1..08ca4962b 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 bbb036dd0..bb8d40b75 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 0548e19ee..e6c0e36f0 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 358e055aa..4ddf71709 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 3f5170700..6c51c93ff 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 ea7810496..1488b1eab 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 643e0b68e..7891b10fb 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 d7a7925eb..9bd97f988 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 a212b0d3e..4deae51c9 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 418d6e234..cf5fcb178 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 8e72d8aab..42b47ab15 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 66d780584..87d6accc3 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 037c13c43..1bffb23fa 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 160ca60f7..fa1c8f4d8 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 a0e2ce152..94410ec58 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 e338242c6..79ad019c9 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 a9ffe7369..6eb923424 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 0ea26427f..2b6f411ef 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 a909ab3ba..2f4b7d678 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 5f9af88db..e97fdd5ef 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 616bedc0d..253681695 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 50bcccbb7..a570400b7 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 dbc1f16a4..c1657b4be 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 f6a869985..b3832d9f0 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 83f1ec8fd..166c9e4cd 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 a65b46cc0..c2467fb5f 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 162862180..965500ac2 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 9fb144abb..0ef81971f 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 c79679366..7e12a6fac 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 1c04c3678..45852a603 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 857c2629e..8bddf34bc 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 f862276b4..28e0b867a 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 235fc2839..8ce2c367d 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 32f5f6bbe..8552b797b 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 ae263e19b..9919b7c53 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 afac27921..6151c96ad 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 1b3f94b70..4fbfdb234 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 7d71a20ad..77da162f6 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 02df58736..f3a267790 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 5191caea4..8aeacf462 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 ba69b11e8..846ccf9c0 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 9281e494d..ef07966e4 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 9c4e3418f..f95f64f77 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 393516add..4350ef133 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 354bd4f6e..78ef75ade 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 9a2fac1e7..c813414f9 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 7e2e73bdd..ae7e507c9 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 f6068fac7..680749f4d 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 9e4f1b316..5e230ad7b 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 1187b95ac..c253f9f66 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 b5c47990a..f8dabe06e 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 2f75841e5..1f94df033 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 b6d5e96d0..4c06a9de0 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 ad5e1a2ed..1f44c3556 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 e80e63278..aa2f1f536 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 49c1a7ee0..a9e92fa22 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 6aea8e57c..254a12160 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 715cc379b..2b65ff738 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 93cc36ec4..7f1519ac2 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 72751e83c..824ac69d7 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 ed86a4aa2..00f08842f 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 7d7b3386c..11a994a83 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 2383eea5b..8f6eab685 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 822c1e494..c2ac31bf8 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 844b067e2..4ac669857 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 6715adcf7..6a43c1444 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 05937c685..27d433eb3 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 08ac63035..01aeba1fb 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 7888c698e..9e8a81bb3 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 b5e9181d3..102b7b134 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 c5b0ea53c..a16009296 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 dca64d0a6..097a71661 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 659d3ac9a..88661865d 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 0a77465e3..9e75864c6 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 3e1881558..a3c10ba94 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 ad1d63b93..9a8a16d74 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 1bf36d470..da1bfe6e8 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 c3d9e120d..679c8bbfe 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 3d86e37cc..cd2fd74d4 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 d28778e22..46631df9e 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 aa37013c4..6fc6f2c4d 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 21be0a251..133dd3606 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 129881a71..c03fd1049 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 5d1d7c59e..4d4f73316 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 23110ce87..0af1f424f 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 57c126752..6b9592bcf 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 7fdf14666..65ccfcf55 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 5c5ef6601..9a85e4565 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 c5a47876d..783ea9ef7 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 b7dd8028b..f7cdd365e 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 b467b7b70..b0da8118b 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 b0ce54d37..3638518e6 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 163f132a4..f1dccee2c 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 7689e8233..abc142472 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 4e6d041a2..152989434 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 6b4eff078..e2db03f13 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 2820c7993..5f8921999 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 01754ec8f..629d70058 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 3663d974c..0ead2be00 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 89c421db6..650f8670e 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 41570078d..33076b6d3 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 4871dc4e1..c17765f4f 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 0ba0a8ee0..1d8eb1d61 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 138337ef5..61ce106d8 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 90e65ec4c..dc1976624 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 3e7d43fb0..3db942e6c 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 646d85a1e..119c4db19 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 e82116009..039df74f1 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 77a4adea6..232fbbd57 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 c5787d59d..67349c24e 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 a0eaa6125..7010ade0d 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 38a2d65cf..93a6c4e4e 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 a0cffb47f..67dbf28d8 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 f19c3ddbd..d16689a28 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 f488a71a0..6192978fa 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 dbad93d33..a4da40fff 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 470b9bef0..ae3b190f4 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 40e4c0199..cff58d617 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 c6d07e16f..5b547b3b7 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 d4251f2fe..da5420147 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 86978cc30..cb2be5ab3 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 ccd07489c..b7ee27db6 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 198108242..278131fb3 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 37878c414..dd16473f3 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 7cf2d5ffd..aca2540fd 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 000000000..8d50486ef --- /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 4206e3a3c..7b209c2d9 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 143119bfe..a01e9f3c6 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 2eec5bf0d..68a17867b 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 1fd5c8d5a..be4c2dcee 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 a9e565841..3edae4a85 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 8bf1c4341..9c150420b 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 a435dcc28..bf6cc1653 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 1ec86fef2..2703b4b9d 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 4a74c3fb0..17b9f3b5a 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 adaa7b86c..ed513eda0 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 4e6366aa1..a4b373f5c 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 3b9e0a94e..5cf26251b 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 66b634c87..3c39bf0f3 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 4c04e019d..0546acfac 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 bba66e98c..222efdcef 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 92cbba817..7a4923b94 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 4e6b8b3d2..ced34ba31 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 accca5de7..c519b452e 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 f0cb2ba3c..004e4c131 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 36c4da81b..dd693789c 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 a99964555..edfedc175 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 7595c38e8..3c0c0642e 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 e0cf8632f..a0a735082 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 54e714e19..451969d97 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 0760eef52..b5c06792b 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 bd571327d..717c3e508 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 d40c9363c..3f3a4d142 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 b699c6fa2..89884ed38 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 c5e73c013..0540e6ab0 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 a45547290..77e17b3ba 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 16456b9c0..94dcfe502 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 4372c283c..895ffed0b 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 e69de29bb..000000000 diff --git a/packages/backend/src/server/api/endpoints/channels/show.ts b/packages/backend/src/server/api/endpoints/channels/show.ts index ea4e01307..87665a986 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 57a9fa44b..deaa29901 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 32beb24d6..e065d897a 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 2f2b4aeeb..13104f324 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 c630302b9..5d72f5c1b 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 531847d15..4afe4222a 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 675db1d57..b6c0eb702 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 1c955d64f..378811eba 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 2627884ee..4b6782fca 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 0a3b25c94..c3d73c168 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 0ac5ccd04..b67d844f6 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 3c68beee1..7ffe89a1e 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 7e5cb2498..80293df5d 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 5f565a63f..61c56e631 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 e45ec633d..f9b4ea89e 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 974fc9fba..4938a69d1 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 181365c7e..a2bc0c7aa 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 ab8e4aeeb..4b3f5f2dc 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 4ae10f062..3d7f514c8 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 4994615cc..ab9d411ec 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 9bf0e3d61..1feab273a 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 f09816d57..1e7aa2b16 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 c020b243e..1aa2e8429 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 9db140183..c17412677 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 e27297176..07e5c07c6 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 5bfe43fc9..2fbb8a15c 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 8758a64a3..02a030cd5 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 47efc59b8..2f41b16e9 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 24d8256ca..18ec5affe 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 3b60b89b3..a8f42c481 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 eb6c0f3eb..8074a3b34 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 f8bf785ee..b00ee0e2a 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 d154bfc3c..b858114ae 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 5b4594070..4f6dafd7c 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 b00008a86..d136239e5 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 123794d08..82fe38078 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 80a2334cf..b0c1225be 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 6e6afa4f1..5b78f6ac7 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 70478430d..35806b2bc 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 f33237c8b..0116a55fb 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 0c4c99271..e906b8204 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 7951e393b..d5e1b19e5 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 2b69b1f8c..eb2f75308 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 c5633f68b..45e7a9863 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 16509d2dc..f9f6a33a8 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 8cb6b6a63..184005eb5 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 bc3e0aff4..e7d7518c5 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 c70704f9a..0bcbf37dd 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 7e9175cbf..ee2abbea1 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 abbf07212..b3b3b3923 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 be162817f..64f5ec05f 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 7d9bd44d1..6ea8cb357 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 2e291a34a..7ff6409ca 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 647fa77fa..45b6e98c8 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 771c98b21..af929b04e 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 b957fd079..c69245379 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 389ff1b81..331807852 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 85d0a6254..b2964e68c 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 000000000..2e2fd00b8 --- /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 000000000..2821eaa5f --- /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 000000000..54e456373 --- /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 000000000..02fa1edb5 --- /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 000000000..f87b9753f --- /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 14de4e102..ea0600d0e 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 49ace2160..dbf1f6c86 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 a9b926c4f..405af5ec1 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 a0945af51..f66d75873 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 8d38e509a..db12ae922 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 1aff1f63f..e1ae282a9 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 dacee40d0..7e857e673 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 a8cf2a666..0b173dbe2 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 96657f8d3..99c8b973f 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 9a863b714..8683a7f75 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 2552c0f99..8f5d21db6 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 e4a9b2889..4b18ab602 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 22ff2275c..804e146fa 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 bcc2c44c0..41dc5ac8e 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 d41fab22d..a48f7a0aa 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 26aaa0919..cb402ecaa 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 9bcb64b65..f9893527e 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 12fc88b1f..03edf30b3 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 bdd1aeecd..28bfade2f 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 ef52d0366..6380b331f 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 43e5d1ef6..3555424fa 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 c6503eb05..bb85c9200 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 e77892b15..af9b5f0a1 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 6fdb8e88f..069f11fa4 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 fde66b241..0f976d18b 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 a9aadba33..5e8c31eaf 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 0829d0e4c..6c6402603 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 34f4c155f..c7bc5dc0a 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 acaa11847..7096aaa3d 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 7cac53060..c171cd39f 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 ddf691f53..e35ad9ebf 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 cab78e576..20793db98 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 4e3facae5..3dcce8550 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 31cd1a335..636f3c714 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 24c8f467e..bf95ab36f 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 1d26ab266..8d253c1f3 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 ea34ca3aa..cc602857d 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 18cd98b16..046337f04 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 3abf232af..7acc545c4 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 92fea4de6..f8a1ee29d 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 6c7714e19..a48973a0d 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 5a1c4128a..04b754f4a 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 1e104b6bc..26b1f20df 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 b0a1036c7..42cf5216e 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 9f6d8464d..fc775d7cc 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 f4898a3c7..f68006994 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 efbdf968f..75c1acc30 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 fe5d431ea..46bc780ab 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 10bfb7eca..30a5beb1d 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 e52de7859..77dc59d3e 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 c1a8c2c02..33abd5439 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 11aad0f73..b1289e601 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 55ec9f915..b31990b2e 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 28ca1162c..3ffb0f5ba 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 f48e1ddbf..41ceee3b2 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 b3e17dfd9..1016aa892 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 1a0599f9e..d5260256d 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 aeefb98c8..b7ad96eef 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 a8663ada8..78311292c 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 2c4c61d51..76863f07d 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 034a9d2db..260665c63 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 fadb94c90..5f51980e9 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 5ec99031e..52353a14c 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 7b55a1671..c2d199434 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 e091b8e1b..0be385dbb 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 897b5de3f..f74d80e2a 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 263c102a7..b1a568145 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 180a9386d..d138019a7 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 ba2a71951..02bec31b1 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 7e6b93b39..e74db8466 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 b0f88948a..3f7118ad2 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 1a667ddb4..e5e39ba00 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 01f284a57..26f172637 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 089f7de0c..04197574c 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 ce032db18..61bb768a6 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 e6e4398fa..e72b71e2f 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 94bbdeca5..877d44c38 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 57523c848..d8034e83f 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 0cb38e2a9..b80347828 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 90cf59038..3b0a75d79 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 95a9ec6a0..a68cebfeb 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 f4b56fc8a..13a362a75 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 b98e3f8bf..eba8dc58d 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 cc4c2cc9c..48bf6f733 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 7a5d08541..7530b4e0b 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 e88c38723..f86f394f8 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 198d28705..5c6719007 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 c4f3784b0..d7b5ddd5f 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 f1257fdf1..fe29ba522 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 ab6a37e3c..bb14b62f3 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 131befa39..5f75dc688 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 5d5dd1fa1..02b149f52 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 9c5dea1aa..b9191dd08 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 fb9d5e15f..acb16ead8 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 39fad71dd..cf69e2194 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 d78e707ec..9a53db1f3 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 781e0560d..bae91ec4c 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 839794566..549b11c9f 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 f3a0424ab..2b6f82a91 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 a41641213..d243317d9 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 d82c0be52..85e40f136 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 3b0cb2ba8..e1744e05b 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 a240bec8f..5fbb549e0 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 b8113cd1b..20829f70c 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 ca9777d38..56531fa1f 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 bca607d7e..bda2f8f92 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 06d7e79e8..f35392a34 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 1fbaf40df..27bd38bd3 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 e27171048..bddd0355a 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 c3908b255..e5cd5a30d 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 82eb6cb21..1e7ce1981 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 8c5f13362..6f373aaf4 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 cf23656f8..ffd609dd8 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 88baf16b6..43ca3eff4 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 9b83b1953..84d98769d 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 236aa7993..5a0948bca 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 62b00f56f..a7cbcb1c1 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 28827c596..915a9e9ee 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 ef95dc7e8..d9ed711e0 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 8d5c09e6a..41122c92e 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 152930dbd..df7d125d0 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 6f0da503f..1ab45588d 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 debaf3476..4a2f94b42 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 033311a3c..e96b06a35 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 3be081d0e..44a0d01ca 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 b6fb38bc5..23b210b7a 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 000000000..407301f2f --- /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 3c49d37ee..132168fb3 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 adbfcdadf..000000000 --- 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 7f6722247..000000000 --- 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 630179e7a..000000000 --- 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 0111a2257..000000000 --- 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 8c02ef7ef..000000000 --- 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 c3388fd1b..000000000 --- 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 62cea5208..942b2709d 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 994c098b7..32a030f93 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 67a02742d..970a3f8b5 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 7a1ae47c0..c6b0363ad 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 4aeceecca..4772c0baa 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 3eb74425b..b74e9bd68 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 56a8c3453..55f6c5d5f 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 4fe36bfef..ffefc1b08 100644 --- a/packages/client/src/components/launch-pad.vue +++ b/packages/client/src/components/launch-pad.vue @@ -1,5 +1,5 @@ -