diff --git a/.config/example.yml b/.config/example.yml index 4da7399359..ef91c86f52 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -15,10 +15,7 @@ url: https://example.tld/ #───┘ Port and TLS settings └─────────────────────────────────── # -# Misskey supports two deployment options for public. -# - -# Option 1: With Reverse Proxy +# Misskey requires a reverse proxy to support HTTPS connections. # # +----- https://example.tld/ ------------+ # +------+ |+-------------+ +----------------+| @@ -26,30 +23,12 @@ url: https://example.tld/ # +------+ |+-------------+ +----------------+| # +---------------------------------------+ # -# You need to setup reverse proxy. (eg. nginx) -# You do not define 'https' section. +# You need to set up a reverse proxy. (e.g. nginx) +# An encrypted connection with HTTPS is highly recommended +# because tokens may be transferred in GET requests. -# Option 2: Standalone -# -# +- https://example.tld/ -+ -# +------+ | +---------------+ | -# | User | ---> | | Misskey (443) | | -# +------+ | +---------------+ | -# +------------------------+ -# -# You need to run Misskey as root. -# You need to set Certificate in 'https' section. - -# To use option 1, uncomment below line. -#port: 3000 # A port that your Misskey server should listen. - -# To use option 2, uncomment below lines. -#port: 443 - -#https: -# # path for certification -# key: /etc/letsencrypt/live/example.tld/privkey.pem -# cert: /etc/letsencrypt/live/example.tld/fullchain.pem +# The port that your Misskey server should listen on. +port: 3000 # ┌──────────────────────────┐ #───┘ PostgreSQL configuration └──────────────────────────────── diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fc8122339..e32e71146d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,21 +12,52 @@ You should also include the user name that made the change. ## 12.x.x (unreleased) +### Improvements +- Bull Dashboardを組み込み、ジョブキューの確認や操作を行えるように @syuilo +- Check that installed Node.js version fulfills version requirement @ThatOneCalculator +- Server: performance improvements @syuilo + +### Bugfixes +- API: fix endpoint endpoint @Johann150 + +## 12.108.1 (2022/03/12) + +### Bugfixes +- リレーが動作しない問題を修正 @xianonn +- ulidを使用していると動作しない問題を修正 @syuilo +- 外部からOGPが正しく取得できない問題を修正 @syuilo +- instance can not get the files from other instance when there are items in allowedPrivateNetworks in .config/default.yml @ybw2016v + +## 12.108.0 (2022/03/09) + ### NOTE このバージョンからNode v16.14.0以降が必要です ### Changes - ノートの最大文字数を設定できる機能が廃止され、デフォルトで一律3000文字になりました @syuilo +- Misskey can no longer terminate HTTPS connections. @Johann150 + - If you did not use a reverse proxy (e.g. nginx) before, you will probably need to adjust + your configuration file and set up a reverse proxy. The `https` configuration key is no + longer recognized! ### Improvements - インスタンスデフォルトテーマを設定できるように @syuilo +- ミュートに期限を設定できるように @syuilo +- アンケートが終了したときに通知が作成されるように @syuilo - プロフィールの追加情報を最大16まで保存できるように @syuilo - 連合チャートにPub&Subを追加 @syuilo +- 連合チャートにActiveを追加 @syuilo +- デフォルトで10秒以上時間がかかるデータベースへのクエリは中断されるように @syuilo + - 設定ファイルの`db.extra`に`statement_timeout`を設定することでタイムアウト時間を変更できます +- Client: スプラッシュスクリーンにインスタンスのアイコンを表示するように @syuilo ### Bugfixes - Client: リアクションピッカーの高さが低くなったまま戻らないことがあるのを修正 @syuilo - Client: ユーザー名オートコンプリートが正しく動作しない問題を修正 @syuilo - Client: タッチ操作だとウィジェットの編集がしにくいのを修正 @xianonn +- Client: register_note_view_interruptor()が動かないのを修正 @syuilo +- Client: iPhone X以降(?)でページの内容が全て表示しきれないのを修正 @tamaina +- Client: fix image caption on mobile @nullobsi ## 12.107.0 (2022/02/12) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6e0f500be5..a696bc5ceb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,6 +6,9 @@ Also, you might receive comments on your Issue/PR in Japanese, but you do not ne The accuracy of machine translation into Japanese is not high, so it will be easier for us to understand if you write it in the original language. It will also allow the reader to use the translation tool of their preference if necessary. +## Roadmap +See [ROADMAP.md](./ROADMAP.md) + ## Issues Before creating an issue, please check the following: - To avoid duplication, please search for similar issues before creating a new issue. @@ -198,11 +201,13 @@ MongoDBの時とは違い、findOneでレコードを取得する時に対象レ MongoDBは`null`で返してきてたので、その感覚で`if (x === null)`とか書くとバグる。代わりに`if (x == null)`と書いてください ### Migration作成方法 -``` -npx ts-node ./node_modules/typeorm/cli.js migration:generate -n 変更の名前 -o +packages/backendで: +```sh +npx typeorm migration:generate -d ormconfig.js -o ``` -作成されたスクリプトは不必要な変更を含むため除去してください。 +- 生成後、ファイルをmigration下に移してください +- 作成されたスクリプトは不必要な変更を含むため除去してください ### コネクションには`markRaw`せよ **Vueのコンポーネントのdataオプションとして**misskey.jsのコネクションを設定するとき、必ず`markRaw`でラップしてください。インスタンスが不必要にリアクティブ化されることで、misskey.js内の処理で不具合が発生するとともに、パフォーマンス上の問題にも繋がる。なお、Composition APIを使う場合はこの限りではない(リアクティブ化はマニュアルなため)。 diff --git a/README.md b/README.md index 799be299d5..c7bc9ef219 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ ## Sponsors
- RSS3 + RSS3
## Backers diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 0000000000..c12526bbce --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,28 @@ +# 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? diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml index 914a16bb2f..def791f9d4 100644 --- a/locales/ar-SA.yml +++ b/locales/ar-SA.yml @@ -32,7 +32,7 @@ uploading: "يرفع..." save: "حفظ" users: "المستخدمون" addUser: "اضافة مستخدم" -favorite: "إضافة إلى المفضلة" +favorite: "أضفها للمفضلة" favorites: "المفضلات" unfavorite: "إزالة من المفضلة" favorited: "أُضيف إلى المفضلة." @@ -106,6 +106,7 @@ clickToShow: "اضغط للعرض" sensitive: "محتوى حساس" add: "إضافة" reaction: "التفاعلات" +reactionSetting: "التفاعلات المراد عرضها في منتقي التفاعلات." reactionSettingDescription2: "اسحب لترتيب ، انقر للحذف ، استخدم \"+\" للإضافة." rememberNoteVisibility: "تذكر إعدادت مدى رؤية الملاحظات" attachCancel: "أزل المرفق" @@ -139,6 +140,8 @@ flagAsBot: "علّمه كحساب آلي" flagAsBotDescription: "فعّل هذا الخيار إذا كان هذا الحساب يُدار عبر برمجية. إذا فُعل فسيكون بمثابة علامة للمطورين الآخرين لتجنب سلاسل لا متناهية من التفاعل بين حسابات الآلية وضبط أنظمة ميسكي للتعامل مع هذا الحساب كآلي." flagAsCat: "علّم هذا الحساب كحساب قط" flagAsCatDescription: "فعّل هذا الخيار لوضع علامة على الحساب لتوضيح أنه حساب قط." +flagShowTimelineReplies: "أظهر التعليقات في الخيط الزمني" +flagShowTimelineRepliesDescription: "يظهر الردود في الخط الزمني" autoAcceptFollowed: "اقبل طلبات المتابعة تلقائيا من الحسابات المتابَعة" addAccount: "أضف حساباً" loginFailed: "فشل الولوج" @@ -230,6 +233,8 @@ resetAreYouSure: "هل تريد إعادة التعيين؟" saved: "حُفظ" messaging: "المحادثة" upload: "ارفع" +keepOriginalUploading: "ابق الصورة الأصلية" +keepOriginalUploadingDescription: "يحفظ الصور المرفوعة على حالتها الأصلية، وان عطّل ستولد نسخة مخصصة من الصورة." fromDrive: "من المخزن" fromUrl: "عبر رابط" uploadFromUrl: "ارفع عبر رابط" @@ -276,6 +281,7 @@ emptyDrive: "قرص التخزين فارغ" emptyFolder: "هذا المجلد فارغ" unableToDelete: "لا يمكن حذفه" inputNewFileName: "ادخل الإسم الجديد للملف" +inputNewDescription: "أدخل تعليقًا توضيحيًا" inputNewFolderName: "ادخل الإسم الجديد للمجلد" circularReferenceFolder: "المجلد المستهدف ينتمي للمجلد الذي تريد حذفه" hasChildFilesOrFolders: "الان الملف غير فارغ. لا يمكن حذفه" @@ -315,8 +321,6 @@ disablingTimelinesInfo: "سيتمكن المديرون والمشرفون من registration: "إنشاء حساب" enableRegistration: "تفعيل إنشاء الحسابات الجديدة" invite: "دعوة" -proxyRemoteFiles: "جلب الملفات البعيدة عبر وكيل" -proxyRemoteFilesDescription: "إذا فُعّل هذا الإعداد ، ستُجلب الملفات البعيدة غير الموجودة في التخزين المحلي للخادم عبر وكيل وتُنشأ لها صور مصغرة. لن يأثر على تخزين الخادم." driveCapacityPerLocalAccount: "حصة التخزين لكل مستخدم محلي" driveCapacityPerRemoteAccount: "حصة التخزين لكل مستخدم بعيد" inMb: "بالميغابايت" @@ -345,6 +349,7 @@ name: "الإسم" antennaSource: "مصدر الهوائي" antennaKeywords: "الكلمات المفتاحية للإستقبال" antennaExcludeKeywords: "الكلمات المفتاحية المستثناة" +antennaKeywordsDescription: "افصل بينهم بمسافة لاستخدام معامل \"و\" أو بسطر لاستخدام معامل \"أو\"" notifyAntenna: "نبهني بصول ملاحظات جديدة" withFileAntenna: "ملاحظات تحوي ملفات فقط" antennaUsersDescription: "اكتب اسم مستخدم لكل سطر" @@ -410,7 +415,6 @@ next: "التالية" retype: "أعد الكتابة" noteOf: "ملاحظات {user}" inviteToGroup: "دعوة إلى فريق" -maxNoteTextLength: "حد عدد المحارف لكل ملاحظة" quoteAttached: "اِقتُبسَ" quoteQuestion: "أتريد تضمينها كاقتباس" noMessagesYet: "ليس هناك رسائل بعد" @@ -469,6 +473,7 @@ hideThisNote: "إخفاء هذه الملاحظة" showFeaturedNotesInTimeline: "أظهر الملاحظات الشائعة في الخيط الزمني" objectStorageBaseUrl: "الرابط الأساسي" objectStoragePrefix: "البادئة" +objectStoragePrefixDesc: "ستُحفظ الملفات في مجلدات تحوي اسماءها هذه البادئة." objectStorageEndpoint: "نقطة النهاية" objectStorageRegion: "المنطقة" objectStorageUseSSL: "استخدم SSL" @@ -630,7 +635,10 @@ experimentalFeatures: "ميّزات اختبارية" developer: "المطور" makeExplorable: "أظهر الحساب في صفحة \"استكشاف\"" makeExplorableDescription: "بتعطيل هذا الخيار لن يظهر حسابك في صفحة \"استكشاف\"" +wide: "عريض" +narrow: "رفيع" reloadToApplySetting: "سيُطبق هذا الإعداد بعد إعادة تحميل الصفحة، أتريد إعادة تحميلها الآن؟" +needReloadToApply: "سيطبق هذا بعد إعادة التحميل." showTitlebar: "اعرض شريط العنوان" clearCache: "امسح التخزين المؤقت" onlineUsersCount: "{n} مستخدم متصل" @@ -661,6 +669,7 @@ capacity: "السعة" inUse: "مستخدم" editCode: "حرر الشفرة" apply: "تطبيق" +receiveAnnouncementFromInstance: "استلم إشعارات من هذا المثيل" emailNotification: "إشعارات البريد الكتروني" inChannelSearch: "ابحث عن قناة" useReactionPickerForContextMenu: "افتح منتقي التفاعلات عند النقر بالزر الأيمن" @@ -674,6 +683,7 @@ unlikeConfirm: "أتريد إلغاء إعجابك؟" fullView: "ملء الشاشة" quitFullView: "اخرج من وضع ملء للشاشة" addDescription: "أضف وصفًا" +userPagePinTip: "لعرض ملاحظة هنا اختر \"دبسها على الصفحة الشخصية\" من قائمة تلك الملاحظة." notSpecifiedMentionWarning: "في الملاحظة ذكر لمستخدمين لن يستلموها." info: "عن" userInfo: "معلومات المستخدم" @@ -748,11 +758,30 @@ makeReactionsPublicDescription: "هذا سيجعل قائمة تفاعلاتك classic: "تقليدي" muteThread: "اكتم النقاش" unmuteThread: "ارفع الكتم عن النقاش" +ffVisibility: "مرئية المتابِعين/المتابَعين" +ffVisibilityDescription: "يسمح لك بتحديد من يمكنهم رؤية متابِعيك ومتابَعيك." deleteAccountConfirm: "سيحذف حسابك نهائيًا، أتريد المتابعة؟" incorrectPassword: "كلمة السر خاطئة." +voteConfirm: "متيقِّن من تصويتك لـ {choice}؟" hide: "إخفاء" leaveGroup: "مغادرة الفريق" +leaveGroupConfirm: "متيقن من مغادرة \"{name}\"؟" welcomeBackWithName: "مرحبًا بك مجددًا {name}" +clickToFinishEmailVerification: "انقر [{ok}] لاستيثاق بريدك الإلكتروني." +overridedDeviceKind: "نوع الجهاز" +smartphone: "هاتف ذكي" +tablet: "جهاز لوحي" +auto: "تلقائي" +themeColor: "لون السمة" +size: "الحجم" +numberOfColumn: "عدد الأعمدة" +searchByGoogle: "غوغل" +mutePeriod: "مدة الكتم" +indefinitely: "أبدًا" +tenMinutes: "10 دقائق" +oneHour: "ساعة" +oneDay: "يوم" +oneWeek: "أسبوع" _emailUnavailable: used: "هذا البريد الإلكتروني مستخدم" format: "صيغة البريد الإلكتروني غير صالحة" @@ -775,6 +804,7 @@ _accountDelete: inProgress: "عملية الحذف جارية" _ad: back: "رجوع" + reduceFrequencyOfThisAd: "قلل عرض هذا الإعلان" _forgotPassword: enterEmail: "أدخل البريد الإلكتروني المرتبط بحسابك لكي يرسل إليك رابط لإعادة تعيين كلمة المرور." ifNoEmail: "إذا لم تربط حسابك ببريد إلكتروني سيتوجب عليك التواصل مع مدير الموقع." @@ -801,7 +831,7 @@ _registry: createKey: "أنشئ مفتاحًا" _aboutMisskey: about: "ميسكي هو برمجية مفتوحة المصدر يطورها syuilo منذ 2014." - contributors: "المساهم الرئيسي" + contributors: "المساهمون الرئيسيون" allContributors: "كل المساهمين" source: "الشفرة المصدرية" translation: "ترجم ميسكي" @@ -823,8 +853,11 @@ _mfm: urlDescription: "يمكن عرض الروابط" link: "رابط" bold: "عريض" + boldDescription: "جعل الحروف أثخن لإبرازها." small: "صغير" + smallDescription: "يعرض المحتوى صغيرًا ورفيعًا." center: "وسط" + centerDescription: "يمركز المحتوى في الوَسَط." quote: "اقتبس" emoji: "إيموجي مخصص" search: "البحث" @@ -1088,9 +1121,12 @@ _postForm: quotePlaceholder: "اقتبس هذه الملاحظة…" channelPlaceholder: "انشر في قناة..." _placeholders: + a: "ما الذي تنوي فعله؟" + b: "ماذا يحدث حولك ؟" c: "ما الذي تفكر فيه؟" d: "ما الذي تريد قوله؟" e: "أكتب..." + f: "بانتظارك لتكتب..." _profile: name: "الإسم" username: "اسم المستخدم" @@ -1109,23 +1145,25 @@ _exportOrImport: muteList: "المستخدمون المكتومون" blockingList: "المستخدمون المحجوبون" userLists: "القوائم" + excludeMutingUsers: "استثن الحسابات المكتومة" + excludeInactiveUsers: "استثن المستخدمين الخاملين" _charts: federation: "الفديرالية" apRequest: "الطلبات" - usersIncDec: "اختلاف عدد المستخدمين" + usersIncDec: "تباين عدد المستخدمين" usersTotal: "مجموع عدد المستخدمين والمستخدمات" activeUsers: "المستخدمون النشطون" - notesIncDec: "اختلاف عدد الملاحظات" - localNotesIncDec: "اختلاف عدد الملاحظات المحلية" - remoteNotesIncDec: "اختلاف عدد الملاحظات البعيدة" + notesIncDec: "تباين عدد الملاحظات" + localNotesIncDec: "تباين عدد الملاحظات المحلية" + remoteNotesIncDec: "تباين عدد الملاحظات البعيدة" notesTotal: "إجمالي الملاحظات" - filesIncDec: "اختلاف عدد الملفات" + filesIncDec: "تباين عدد الملفات" filesTotal: "العدد الإجمالي للملفات" _instanceCharts: requests: "الطلبات" - users: "اختلاف عدد المستخدمين" - notes: "اختلاف عدد الملاحظات" - files: "اختلاف عدد الملفات" + users: "تباين عدد المستخدمين" + notes: "تباين عدد الملاحظات" + files: "تباين عدد الملفات" _timelines: home: "الرئيسي" local: "المحلي" @@ -1139,14 +1177,21 @@ _pages: updated: "نجح تعديل الصفحة" deleted: "نجح حذف الصفحة" pageSetting: "إعدادات الصفحة" + nameAlreadyExists: "رابط الصفحة موجود مسبقًا" + invalidNameTitle: "رابط الصفحة ليس صالحًا" + invalidNameText: "تأكد أن عنوان الصفحة ليس فارغًا" + editThisPage: "عدّل هذه الصفحة" viewSource: "اظهر المصدر" viewPage: "اعرض صفحاتك" like: "أعجبني" unlike: "أزل الإعجاب" my: "صفحاتي" + liked: "الصفحات المُعجب بها" featured: "الأكثر شعبية" contents: "المحتوى" + variables: "متغيّرات" title: "العنوان" + url: "رابط الصفحة" summary: "ملخص الصفحة" alignCenter: "توسيط العناصر" hideTitleWhenPinned: "اخف عنوان الصفحة عند تدبيسها في ملف الشخصي" @@ -1401,6 +1446,7 @@ _notification: youReceivedFollowRequest: "تلقيتَ طلب متابعة" yourFollowRequestAccepted: "قُبل طلب المتابعة" youWereInvitedToGroup: "دُعيت إلى فريقٍ" + pollEnded: "ظهرت نتائج الاستطلاع" _types: all: "الكل" follow: "متابِعون جدد" @@ -1409,6 +1455,7 @@ _notification: renote: "أعد النشر" quote: "الاقتباسات" reaction: "التفاعلات" + pollVote: "مصوِت شارك في الاستطلاع" receiveFollowRequest: "طلبات المتابعة المتلقاة" followRequestAccepted: "طلبات المتابعة المقبولة" app: "إشعارات التطبيقات المرتبطة" diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml index 5cc2bb91b6..870a16bc09 100644 --- a/locales/bn-BD.yml +++ b/locales/bn-BD.yml @@ -325,8 +325,6 @@ disablingTimelinesInfo: "আপনি এই টাইমলাইনগুল registration: "নিবন্ধন" enableRegistration: "নতুন ব্যাবহারকারী নিবন্ধন চালু করুন" invite: "আমন্ত্রণ" -proxyRemoteFiles: "রিমোট ফাইলসমুহ প্রক্সি করুন" -proxyRemoteFilesDescription: "যখন এই সেটিংটি চালু থাকে, তখন অসংরক্ষিত বা অতিরিক্ত ক্ষমতার কারণে দূরবর্তী ফাইলগুলিকে স্থানীয়ভাবে প্রক্সি করা হবে এবং থাম্বনেলগুলিও তৈরি করা হবে৷ সার্ভার স্টোরেজ ব্যাবহার করে না," driveCapacityPerLocalAccount: "প্রত্যেক স্থানীয় ব্যাবহারকারীর জন্য ড্রাইভের জায়গা" driveCapacityPerRemoteAccount: "প্রত্যেক রিমোট ব্যাবহারকারীর জন্য ড্রাইভের জায়গা" inMb: "মেগাবাইটে লিখুন" @@ -422,7 +420,6 @@ next: "পরবর্তী" retype: "পুনঃ প্রবেশ" noteOf: "{user} এর নোট" inviteToGroup: "গ্রুপে আমন্ত্রণ জানান" -maxNoteTextLength: "নোট এর সর্বোচ্চ দৈর্ঘ্য" quoteAttached: "উদ্ধৃত" quoteQuestion: "উদ্ধৃতি হিসাবে সংযুক্ত করবেন?" noMessagesYet: "কোন মেসেজ নেই" @@ -831,6 +828,10 @@ smartphone: "স্মার্টফোন" tablet: "ট্যাবলেট" auto: "স্বয়ংক্রিয়" themeColor: "থিমের রং" +size: "আকার" +numberOfColumn: "কলামের সংখ্যা" +searchByGoogle: "গুগল" +indefinitely: "অনির্দিষ্ট" _emailUnavailable: used: "এই ইমেইল ঠিকানাটি ইতোমধ্যে ব্যবহৃত হয়েছে" format: "এই ইমেল ঠিকানাটি সঠিকভাবে লিখা হয়নি" diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml new file mode 100644 index 0000000000..5f74cb6bef --- /dev/null +++ b/locales/ca-ES.yml @@ -0,0 +1,26 @@ +--- +_lang_: "Català" +headlineMisskey: "Una xarxa connectada per notes" +search: "Cercar" +notifications: "Notificacions" +username: "Nom d'usuari" +password: "Contrasenya" +forgotPassword: "Contrasenya oblidada" +fetchingAsApObject: "Cercant en el Fediverse..." +ok: "OK" +gotIt: "Ho he entès!" +cancel: "Cancel·lar" +smtpUser: "Nom d'usuari" +smtpPass: "Contrasenya" +searchByGoogle: "Cercar" +_mfm: + search: "Cercar" +_sfx: + notification: "Notificacions" +_widgets: + notifications: "Notificacions" +_profile: + username: "Nom d'usuari" +_deck: + _columns: + notifications: "Notificacions" diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml index dcbbfcab82..2f5e375372 100644 --- a/locales/cs-CZ.yml +++ b/locales/cs-CZ.yml @@ -449,6 +449,7 @@ clearCache: "Vyprázdnit mezipaměť" info: "Informace" user: "Uživatelé" administration: "Administrace" +searchByGoogle: "Vyhledávání" _email: _follow: title: "Máte nového následovníka" diff --git a/locales/de-DE.yml b/locales/de-DE.yml index c5bf407407..e70249da16 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -14,16 +14,16 @@ gotIt: "Verstanden!" cancel: "Abbrechen" enterUsername: "Benutzername eingeben" renotedBy: "Renote von {user}" -noNotes: "Keine Notizen" -noNotifications: "Keine Benachrichtigungen" +noNotes: "Keine Notizen gefunden" +noNotifications: "Keine Benachrichtigungen gefunden" instance: "Instanz" settings: "Einstellungen" basicSettings: "Allgemeine Einstellungen" otherSettings: "Weitere Einstellungen" -openInWindow: "In Fenster öffnen" +openInWindow: "In einem Fenster öffnen" profile: "Profil" timeline: "Chronik" -noAccountDescription: "Dieser Nutzer hat seine Profilbeschreibung noch nicht ausgefüllt." +noAccountDescription: "Dieser Nutzer hat seine Profilbeschreibung noch nicht ausgefüllt" login: "Anmelden" loggingIn: "Du wirst angemeldet …" logout: "Abmelden" @@ -38,8 +38,8 @@ unfavorite: "Aus Favoriten entfernen" favorited: "Zu Favoriten hinzugefügt." alreadyFavorited: "Bereits zu den Favoriten hinzugefügt." cantFavorite: "Hinzufügen zu Favoriten fehlgeschlagen." -pin: "Anheften" -unpin: "Lösen" +pin: "An dein Profil anheften" +unpin: "Von deinem Profil lösen" copyContent: "Inhalt kopieren" copyLink: "Link kopieren" delete: "Löschen" @@ -47,7 +47,7 @@ deleteAndEdit: "Löschen und Bearbeiten" deleteAndEditConfirm: "Möchtest du diese Notiz wirklich löschen und bearbeiten? Alle Reaktionen, Renotes und Antworten dieser Notiz werden verloren gehen." addToList: "Zu Liste hinzufügen" sendMessage: "Nachricht senden" -copyUsername: "Benutzername kopieren" +copyUsername: "Benutzernamen kopieren" searchUser: "Nach einem Benutzer suchen" reply: "Antworten" loadMore: "Mehr laden" @@ -63,12 +63,12 @@ import: "Import" export: "Export" files: "Dateien" download: "Herunterladen" -driveFileDeleteConfirm: "Möchtest du die Datei „{name}“ wirklich löschen? Die zugehörige Notiz wird ebenso verschwinden." +driveFileDeleteConfirm: "Möchtest du die Datei „{name}“ wirklich löschen? Notizen mit dieser Datei werden ebenso verschwinden." unfollowConfirm: "Möchtest du {name} nicht mehr folgen?" exportRequested: "Du hast einen Export angefragt. Dies kann etwas Zeit in Anspruch nehmen. Sobald der Export abgeschlossen ist, wird er deiner Drive hinzugefügt." importRequested: "Du hast einen Import angefragt. Dies kann etwas Zeit in Anspruch nehmen." lists: "Listen" -noLists: "Keine Listen" +noLists: "Keine Listen gefunden" note: "Notiz" notes: "Notizen" following: "Folgt" @@ -79,16 +79,16 @@ manageLists: "Listen verwalten" error: "Fehler" somethingHappened: "Ein Fehler ist aufgetreten" retry: "Wiederholen" -pageLoadError: "Laden der Seite fehlgeschlagen." +pageLoadError: "Die Seite konnte nicht geladen werden." pageLoadErrorDescription: "Dieser Fehler wird meist durch Netzwerkfehler oder den Browser-Cache verursacht. Bitte leere den Cache oder versuche es nach einiger Zeit erneut." serverIsDead: "Dieser Server antwortet nicht. Bitte warte einen Moment und versuche es dann erneut." youShouldUpgradeClient: "Bitte aktualisiere diese Seite, um eine neuere Version deines Clients zu verwenden." -enterListName: "Name der Liste eingeben" +enterListName: "Listennamen eingeben" privacy: "Privatsphäre" makeFollowManuallyApprove: "Follow-Anfragen benötigen Bestätigung" defaultNoteVisibility: "Standardsichtbarkeit" follow: "Folgen" -followRequest: "Follow-Anfrage" +followRequest: "Follow-Anfrage senden" followRequests: "Follow-Anfragen" unfollow: "Nicht mehr folgen" followRequestPending: "Follow-Anfrage ausstehend" @@ -107,11 +107,11 @@ sensitive: "NSFW" add: "Hinzufügen" reaction: "Reaktionen" reactionSetting: "In der Reaktionsauswahl anzuzeigende Reaktionen" -reactionSettingDescription2: "Ziehe zum Anordnen, klicke zum Löschen, drücke + zum Hinzufügen" +reactionSettingDescription2: "Ziehe um Anzuordnen, klicke um zu löschen, drücke „+“ um hinzuzufügen" rememberNoteVisibility: "Notizsichtbarkeit merken" attachCancel: "Anhang entfernen" markAsSensitive: "Als NSFW markieren" -unmarkAsSensitive: "NSFW-Markierung entfernen" +unmarkAsSensitive: "Als nicht NSFW markieren" enterFileName: "Dateinamen eingeben" mute: "Stummschalten" unmute: "Stummschaltung aufheben" @@ -129,20 +129,20 @@ selectWidget: "Widget auswählen" editWidgets: "Widgets bearbeiten" editWidgetsExit: "Fertig" customEmojis: "Benutzerdefinierte Emojis" -emoji: "Emojis" +emoji: "Emoji" emojis: "Emojis" emojiName: "Emoji-Name" emojiUrl: "Emoji-URL" addEmoji: "Emoji hinzufügen" settingGuide: "Empfohlene Einstellung" cacheRemoteFiles: "Dateien von fremden Instanzen im Cache speichern" -cacheRemoteFilesDescription: "Ist diese Einstellung deaktiviert, so werden Dateien fremder Instanzen direkt von dort geladen. Hierdurch wird Speicherplatz auf dem Server gespart, aber durch fehlende Generierung von Vorschaubildern mehr Bandbreite verwendet." +cacheRemoteFilesDescription: "Ist diese Einstellung deaktiviert, so werden Dateien fremder Instanzen direkt von dort geladen. Hierdurch wird Speicherplatz auf diesem Server gespart, aber durch fehlende Generierung von Vorschaubildern mehr Bandbreite verwendet." flagAsBot: "Als Bot markieren" flagAsBotDescription: "Aktiviere diese Option, falls dieses Benutzerkonto durch ein Programm gesteuert wird. Falls aktiviert, agiert es als Flag für andere Entwickler zur Verhinderung von endlosen Kettenreaktionen mit anderen Bots und lässt Misskeys interne Systeme dieses Benutzerkonto als Bot behandeln." flagAsCat: "Als Katze markieren" flagAsCatDescription: "Aktiviere diese Option, um dieses Benutzerkonto als Katze zu markieren." flagShowTimelineReplies: "Antworten in der Chronik anzeigen" -flagShowTimelineRepliesDescription: "Ist diese Option aktiviert, so werden Antworten von Benutzern auf die Notizen anderer Benuzter in der Chronik an angezeigt." +flagShowTimelineRepliesDescription: "Ist diese Option aktiviert, so werden Antworten von Benutzern auf die Notizen anderer Benutzer in der Chronik angezeigt." autoAcceptFollowed: "Follow-Anfragen von Benutzern, denen du folgst, automatisch akzeptieren" addAccount: "Benutzerkonto hinzufügen" loginFailed: "Anmeldung fehlgeschlagen" @@ -151,12 +151,12 @@ general: "Allgemein" wallpaper: "Hintergrund" setWallpaper: "Hintergrund festlegen" removeWallpaper: "Hintergrund entfernen" -searchWith: "Suche: {q}" +searchWith: "Suchen: {q}" youHaveNoLists: "Du hast keine Listen" followConfirm: "Möchtest du {name} wirklich folgen?" proxyAccount: "Proxy-Benutzerkonto" proxyAccountDescription: "Ein Proxy-Benutzerkonto ist ein Benutzerkonto, das sich für Nutzer unter bestimmten Konditionen wie ein Follower aus einer fremden Instanz verhält. Zum Beispiel wird die Aktivität eines Nutzers aus einer fremden Instanz nicht an diese Instanz übermittelt, falls es keinen Benutzer dieser Instanz gibt, der diesem Nutzer aus fremder Instanz folgt. In diesem Fall folgt stattdessen das Proxy-Benutzerkonto." -host: "Host" +host: "Hostname" selectUser: "Benutzer auswählen" recipient: "Empfänger" annotation: "Anmerkung" @@ -194,17 +194,17 @@ blockedInstancesDescription: "Gib die Hostnamen der Instanzen, welche blockiert muteAndBlock: "Stummschaltungen und Blockierungen" mutedUsers: "Stummgeschaltete Benutzer" blockedUsers: "Blockierte Benutzer" -noUsers: "Keine Benutzer" +noUsers: "Keine Benutzer gefunden" editProfile: "Profil bearbeiten" noteDeleteConfirm: "Möchtest du diese Notiz wirklich löschen?" -pinLimitExceeded: "Es können nicht noch mehr Notizen angeheftet werden" -intro: "Misskey Installation abgeschlossen! Lass uns nun ein Administratorkonto erstellen." +pinLimitExceeded: "Du kannst nicht noch mehr Notizen anheften." +intro: "Misskey ist installiert! Lass uns nun ein Administratorkonto einrichten." done: "Fertig" processing: "In Bearbeitung …" preview: "Vorschau" default: "Standard" -noCustomEmojis: "Keine benutzerdefinierten Emojis vorhanden" -noJobs: "Es gibt keine Jobs" +noCustomEmojis: "Keine benutzerdefinierten Emojis gefunden" +noJobs: "Keine Jobs vorhanden" federating: "Wird föderiert" blocked: "Blockiert" suspended: "Gesperrt" @@ -217,10 +217,10 @@ instanceFollowers: "Follower der Instanz" instanceUsers: "Benutzer der Instanz" changePassword: "Passwort ändern" security: "Sicherheit" -retypedNotMatch: "Beide Eingaben stimmen nicht überein." +retypedNotMatch: "Die Eingaben stimmen nicht überein." currentPassword: "Aktuelles Passwort" newPassword: "Neues Passwort" -newPasswordRetype: "Neues Passwort (wiederholen)" +newPasswordRetype: "Neues Passwort bestätigen" attachFile: "Datei anhängen" more: "Mehr!" featured: "Beliebt" @@ -234,7 +234,7 @@ removed: "Erfolgreich gelöscht" removeAreYouSure: "Möchtest du „{x}“ wirklich entfernen?" deleteAreYouSure: "Möchtest du „{x}“ wirklich löschen?" resetAreYouSure: "Wirklich zurücksetzen?" -saved: "Gespeichert" +saved: "Erfolgreich gespeichert" messaging: "Chat" upload: "Hochladen" keepOriginalUploading: "Originalbild speichern" @@ -295,7 +295,7 @@ avatar: "Profilbild" banner: "Banner" nsfw: "NSFW" whenServerDisconnected: "Bei Verbindungsverlust zum Server" -disconnectedFromServer: "Verbindung zum Server wurde getrennt" +disconnectedFromServer: "Die Verbindung zum Server wurde getrennt" reload: "Aktualisieren" doNothing: "Ignorieren" reloadConfirm: "Seite neu laden?" @@ -307,7 +307,7 @@ normal: "Normal" instanceName: "Name der Instanz" instanceDescription: "Beschreibung der Instanz" maintainerName: "Betreiber" -maintainerEmail: "Betreiber-E-Mail" +maintainerEmail: "Betreiber-Email" tosUrl: "URL der Nutzungsbedingungen" thisYear: "Jahr" thisMonth: "Monat" @@ -325,20 +325,18 @@ disablingTimelinesInfo: "Administratoren und Moderatoren haben immer Zugriff auf registration: "Registrieren" enableRegistration: "Registration neuer Benutzer erlauben" invite: "Einladen" -proxyRemoteFiles: "Dateien fremder Instanzen durch Proxy leiten" -proxyRemoteFilesDescription: "Wenn diese Einstellung aktiviert ist, dann werden Dateien von fremdem Instanzen, welche entweder nicht lokal gespeichert sind oder durch Überschreiten des Speicherlimits gelöscht wurden, durch einen Proxy geleitet. Hierbei wird auch ein Vorschaubild generiert. \n Dies hat keinen Effekt auf den Speicherplatz des Servers." -driveCapacityPerLocalAccount: "Drive-Kapazität pro lokales Benutzerkonto" +driveCapacityPerLocalAccount: "Drive-Kapazität pro lokalem Benutzerkonto" driveCapacityPerRemoteAccount: "Drive-Kapazität pro Benutzer fremder Instanzen" inMb: "In Megabytes" iconUrl: "Icon-URL (favicon etc)" bannerUrl: "Banner-URL" backgroundImageUrl: "Hintergrundbild-URL" -basicInfo: "Basisdaten" +basicInfo: "Grundlegende Informationen" pinnedUsers: "Angeheftete Benutzer" pinnedUsersDescription: "Gib durch Leerzeichen getrennte Benutzer an, die an die \"Erkunden\"-Seite angeheftet werden sollen." pinnedPages: "Angeheftete Seiten" -pinnedPagesDescription: "Gib durch Leerzeilen getrennte Pfäde zu Seiten an, die du an die Startseite dieser Instanz anheften möchtest.\n" -pinnedClipId: "ID des angehefteten Clips" +pinnedPagesDescription: "Gib durch Leerzeilen getrennte Pfäde zu Seiten an, die an die Startseite dieser Instanz angeheftet werden sollen.\n" +pinnedClipId: "ID des anzuheftenden Clips" pinnedNotes: "Angeheftete Notizen" hcaptcha: "hCaptcha" enableHcaptcha: "hCaptcha aktivieren" @@ -348,14 +346,14 @@ recaptcha: "reCAPTCHA" enableRecaptcha: "reCAPTCHA aktivieren" recaptchaSiteKey: "Site key" recaptchaSecretKey: "Secret key" -avoidMultiCaptchaConfirm: "Das Verwenden von mehreren Captcha-Systemen kann zu Störungen führen. Möchtest du die anderen Systeme deaktivieren? Du kannst mehrere Systeme aktiviert lassen, in dem du auf Abbrechen drückst." +avoidMultiCaptchaConfirm: "Das Verwenden von mehreren Captcha-Systemen kann zu Störungen führen. Sollen die anderen Systeme deaktiviert werden? Durch Abbrechen können mehrere Systeme aktiviert bleiben." antennas: "Antennen" manageAntennas: "Antennen verwalten" name: "Name" antennaSource: "Antennenquelle" antennaKeywords: "Zu beobachtende Schlüsselwörter" antennaExcludeKeywords: "Zu ignorierende Schlüsselwörter" -antennaKeywordsDescription: "Mit Leerzeichen für eine \"UND\"-Verknüpfung trennen, durch Zeilenumbrüche für eine \"ODER\"-Verknüpfung trennen" +antennaKeywordsDescription: "Zum Nutzen einer \"UND\"-Verknüpfung Einträge mit Leerzeichen trennen, zum Nutzen einer \"ODER\"-Verknüpfung Einträge mit einem Zeilenumbruch trennen" notifyAntenna: "Über neue Notizen benachrichtigen" withFileAntenna: "Nur Notizen mit Dateien" enableServiceworker: "ServiceWorker aktivieren" @@ -376,14 +374,14 @@ recentlyDiscoveredUsers: "Vor kurzem gefundene Benutzer" exploreUsersCount: "Es gibt {count} Benutzer" exploreFediverse: "Das Fediverse erkunden" popularTags: "Beliebte Schlagwörter" -userList: "Listen" +userList: "Liste" about: "Über" aboutMisskey: "Über Misskey" administrator: "Administrator" token: "Token" twoStepAuthentication: "Zwei-Faktor-Authentifizierung" moderator: "Moderator" -nUsersMentioned: "{n} Benutzer reden darüber" +nUsersMentioned: "Von {n} Benutzern erwähnt" securityKey: "Sicherheitsschlüssel" securityKeyName: "Schlüsselname" registerSecurityKey: "Sicherheitsschlüssel registrieren" @@ -391,7 +389,7 @@ lastUsed: "Zuletzt benutzt" unregister: "Deaktivieren" passwordLessLogin: "Passwortloses Anmelden einrichten" resetPassword: "Passwort zurücksetzen" -newPasswordIs: "Das neue Passwort ist \"{password}\"" +newPasswordIs: "Das neue Passwort ist „{password}“" reduceUiAnimation: "Animationen der Benutzeroberfläche reduzieren" share: "Teilen" notFound: "Nicht gefunden" @@ -407,7 +405,7 @@ close: "Schließen" group: "Gruppe" groups: "Gruppen" createGroup: "Gruppe erstellen" -ownedGroups: "Eigene Gruppen" +ownedGroups: "Meine Gruppen" joinedGroups: "Beigetretene Gruppen" invites: "Einladungen" groupName: "Gruppenname" @@ -415,30 +413,29 @@ members: "Mitglieder" transfer: "Übertragen" messagingWithUser: "Privatchat" messagingWithGroup: "Gruppenchat" -title: "Betreff" +title: "Titel" text: "Text" enable: "Aktivieren" next: "Weiter" retype: "Erneut eingeben" noteOf: "Notiz von {user}" inviteToGroup: "Zu Gruppe einladen" -maxNoteTextLength: "Maximale Länge von Notizen" -quoteAttached: "Zitiert" -quoteQuestion: "Als Zitat anfügen?" -noMessagesYet: "Noch keine Nachrichten" +quoteAttached: "Zitat" +quoteQuestion: "Als Zitat anhängen?" +noMessagesYet: "Noch keine Nachrichten vorhanden" newMessageExists: "Du hast eine neue Nachricht" onlyOneFileCanBeAttached: "Es kann pro Nachricht nur eine Datei angehängt werden" -signinRequired: "Anmeldung erforderlich" +signinRequired: "Bitte melde dich an" invitations: "Einladungen" invitationCode: "Einladungscode" -checking: "Wird überprüft..." +checking: "Wird überprüft …" available: "Verfügbar" unavailable: "Unverfügbar" -usernameInvalidFormat: "Klein- und Großbuchstaben, Zahlen sowie Unterstriche sind verwendbar." +usernameInvalidFormat: "Du kannst Klein- und Großbuchstaben, Zahlen sowie Unterstriche verwenden" tooShort: "Zu kurz" tooLong: "Zu lang" weakPassword: "Schwaches Passwort" -normalPassword: "Normales Passwort" +normalPassword: "Durchschnittliches Passwort" strongPassword: "Starkes Passwort" passwordMatched: "Stimmt überein" passwordNotMatched: "Stimmt nicht überein" @@ -454,18 +451,18 @@ useOsNativeEmojis: "Eingebaute Emojis des Betriebssystems benutzen" disableDrawer: "Keine ausfahrbaren Menüs verwenden" youHaveNoGroups: "Keine Gruppen vorhanden" joinOrCreateGroup: "Lass dich zu einer Gruppe einladen oder erstelle deine eigene." -noHistory: "Kein Verlauf" +noHistory: "Kein Verlauf gefunden" signinHistory: "Anmeldungsverlauf" disableAnimatedMfm: "MFM, die Animationen enthalten, deaktivieren" -doing: "In Bearbeitung..." +doing: "In Bearbeitung …" category: "Kategorie" tags: "Schlagwörter" -docSource: "Quelle dieses Dokuments" +docSource: "Quellcode dieses Dokuments" createAccount: "Benutzerkonto erstellen" existingAccount: "Bestehendes Benutzerkonto" regenerate: "Regenerieren" fontSize: "Schriftgröße" -noFollowRequests: "Du hast keine ausstehende Follow-Anfragen" +noFollowRequests: "Keine ausstehenden Follow-Anfragen vorhanden" openImageInNewTab: "Bilder in neuem Tab öffnen" dashboard: "Dashboard" local: "Lokal" @@ -476,25 +473,25 @@ dayOverDayChanges: "Veränderung zu Gestern" appearance: "Aussehen" clientSettings: "Client-Einstellungen" accountSettings: "Benutzerkonto-Einstellungen" -promotion: "Hervorgehoben" -promote: "Hervorheben" +promotion: "Werbung" +promote: "Werbung schalten" numberOfDays: "Anzahl der Tage" hideThisNote: "Diese Notiz verstecken" -showFeaturedNotesInTimeline: "Beliebte Notizen in Chronik anzeigen" +showFeaturedNotesInTimeline: "Beliebte Notizen in der Chronik anzeigen" objectStorage: "Objektspeicher" useObjectStorage: "Objektspeicher verwenden" objectStorageBaseUrl: "Basis-URL" -objectStorageBaseUrlDesc: "Als Referenz verwendete URL. Verwendest du einen CDN oder Proxy, gib dessen URL an. S3: 'https://.s3.amazonaws.com', GCS: 'https://storage.googleapis.com/' etc." +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" -objectStorageBucketDesc: "Bitte gib den Bucket-Namen an, der bei deinem Anbieter verwendet wird." +objectStorageBucketDesc: "Bitte gib den Namen des Buckets an, der bei deinem Anbieter verwendet wird." objectStoragePrefix: "Prefix" objectStoragePrefixDesc: "Dateien werden in Ordnern unter diesem Prefix gespeichert." objectStorageEndpoint: "Endpoint" -objectStorageEndpointDesc: "Im Falle von S3 leerlassen, für andere Anbieter den relevanten Endpoint im Format \"\" oder \":\" angeben." +objectStorageEndpointDesc: "Im Falle von S3 leerlassen, für andere Anbieter den relevanten Endpoint im Format „“ oder „:“ angeben." objectStorageRegion: "Region" -objectStorageRegionDesc: "Gib eine Region wie z.B. \"xx-east-1\" an. Falls dein Anbieter nicht zwischen Regionen unterscheidet, lass dieses Feld leer oder gib \"us-east-1\" an." +objectStorageRegionDesc: "Gib eine Region wie z.B. „xx-east-1“ an. Falls dein Anbieter nicht zwischen Regionen unterscheidet, lass dieses Feld leer oder gib „us-east-1“ an." objectStorageUseSSL: "SSL verwenden" -objectStorageUseSSLDesc: "Deaktiviere dies, falls du für die API-Verbindungen kein HTTPS verwenden wirst" +objectStorageUseSSLDesc: "Deaktiviere dies, falls du für API-Verbindungen kein HTTPS verwenden wirst" objectStorageUseProxy: "Über Proxy verbinden" objectStorageUseProxyDesc: "Deaktiviere dies, falls du keinen Proxy für den Objektspeicher verwenden wirst" objectStorageSetPublicRead: "Bei Upload auf \"public-read\" stellen" @@ -505,7 +502,7 @@ newNoteRecived: "Es gibt neue Notizen" sounds: "Töne" listen: "Anhören" none: "Nichts" -showInPage: "In Seite anzeigen" +showInPage: "In einer Seite anzeigen" popout: "Pop-Up" volume: "Lautstärke" masterVolume: "Gesamtlautstärke" @@ -524,7 +521,7 @@ sort: "Sortieren" ascendingOrder: "Aufsteigende Reihenfolge" descendingOrder: "Absteigende Reihenfolge" scratchpad: "Testumgebung" -scratchpadDescription: "Die Testumgebung bietet eine Umgebung für AiScript-Experimente. Dort kannst du AiScript schreiben, ausführen sowie dessen Auswirkungen auf Misskey überprüfen." +scratchpadDescription: "Die Testumgebung bietet einen Bereich für AiScript-Experimente. Dort kannst du AiScript schreiben, ausführen sowie dessen Auswirkungen auf Misskey überprüfen." output: "Ausgabe" script: "Skript" disablePagesScript: "AiScript auf Seiten deaktivieren" @@ -547,14 +544,14 @@ addedRelays: "Hinzugefügte Relays" serviceworkerInfo: "Muss für Push-Benachrichtigungen aktiviert sein." deletedNote: "Gelöschte Notiz" invisibleNote: "Private Notiz" -enableInfiniteScroll: "Automatisch mehr Notizen laden" +enableInfiniteScroll: "Automatisch mehr laden" visibility: "Sichtbarkeit" poll: "Umfrage" useCw: "Inhaltswarnung verwenden" enablePlayer: "Video-Player öffnen" disablePlayer: "Video-Player schließen" expandTweet: "Tweet ausklappen" -themeEditor: "Farbthemen-Editor" +themeEditor: "Farbschema-Editor" description: "Beschreibung" describeFile: "Beschreibung hinzufügen" enterFileDescription: "Beschreibung eingeben" @@ -590,13 +587,13 @@ smtpHost: "Host" smtpPort: "Port" smtpUser: "Benutzername" smtpPass: "Passwort" -emptyToDisableSmtpAuth: "Benutzername und Passwort leer lassen um SMTP-Verifizierung zu deaktivieren" +emptyToDisableSmtpAuth: "Benutzername und Passwort leer lassen, um SMTP-Verifizierung zu deaktivieren" smtpSecure: "Für SMTP-Verbindungen implizit SSL/TLS verwenden" -smtpSecureInfo: "Schalte dies aus, falls du STARTTLS verwendest" -testEmail: "Email-Versand testen" -wordMute: "Wort-Stummschaltung" -regexpError: "Regular Expression error" -regexpErrorDescription: "Error in the regular expression on line {line} in your {tab} word mutes:" +smtpSecureInfo: "Schalte dies aus, falls du STARTTLS verwendest." +testEmail: "Emailversand testen" +wordMute: "Wortstummschaltung" +regexpError: "Fehler in einem regulären Ausdruck" +regexpErrorDescription: "Im regulären Ausdruck deiner {tab}en Wortstummschaltungen ist ein Fehler aufgetreten:" instanceMute: "Instanzstummschaltungen" userSaysSomething: "{name} hat etwas gesagt" makeActive: "Aktivieren" @@ -610,11 +607,11 @@ database: "Datenbank" channel: "Kanäle" create: "Erstellen" notificationSetting: "Benachrichtigungseinstellungen" -notificationSettingDesc: "Wähle die Art der anzuzeigenden Benachrichtigung" +notificationSettingDesc: "Wähle die Art der anzuzeigenden Benachrichtigungen." useGlobalSetting: "Globale Einstellung verwenden" -useGlobalSettingDesc: "Ist dies eingeschaltet, so werden die Benachrichtigungseinstellungen deines Benutzerkontos verwendet. Wenn dies ausgeschaltet ist, können individuelle Einstellungen vorgenommen werden." -other: "Andere" -regenerateLoginToken: "Anmeldungstoken regenerieren" +useGlobalSettingDesc: "Ist diese Option aktiviert, werden die Benachrichtigungseinstellungen deines Benutzerkontos verwendet. Durch ausschalten dieser Option können individuelle Einstellungen vorgenommen werden." +other: "Anderes" +regenerateLoginToken: "Anmeldetoken regenerieren" regenerateLoginTokenDescription: "Den zur Anmeldung intern verwendeten Token regenerieren. Normalerweise wird dies nicht benötigt. Bei Regeneration werden alle Geräte ausgeloggt." setMultipleBySeparatingWithSpace: "Trenne Elemente durch ein Leerzeichen um mehrere Einstellungen zu kofigurieren." fileIdOrUrl: "Datei-ID oder URL" @@ -624,7 +621,7 @@ abuseReports: "Meldungen" reportAbuse: "Melden" reportAbuseOf: "{name} melden" fillAbuseReportDescription: "Bitte gib zusätzliche Informationen zu dieser Meldung an. Falls es sich um eine spezielle Notiz handelt, bitte gib dessen URL an." -abuseReported: "Die Meldung wurde versendet. Vielen Dank." +abuseReported: "Deine Meldung wurde versendet. Vielen Dank." reporter: "Melder" reporteeOrigin: "Herkunft des Gemeldeten" reporterOrigin: "Herkunft des Meldenden" @@ -637,18 +634,18 @@ openInSideView: "In Seitenansicht öffnen" defaultNavigationBehaviour: "Standardnavigationsverhalten" editTheseSettingsMayBreakAccount: "Bei Bearbeitung dieser Einstellungen besteht die Gefahr, dein Benutzerkonto zu beschädigen." instanceTicker: "Instanz-Informationen von Notizen" -waitingFor: "Warte auf {x}" +waitingFor: "Warte auf {x} …" random: "Zufällig" system: "System" switchUi: "UI wechseln" desktop: "Desktop" -clip: "Clip" +clip: "Clip erstellen" createNew: "Neu erstellen" optional: "Optional" createNewClip: "Neuen Clip erstellen" public: "Öffentlich" i18nInfo: "Misskey wird durch freiwillige Helfer in viele verschiedene Sprachen übersetzt. Auf {link} kannst du mithelfen." -manageAccessTokens: "Zugriffstoken verwalten" +manageAccessTokens: "Zugriffstokens verwalten" accountInfo: "Benutzerkonto-Informationen" notesCount: "Anzahl der Notizen" repliesCount: "Anzahl gesendeter Antworten" @@ -663,15 +660,15 @@ pollVotesCount: "Anzahl gesendeter Antworten auf Umfragen" pollVotedCount: "Anzahl erhaltener Antworten auf Umfragen" yes: "Ja" no: "Nein" -driveFilesCount: "Anzahl an Drive-Dateien" +driveFilesCount: "Anzahl der Dateien in Drive" driveUsage: "Drive-Auslastung" noCrawle: "Crawler-Indexierung ablehnen" -noCrawleDescription: "Suchmaschinen bitten, die eigene Profilseite, Notizen, Seiten usw. nicht zu indexieren" -lockedAccountInfo: "Auch wenn du Follow-Anfragen auf manuelle Bestätigung setzt, wird jeder deine Notizen öffentlich sehen können, sofern du die Notizsichtbarkeit nicht auf \"Nur Follower\" setzt." +noCrawleDescription: "Suchmaschinen bitten, die eigene Profilseite, Notizen, Seiten usw. nicht zu indexieren." +lockedAccountInfo: "Auch wenn du Follow-Anfragen auf manuelle Bestätigung setzt, wird jede deiner Notizen öffentlich sichtbar sein, sofern du ihre Notizsichtbarkeit nicht auf \"Nur Follower\" setzt." alwaysMarkSensitive: "Medien standardmäßig als NSFW markieren" loadRawImages: "Anstatt Vorschaubilder immer Originalbilder anzeigen" disableShowingAnimatedImages: "Animierte Bilder nicht abspielen" -verificationEmailSent: "Eine Verifizierungsnachricht wurde versendet. Besuche den dort enthaltenen Link, um die Verifizierung abzuschließen." +verificationEmailSent: "Eine Bestätigungsmail wurde an deine Email-Adresse versendet. Besuche den dort enthaltenen Link, um die Verifizierung abzuschließen." notSet: "Nicht konfiguriert" emailVerified: "Email-Adresse bestätigt" noteFavoritesCount: "Anzahl an als Favorit markierter Notizen" @@ -682,12 +679,12 @@ useSystemFont: "Standardschriftart des Systems verwenden" clips: "Clips" experimentalFeatures: "Experimentelle Funktionalitäten" developer: "Entwickler" -makeExplorable: "Benutzerkonto in \"Erkunden\" sichtbar machen" -makeExplorableDescription: "Wenn diese Option deaktiviert ist, ist dein Benutzerkonto nicht im \"Erkunden\"-Bereich sichtbar." +makeExplorable: "Benutzerkonto in „Erkunden“ sichtbar machen" +makeExplorableDescription: "Wenn diese Option deaktiviert ist, ist dein Benutzerkonto nicht im „Erkunden“-Bereich sichtbar." showGapBetweenNotesInTimeline: "Abstände zwischen Notizen auf der Chronik anzeigen" duplicate: "Duplizieren" left: "Links" -center: "Mitte" +center: "Mittig" wide: "Breit" narrow: "Schmal" reloadToApplySetting: "Diese Einstellung tritt nach einer Aktualisierung der Seite in Kraft. Jetzt aktualisieren?" @@ -698,19 +695,19 @@ onlineUsersCount: "{n} Benutzer sind online" nUsers: "{n} Benutzer" nNotes: "{n} Notizen" sendErrorReports: "Fehlerberichte senden" -sendErrorReportsDescription: "Ist diese Option aktiviert, so werden beim Auftreten von Fehlern detaillierte Fehlerinformationen an Misskey weitergegeben, was zur Verbesserung der Qualität von Misskey beiträgt.\nEnthalten in diesen Informationen sind u.a. die Version deines Betriebssystems, welchen Browser du verwendest und ein Verlauf deiner Aktivitäten." -myTheme: "Mein Farbthema" +sendErrorReportsDescription: "Ist diese Option aktiviert, so werden beim Auftreten von Fehlern detaillierte Fehlerinformationen an Misskey weitergegeben, was zur Verbesserung der Qualität von Misskey beiträgt.\nEnthalten in diesen Informationen sind u.a. die Version deines Betriebssystems, welchen Browser du verwendest und ein Verlauf deiner Aktivitäten innerhalb Misskey." +myTheme: "Mein Farbschema" backgroundColor: "Hintergrundfarbe" accentColor: "Akzentfarbe" textColor: "Textfarbe" -saveAs: "Speichern als…" +saveAs: "Speichern als …" advanced: "Fortgeschritten" value: "Wert" createdAt: "Erstellt am" updatedAt: "Zuletzt geändert am" saveConfirm: "Änderungen speichern?" deleteConfirm: "Wirklich löschen?" -invalidValue: "Ungültiger Wert." +invalidValue: "Dieser Wert ist ungültig." registry: "Registry" closeAccount: "Benutzerkonto schließen" currentVersion: "Momentane Version" @@ -723,11 +720,11 @@ inUse: "Verwendet" editCode: "Code bearbeiten" apply: "Anwenden" receiveAnnouncementFromInstance: "Benachrichtigungen von dieser Instanz empfangen" -emailNotification: "E-Mail-Benachrichtigungen" +emailNotification: "Email-Benachrichtigungen" publish: "Veröffentlichen" inChannelSearch: "In Kanal suchen" useReactionPickerForContextMenu: "Reaktionsauswahl durch Rechtsklick öffnen" -typingUsers: "{users} ist/sind am schreiben..." +typingUsers: "{users} ist/sind am schreiben …" jumpToSpecifiedDate: "Zu bestimmtem Datum springen" showingPastTimeline: "Es wird eine alte Chronik angezeigt" clear: "Zurückkehren" @@ -737,19 +734,19 @@ unlikeConfirm: "\"Gefällt mir\" wirklich entfernen?" fullView: "Vollansicht" quitFullView: "Vollansicht verlassen" addDescription: "Beschreibung hinzufügen" -userPagePinTip: "Um Notizen hier erscheinen zu lassen, drücke \"Anheften\" im Menü individueller Notizen." +userPagePinTip: "Um Notizen hier erscheinen zu lassen, drücke \"An dein Profil anheften\" im Menü individueller Notizen." notSpecifiedMentionWarning: "Diese Notiz enthält Erwähnungen von Nutzern, die nicht als Empfänger ausgewählt sind" info: "Über" userInfo: "Benutzerinformation" unknown: "Unbekannt" -onlineStatus: "Online-Status" -hideOnlineStatus: "Online-Status verbergen" -hideOnlineStatusDescription: "Das Verbergen deines Online-Statuses reduziert die Nützlichkeit von Funktionen wie der Suche." +onlineStatus: "Onlinestatus" +hideOnlineStatus: "Onlinestatus verbergen" +hideOnlineStatusDescription: "Das Verbergen deines Onlinestatuses reduziert die Nützlichkeit von Funktionen wie der Suche." online: "Online" active: "Aktiv" offline: "Offline" notRecommended: "Nicht empfohlen" -botProtection: "Bot-Schutz" +botProtection: "Schutz vor Bots" instanceBlocking: "Blockierte Instanzen" selectAccount: "Benutzerkonto auswählen" switchAccount: "Konto wechseln" @@ -761,9 +758,9 @@ administration: "Verwaltung" accounts: "Benutzerkonten" switch: "Wechseln" noMaintainerInformationWarning: "Betreiberinformationen sind nicht konfiguriert." -noBotProtectionWarning: "Bot-Schutz ist nicht konfiguriert." +noBotProtectionWarning: "Schutz vor Bots ist nicht konfiguriert." configure: "Konfigurieren" -postToGallery: "Neuen Galerie-Beitrag erstellen" +postToGallery: "Neuen Galeriebeitrag erstellen" gallery: "Galerie" recentPosts: "Neue Beiträge" popularPosts: "Beliebte Beiträge" @@ -775,7 +772,7 @@ priority: "Priorität" high: "Hoch" middle: "Mittel" low: "Niedrig" -emailNotConfiguredWarning: "Keine Email-Adresse hinterlegt" +emailNotConfiguredWarning: "Keine Email-Adresse hinterlegt." ratio: "Verhältnis" previewNoteText: "Vorschau anzeigen" customCss: "Benutzerdefiniertes CSS" @@ -793,9 +790,9 @@ misskeyUpdated: "Misskey wurde aktualisiert!" whatIsNew: "Änderungen anzeigen" translate: "Übersetzen" translatedFrom: "Aus {x} übersetzt" -accountDeletionInProgress: "Löschung des Benutzerkontos momentan in Bearbeitung" +accountDeletionInProgress: "Die Löschung deines Benutzerkontos ist momentan in Bearbeitung." usernameInfo: "Ein Name, durch den dein Benutzerkonto auf diesem Server identifiziert werden kann. Du kannst das Alphabet (a~z, A~Z), Ziffern (0~9) oder Unterstriche (_) verwenden. Benutzernamen können später nicht geändert werden." -aiChanMode: "Ai Modus" +aiChanMode: "Ai-Modus" keepCw: "Inhaltswarnungen beibehalten" pubSub: "Pub/Sub Benutzerkonten" lastCommunication: "Letzte Kommunikation" @@ -804,7 +801,7 @@ unresolved: "Ungelöst" breakFollow: "Follower entfernen" itsOn: "Eingeschaltet" itsOff: "Ausgeschaltet" -emailRequiredForSignup: "Angaben einer Email-Adresse als benötigt markieren" +emailRequiredForSignup: "Angabe einer Email-Adresse als benötigt markieren" unread: "Ungelesen" filter: "Filter" controlPanel: "Systemsteuerung" @@ -819,10 +816,10 @@ ffVisibilityDescription: "Konfiguriere wer sehen kann, wem du folgst sowie wer d continueThread: "Weiteren Threadverlauf anzeigen" deleteAccountConfirm: "Dein Benutzerkonto wird unwiderruflich gelöscht. Trotzdem fortfahren?" incorrectPassword: "Falsches Passwort." -voteConfirm: "Wirklich für \"{choice}\" abstimmen?" +voteConfirm: "Wirklich für „{choice}“ abstimmen?" hide: "Inhalt verbergen" leaveGroup: "Gruppe verlassen" -leaveGroupConfirm: "Möchtest du \"{name}\" wirklich verlassen?" +leaveGroupConfirm: "Möchtest du „{name}“ wirklich verlassen?" useDrawerReactionPickerForMobile: "Auf mobilen Geräten ausfahrbare Reaktionsauswahl anzeigen" welcomeBackWithName: "Willkommen zurück, {name}" clickToFinishEmailVerification: "Drücke bitte auf [{ok}], um die Email-Bestätigung abzuschließen." @@ -833,6 +830,16 @@ auto: "Automatisch" themeColor: "Instanzfarbe" size: "Größe" numberOfColumn: "Spaltenanzahl" +searchByGoogle: "Googlen" +instanceDefaultLightTheme: "Instanzweites Standardfarbschema (Hell)" +instanceDefaultDarkTheme: "Instanzweites Standardfarbschema (Dunkel)" +instanceDefaultThemeDescription: "Gib den Farbschemencode im Objektformat ein." +mutePeriod: "Stummschaltungsdauer" +indefinitely: "Dauerhaft" +tenMinutes: "10 Minuten" +oneHour: "Eine Stunde" +oneDay: "Einen Tag" +oneWeek: "Eine Woche" _emailUnavailable: used: "Diese Email-Adresse wird bereits verwendet" format: "Das Format dieser Email-Adresse ist ungültig" @@ -845,20 +852,20 @@ _ffVisibility: private: "Privat" _signup: almostThere: "Fast geschafft" - emailAddressInfo: "Bitte gib deine Email-Adresse ein." + emailAddressInfo: "Bitte gib deine Email-Adresse ein. Sie wird nicht öffentlich einsehbar sein." emailSent: "An deine Email-Adresse ({email}) wurde soeben eine Bestätigungsmail geschickt. Bitte klicke auf den enthaltenen Link, um die Erstellung deines Benutzerkontos abzuschließen." _accountDelete: accountDelete: "Benutzerkonto löschen" - mayTakeTime: "Da die Löschung eines Benutzerkontos ein aufwendiger Prozess ist, kann dessen Dauer davon abhängen, wie viel Inhalt in diesem erstellt wurde oder wie viele Dateien hochgeladen wurden." + mayTakeTime: "Da die Löschung eines Benutzerkontos ein aufwendiger Prozess ist, kann dessen Dauer davon abhängen, wie viel Inhalt von diesem erstellt wurde oder wie viele Dateien von diesem hochgeladen wurden." sendEmail: "Sobald die Löschung abgeschlossen ist, wird an die mit ihm verknüpfte Email-Adresse eine Benachrichtigung versendet." - requestAccountDelete: "Löschung des Benutzerkontos anfordern" - started: "Löschung wurde eingeleitet." + requestAccountDelete: "Löschung deines Benutzerkontos anfordern" + started: "Die Löschung wurde eingeleitet." inProgress: "Löschung in Bearbeitung" _ad: back: "Zurück" reduceFrequencyOfThisAd: "Diese Werbung weniger anzeigen" _forgotPassword: - enterEmail: "Gib die Email-Adresse ein, mit der du dich registriert hast. An diese wird ein Link gesendet, mit der du dein Passwort zurücksetzen kannst." + enterEmail: "Gib die Email-Adresse ein, mit der du dich registriert hast. An diese wird ein Link gesendet, mit dem du dein Passwort zurücksetzen kannst." ifNoEmail: "Solltest du bei der Registrierung keine Email-Adresse angegeben haben, wende dich bitte an den Administrator." contactAdmin: "Diese Instanz unterstützt die Verwendung von Email-Adressen nicht. Wende dich an den Administrator, um dein Passwort zurückzusetzen." _gallery: @@ -882,7 +889,7 @@ _registry: domain: "Domain" createKey: "Schlüssel erstellen" _aboutMisskey: - about: "Misskey ist Open-Source-Software die von syuilo seit 2014 entwickelt wird." + about: "Misskey ist Open-Source-Software, welche von syuilo seit 2014 entwickelt wird." contributors: "Hauptmitwirkende" allContributors: "Alle Mitwirkenden" source: "Quellcode" @@ -899,13 +906,13 @@ _mfm: intro: "MFM ist eine Misskey-exklusive Markup-Sprache, die in Misskey an vielen Stellen verwendet werden kann. Hier kannst du eine Liste von verfügbarer MFM-Syntax einsehen." dummy: "Misskey erweitert die Welt des Fediverse" mention: "Erwähnung" - mentionDescription: "Mit At-Zeichen und Nutzername kann ein individueller Nutzer angegeben werden." + mentionDescription: "Mit At-Zeichen und Benutzername kann ein individueller Nutzer angegeben werden." hashtag: "Hashtag" hashtagDescription: "Mit einer Raute und Text kann ein Hashtag angegeben werden." url: "URL" urlDescription: "Zeigt URLs an." link: "Link" - linkDescription: "Spezifische Textabschnitte als URL anzeigen." + linkDescription: "Zeigt spezifische Textabschnitte als URL an." bold: "Fett" boldDescription: "Zeichen zur Betonung dicker erscheinen lassen." small: "Klein" @@ -929,19 +936,19 @@ _mfm: flip: "Spiegelung" flipDescription: "Inhalt horizontal oder vertikal gespiegelt anzeigen." jelly: "Animation (Dehnen)" - jellyDescription: "Verleiht dem Inhalt eine sich dehnende Animation." + jellyDescription: "Verleiht Inhalt eine sich dehnende Animation." tada: "Animation (Tada)" - tadaDescription: "Verleiht eine Animation mit \"Tada!\"-Gefühl" + tadaDescription: "Verleiht Inhalt eine Animation mit \"Tada!\"-Gefühl" jump: "Animation (Sprung)" - jumpDescription: "Verleiht dem Inhalt eine springende Animation." + jumpDescription: "Verleiht Inhalt eine springende Animation." bounce: "Animation (Federn)" - bounceDescription: "Verleiht dem Inhalt eine federnde Animation." + bounceDescription: "Verleiht Inhalt eine federnde Animation." shake: "Animation (Zittern)" - shakeDescription: "Verleiht dem Inhalt eine zitternde Animation." + shakeDescription: "Verleiht Inhalt eine zitternde Animation." twitch: "Animation (Zucken)" - twitchDescription: "Verleiht dem Inhalt eine sehr stark zuckende Animation." + twitchDescription: "Verleiht Inhalt eine sehr stark zuckende Animation." spin: "Animation (Rotieren)" - spinDescription: "Verleiht dem Inhalt eine rotierende Animation." + spinDescription: "Verleiht Inhalt eine rotierende Animation." x2: "Groß" x2Description: "Inhalte größer anzeigen." x3: "Sehr groß" @@ -949,7 +956,7 @@ _mfm: x4: "Unglaublich groß" x4Description: "Lässt Inhalte noch größer als größer als groß angezeigt werden." blur: "Weichzeichnen" - blurDescription: "Inhalte durch Weihzeichnung verschwimmen lassen. Durch das Bewegen des Mauszeigers auf den Inhalt wird er klar angezeigt." + blurDescription: "Inhalte durch Weihzeichnung verschwimmen lassen. Durch das Bewegen des Mauszeigers über den Inhalt wird er klar angezeigt." font: "Schriftart" fontDescription: "Setzt die Schriftart des Inhaltes fest." rainbow: "Regenbogen" @@ -957,7 +964,7 @@ _mfm: sparkle: "Glitzer" sparkleDescription: "Verleiht Inhalt einen glitzernden Partikeleffekt." rotate: "Drehen" - rotateDescription: "Dreht den Inhalt um einen angegebenen Winkel" + rotateDescription: "Dreht den Inhalt um einen angegebenen Winkel." _instanceTicker: none: "Nie anzeigen" remote: "Für Benutzer fremder Instanzen anzeigen" @@ -983,7 +990,7 @@ _menuDisplay: hide: "Ausblenden" _wordMute: muteWords: "Stummgeschaltete Wörter" - muteWordsDescription: "Mit Leerzeichen für eine \"UND\"-Verknüpfung trennen, durch Zeilenumbrüche für eine \"ODER\"-Verknüpfung trennen." + muteWordsDescription: "Zum Nutzen einer \"UND\"-Verknüpfung Einträge mit Leerzeichen trennen, zum Nutzen einer \"ODER\"-Verknüpfung Einträge mit einem Zeilenumbruch trennen." muteWordsDescription2: "Umgib Schlüsselworter mit Schrägstrichen, um Reguläre Ausdrücke zu verwenden." softDescription: "Notizen, die die angegebenen Konditionen erfüllen, in der Chronik ausblenden." hardDescription: "Verhindern, dass Notizen, die die angegebenen Konditionen erfüllen, der Chronik hinzugefügt werden. Zudem werden diese Notizen auch nicht der Chronik hinzugefügt, falls die Konditionen geändert werden." @@ -996,17 +1003,17 @@ _instanceMute: title: "Blendet Notizen von stummgeschalteten Instanzen aus." heading: "Liste der stummzuschaltenden Instanzen" _theme: - explore: "Themen erforschen" - install: "Thema installieren" - manage: "Themaverwaltung" - code: "Themencode" + explore: "Farbschemata erforschen" + install: "Farbschmata installieren" + manage: "Farbschemaverwaltung" + code: "Farbschemencode" description: "Beschreibung" installed: "{name} wurde installiert" - installedThemes: "Installierte Themen" - builtinThemes: "Eingebaute Themen" - alreadyInstalled: "Dieses Thema ist bereits installiert" - invalid: "Der Themencode dieses Themas ist ungültig" - make: "Farbthema erstellen" + installedThemes: "Installierte Farbschemata" + builtinThemes: "Eingebaute Farbschemata" + alreadyInstalled: "Dieses Farbschema ist bereits installiert" + invalid: "Der Code dieses Farbschemas ist ungültig" + make: "Farbschema erstellen" base: "Vorlage" addConstant: "Konstante hinzufügen" constant: "Konstante" @@ -1023,7 +1030,7 @@ _theme: darken: "Verdunkeln" lighten: "Erhellen" inputConstantName: "Name der Konstanten eingeben" - importInfo: "Du kannst hier Themencode einfügen, um ihn in den Editor zu importieren" + importInfo: "Hier kannst du Farbschemencode einfügen, um ihn in den Editor zu importieren" deleteConstantConfirm: "Die Konstante {const} wirklich löschen?" keys: accent: "Akzentfarbe" @@ -1060,7 +1067,7 @@ _theme: toastFg: "Text von Benachrichtigungen" buttonBg: "Hintergrund von Schaltflächen" buttonHoverBg: "Hintergrund von Schaltflächen (Mouseover)" - inputBorder: "Rahmen des Eingabefelds" + inputBorder: "Rahmen von Eingabefeldern" listItemHoverBg: "Hintergrund von Listeneinträgen (Mouseover)" driveFolderBg: "Hintergrund von Drive-Ordnern" wallpaperOverlay: "Hintergrundbild-Overlay" @@ -1101,7 +1108,7 @@ _tutorial: step2_1: "Lass uns zuerst dein Profil vervollständigen, bevor du Notizen schreibst oder jemandem folgst." step2_2: "Informationen darüber, was für eine Person du bist, macht es anderen leichter zu wissen, ob sie deine Notizen sehen wollen und ob sie dir folgen möchten." step3_1: "Mit dem Einrichten deines Profils fertig?" - step3_2: "Dann lass uns als nächstes versuchen, eine Notiz zu schreiben. Dies kannst du tun, indem du auf das Stift-Icon oben auf dem Bildschirm drückst." + step3_2: "Dann lass uns als nächstes versuchen, eine Notiz zu schreiben. Dies kannst du tun, indem du auf den Knopf mit dem Stift-Icon auf dem Bildschirm drückst." step3_3: "Fülle das Fenster aus und drücke auf den Knopf oben rechts zum Senden." step3_4: "Fällt dir nichts ein, das du schreiben möchtest? Versuch's mit \"Hallo Misskey!\"" step4_1: "Fertig mit dem Senden deiner ersten Notiz?" @@ -1111,8 +1118,8 @@ _tutorial: step5_3: "Klicke zum Anzeigen des Profils eines Benutzers auf dessen Profilbild und dann auf den \"Folgen\"-Knopf, um diesem zu folgen." step5_4: "Je nach Benutzer kann es etwas Zeit in Anspruch nehmen, bis dieser deine Follow-Anfrage bestätigt." step6_1: "Wenn du nun auch die Notizen anderer Benutzer in deiner Chronik siehst, hast du auch diesmal alles richtig gemacht." - step6_2: "Du kannst ebenso \"Reaktionen\" verwenden, um schnell auf Notizen anderer Benutzer zu reagieren." - step6_3: "Um eine Reaktion anzufügen, klicke auf das \"+\"-Symbol in der Notiz und wähle ein Emoji aus, mit dem du reagieren möchtest." + step6_2: "Du kannst ebenso „Reaktionen“ verwenden, um schnell auf Notizen anderer Benutzer zu reagieren." + step6_3: "Um eine Reaktion anzufügen, klicke auf das „+“-Symbol in der Notiz und wähle ein Emoji aus, mit dem du reagieren möchtest." step7_1: "Glückwunsch! Du hast die Einführung in die Verwendung von Misskey abgeschlossen." step7_2: "Wenn du mehr über Misskey lernen möchtest, schau dich im {help}-Bereich um." step7_3: "Und nun, viel Spaß mit Misskey! 🚀" @@ -1159,7 +1166,7 @@ _permissions: "read:gallery-likes": "Liste deiner mit \"Gefällt mir\" markierten Galerie-Beiträge lesen" "write:gallery-likes": "Liste deiner mit \"Gefällt mir\" markierten Galerie-Beiträge bearbeiten" _auth: - shareAccess: "Möchtest du \"{name}\" authorisieren, auf dieses Benuzerkonto zugreifen zu können?" + shareAccess: "Möchtest du „{name}“ authorisieren, auf dieses Benutzerkonto zugreifen zu können?" shareAccessAsk: "Bist du dir sicher, dass du diese Anwendung authorisieren möchtest, auf dein Benutzerkonto zugreifen zu können?" permissionAsk: "Diese Anwendung fordert folgende Berechtigungen" pleaseGoBack: "Bitte kehre zur Anwendung zurück" @@ -1180,7 +1187,7 @@ _weekday: friday: "Freitag" saturday: "Samstag" _widgets: - memo: "Memo" + memo: "Merkzettel" notifications: "Benachrichtigungen" timeline: "Chronik" calendar: "Kalender" @@ -1211,8 +1218,8 @@ _poll: canMultipleVote: "Auswahl mehrerer Antworten erlauben" expiration: "Abstimmung beenden" infinite: "Nie" - at: "Beenden am..." - after: "Beenden nach..." + at: "Beenden am …" + after: "Beenden nach …" deadlineDate: "Enddatum" deadlineTime: "Zeit" duration: "Dauer" @@ -1238,24 +1245,24 @@ _visibility: localOnly: "Nur Lokal" localOnlyDescription: "Unsichtbar für Benutzer anderer Instanzen" _postForm: - replyPlaceholder: "Dieser Notiz antworten..." - quotePlaceholder: "Diese Notiz zitieren..." + replyPlaceholder: "Dieser Notiz antworten …" + quotePlaceholder: "Diese Notiz zitieren …" channelPlaceholder: "In einen Kanal senden" _placeholders: a: "Was machst du momentan?" b: "Was ist um dich herum los?" c: "Was geht dir durch den Kopf?" d: "Was möchtest du sagen?" - e: "Fang an zu schreiben..." - f: "Ich warte darauf, dass du schreibst..." + e: "Fang an zu schreiben …" + f: "Ich warte darauf, dass du schreibst …" _profile: name: "Name" username: "Benutzername" - description: "Über mich" - youCanIncludeHashtags: "Du kannst auch Hashtags in deiner Beschreibung verwenden." + description: "Profilbeschreibung" + youCanIncludeHashtags: "Du kannst auch Hashtags in deiner Profilbeschreibung verwenden." metadata: "Zusätzliche Informationen" metadataEdit: "Zusätzliche Informationen bearbeiten" - metadataDescription: "Du kannst auf deinem Profil vier zusätzliche Informationsblöcke anzeigen lassen." + metadataDescription: "Hierdurch kannst du auf deinem Profil zusätzliche Informationsblöcke anzeigen lassen." metadataLabel: "Beschriftung" metadataContent: "Inhalt" changeAvatar: "Profilbild ändern" @@ -1274,25 +1281,25 @@ _charts: usersIncDec: "Unterschied in der Anzahl von Benutzern" usersTotal: "Anzahl aller Benutzer" activeUsers: "Aktive Benutzer" - notesIncDec: "Unterschied in der Anzahl von Notizen" - localNotesIncDec: "Unterschied in der Anzahl von lokalen Notizen" - remoteNotesIncDec: "Unterschied in Anzahl der Notizen von fremden Instanzen" + notesIncDec: "Unterschied in der Anzahl an Notizen" + localNotesIncDec: "Unterschied in der Anzahl an lokalen Notizen" + remoteNotesIncDec: "Unterschied in der Anzahl an Notizen von fremden Instanzen" notesTotal: "Anzahl aller Notizen" - filesIncDec: "Unterschied an Dateien" - filesTotal: "Summe der Dateien" + filesIncDec: "Unterschied in der Anzahl an Dateien" + filesTotal: "Anzahl aller Dateien" storageUsageIncDec: "Unterschied in der Höhe der Speichernutzung" storageUsageTotal: "Gesamte Speichernutzung" _instanceCharts: requests: "Anfragen" - users: "Unterschied in der Anzahl von Benutzern" + users: "Unterschied in der Anzahl an Benutzern" usersTotal: "Gesamtanzahl an Benutzern" - notes: "Unterschied in der Anzahl von Notizen" + notes: "Unterschied in der Anzahl an Notizen" notesTotal: "Gesamtanzahl an Notizen" - ff: "Unterschied in der Anzahl von gefolgten Benutzern und Followern" + ff: "Unterschied in der Anzahl an gefolgten Benutzern und Followern" ffTotal: "Gesamtanzahl an gefolgten Benutzern und Followern" cacheSize: "Unterschied in der Größe des Caches" cacheSizeTotal: "Gesamtgröße des Caches" - files: "Unterschied in der Anzahl der Dateien" + files: "Unterschied in der Anzahl an Dateien" filesTotal: "Gesamtanzahl an Dateien" _timelines: home: "Startseite" @@ -1302,7 +1309,7 @@ _timelines: _pages: newPage: "Seite erstellen" editPage: "Seite bearbeiten" - readPage: "Quelltext-Ansicht" + readPage: "Quelltextansicht" created: "Seite erfolgreich erstellt" updated: "Seite erfolgreich aktualisiert" deleted: "Seite erfolgreich gelöscht" @@ -1326,7 +1333,7 @@ _pages: url: "Seiten-URL" summary: "Zusammenfassung" alignCenter: "Zentrieren" - hideTitleWhenPinned: "Seitentitel ausblenden, wenn angeheftet" + hideTitleWhenPinned: "Seitentitel wenn angeheftet ausblenden" font: "Schriftart" fontSerif: "Serif" fontSansSerif: "Sans Serif" @@ -1363,7 +1370,7 @@ _pages: name: "Variablenname" text: "Titel" default: "Standardwert" - numberInput: "Nummereingabe" + numberInput: "Zahleneingabe" _numberInput: name: "Variablenname" text: "Titel" @@ -1387,7 +1394,7 @@ _pages: _counter: name: "Variablenname" text: "Titel" - inc: "Erhöhen um" + inc: "Schrittgröße" _button: text: "Titel" colored: "Farbig" @@ -1433,10 +1440,10 @@ _pages: strLen: "Textlänge" _strLen: arg1: "Text" - strPick: "Zeichen extrahieren" + strPick: "Text extrahieren" _strPick: arg1: "Text" - arg2: "Zeichenposition" + arg2: "Textposition" strReplace: "Textersetzung" _strReplace: arg1: "Text" @@ -1447,7 +1454,7 @@ _pages: arg1: "Text" join: "Text zusammenfügen" _join: - arg1: "Listen" + arg1: "Liste" arg2: "Trennzeichen" add: "Addieren" _add: @@ -1576,7 +1583,7 @@ _pages: _for: arg1: "Anzahl der Schleifendurchläufe" arg2: "Aktion" - typeError: "Slot {slot} akzeptiert Werte vom Typ \"{expect}\", aber es wurde ein \"{actual}\" Wert angegeben!" + typeError: "Slot {slot} akzeptiert Werte vom Typ „{expect}“, aber es wurde ein „{actual}“ Wert angegeben!" thereIsEmptySlot: "Slot {slot} ist leer!" types: string: "Text" @@ -1587,7 +1594,7 @@ _pages: emptySlot: "Leerer Slot" enviromentVariables: "Umgebungsvariable" pageVariables: "Seitenelemente" - argVariables: "Eingabe-Slots" + argVariables: "Eingabeslots" _relayStatus: requesting: "Ausstehend" accepted: "Akzeptiert" @@ -1605,6 +1612,7 @@ _notification: youReceivedFollowRequest: "Du hast eine Follow-Anfrage erhalten" yourFollowRequestAccepted: "Deine Follow-Anfrage wurde akzeptiert" youWereInvitedToGroup: "Du wurdest in eine Gruppe eingeladen" + pollEnded: "Umfrageergebnisse sind verfügbar" _types: all: "Alle" follow: "Neue Follower" @@ -1614,6 +1622,7 @@ _notification: quote: "Zitationen" reaction: "Reaktionen" pollVote: "Antworten auf Umfragen" + pollEnded: "Ende von Umfragen" receiveFollowRequest: "Erhaltene Follow-Anfragen" followRequestAccepted: "Akzeptierte Follow-Anfragen" groupInvited: "Erhaltene Gruppeneinladungen" @@ -1624,10 +1633,10 @@ _deck: columnMargin: "Spaltenabstand" columnHeaderHeight: "Spaltenkopfhöhe" addColumn: "Spalte hinzufügen" - swapLeft: "Nach links verschieben" - swapRight: "Nach rechts verschieben" - swapUp: "Nach oben verschieben" - swapDown: "Nach unten verschieben" + swapLeft: "Mit linker Spalte tauschen" + swapRight: "Mit rechter Spalte tauschen" + swapUp: "Mit oberer Spalte tauschen" + swapDown: "Mit unterer Spalte tauschen" stackLeft: "Auf linke Spalte stapeln" popRight: "Nach rechts vom Stapel nehmen" profile: "Profil" diff --git a/locales/en-US.yml b/locales/en-US.yml index 53434d7e60..5ec97f05f5 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -8,7 +8,7 @@ notifications: "Notifications" username: "Username" password: "Password" forgotPassword: "Forgot password" -fetchingAsApObject: "Fetching from Fediverse..." +fetchingAsApObject: "Fetching from the Fediverse..." ok: "OK" gotIt: "Got it!" cancel: "Cancel" @@ -32,9 +32,9 @@ uploading: "Uploading..." save: "Save" users: "Users" addUser: "Add a user" -favorite: "Favorite" +favorite: "Add to favorites" favorites: "Favorites" -unfavorite: "Unfavorite" +unfavorite: "Remove from favorites" favorited: "Added to favorites." alreadyFavorited: "Already added to favorites." cantFavorite: "Couldn't add to favorites." @@ -43,7 +43,7 @@ unpin: "Unpin from profile" copyContent: "Copy contents" copyLink: "Copy link" delete: "Delete" -deleteAndEdit: "Delete and Edit" +deleteAndEdit: "Delete and edit" deleteAndEditConfirm: "Are you sure you want to delete this note and edit it? You will lose all reactions, renotes and replies to it." addToList: "Add to list" sendMessage: "Send a message" @@ -77,21 +77,21 @@ followsYou: "Follows you" createList: "Create list" manageLists: "Manage lists" error: "Error" -somethingHappened: "An error occurred" +somethingHappened: "An error has occurred" retry: "Retry" -pageLoadError: "Failed to load page." +pageLoadError: "An error occurred loading the page." pageLoadErrorDescription: "This is normally caused by network errors or the browser's cache. Try clearing the cache and then try again after waiting a little while." serverIsDead: "This server is not responding. Please wait for a while and try again." youShouldUpgradeClient: "To view this page, please refresh to update your client." -enterListName: "Enter a list name" +enterListName: "Enter a name for the list" privacy: "Privacy" makeFollowManuallyApprove: "Follow requests require approval" defaultNoteVisibility: "Default visibility" follow: "Follow" -followRequest: "Request follow" +followRequest: "Send follow request" followRequests: "Follow requests" unfollow: "Unfollow" -followRequestPending: "Pending follow request" +followRequestPending: "Follow request pending" enterEmoji: "Enter an emoji" renote: "Renote" unrenote: "Take back renote" @@ -107,12 +107,12 @@ sensitive: "NSFW" add: "Add" reaction: "Reactions" reactionSetting: "Reactions to show in the reaction picker" -reactionSettingDescription2: "Drag to reorder, Click to delete, Press \"+\" to add" +reactionSettingDescription2: "Drag to reorder, click to delete, press \"+\" to add." rememberNoteVisibility: "Remember note visibility settings" attachCancel: "Remove attachment" markAsSensitive: "Mark as NSFW" -unmarkAsSensitive: "Undo NSFW" -enterFileName: "Enter file name" +unmarkAsSensitive: "Unmark as NSFW" +enterFileName: "Enter filename" mute: "Mute" unmute: "Unmute" block: "Block" @@ -168,8 +168,8 @@ latestRequestReceivedAt: "Last request received" latestStatus: "Latest status" storageUsage: "Storage usage" charts: "Charts" -perHour: "per Hour" -perDay: "per Day" +perHour: "Per Hour" +perDay: "Per Day" stopActivityDelivery: "Stop sending activities" blockThisInstance: "Block this instance" operations: "Operations" @@ -190,7 +190,7 @@ clearQueueConfirmText: "Any undelivered notes remaining in the queue will not be clearCachedFiles: "Clear cache" clearCachedFilesConfirm: "Are you sure that you want to delete all cached remote files?" blockedInstances: "Blocked Instances" -blockedInstancesDescription: "List the hosts of the instances to be blocked separated by line breaks. Blocked instances will no longer be able to communicate with this instance." +blockedInstancesDescription: "List the hostnames of the instances that you want to block. Listed instances will no longer be able to communicate with this instance." muteAndBlock: "Mutes and Blocks" mutedUsers: "Muted users" blockedUsers: "Blocked users" @@ -325,8 +325,6 @@ disablingTimelinesInfo: "Adminstrators and Moderators will always have access to registration: "Register" enableRegistration: "Enable new user registration" invite: "Invite" -proxyRemoteFiles: "Proxy remote files" -proxyRemoteFilesDescription: "If enabled, remote files that are either not stored locally or were deleted due to exceeding the storage limit will be proxied, including generation of thumbnails. This does not affect the server's storage." driveCapacityPerLocalAccount: "Drive capacity per local user" driveCapacityPerRemoteAccount: "Drive capacity per remote user" inMb: "In megabytes" @@ -336,9 +334,9 @@ backgroundImageUrl: "Background image URL" basicInfo: "Basic info" pinnedUsers: "Pinned users" pinnedUsersDescription: "List usernames separated by line breaks to be pinned in the \"Explore\" tab." -pinnedPages: "Pinned pages" -pinnedPagesDescription: "Enter the paths of the pages you want to pin to the top page of this instance, separated by line breaks." -pinnedClipId: "ID of the pinned clip" +pinnedPages: "Pinned Pages" +pinnedPagesDescription: "Enter the paths of the Pages you want to pin to the top page of this instance, separated by line breaks." +pinnedClipId: "ID of the clip to pin" pinnedNotes: "Pinned notes" hcaptcha: "hCaptcha" enableHcaptcha: "Enable hCaptcha" @@ -348,7 +346,7 @@ recaptcha: "reCAPTCHA" enableRecaptcha: "Enable reCAPTCHA" recaptchaSiteKey: "Site key" recaptchaSecretKey: "Secret key" -avoidMultiCaptchaConfirm: "Using multiple Captcha systems may cause interferences. Would you like to disable the other Captcha systems? You can leave multiple enabled by pressing cancel." +avoidMultiCaptchaConfirm: "Using multiple Captcha systems may cause interference between them. Would you like to disable the other Captcha systems currently active? If you would like them to stay enabled, press cancel." antennas: "Antennas" manageAntennas: "Manage Antennas" name: "Name" @@ -369,13 +367,13 @@ silence: "Silence" silenceConfirm: "Are you sure that you want to silence this user?" unsilence: "Undo silencing" unsilenceConfirm: "Are you sure that you want to undo the silencing of this user?" -popularUsers: "Trending users" -recentlyUpdatedUsers: "Users with recent activity" +popularUsers: "Popular users" +recentlyUpdatedUsers: "Recently active users" recentlyRegisteredUsers: "Newly joined users" recentlyDiscoveredUsers: "Newly discovered users" exploreUsersCount: "There are {count} users" exploreFediverse: "Explore the Fediverse" -popularTags: "Trending Tags" +popularTags: "Popular tags" userList: "Lists" about: "About" aboutMisskey: "About Misskey" @@ -383,13 +381,13 @@ administrator: "Administrator" token: "Token" twoStepAuthentication: "Two-factor authentication" moderator: "Moderator" -nUsersMentioned: "{n} users mentioned" +nUsersMentioned: "Mentioned by {n} users" securityKey: "Security key" securityKeyName: "Key name" registerSecurityKey: "Register a security key" lastUsed: "Last used" unregister: "Unregister" -passwordLessLogin: "Set up password-less login" +passwordLessLogin: "Password-less login" resetPassword: "Reset password" newPasswordIs: "The new password is \"{password}\"" reduceUiAnimation: "Reduce UI animations" @@ -422,11 +420,10 @@ next: "Next" retype: "Enter again" noteOf: "Note by {user}" inviteToGroup: "Invite to group" -maxNoteTextLength: "Character limit for notes" -quoteAttached: "Quoted" +quoteAttached: "Quote" quoteQuestion: "Append as quote?" noMessagesYet: "No messages yet" -newMessageExists: "You've got a new message" +newMessageExists: "There are new messages" onlyOneFileCanBeAttached: "You can only attach one file to a message" signinRequired: "Please sign in" invitations: "Invites" @@ -438,7 +435,7 @@ usernameInvalidFormat: "You can use upper- and lowercase letters, numbers, and u tooShort: "Too short" tooLong: "Too long" weakPassword: "Weak password" -normalPassword: "Normal password" +normalPassword: "Average password" strongPassword: "Strong password" passwordMatched: "Matches" passwordNotMatched: "Does not match" @@ -466,7 +463,7 @@ existingAccount: "Existing account" regenerate: "Regenerate" fontSize: "Font size" noFollowRequests: "You don't have any pending follow requests" -openImageInNewTab: "Open image in new tab" +openImageInNewTab: "Open images in new tab" dashboard: "Dashboard" local: "Local" remote: "Remote" @@ -474,17 +471,17 @@ total: "Total" weekOverWeekChanges: "Changes to last week" dayOverDayChanges: "Changes to yesterday" appearance: "Appearance" -clientSettings: "Client settings" +clientSettings: "Client Settings" accountSettings: "Account Settings" promotion: "Promoted" promote: "Promote" numberOfDays: "Number of days" hideThisNote: "Hide this note" -showFeaturedNotesInTimeline: "Show Featured notes in Timelines" +showFeaturedNotesInTimeline: "Show featured notes in timelines" objectStorage: "Object Storage" useObjectStorage: "Use object storage" objectStorageBaseUrl: "Base URL" -objectStorageBaseUrlDesc: "URL used as reference. Specify the URL of your CDN or Proxy if you are using either. S3: 'https://.s3.amazonaws.com', GCS: 'https://storage.googleapis.com/' etc." +objectStorageBaseUrlDesc: "The URL used as reference. Specify the URL of your CDN or Proxy if you are using either.\nFor S3 use 'https://.s3.amazonaws.com' and for GCS or equivalent services use 'https://storage.googleapis.com/', etc." objectStorageBucket: "Bucket" objectStorageBucketDesc: "Please specify the bucket name used at your provider." objectStoragePrefix: "Prefix" @@ -492,7 +489,7 @@ objectStoragePrefixDesc: "Files will be stored under directories with this prefi objectStorageEndpoint: "Endpoint" objectStorageEndpointDesc: "Leave this empty if you are using AWS S3, otherwise specify the endpoint as '' or ':', depending on the service you are using." objectStorageRegion: "Region" -objectStorageRegionDesc: "Specify a region like 'xx-east-1'. If your service does not distinct between regions, leave this blank or enter 'us-east-1'." +objectStorageRegionDesc: "Specify a region like 'xx-east-1'. If your service does not distinguish between regions, leave this blank or enter 'us-east-1'." objectStorageUseSSL: "Use SSL" objectStorageUseSSLDesc: "Turn this off if you are not going to use HTTPS for API connections" objectStorageUseProxy: "Connect over Proxy" @@ -524,17 +521,17 @@ sort: "Sort" ascendingOrder: "Ascending" descendingOrder: "Descending" scratchpad: "Scratchpad" -scratchpadDescription: "The Scratchpad provides an environment for AiScript experiments. You can write, execute, and check the results of it interacting with Misskey." +scratchpadDescription: "The Scratchpad provides an environment for AiScript experiments. You can write, execute, and check the results of it interacting with Misskey in it." output: "Output" script: "Script" disablePagesScript: "Disable AiScript on Pages" updateRemoteUser: "Update remote user information" -deleteAllFiles: "Delete All Files" +deleteAllFiles: "Delete all files" deleteAllFilesConfirm: "Are you sure that you want to delete all files?" removeAllFollowing: "Unfollow all followed users" removeAllFollowingDescription: "Executing this unfollows all accounts from {host}. Please run this if the instance e.g. no longer exists." userSuspended: "This user has been suspended." -userSilenced: "This user has been silenced." +userSilenced: "This user is being silenced." yourAccountSuspendedTitle: "This account is suspended" yourAccountSuspendedDescription: "This account has been suspended due to breaking the server's terms of services or similar. Contact the administrator if you would like to know a more detailed reason. Please do not create a new account." menu: "Menu" @@ -547,7 +544,7 @@ addedRelays: "Added Relays" serviceworkerInfo: "Must be enabled for push notifications." deletedNote: "Deleted note" invisibleNote: "Invisible note" -enableInfiniteScroll: "Enable infinite scrolling" +enableInfiniteScroll: "Automatically load more" visibility: "Visiblility" poll: "Poll" useCw: "Hide content" @@ -585,7 +582,7 @@ enableEmail: "Enable email distribution" emailConfigInfo: "Used to confirm your email during sign-up or if you forget your password" email: "Email" emailAddress: "Email address" -smtpConfig: "SMTP Server configuration" +smtpConfig: "SMTP Server Configuration" smtpHost: "Host" smtpPort: "Port" smtpUser: "Username" @@ -595,7 +592,9 @@ smtpSecure: "Use implicit SSL/TLS for SMTP connections" smtpSecureInfo: "Turn this off when using STARTTLS" testEmail: "Test email delivery" wordMute: "Word mute" -instanceMute: "Instance mutes" +regexpError: "Regular Expression error" +regexpErrorDescription: "An error occurred in the regular expression on line {line} of your {tab} word mutes:" +instanceMute: "Instance Mutes" userSaysSomething: "{name} said something" makeActive: "Activate" display: "Display" @@ -608,14 +607,14 @@ database: "Database" channel: "Channels" create: "Create" notificationSetting: "Notification settings" -notificationSettingDesc: "Select the type of notification to display" -useGlobalSetting: "Use global setting" +notificationSettingDesc: "Select the types of notification to display." +useGlobalSetting: "Use global settings" useGlobalSettingDesc: "If turned on, your account's notification settings will be used. If turned off, individual configurations can be made." other: "Other" regenerateLoginToken: "Regenerate login token" -regenerateLoginTokenDescription: "Regenerate the token used internally during login. Normally this action is not necessary. If regenerated, all devices will be logged out." +regenerateLoginTokenDescription: "Regenerates the token used internally during login. Normally this action is not necessary. If regenerated, all devices will be logged out." setMultipleBySeparatingWithSpace: "Separate multiple entries with spaces." -fileIdOrUrl: "File-ID or URL" +fileIdOrUrl: "File ID or URL" behavior: "Behavior" sample: "Sample" abuseReports: "Reports" @@ -689,14 +688,14 @@ center: "Center" wide: "Wide" narrow: "Narrow" reloadToApplySetting: "This setting will only apply after a page reload. Reload now?" -needReloadToApply: "This setting will only apply after a page reload." +needReloadToApply: "A reload is required for this to be reflected." showTitlebar: "Show title bar" clearCache: "Clear cache" onlineUsersCount: "{n} users are online" nUsers: "{n} Users" nNotes: "{n} Notes" sendErrorReports: "Send error reports" -sendErrorReportsDescription: "When turned on, detailed error information will be shared with Misskey when a problem occurs, helping to improve the quality of Misskey.\nThis will include information such the version of your OS, what browser you're using, your activity history, etc." +sendErrorReportsDescription: "When turned on, detailed error information will be shared with Misskey when a problem occurs, helping to improve the quality of Misskey.\nThis will include information such the version of your OS, what browser you're using, your activity in Misskey, etc." myTheme: "My theme" backgroundColor: "Background color" accentColor: "Accent color" @@ -794,12 +793,12 @@ translatedFrom: "Translated from {x}" accountDeletionInProgress: "Account deletion is currently in progress" usernameInfo: "A name that identifies your account from others on this server. You can use the alphabet (a~z, A~Z), digits (0~9) or underscores (_). Usernames cannot be changed later." aiChanMode: "Ai Mode" -keepCw: "Keep Content Warnings" +keepCw: "Keep content warnings" pubSub: "Pub/Sub Accounts" lastCommunication: "Last communication" resolved: "Resolved" unresolved: "Unresolved" -breakFollow: "Unfollow" +breakFollow: "Remove follower" itsOn: "Enabled" itsOff: "Disabled" emailRequiredForSignup: "Require email address for sign-up" @@ -819,7 +818,7 @@ deleteAccountConfirm: "This will irreversibly delete your account. Proceed?" incorrectPassword: "Incorrect password." voteConfirm: "Confirm your vote for \"{choice}\"?" hide: "Hide" -leaveGroup: "Leave Group" +leaveGroup: "Leave group" leaveGroupConfirm: "Are you sure you want to leave \"{name}\"?" useDrawerReactionPickerForMobile: "Display reaction picker as drawer on mobile" welcomeBackWithName: "Welcome back, {name}" @@ -831,6 +830,16 @@ auto: "Auto" themeColor: "Theme Color" size: "Size" numberOfColumn: "Number of columns" +searchByGoogle: "Google" +instanceDefaultLightTheme: "Instance-wide default light theme" +instanceDefaultDarkTheme: "Instance-wide default dark theme" +instanceDefaultThemeDescription: "Enter the theme code in object format." +mutePeriod: "Mute duration" +indefinitely: "Permanently" +tenMinutes: "10 minutes" +oneHour: "One hour" +oneDay: "One day" +oneWeek: "One week" _emailUnavailable: used: "This email address is already being used" format: "The format of this email address is invalid" @@ -843,10 +852,10 @@ _ffVisibility: private: "Private" _signup: almostThere: "Almost there" - emailAddressInfo: "Please enter your email address." + emailAddressInfo: "Please enter your email address. It will not be made public." emailSent: "A confirmation email has been sent to your email address ({email}). Please click the included link to complete account creation." _accountDelete: - accountDelete: "Delete Account" + accountDelete: "Delete account" mayTakeTime: "As account deletion is a resource-heavy process, it may take some time to complete depending on how much content you have created and how many files you have uploaded." sendEmail: "Once account deletion has been completed, an email will be sent to the email address registered to this account." requestAccountDelete: "Request account deletion" @@ -857,8 +866,8 @@ _ad: reduceFrequencyOfThisAd: "Show this ad less" _forgotPassword: enterEmail: "Enter the email address you used to register. A link with which you can reset your password will then be sent to it." - ifNoEmail: "If you did not use an email during registration, please contact the administrator instead." - contactAdmin: "This instance does not support using email addresses, please contact the administrator to reset your password instead." + ifNoEmail: "If you did not use an email during registration, please contact the instance administrator instead." + contactAdmin: "This instance does not support using email addresses, please contact the instance administrator to reset your password instead." _gallery: my: "My Gallery" liked: "Liked Posts" @@ -910,14 +919,14 @@ _mfm: smallDescription: "Displays content small and thin." center: "Center" centerDescription: "Displays content centered." - inlineCode: "Code (In-line)" + inlineCode: "Code (Inline)" inlineCodeDescription: "Displays inline syntax highlighting for (program) code." blockCode: "Code (Block)" blockCodeDescription: "Displays syntax highlighting for multi-line (program) code in a block." - inlineMath: "Math (In-line)" + inlineMath: "Math (Inline)" inlineMathDescription: "Display math formulas (KaTeX) in-line" blockMath: "Math (Block)" - blockMathDescription: "Display multi-line Math formulas (KaTeX) in a block" + blockMathDescription: "Display multi-line math formulas (KaTeX) in a block" quote: "Quote" quoteDescription: "Displays content as a quote." emoji: "Custom Emoji" @@ -947,7 +956,7 @@ _mfm: x4: "Unbelievably big" x4Description: "Displays content even bigger than bigger than big." blur: "Blur" - blurDescription: "Content can be blurred via this effect. It will be displayed clearly when hovered over." + blurDescription: "Blurs content. It will be displayed clearly when hovered over." font: "Font" fontDescription: "Sets the font to display content in." rainbow: "Rainbow" @@ -1079,10 +1088,10 @@ _ago: unknown: "Unknown" future: "Future" justNow: "Just now" - secondsAgo: "{n} seconds ago" - minutesAgo: "{n} minutes ago" - hoursAgo: "{n} hours ago" - daysAgo: "{n} days ago" + secondsAgo: "{n} second(s) ago" + minutesAgo: "{n} minute(s) ago" + hoursAgo: "{n} hour(s) ago" + daysAgo: "{n} day(s) ago" weeksAgo: "{n} week(s) ago" monthsAgo: "{n} month(s) ago" yearsAgo: "{n} year(s) ago" @@ -1099,17 +1108,17 @@ _tutorial: step2_1: "Let's finish setting up your profile before writing a note or following anyone." step2_2: "Providing some information about who you are will make it easier for others to tell if they want to see your notes or follow you." step3_1: "Finished setting up your profile?" - step3_2: "Then let's try posting a note next. You can do this by pressing the pencil icon on the top of the screen." + step3_2: "Then let's try posting a note next. You can do so by pressing the button with a pencil icon on the screen." step3_3: "Fill in the modal and press the button on the top right to post." step3_4: "Have nothing to say? Try \"just setting up my msky\"!" step4_1: "Finished posting your first note?" step4_2: "Hurray! Now your first note should be displayed on your timeline." step5_1: "Now, let's try making your timeline more lively by following other people." - step5_2: "{featured} will show you trending notes in this instance. {explore} will let you find trending users. Try finding people you'd like to follow there!" + step5_2: "{featured} will show you popular notes in this instance. {explore} will let you find popular users. Try finding people you'd like to follow there!" step5_3: "To follow other users, click on their icon and press the \"Follow\" button on their profile." step5_4: "If the other user has a lock icon next to their name, it may take some time for that user to manually approve your follow request." step6_1: "You should be able to see other users' notes on your timeline now." - step6_2: "You can also put \"reactions\" on other people's notes to quickly respond." + step6_2: "You can also put \"reactions\" on other people's notes to quickly respond to them." step6_3: "To attach a \"reaction\", press the \"+\" mark on another user's note and choose an emoji you'd like to react with." step7_1: "Congratulations! You have now finished Misskey's basic tutorial." step7_2: "If you would like to learn more about Misskey, try the {help} section." @@ -1140,7 +1149,7 @@ _permissions: "write:mutes": "Edit your list of muted users" "write:notes": "Compose or delete notes" "read:notifications": "View your notifications" - "write:notifications": "Work with your notifications" + "write:notifications": "Manage your notifications" "read:reactions": "View your reactions" "write:reactions": "Edit your reactions" "write:votes": "Vote on a poll" @@ -1150,18 +1159,18 @@ _permissions: "write:page-likes": "Edit your likes on pages" "read:user-groups": "View your user groups" "write:user-groups": "Edit or delete your user groups" - "read:channels": "Read your channels" - "write:channels": "Modify your channels" + "read:channels": "View your channels" + "write:channels": "Edit your channels" "read:gallery": "View your gallery" "write:gallery": "Edit your gallery" - "read:gallery-likes": "View list of liked gallery posts" - "write:gallery-likes": "Edit list of liked gallery posts" + "read:gallery-likes": "View your list of liked gallery posts" + "write:gallery-likes": "Edit your list of liked gallery posts" _auth: shareAccess: "Would you like to authorize \"{name}\" to access this account?" shareAccessAsk: "Are you sure you want to authorize this application to access your account?" permissionAsk: "This application requests the following permissions" pleaseGoBack: "Please go back to the application" - callback: "Returning back to the application" + callback: "Returning to the application" denied: "Access denied" _antennaSources: all: "All notes" @@ -1189,7 +1198,7 @@ _widgets: photos: "Photos" digitalClock: "Digital clock" federation: "Federation" - postForm: "Compose a note" + postForm: "Posting form" slideshow: "Slideshow" button: "Button" onlineUsers: "Online users" @@ -1220,10 +1229,10 @@ _poll: showResult: "View results" voted: "Voted" closed: "Ended" - remainingDays: "{d} days {h} hours remaining" - remainingHours: "{h} hours {m} minutes remaining" - remainingMinutes: "{m} minutes {s} seconds remaining" - remainingSeconds: "{s} seconds remaining" + remainingDays: "{d} day(s) {h} hour(s) remaining" + remainingHours: "{h} hour(s) {m} minute(s) remaining" + remainingMinutes: "{m} minute(s) {s} second(s) remaining" + remainingSeconds: "{s} second(s) remaining" _visibility: public: "Public" publicDescription: "Your note will be visible for all users" @@ -1253,7 +1262,7 @@ _profile: youCanIncludeHashtags: "You can also include hashtags in your bio." metadata: "Additional Information" metadataEdit: "Edit additional Information" - metadataDescription: "You can display up to four additional information fields in your profile." + metadataDescription: "Using these, you can display additional information fields in your profile." metadataLabel: "Label" metadataContent: "Content" changeAvatar: "Change avatar" @@ -1269,29 +1278,29 @@ _exportOrImport: _charts: federation: "Federation" apRequest: "Requests" - usersIncDec: "Difference in # of users" - usersTotal: "Total # of users" + usersIncDec: "Difference in the number of users" + usersTotal: "Total number of users" activeUsers: "Active users" - notesIncDec: "Difference in # of notes" - localNotesIncDec: "Difference in # of local notes" - remoteNotesIncDec: "Difference in # of remote notes" - notesTotal: "Total # of notes" - filesIncDec: "Difference in # of files" - filesTotal: "Total # of files" + notesIncDec: "Difference in the number of notes" + localNotesIncDec: "Difference in the number of local notes" + remoteNotesIncDec: "Difference in the number of remote notes" + notesTotal: "Total number of notes" + filesIncDec: "Difference in the number of files" + filesTotal: "Total number of files" storageUsageIncDec: "Difference in storage usage" storageUsageTotal: "Total storage usage" _instanceCharts: requests: "Requests" - users: "Difference in # of users" - usersTotal: "Cumulative total # of users" - notes: "Difference in # of notes" - notesTotal: "Cumulative total # of notes" - ff: "Difference in # of followed users / followers " - ffTotal: "Cumulative total # of followed users / followers" + users: "Difference in the number of users" + usersTotal: "Cumulative number of users" + notes: "Difference in the number of notes" + notesTotal: "Cumulative number of notes" + ff: "Difference in the number of followed users / followers " + ffTotal: "Cumulative number of followed users / followers" cacheSize: "Difference in cache size" cacheSizeTotal: "Cumulative total cache size" - files: "Difference in # of files" - filesTotal: "Cumulative total # of files" + files: "Difference in the number of files" + filesTotal: "Cumulative number of files" _timelines: home: "Home" local: "Local" @@ -1300,7 +1309,7 @@ _timelines: _pages: newPage: "Create a new Page" editPage: "Edit this Page" - readPage: "Source view activated" + readPage: "Viewing this Page's source" created: "Page successfully created" updated: "Page successfully edited" deleted: "Page successfully deleted" @@ -1315,7 +1324,7 @@ _pages: unlike: "Remove like" my: "My Pages" liked: "Liked Pages" - featured: "Featured" + featured: "Popular" inspector: "Inspector" contents: "Contents" content: "Page block" @@ -1346,10 +1355,10 @@ _pages: if: "If" _if: variable: "Variable" - post: "Compose a note" + post: "Posting form" _post: text: "Content" - attachCanvasImage: "Post with canvas image" + attachCanvasImage: "Attach canvas image" canvasId: "Canvas ID" textInput: "Text input" _textInput: @@ -1385,11 +1394,11 @@ _pages: _counter: name: "Variable name" text: "Title" - inc: "Increase by" + inc: "Step" _button: text: "Title" colored: "Colored" - action: "Operation when the button is pressed" + action: "Behavior when the button is pressed" _action: dialog: "Show a dialog" _dialog: @@ -1431,11 +1440,11 @@ _pages: strLen: "Text length" _strLen: arg1: "Text" - strPick: "Extract character" + strPick: "Extract string" _strPick: arg1: "Text" - arg2: "Character location" - strReplace: "Text replacement" + arg2: "String location" + strReplace: "Replacement string" _strReplace: arg1: "Text" arg2: "Text to be replaced" @@ -1529,7 +1538,7 @@ _pages: arg2: "Maximum value" dailyRandomPick: "Randomly choose from a list (Changes once a day for each user)" _dailyRandomPick: - arg1: "Lists" + arg1: "List" seedRandom: "Random (with seed)" _seedRandom: arg1: "Seed" @@ -1603,6 +1612,7 @@ _notification: youReceivedFollowRequest: "You've received a follow request" yourFollowRequestAccepted: "Your follow request was accepted" youWereInvitedToGroup: "You've been invited to a group" + pollEnded: "Poll results have become available" _types: all: "All" follow: "New followers" @@ -1612,6 +1622,7 @@ _notification: quote: "Quotes" reaction: "Reactions" pollVote: "Votes on polls" + pollEnded: "Polls ending" receiveFollowRequest: "Received follow requests" followRequestAccepted: "Accepted follow requests" groupInvited: "Group invitations" @@ -1622,12 +1633,12 @@ _deck: columnMargin: "Margin between columns" columnHeaderHeight: "Column header height" addColumn: "Add column" - swapLeft: "Swap left" - swapRight: "Swap right" - swapUp: "Swap with above" - swapDown: "Swap with below" - stackLeft: "Stack on left column" - popRight: "Pop to the right" + swapLeft: "Swap with the left column" + swapRight: "Swap with the right column" + swapUp: "Swap with the above column" + swapDown: "Swap with the below column" + stackLeft: "Stack with the left column" + popRight: "Pop column to the right" profile: "Profile" _columns: main: "Main" diff --git a/locales/eo-UY.yml b/locales/eo-UY.yml index 062bf85aa9..934ffd2d43 100644 --- a/locales/eo-UY.yml +++ b/locales/eo-UY.yml @@ -9,7 +9,7 @@ username: "Uzantnomo" password: "Pasvorto" forgotPassword: "Ĉu vi forgesis pasvorton?" fetchingAsApObject: "Informpetado de la Fediverso…" -ok: "Bone" +ok: "Okej" gotIt: "Kompreni" cancel: "Nuligi" enterUsername: "Entajpu uzantnomon" @@ -71,7 +71,7 @@ lists: "Listoj" noLists: "Neniu listo" note: "Noti" notes: "Notoj" -following: "Sekvata" +following: "Sekvatoj" followers: "Sekvantoj" followsYou: "Sekvas vin" createList: "Krei liston" @@ -224,7 +224,7 @@ resetAreYouSure: "Ĉu vi certas restarigi?" saved: "Konservita" messaging: "Retbabili" upload: "Alŝuti" -keepOriginalUploading: "Konservi la originalan bildon" +keepOriginalUploading: "Konservi originalon" fromDrive: "De la disko" fromUrl: "De URL" uploadFromUrl: "Alŝuti de URL" @@ -278,6 +278,7 @@ rename: "Alinomi" avatar: "Bildsimbolo" banner: "Standardo" nsfw: "Enhavo ne estas deca por laborejo (NSFW)" +whenServerDisconnected: "Kiam vi malligiĝas de servilo" disconnectedFromServer: "Malkonektita de servilo" reload: "Reŝargi" doNothing: "Ignori" @@ -396,7 +397,6 @@ next: "Sekve" retype: "Retajpu" noteOf: "Noto de {user}" inviteToGroup: "Inviti al grupo" -maxNoteTextLength: "Limnombro de leteroj en noto" quoteAttached: "Kun citaĵo" quoteQuestion: "Ĉu vi volas aldoni citaĵon?" noMessagesYet: "Ankoraŭ neniu mesaĝo" @@ -420,7 +420,7 @@ signinWith: "Saluti kun {x}" signinFailed: "Fuŝiĝis ensaluti. Bonvolu kontroli uzantnomon kaj pasvorton." or: "Aŭ" language: "Lingvo" -uiLanguage: "Lingvo de fasado" +uiLanguage: "Lingvo de la fasado" groupInvited: "Invitita al grupo" aboutX: "Pri {x}" useOsNativeEmojis: "Uzi la emoĵiojn implicitan de la operaciumo" @@ -648,7 +648,7 @@ high: "Alta" middle: "Meza" low: "Malalta" emailNotConfiguredWarning: "Vi ne agordis retpoŝtadreso." -customCss: "Personecigita CSS" +customCss: "Propra CSS" global: "Malloka" squareAvatars: "Montri bildsimbolon kiel kvadrata" sent: "Sendi" @@ -683,14 +683,20 @@ hide: "Kaŝi" 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 la konfirmon de vian retadreson" +clickToFinishEmailVerification: "Volu klaki [{ok}] por fini konfirmon de via retadreso." smartphone: "Saĝtelefono" tablet: "Platkomputilo" auto: "Aŭtomate" +searchByGoogle: "Serĉi en Google-Serĉo" +tenMinutes: "10 minutoj" +oneHour: "1 horo" +oneDay: "1 tago" +oneWeek: "1 semajno" _emailUnavailable: used: "La retpoŝto jam estas uzita." format: "Nevalida formato." disposable: "Dumtempa retpoŝto ne estas uzebla." + mx: "Ĉi retpoŝto-servilo ne estas uzebla." smtp: "Tiu retpoŝta servilo ne respondas" _ffVisibility: public: "Publika" @@ -770,9 +776,9 @@ _channel: usersCount: "{n} partoprenantoj" notesCount: "{n} notoj" _menuDisplay: - sideFull: "Flanko" - sideIcon: "Flanko (bildsimbolo)" - top: "Supro" + sideFull: "Sur la flanko" + sideIcon: "Sur la flanko (bildsimbolo)" + top: "Sur la supro" hide: "Kaŝi" _wordMute: muteWords: "Silentigitaj vortoj" @@ -920,7 +926,6 @@ _postForm: c: "Kio estas sur via penso?" d: "Kion vi volas diri?" e: "Komencu skribi tie" - f: "Atendanta de vi skribon…" _profile: name: "Nomo" username: "Uzantnomo" diff --git a/locales/es-ES.yml b/locales/es-ES.yml index 1e85e7d8c4..fd69f62ff5 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -321,8 +321,6 @@ disablingTimelinesInfo: "Aunque se desactiven estas lineas de tiempo, por conven registration: "Registro" enableRegistration: "Permitir nuevos registros" invite: "Invitar" -proxyRemoteFiles: "Hacer proxy de archivos remotos" -proxyRemoteFilesDescription: "Si activa esta configuración, los archivos remotos no almacenados o borrados por exceso de capacidad serán mostrados via proxy local y generarán una miniatura. Eso no afectará el almacenamiento del servidor." driveCapacityPerLocalAccount: "Capacidad del drive por usuario local" driveCapacityPerRemoteAccount: "Capacidad del drive por usuario remoto" inMb: "En megabytes" @@ -418,7 +416,6 @@ next: "Siguiente" retype: "Intentar de nuevo" noteOf: "Notas de {user}" inviteToGroup: "Invitar al grupo" -maxNoteTextLength: "Límite de caracteres en una nota" quoteAttached: "Cita añadida" quoteQuestion: "¿Quiere añadir una cita?" noMessagesYet: "Aún no hay chat" @@ -765,6 +762,8 @@ muteThread: "Ocultar hilo" unmuteThread: "Mostrar hilo" ffVisibility: "Visibilidad de seguidores y seguidos" hide: "Ocultar" +searchByGoogle: "Buscar" +indefinitely: "Sin límite de tiempo" _ffVisibility: public: "Publicar" _accountDelete: diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index 1deda414ce..5ccf1b2b6e 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -323,8 +323,6 @@ disablingTimelinesInfo: "Même si vous désactivez ces fils, les administrateur registration: "S’inscrire" enableRegistration: "Autoriser les nouvelles inscriptions" invite: "Inviter" -proxyRemoteFiles: "Utiliser les fichiers distants comme proxy" -proxyRemoteFilesDescription: "Si vous activez ce paramètre, les fichiers distants non stockés ou supprimés en raison d'une capacité excédentaire seront affichés via un proxy local et généreront une miniature. Cela n'affectera pas le stockage du serveur." driveCapacityPerLocalAccount: "Volume du Drive par utilisateur local" driveCapacityPerRemoteAccount: "Volume du Drive par utilisateur distant" inMb: "en mégaoctets" @@ -420,7 +418,6 @@ next: "Suivant" retype: "Confirmation" noteOf: "Notes de {user}" inviteToGroup: "Inviter dans un groupe" -maxNoteTextLength: "Limite du nombre de caractères pour les notes" quoteAttached: "Avec citation" quoteQuestion: "Souhaitez-vous ajouter une citation ?" noMessagesYet: "Pas encore de discussion" @@ -592,6 +589,7 @@ smtpSecure: "Utiliser SSL/TLS implicitement dans les connexions SMTP" smtpSecureInfo: "Désactiver cette option lorsque STARTTLS est utilisé" testEmail: "Tester la distribution de courriel" wordMute: "Filtre de mots" +regexpError: "Erreur d’expression régulière" instanceMute: "Instance en sourdine" userSaysSomething: "{name} a dit quelque chose" makeActive: "Activer" @@ -620,6 +618,7 @@ reportAbuse: "Signaler" reportAbuseOf: "Signaler {name}" fillAbuseReportDescription: "Veuillez expliquer les raisons du signalement. S'il s'agit d'une note précise, veuillez en donner le lien." abuseReported: "Le rapport est envoyé. Merci." +reporter: "Signalé par" reporteeOrigin: "Origine du signalement" reporterOrigin: "Signalé par" forwardReport: "Transférer le signalement à l’instance distante" @@ -818,6 +817,22 @@ leaveGroup: "Quitter le groupe" leaveGroupConfirm: "Êtes vous sûr de vouloir quitter \"{name}\" ?" welcomeBackWithName: "Heureux de vous revoir, {name}" clickToFinishEmailVerification: "Veuillez cliquer sur [{ok}] afin de compléter la vérification par courriel." +overridedDeviceKind: "Type d’appareil" +smartphone: "Smartphone" +tablet: "Tablette" +auto: "Automatique" +themeColor: "Couleur du thème" +size: "Taille" +numberOfColumn: "Nombre de colonnes" +searchByGoogle: "Google" +instanceDefaultLightTheme: "Thème clair par défaut sur toute l’instance" +instanceDefaultDarkTheme: "Thème sombre par défaut sur toute l’instance" +mutePeriod: "Durée de mise en sourdine" +indefinitely: "Illimité" +tenMinutes: "10 minutes" +oneHour: "1 heure" +oneDay: "1 jour" +oneWeek: "1 semaine" _emailUnavailable: used: "Non disponible" format: "Le format de cette adresse de courriel est invalide" diff --git a/locales/hr-HR.yml b/locales/hr-HR.yml new file mode 100644 index 0000000000..ed97d539c0 --- /dev/null +++ b/locales/hr-HR.yml @@ -0,0 +1 @@ +--- diff --git a/locales/id-ID.yml b/locales/id-ID.yml index be766e72c5..cf89158676 100644 --- a/locales/id-ID.yml +++ b/locales/id-ID.yml @@ -325,8 +325,6 @@ disablingTimelinesInfo: "Admin dan Moderator akan selalu memiliki akses ke semua registration: "Pendaftaran" enableRegistration: "Nyalakan pendaftaran pengguna baru" invite: "Undang" -proxyRemoteFiles: "Proksi berkas remote" -proxyRemoteFilesDescription: "Jika diaktifkan, berkas luar yang (1) tidak disimpan secara lokal atau (2) terhapus dari melewati batas penyimpanan akan diproksi secara lokal (dengan thumbnail). Ini tidak akan mempengaruhi server penyimpanan." driveCapacityPerLocalAccount: "Kapasitas drive per pengguna lokal" driveCapacityPerRemoteAccount: "Kapasitas drive per pengguna remote" inMb: "dalam Megabytes" @@ -422,7 +420,6 @@ next: "Selanjutnya" retype: "Masukkan ulang" noteOf: "Catatan milik {user}" inviteToGroup: "Undang ke grup" -maxNoteTextLength: "Batas karakter catatan" quoteAttached: "Dikutip" quoteQuestion: "Apakah kamu ingin menambahkan kutipan?" noMessagesYet: "Tidak ada pesan" @@ -828,6 +825,8 @@ overridedDeviceKind: "Tipe perangkat" smartphone: "Ponsel" tablet: "Tablet" auto: "Otomatis" +searchByGoogle: "Penelusuran" +indefinitely: "Selamanya" _emailUnavailable: used: "Alamat surel ini telah digunakan" format: "Format tidak valid." diff --git a/locales/it-IT.yml b/locales/it-IT.yml index c4ec4232a5..1eaa78b646 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -321,8 +321,6 @@ disablingTimelinesInfo: "Anche se disabiliti queste timeline, gli amministratori registration: "Iscriviti" enableRegistration: "Permettere nuove registrazioni" invite: "Invita" -proxyRemoteFiles: "Usare file remoti come proxy" -proxyRemoteFilesDescription: "Attivando questa opzione i file remoti non salvati o cancellati perché eccedenti il limite di archiviazione verranno inoltrati tramite proxy, inclusa la generazione di anteprime. Non ha effetto sullo spazio di archiviazione del server." driveCapacityPerLocalAccount: "Volume del Drive per utente locale" driveCapacityPerRemoteAccount: "Volume del Drive per utente remoto" inMb: "in Megabytes" @@ -418,7 +416,6 @@ next: "Avanti" retype: "Conferma" noteOf: "Note di {user}" inviteToGroup: "Invitare al gruppo" -maxNoteTextLength: "Lunghezza massima delle note" quoteAttached: "Citazione allegata" quoteQuestion: "Vuoi aggiungere una citazione?" noMessagesYet: "Ancora nessuna chat" @@ -805,6 +802,8 @@ leaveGroupConfirm: "Uscire da「{name}」?" useDrawerReactionPickerForMobile: "Mostra sul drawer da dispositivo mobile" welcomeBackWithName: "Bentornato/a, {name}" clickToFinishEmailVerification: "Fai click su [{ok}] per completare la verifica dell'indirizzo email." +searchByGoogle: "Cerca" +indefinitely: "Non scade" _emailUnavailable: used: "Email già in uso" format: "Formato email non valido" diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 90dc14815e..6326094dd8 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -830,10 +830,18 @@ auto: "自動" themeColor: "テーマカラー" size: "サイズ" numberOfColumn: "列の数" -searchByGoogle: "ググる" +searchByGoogle: "検索" instanceDefaultLightTheme: "インスタンスデフォルトのライトテーマ" instanceDefaultDarkTheme: "インスタンスデフォルトのダークテーマ" instanceDefaultThemeDescription: "オブジェクト形式のテーマコードを記入します。" +mutePeriod: "ミュートする期限" +indefinitely: "無期限" +tenMinutes: "10分" +oneHour: "1時間" +oneDay: "1日" +oneWeek: "1週間" +reflectMayTakeTime: "反映されるまで時間がかかる場合があります。" +failedToFetchAccountInformation: "アカウント情報の取得に失敗しました" _emailUnavailable: used: "既に使用されています" @@ -1661,6 +1669,7 @@ _notification: youReceivedFollowRequest: "フォローリクエストが来ました" yourFollowRequestAccepted: "フォローリクエストが承認されました" youWereInvitedToGroup: "グループに招待されました" + pollEnded: "アンケートの結果が出ました" _types: all: "すべて" @@ -1671,6 +1680,7 @@ _notification: quote: "引用" reaction: "リアクション" pollVote: "アンケートに投票された" + pollEnded: "アンケートが終了" receiveFollowRequest: "フォロー申請を受け取った" followRequestAccepted: "フォローが受理された" groupInvited: "グループに招待された" diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml index b5b4e72576..52ecd8c24e 100644 --- a/locales/ja-KS.yml +++ b/locales/ja-KS.yml @@ -323,8 +323,6 @@ disablingTimelinesInfo: "ここらへんのタイムラインを使えんよう registration: "登録" enableRegistration: "一見さんでも誰でもいらっしゃ~い" invite: "来てや" -proxyRemoteFiles: "リモートのファイルをプロキシする" -proxyRemoteFilesDescription: "この設定を有効にしたら、保存してなかったり容量が足らんくて消されたリモートファイルをローカルでプロキシして、サムネイルを作るようになるで。サーバーの容量には関係ないで。" driveCapacityPerLocalAccount: "ローカルユーザーひとりあたりのドライブ容量" driveCapacityPerRemoteAccount: "リモートユーザーひとりあたりのドライブ容量" inMb: "メガバイト単位" @@ -417,7 +415,6 @@ next: "次" retype: "もっかい入力" noteOf: "{user}のノート" inviteToGroup: "グループに招く" -maxNoteTextLength: "ノートの文字数制限" quoteAttached: "引用付いとるで" quoteQuestion: "引用として添付してもええか?" noMessagesYet: "まだチャットはあらへんで" @@ -658,6 +655,8 @@ global: "グローバル" sent: "送信" hashtags: "ハッシュタグ" hide: "隠す" +searchByGoogle: "探す" +indefinitely: "無期限" _ad: back: "戻る" _gallery: diff --git a/locales/kab-KAB.yml b/locales/kab-KAB.yml index 364a935310..6a14cbe1ba 100644 --- a/locales/kab-KAB.yml +++ b/locales/kab-KAB.yml @@ -55,6 +55,7 @@ accountInfo: "Talɣut n umiḍan" emailNotification: "Ilɣa imayl" selectAccount: "Fren amiḍan" accounts: "Imiḍan" +searchByGoogle: "Nadi" _email: _follow: title: "Yeṭṭafaṛ-ik·em-id" diff --git a/locales/kn-IN.yml b/locales/kn-IN.yml index fd52842b2c..3111c90dd5 100644 --- a/locales/kn-IN.yml +++ b/locales/kn-IN.yml @@ -59,6 +59,7 @@ remove: "ಅಳಿಸು" smtpUser: "ಬಳಕೆಹೆಸರು" smtpPass: "ಗುಪ್ತಪದ" user: "ಬಳಕೆದಾರ" +searchByGoogle: "ಹುಡುಕು" _email: _follow: title: "ಹಿಂಬಾಲಿಸಿದರು" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 116e397ff5..e1ad77cbc9 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -325,8 +325,6 @@ disablingTimelinesInfo: "특정 타임라인을 비활성화하더라도 관리 registration: "등록" enableRegistration: "신규 회원가입을 활성화" invite: "초대" -proxyRemoteFiles: "리모트 파일 프록시" -proxyRemoteFilesDescription: "이 설정을 활성화할 경우, 저장되지 않았거나 저장용량 초과로 삭제된 리모트 파일을 로컬에서 프록시하여 썸네일을 생성하게 됩니다. 서버의 스토리지에는 영향을 주지 않습니다." driveCapacityPerLocalAccount: "로컬 유저 한 명당 드라이브 용량" driveCapacityPerRemoteAccount: "리모트 유저 한 명당 드라이브 용량" inMb: "메가바이트 단위" @@ -422,7 +420,6 @@ next: "다음" retype: "다시 입력" noteOf: "{user}의 노트" inviteToGroup: "그룹에 초대하기" -maxNoteTextLength: "노트의 문자 수 제한" quoteAttached: "인용함" quoteQuestion: "인용해서 작성하시겠습니까?" noMessagesYet: "아직 대화가 없습니다" @@ -828,6 +825,8 @@ overridedDeviceKind: "장치 유형" smartphone: "스마트폰" tablet: "태블릿" auto: "자동" +searchByGoogle: "검색" +indefinitely: "무기한" _emailUnavailable: used: "이 메일 주소는 사용중입니다" format: "형식이 올바르지 않습니다" diff --git a/locales/nl-NL.yml b/locales/nl-NL.yml index 38f9a88af8..a00716fe05 100644 --- a/locales/nl-NL.yml +++ b/locales/nl-NL.yml @@ -256,6 +256,7 @@ user: "Gebruikers" muteThread: "Discussies dempen " unmuteThread: "Dempen van discussie ongedaan maken" hide: "Verbergen" +searchByGoogle: "Zoeken" _email: _follow: title: "volgde jou" diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml index 0b57a3a46f..78d86dd7e3 100644 --- a/locales/pl-PL.yml +++ b/locales/pl-PL.yml @@ -320,8 +320,6 @@ disablingTimelinesInfo: "Administratorzy i moderatorzy będą zawsze mieć dost registration: "Zarejestruj się" enableRegistration: "Włącz rejestrację nowych użytkowników" invite: "Zaproś" -proxyRemoteFiles: "Przekierowuj pliki obcych instancji przez proxy" -proxyRemoteFilesDescription: "Gdy ta opcja jest włączona, zdalne pliki które nie są przechowywane lokalnie, lub zostały usunięte z powodu przekroczenia limitu miejsca będą kierowane przez proxy, razem z generowaniem miniatur. Nie ma to żadnego wpływu na przestrzeń dyskową serwera." driveCapacityPerLocalAccount: "Powierzchnia dyskowa na lokalnego użytkownika" driveCapacityPerRemoteAccount: "Powierzchnia dyskowa na zdalnego użytkownika" inMb: "W megabajtach" @@ -417,7 +415,6 @@ next: "Dalej" retype: "Wprowadź ponownie" noteOf: "Wpisy {user}" inviteToGroup: "Zaproś do grupy" -maxNoteTextLength: "Limit znaków dla wpisów" quoteAttached: "Zacytowano" quoteQuestion: "Czy na pewno chcesz umieścić cytat?" noMessagesYet: "Nie napisano jeszcze wiadomości" @@ -761,6 +758,8 @@ received: "Otrzymane" hashtags: "Hashtag" pubSub: "Konta Pub/Sub" hide: "Ukryj" +searchByGoogle: "Szukaj" +indefinitely: "Nigdy" _ffVisibility: public: "Publikuj" _ad: diff --git a/locales/pt-PT.yml b/locales/pt-PT.yml index d095887bdc..0f12155e33 100644 --- a/locales/pt-PT.yml +++ b/locales/pt-PT.yml @@ -61,6 +61,7 @@ pinnedNotes: "Post fixado" smtpUser: "Nome de usuário" smtpPass: "Senha" user: "Usuários" +searchByGoogle: "Pesquisar" _email: _follow: title: "Você tem um novo seguidor" diff --git a/locales/ro-RO.yml b/locales/ro-RO.yml new file mode 100644 index 0000000000..7f8ed82b8d --- /dev/null +++ b/locales/ro-RO.yml @@ -0,0 +1,556 @@ +--- +_lang_: "Română" +headlineMisskey: "O rețea conectată prin note" +introMisskey: "Bine ai venit! Misskey este un serviciu de microblogging open source și decentralizat.\nCreează \"note\" cu care să îți poți împărți gândurile cu oricine din jurul tău. 📡\nCu \"reacții\" îți poți expirma rapid părerea despre notele oricui. 👍\nHai să explorăm o lume nouă! 🚀" +monthAndDay: "{day}/{month}" +search: "Caută" +notifications: "Notificări" +username: "Nume de utilizator" +password: "Parolă" +forgotPassword: "Am uitat parola" +fetchingAsApObject: "Se aduce din Fediverse..." +ok: "OK" +gotIt: "Am înțeles!" +cancel: "Anulează" +enterUsername: "Introdu numele de utilizator" +renotedBy: "Re-notat de {user}" +noNotes: "Nicio notă" +noNotifications: "Nicio notificare" +instance: "Instanță" +settings: "Setări" +basicSettings: "Setări generale" +otherSettings: "Alte Setări" +openInWindow: "Deschide într-o fereastră" +profile: "Profil" +timeline: "Cronologie" +noAccountDescription: "Acest utilizator încă nu a scris un bio." +login: "Autentifică-te" +loggingIn: "Se autentifică" +logout: "Deconectează-te" +signup: "Înregistrează-te" +uploading: "Se încarcă" +save: "Salvează" +users: "Utilizatori" +addUser: "Adăugă utilizator" +favorite: "Adaugă la favorite" +favorites: "Favorite" +unfavorite: "Elimină din favorite" +favorited: "Adăugat la favorite." +alreadyFavorited: "Deja adăugat la favorite." +cantFavorite: "Nu se poate adăuga la favorite." +pin: "Fixează pe profil" +unpin: "Anulati fixare" +copyContent: "Copiază conținutul" +copyLink: "Copiază link-ul" +delete: "Şterge" +deleteAndEdit: "Șterge și editează" +deleteAndEditConfirm: "Ești sigur că vrei să ștergi această notă și să o editezi? Vei pierde reacțiile, re-notele și răspunsurile acesteia." +addToList: "Adaugă în listă" +sendMessage: "Trimite un mesaj" +copyUsername: "Copiază numele de utilizator" +searchUser: "Caută un utilizator" +reply: "Răspunde" +loadMore: "Incarcă mai mult" +showMore: "Arată mai mult" +youGotNewFollower: "te-a urmărit" +receiveFollowRequest: "Cerere de urmărire primită" +followRequestAccepted: "Cerere de urmărire acceptată" +mention: "Mențiune" +mentions: "Mențiuni" +directNotes: "Note directe" +importAndExport: "Importă / Exportă" +import: "Importă" +export: "Exportă" +files: "Fișiere" +download: "Descarcă" +driveFileDeleteConfirm: "Ești sigur ca vrei să ștergi fișierul \"{name}\"? Notele atașate fișierului vor fi șterse și ele." +unfollowConfirm: "Ești sigur ca vrei să nu mai urmărești pe {name}?" +exportRequested: "Ai cerut un export. S-ar putea să ia un pic. Va fi adăugat in Drive-ul tău odată completat." +importRequested: "Ai cerut un import. S-ar putea să ia un pic." +lists: "Liste" +noLists: "Nu ai nici o listă" +note: "Notă" +notes: "Note" +following: "Urmărești" +followers: "Urmăritori" +followsYou: "Te urmărește" +createList: "Creează listă" +manageLists: "Gestionează listele" +error: "Eroare" +somethingHappened: "A survenit o eroare" +retry: "Reîncearcă" +pageLoadError: "A apărut o eroare la încărcarea paginii." +pageLoadErrorDescription: "De obicei asta este cauzat de o eroare de rețea sau cache-ul browser-ului. Încearcă să cureți cache-ul și apoi să încerci din nou puțin mai târziu." +serverIsDead: "Serverul nu răspunde. Te rugăm să aștepți o perioadă și să încerci din nou." +youShouldUpgradeClient: "Pentru a vedea această pagină, te rugăm să îți actualizezi clientul." +enterListName: "Introdu un nume pentru listă" +privacy: "Confidenţialitate" +makeFollowManuallyApprove: "Fă cererile de urmărire să necesite aprobare" +defaultNoteVisibility: "Vizibilitate implicită" +follow: "Urmărești" +followRequest: "Trimite cerere de urmărire" +followRequests: "Cereri de urmărire" +unfollow: "Nu mai urmări" +followRequestPending: "Cerere de urmărire în așteptare" +enterEmoji: "Introdu un emoji" +renote: "Re-notează" +unrenote: "Ia înapoi re-nota" +renoted: "Re-notat." +cantRenote: "Această postare nu poate fi re-notată." +cantReRenote: "O re-notă nu poate fi re-notată." +quote: "Citează" +pinnedNote: "Notă fixată" +pinned: "Fixat pe profil" +you: "Tu" +clickToShow: "Click pentru a afișa" +sensitive: "NSFW" +add: "Adaugă" +reaction: "Reacție" +reactionSetting: "Reacții care să apară in selectorul de reacții" +reactionSettingDescription2: "Trage pentru a rearanja, apasă pe \"+\" pentru a adăuga." +rememberNoteVisibility: "Amintește setarea de vizibilitate a notelor" +attachCancel: "Înlătură atașament" +markAsSensitive: "Marchează ca NSFW" +unmarkAsSensitive: "Demarchează ca NSFW" +enterFileName: "Introduceţi numele fişierului" +mute: "Amuțește" +unmute: "Înlătură amuțirea" +block: "Blochează" +unblock: "Deblochează" +suspend: "Suspendă" +unsuspend: "Anulează suspendare" +blockConfirm: "Ești sigur că vrei să blochezi acest cont?" +unblockConfirm: "Ești sigur ca vrei să deblochezi acest cont?" +suspendConfirm: "Ești sigur ca vrei să suspendezi acest cont?" +unsuspendConfirm: "Ești sigur ca vrei să nu mai suspendezi acest cont?" +selectList: "Selectează o listă" +selectAntenna: "Selectează o antenă" +selectWidget: "Selectați un widget" +editWidgets: "Editează widget-urile" +editWidgetsExit: "Terminat" +customEmojis: "Emoji personalizat" +emoji: "Emoji" +emojis: "Emoji-uri" +emojiName: "Numele emoji-ului" +emojiUrl: "URL-ul emoji-ului" +addEmoji: "Adaugă un emoji" +settingGuide: "Setări recomandate" +cacheRemoteFiles: "Ține fișierele externe in cache" +cacheRemoteFilesDescription: "Când această setare este dezactivată, fișierele externe sunt încărcate direct din instanța externă. Dezactivarea va scădea utilizarea spațiului de stocare, dar va crește traficul, deoarece thumbnail-urile nu vor fi generate." +flagAsBot: "Marchează acest cont ca bot" +flagAsBotDescription: "Activează această opțiune dacă acest cont este controlat de un program. Daca e activată, aceasta va juca rolul unui indicator pentru dezvoltatori pentru a preveni interacțiunea în lanțuri infinite cu ceilalți boți și ajustează sistemele interne al Misskey pentru a trata acest cont drept un bot." +flagAsCat: "Marchează acest cont ca pisică" +flagAsCatDescription: "Activează această opțiune dacă acest cont este o pisică." +flagShowTimelineReplies: "Arată răspunsurile în cronologie" +flagShowTimelineRepliesDescription: "Dacă e activată vor fi arătate în cronologie răspunsurile utilizatorilor către alte notele altor utilizatori." +autoAcceptFollowed: "Aprobă automat cererile de urmărire de la utilizatorii pe care îi urmărești" +addAccount: "Adaugă un cont" +loginFailed: "Autentificare eșuată" +showOnRemote: "Vezi mai multe pe instanța externă" +general: "General" +wallpaper: "Imagine de fundal" +setWallpaper: "Setați imaginea de fundal" +removeWallpaper: "Șterge imagine de fundal" +searchWith: "Caută: {q}" +youHaveNoLists: "Nu ai nici o listă" +followConfirm: "Ești sigur ca vrei să urmărești pe {name}?" +proxyAccount: "Cont proxy" +proxyAccountDescription: "Un cont proxy este un cont care se comportă ca un urmăritor extern pentru utilizatorii puși sub anumite condiții. De exemplu, când un cineva adaugă un utilizator extern intr-o listă, activitatea utilizatorului extern nu va fi adusă în instanță daca nici un utilizator local nu urmărește acel utilizator, așa că în schimb contul proxy îl va urmări." +host: "Gazdă" +selectUser: "Selectează un utilizator" +recipient: "Destinatar" +annotation: "Adnotări" +federation: "Federație" +instances: "Instanțe" +registeredAt: "Înregistrat în" +latestRequestSentAt: "Ultima cerere trimisă" +latestRequestReceivedAt: "Ultima cerere primită" +latestStatus: "Ultimul status" +storageUsage: "Utilizare stocare" +charts: "Diagrame" +perHour: "Pe oră" +perDay: "Pe zi" +stopActivityDelivery: "Nu mai trimite activități" +blockThisInstance: "Blochează această instanță" +operations: "Operațiuni" +software: "Software" +version: "Versiune" +metadata: "Metadata" +withNFiles: "{n} fișier(e)" +monitor: "Monitor" +jobQueue: "coada de job-uri" +cpuAndMemory: "CPU și memorie" +network: "Rețea" +disk: "Disk" +instanceInfo: "Informații despre instanță" +statistics: "Statistici" +clearQueue: "Șterge coada" +clearQueueConfirmTitle: "Ești sigur că vrei să cureți coada?" +clearQueueConfirmText: "Orice notă rămasă în coadă nu va fi federată. De obicei această operație nu este necesară." +clearCachedFiles: "Golește cache-ul" +clearCachedFilesConfirm: "Ești sigur că vrei să ștergi toate fișierele externe din cache?" +blockedInstances: "Instanțe blocate" +blockedInstancesDescription: "Scrie hostname-urile instanțelor pe care dorești să le blochezi. Instanțele listate nu vor mai putea să comunice cu această instanță." +muteAndBlock: "Amuțiri și Blocări" +mutedUsers: "Utilizatori amuțiți" +blockedUsers: "Utilizatori blocați" +noUsers: "Niciun utilizator" +editProfile: "Editează profilul" +noteDeleteConfirm: "Ești sigur că vrei să ștergi această notă?" +pinLimitExceeded: "Nu poți mai fixa mai multe note" +intro: "Misskey s-a instalat! Te rog crează un utilizator admin." +done: "Gata" +processing: "Se procesează" +preview: "Previzualizare" +default: "Prestabilit" +noCustomEmojis: "Nu e niciun emoji" +noJobs: "Nu e niciun job" +federating: "Federație" +blocked: "Blocat" +suspended: "Suspendat" +all: "Tot" +subscribing: "Abonare" +publishing: "Publicare" +notResponding: "Nu răspunde" +instanceFollowing: "Urmărind în instanță" +instanceFollowers: "Urmăritori ai instanței" +instanceUsers: "Utilizatori ai acestei instanțe" +changePassword: "Schimbă parolă" +security: "Securitate" +retypedNotMatch: "Intrările nu corespund" +currentPassword: "Parola curentă" +newPassword: "Parola nouă" +newPasswordRetype: "Rescrie parola nouă" +attachFile: "Atașează fișiere" +more: "Mai mult!" +featured: "Evidențiat" +usernameOrUserId: "Nume sau ID de utilizator" +noSuchUser: "Utilizatorul nu a fost găsit" +lookup: "Privire" +announcements: "Anunțuri" +imageUrl: "URL-ul imaginii" +remove: "Şterge" +removed: "Șterș cu succes" +removeAreYouSure: "Ești sigur că vrei să înlături {x}?" +deleteAreYouSure: "Ești sigur că vrei să ștergi {x}?" +resetAreYouSure: "Sigur vrei să resetezi?" +saved: "Salvat" +messaging: "Chat" +upload: "Încarcă" +keepOriginalUploading: "Păstrează imaginea originală" +keepOriginalUploadingDescription: "Salvează imaginea originala încărcată fără modificări. Dacă e oprită, o versiune pentru afișarea pe web va fi generată la încărcare." +fromDrive: "Din Drive" +fromUrl: "Din URL" +uploadFromUrl: "Încarcă dintr-un URL" +uploadFromUrlDescription: "URL-ul fișierului pe care dorești să îl încarci" +uploadFromUrlRequested: "Încărcare solicitată" +uploadFromUrlMayTakeTime: "S-ar putea să ia puțin până se finalizează încărcarea." +explore: "Explorează" +messageRead: "Citit" +noMoreHistory: "Nu există mai mult istoric" +startMessaging: "Începe un chat nou" +nUsersRead: "citit de {n}" +agreeTo: "Sunt de acord cu {0}" +tos: "Termenii de utilizare" +start: "Să începem" +home: "Acasă" +remoteUserCaution: "Deoarece acest utilizator este dintr-o instanță externă, informația afișată poate fi incompletă." +activity: "Activitate" +images: "Imagini" +birthday: "Zi de naștere" +yearsOld: "{age} ani" +registeredDate: "Data înregistrării" +location: "Locație" +theme: "Teme" +themeForLightMode: "Temă folosită pentru Modul Luminat" +themeForDarkMode: "Temă folosită pentru Modul Întunecat" +light: "Luminos" +dark: "Întunecat" +lightThemes: "Teme luminoase" +darkThemes: "Teme întunecate" +syncDeviceDarkMode: "Sincronizează Modul Întunecat cu setările dispozitivului" +drive: "Drive" +fileName: "Nume fișier" +selectFile: "Alege un fisier" +selectFiles: "Alege fișiere" +selectFolder: "Selectează un folder" +selectFolders: "Selectează folderele" +renameFile: "Redenumește fișier" +folderName: "Nume folder" +createFolder: "Crează folder" +renameFolder: "Redenumește acest folder" +deleteFolder: "Șterge acest folder" +addFile: "Adăugați un fișier" +emptyDrive: "Drive-ul tău e gol" +emptyFolder: "Folder-ul acesta este gol" +unableToDelete: "Nu se poate șterge" +inputNewFileName: "Introdu un nou nume de fișier" +inputNewDescription: "Introdu o descriere nouă" +inputNewFolderName: "Introdu un nume de folder nou" +circularReferenceFolder: "Destinația folderului este un subfolder al folderului pe care dorești să îl muți." +hasChildFilesOrFolders: "Acest folder nu este gol, așa că nu poate fi șters." +copyUrl: "Copiază URL" +rename: "Redenumește" +avatar: "Avatar" +banner: "Banner" +nsfw: "NSFW" +whenServerDisconnected: "Când pierzi conexiunea cu serverul" +disconnectedFromServer: "Conecțiunea cu serverul a fost pierdută" +reload: "Reîncarcă" +doNothing: "Ignoră" +reloadConfirm: "Ai dori să reîmprospătezi cronologia?" +watch: "Vezi" +unwatch: "Oprește-te din văzut" +accept: "Acceptă" +reject: "Respinge" +normal: "Normal" +instanceName: "Numele instanței" +instanceDescription: "Descrierea instanței" +maintainerName: "Administrator" +maintainerEmail: "Email-ul administratorului" +tosUrl: "URL-ul Termenilor de utilizare" +thisYear: "An" +thisMonth: "Lună" +today: "Azi" +dayX: "{day}" +monthX: "{month}" +yearX: "{year}" +pages: "Pagini" +integration: "Integrare" +connectService: "Conectează" +disconnectService: "Deconectează" +enableLocalTimeline: "Activează cronologia locală" +enableGlobalTimeline: "Activeaza cronologia globală" +disablingTimelinesInfo: "Administratorii și Moderatorii vor avea mereu access la toate cronologiile, chiar dacă nu sunt activate." +registration: "Inregistrare" +enableRegistration: "Activează înregistrările pentru utilizatori noi" +invite: "Invită" +driveCapacityPerLocalAccount: "Capacitatea Drive-ului per utilizator local" +driveCapacityPerRemoteAccount: "Capacitatea Drive-ului per utilizator extern" +inMb: "În megabytes" +iconUrl: "URL-ul iconiței" +bannerUrl: "URL-ul imaginii de banner" +backgroundImageUrl: "URL-ul imaginii de fundal" +basicInfo: "Informații de bază" +pinnedUsers: "Utilizatori fixați" +pinnedUsersDescription: "Scrie utilizatorii, separați prin pauză de rând, care vor fi fixați pe pagina \"Explorează\"." +pinnedPages: "Pagini fixate" +pinnedPagesDescription: "Introdu linkurile Paginilor pe care le vrei fixate in vâruful paginii acestei instanțe, separate de pauze de rând." +pinnedClipId: "ID-ul clip-ului pe care să îl fixezi" +pinnedNotes: "Notă fixată" +hcaptcha: "hCaptcha" +enableHcaptcha: "Activează hCaptcha" +hcaptchaSiteKey: "Site key" +hcaptchaSecretKey: "Secret key" +recaptcha: "reCAPTCHA" +enableRecaptcha: "Activează reCAPTCHA" +recaptchaSiteKey: "Site key" +recaptchaSecretKey: "Secret key" +avoidMultiCaptchaConfirm: "Folosirea mai multor sisteme Captcha poate cauza interferență între acestea. Ai dori să dezactivezi alte sisteme Captcha acum active? Dacă preferi să rămână activate, apasă Anulare." +antennas: "Antene" +manageAntennas: "Gestionează Antenele" +name: "Nume" +antennaSource: "Sursa antenei" +antennaKeywords: "Cuvinte cheie ascultate" +antennaExcludeKeywords: "Cuvinte cheie excluse" +antennaKeywordsDescription: "Separă cu spații pentru o condiție ȘI sau cu o întrerupere de rând pentru o condiție SAU." +notifyAntenna: "Notifică-mă pentru note noi" +withFileAntenna: "Doar note cu fișiere" +enableServiceworker: "Activează ServiceWorker" +antennaUsersDescription: "Scrie un nume de utilizator per linie" +caseSensitive: "Sensibil la majuscule și minuscule" +withReplies: "Include răspunsuri" +connectedTo: "Următoarele conturi sunt conectate" +notesAndReplies: "Note și răspunsuri" +withFiles: "Incluzând fișiere" +silence: "Amuțește" +silenceConfirm: "Ești sigur că vrei să amuțești acest utilizator?" +unsilence: "Anulează amuțirea" +unsilenceConfirm: "Ești sigur că vrei să anulezi amuțirea acestui utilizator?" +popularUsers: "Utilizatori populari" +recentlyUpdatedUsers: "Utilizatori activi recent" +recentlyRegisteredUsers: "Utilizatori ce s-au alăturat recent" +recentlyDiscoveredUsers: "Utilizatori descoperiți recent" +exploreUsersCount: "Aici sunt {count} utilizatori" +exploreFediverse: "Explorează Fediverse-ul" +popularTags: "Taguri populare" +userList: "Liste" +about: "Despre" +aboutMisskey: "Despre Misskey" +administrator: "Administrator" +token: "Token" +twoStepAuthentication: "Autentificare în doi pași" +moderator: "Moderator" +nUsersMentioned: "Menționat de {n} utilizatori" +securityKey: "Cheie de securitate" +securityKeyName: "Numele cheii" +registerSecurityKey: "Înregistrează o cheie de securitate" +lastUsed: "Ultima utilizată" +unregister: "Dezînregistrează" +passwordLessLogin: "Autentificare fără parolă" +resetPassword: "Resetează parola" +newPasswordIs: "Noua parolă este \"{password}\"" +reduceUiAnimation: "Redu animațiile interfeței" +share: "Distribuie" +notFound: "Nu a fost găsit" +notFoundDescription: "N-a fost găsită nicio pagină cu acest URL." +uploadFolder: "Folder implicit pentru încărcări" +cacheClear: "Golește cache-ul" +markAsReadAllNotifications: "Marchează toate notificările drept citit" +markAsReadAllUnreadNotes: "Marchează toate notele drept citit" +markAsReadAllTalkMessages: "Marchează toate mesajele drept citit" +help: "Ajutor" +inputMessageHere: "Introdu un mesaj aici" +close: "Închide" +group: "Grup" +groups: "Grupuri" +createGroup: "Crează un grup" +ownedGroups: "Grupuri deținute" +joinedGroups: "Grupuri alăturate" +invites: "Invită" +groupName: "Numele grupului" +members: "Membri" +transfer: "Transferă" +messagingWithUser: "Chat privat" +messagingWithGroup: "Chat de grup" +title: "Titlu" +text: "Text" +enable: "Activează" +next: "Următorul" +retype: "Introdu din nou" +noteOf: "Notă de {user}" +inviteToGroup: "Invită în grup" +quoteAttached: "Citat" +quoteQuestion: "Vrei să adaugi ca citat?" +noMessagesYet: "Niciun mesaj încă" +newMessageExists: "Ai mesaje noi" +onlyOneFileCanBeAttached: "Poți atașa un singur fișier la un mesaj" +signinRequired: "Te rog autentifică-te" +invitations: "Invită" +invitationCode: "Cod de invitație" +checking: "Se verifică..." +available: "Disponibil" +unavailable: "Indisponibil" +usernameInvalidFormat: "Poți folosi litere mari și mici, numere și underscore-uri." +tooShort: "Prea scurt" +tooLong: "Prea lung" +weakPassword: "Parolă slabă" +normalPassword: "Parolă medie" +strongPassword: "Parolă puternică" +passwordMatched: "Se potrivește!" +passwordNotMatched: "Nu se potrivește" +signinWith: "Autentifică-te cu {x}" +signinFailed: "Nu se poate autentifica. Numele de utilizator sau parola introduse sunt incorecte." +tapSecurityKey: "Apasă pe cheia ta de securitate." +or: "Sau" +language: "Limbă" +uiLanguage: "Limba interfeței" +groupInvited: "Ai fost invitat într-un grup" +aboutX: "Despre {x}" +useOsNativeEmojis: "Folosește emojiuri native OS-ului" +disableDrawer: "Nu folosi meniuri în stil sertar" +sounds: "Sunete" +listen: "Ascultă" +none: "Nimic" +showInPage: "Arată în pagină" +popout: "Scoate în afară" +volume: "Volum" +masterVolume: "Volumul principal" +details: "Detalii" +chooseEmoji: "Alege un emoji" +unableToProcess: "Această operație nu poate fi completată" +recentUsed: "Folosit recent" +install: "Instalează" +uninstall: "Dezinstalează" +installedApps: "Aplicații autorizate" +nothing: "Nu e nimic de văzut aici" +installedDate: "Autorizat la data de" +lastUsedDate: "Folosit ultima oara la" +state: "Stare" +sort: "Sortează" +ascendingOrder: "Crescător" +descendingOrder: "Descrescător" +scratchpad: "Scratchpad" +smtpHost: "Gazdă" +smtpUser: "Nume de utilizator" +smtpPass: "Parolă" +clearCache: "Golește cache-ul" +info: "Despre" +user: "Utilizatori" +searchByGoogle: "Caută" +_email: + _follow: + title: "te-a urmărit" +_mfm: + mention: "Mențiune" + quote: "Citează" + emoji: "Emoji personalizat" + search: "Caută" +_theme: + keys: + mention: "Mențiune" + renote: "Re-notează" +_sfx: + note: "Note" + notification: "Notificări" + chat: "Chat" +_widgets: + notifications: "Notificări" + timeline: "Cronologie" + activity: "Activitate" + federation: "Federație" + jobQueue: "coada de job-uri" +_cw: + show: "Incarcă mai mult" +_visibility: + home: "Acasă" + followers: "Urmăritori" +_profile: + name: "Nume" + username: "Nume de utilizator" +_exportOrImport: + followingList: "Urmărești" + muteList: "Amuțește" + blockingList: "Blochează" + userLists: "Liste" +_charts: + federation: "Federație" +_timelines: + home: "Acasă" +_pages: + blocks: + image: "Imagini" + script: + categories: + list: "Liste" + blocks: + _join: + arg1: "Liste" + _randomPick: + arg1: "Liste" + _dailyRandomPick: + arg1: "Liste" + _seedRandomPick: + arg2: "Liste" + _pick: + arg1: "Liste" + _listLen: + arg1: "Liste" + types: + array: "Liste" +_notification: + youWereFollowed: "te-a urmărit" + youWereInvitedToGroup: "Ai fost invitat într-un grup" + _types: + follow: "Urmărești" + mention: "Mențiune" + renote: "Re-notează" + quote: "Citează" + reaction: "Reacție" +_deck: + _columns: + notifications: "Notificări" + tl: "Cronologie" + antenna: "Antene" + list: "Liste" + mentions: "Mențiuni" diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml index b29d2173c8..877e1e185d 100644 --- a/locales/ru-RU.yml +++ b/locales/ru-RU.yml @@ -322,8 +322,6 @@ disablingTimelinesInfo: "У администраторов и модератор registration: "Регистрация" enableRegistration: "Разрешить регистрацию" invite: "Пригласить" -proxyRemoteFiles: "Файлы с других сайтов пускать через прокси" -proxyRemoteFilesDescription: "Когда эта настройка включена, файлы с других серверов, которые не сохранены или удалены для освобождения места, будут проксироваться локально, а так же для них будут создаваться миниатюры. Эта настройка не затрагивает хранение на сервере." driveCapacityPerLocalAccount: "Объём диска на одного локального пользователя" driveCapacityPerRemoteAccount: "Объём диска на одного пользователя с другого сайта" inMb: "В мегабайтах" @@ -419,7 +417,6 @@ next: "Дальше" retype: "Введите ещё раз" noteOf: "Что пишет {user}" inviteToGroup: "Пригласить в группу" -maxNoteTextLength: "Максимальная длина текста" quoteAttached: "Цитата" quoteQuestion: "Хотите добавить цитату?" noMessagesYet: "Пока ни одного сообщения" @@ -818,6 +815,8 @@ leaveGroupConfirm: "Покинуть группу «{name}»?" useDrawerReactionPickerForMobile: "Выдвижная палитра на мобильном устройстве" welcomeBackWithName: "С возвращением, {name}!" clickToFinishEmailVerification: "Пожалуйста, нажмите [{ok}], чтобы завершить подтверждение адреса электронной почты." +searchByGoogle: "Поиск" +indefinitely: "вечно" _emailUnavailable: used: "Уже используется" format: "Неверный формат" diff --git a/locales/si-LK.yml b/locales/si-LK.yml new file mode 100644 index 0000000000..ed97d539c0 --- /dev/null +++ b/locales/si-LK.yml @@ -0,0 +1 @@ +--- diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml index a501591a8b..db45365a51 100644 --- a/locales/sk-SK.yml +++ b/locales/sk-SK.yml @@ -325,8 +325,6 @@ disablingTimelinesInfo: "Administrátori a moderátori majú vždy prístup ku v registration: "Registrácia" enableRegistration: "Povoliť registráciu nových používateľov" invite: "Pozvať" -proxyRemoteFiles: "Proxy vzdialených súborov" -proxyRemoteFilesDescription: "Ak je zapnuté, vzdialené súbory, ktoré nie sú uložené lokálne alebo boli odstránené kvôli obmedzeniam úložiska, budú vyžiadané cez proxy, vrátane generovani miniatúr. Neovplyvní to úložisko na serveri." driveCapacityPerLocalAccount: "Kapacita disku pre používateľa" driveCapacityPerRemoteAccount: "Kapacita disku pre vzdialeného používateľa" inMb: "V megabajtoch" @@ -422,7 +420,6 @@ next: "Ďalší" retype: "Zadajte znovu" noteOf: "Poznámky používateľa {user}" inviteToGroup: "Pozvať do skupiny" -maxNoteTextLength: "Maximálny počet znakov poznámky" quoteAttached: "Citované" quoteQuestion: "Pripojiť ako citát?" noMessagesYet: "Zatiaľ žiadne správy" @@ -832,6 +829,16 @@ auto: "Automaticky" themeColor: "Farba témy" size: "Veľkosť" numberOfColumn: "Počet stĺpcov" +searchByGoogle: "Hľadať cez Google" +instanceDefaultLightTheme: "Predvolená svetlá téma" +instanceDefaultDarkTheme: "Predvolená tmavá téma" +instanceDefaultThemeDescription: "Vložte kód témy v objektovom formáte" +mutePeriod: "Trvanie stíšenia" +indefinitely: "Navždy" +tenMinutes: "10 minút" +oneHour: "1 hodina" +oneDay: "1 deň" +oneWeek: "1 týždeň" _emailUnavailable: used: "Táto emailová adresa sa už používa" format: "Formát emailovej adresy je nesprávny" @@ -1604,6 +1611,7 @@ _notification: youReceivedFollowRequest: "Dostali ste žiadosť o sledovanie" yourFollowRequestAccepted: "Vaša žiadosť o sledovanie bola prijatá" youWereInvitedToGroup: "Pozvať do skupiny" + pollEnded: "Výsledky hlasovania sú k dispozícii." _types: all: "Všetky" follow: "Sledujete" @@ -1613,6 +1621,7 @@ _notification: quote: "Citovať" reaction: "Reakcie" pollVote: "Hlasy v hlasovaniach" + pollEnded: "Hlasovanie skončilo" receiveFollowRequest: "Doručené žiadosti o sledovanie" followRequestAccepted: "Schválené žiadosti o sledovanie" groupInvited: "Pozvánky do skupín" diff --git a/locales/tr-TR.yml b/locales/tr-TR.yml index 46204d33c4..aecb413de7 100644 --- a/locales/tr-TR.yml +++ b/locales/tr-TR.yml @@ -47,6 +47,7 @@ remove: "Sil" smtpUser: "Kullanıcı Adı" smtpPass: "Şifre" user: "Kullanıcı" +searchByGoogle: "Arama" _mfm: search: "Arama" _sfx: diff --git a/locales/ug-CN.yml b/locales/ug-CN.yml index cfdecfbd4a..a7504542d0 100644 --- a/locales/ug-CN.yml +++ b/locales/ug-CN.yml @@ -1,5 +1,6 @@ --- _lang_: "ياپونچە" search: "ئىزدەش" +searchByGoogle: "ئىزدەش" _mfm: search: "ئىزدەش" diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml index 588df8d325..073b2c310e 100644 --- a/locales/uk-UA.yml +++ b/locales/uk-UA.yml @@ -312,8 +312,6 @@ disablingTimelinesInfo: "Адміністратори та модератори registration: "Реєстрація" enableRegistration: "Дозволити реєстрацію" invite: "Запросити" -proxyRemoteFiles: "Проксувати файли з інших інстансів" -proxyRemoteFilesDescription: "При увімкненні віддалені файли, які не зберігаються локально або були видалені через недостатнії обсяг пам'яті, будуть локально проксовані включаючи обкладинки. Це налаштування не впливає на пам'ять серверу. " driveCapacityPerLocalAccount: "Об'єм диска на одного локального користувача" driveCapacityPerRemoteAccount: "Об'єм диска на одного віддаленого користувача" inMb: "В мегабайтах" @@ -407,7 +405,6 @@ next: "Далі" retype: "Введіть ще раз" noteOf: "Нотатка {user}" inviteToGroup: "Запрошення до групи" -maxNoteTextLength: "Максимальна довжина нотатки" quoteAttached: "Цитата" quoteQuestion: "Ви хочете додати цитату?" noMessagesYet: "Ще немає повідомлень" @@ -688,6 +685,8 @@ global: "Глобальна" sent: "Відправити" hashtags: "Хештеґ" hide: "Сховати" +searchByGoogle: "Пошук" +indefinitely: "Ніколи" _ad: back: "Назад" _gallery: diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml new file mode 100644 index 0000000000..42f86b3359 --- /dev/null +++ b/locales/vi-VN.yml @@ -0,0 +1,48 @@ +--- +_lang_: "Tiếng Việt" +headlineMisskey: "Mạng xã hội liên hợp" +monthAndDay: "{day} tháng {month}" +search: "Tìm kiếm" +notifications: "Thông báo" +username: "Tên người dùng" +password: "Mật khẩu" +forgotPassword: "Quên mật khẩu" +ok: "Đồng ý" +renotedBy: "Chia sẻ bởi {user}" +flagAsBot: "Đánh dấu đây là tài khoản bot" +searchWith: "Tìm kiếm: {q}" +followConfirm: "Bạn có chắc muốn theo dõi {name}?" +cpuAndMemory: "CPU và Dung lượng" +dayX: "{day}" +yearX: "{year}" +aboutMisskey: "Về Misskey" +smtpUser: "Tên người dùng" +smtpPass: "Mật khẩu" +reportAbuseOf: "Báo cáo {name}" +renotedCount: "Lượt chia sẻ" +translatedFrom: "Dịch từ {x}" +searchByGoogle: "Google" +_mfm: + search: "Tìm kiếm" +_theme: + installed: "{name} đã được cài đặt" +_sfx: + notification: "Thông báo" +_ago: + unknown: "Không rõ" + future: "Tương lai" + justNow: "Vừa xong" + secondsAgo: "{n}s trước" + minutesAgo: "{n} phút trước" + hoursAgo: "{n} giờ trước" + daysAgo: "{n} ngày trước" + weeksAgo: "{n} tuần trước" + monthsAgo: "{n} tháng trước" + yearsAgo: "{n} năm trước" +_widgets: + notifications: "Thông báo" +_profile: + username: "Tên người dùng" +_deck: + _columns: + notifications: "Thông báo" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index 86d3fb5cdd..483faba0d1 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -325,8 +325,6 @@ disablingTimelinesInfo: "即使时间线功能被禁用,出于便利性的原 registration: "注册" enableRegistration: "允许新用户注册" invite: "邀请" -proxyRemoteFiles: "代理远程文件" -proxyRemoteFilesDescription: "启用此设置后,由于超出存储容量而导致未保存被删除的远程文件将被本地代理,并且会生成缩略图。不会影响服务器的存储。" driveCapacityPerLocalAccount: "每个用户的网盘空间" driveCapacityPerRemoteAccount: "每个远程用户的网盘容量" inMb: "以兆字节(MegaByte)为单位" @@ -422,7 +420,6 @@ next: "下一个" retype: "重新输入" noteOf: "{user}的帖子" inviteToGroup: "群组邀请" -maxNoteTextLength: "帖子的字数限制" quoteAttached: "已引用" quoteQuestion: "是否引用此链接内容?" noMessagesYet: "现在没有新的聊天" @@ -833,6 +830,16 @@ auto: "自动" themeColor: "主题颜色" size: "大小" numberOfColumn: "列数" +searchByGoogle: "Google" +instanceDefaultLightTheme: "实例默认浅色主题" +instanceDefaultDarkTheme: "实例默认深色主题" +instanceDefaultThemeDescription: "以对象格式键入主题代码" +mutePeriod: "屏蔽期限" +indefinitely: "永久" +tenMinutes: "10分钟" +oneHour: "1小时" +oneDay: "1天" +oneWeek: "1周" _emailUnavailable: used: "已经被使用过" format: "无效的格式" @@ -1210,7 +1217,7 @@ _poll: noMore: "无法再添加更多了" canMultipleVote: "允许多个投票" expiration: "截止时间" - infinite: "不限时间" + infinite: "永久" at: "指定日期" after: "指定时间" deadlineDate: "截止日期" @@ -1605,6 +1612,7 @@ _notification: youReceivedFollowRequest: "您有新的关注请求" yourFollowRequestAccepted: "您的关注请求已通过" youWereInvitedToGroup: "您有新的群组邀请" + pollEnded: "问卷调查结果已生成。" _types: all: "全部" follow: "关注中" @@ -1614,6 +1622,7 @@ _notification: quote: "引用" reaction: "回应" pollVote: "问卷调查被投票" + pollEnded: "问卷调查结束" receiveFollowRequest: "收到关注请求" followRequestAccepted: "关注请求已通过" groupInvited: "加入群组邀请" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index b10871ec1d..a1e0a830cd 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -318,8 +318,6 @@ disablingTimelinesInfo: "即使您關閉了時間線功能,管理員和協調 registration: "註冊" enableRegistration: "開啟新使用者註冊" invite: "邀請" -proxyRemoteFiles: "遠端代理檔案" -proxyRemoteFilesDescription: "啟用此設置後,由於超出存儲容量而未保存或刪除的遠程文件將被本地代理,並且將生成預覽圖。這不影響伺服器的存儲。" driveCapacityPerLocalAccount: "每個本地用戶的雲端空間大小" driveCapacityPerRemoteAccount: "每個非本地用戶的雲端容量" inMb: "以Mbps為單位" @@ -415,7 +413,6 @@ next: "下一步" retype: "重新輸入" noteOf: "{user}的貼文" inviteToGroup: "邀請至群組" -maxNoteTextLength: "貼文的字數限制" quoteAttached: "引用" quoteQuestion: "是否要引用?" noMessagesYet: "沒有訊息" @@ -750,6 +747,8 @@ global: "公開" sent: "發送" hashtags: "#tag" hide: "隱藏" +searchByGoogle: "搜尋" +indefinitely: "無期限" _ffVisibility: public: "發佈" _ad: diff --git a/package.json b/package.json index 68421f0e37..cc49d1ffa4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "12.107.0", + "version": "12.108.1", "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.12.0", + "@typescript-eslint/parser": "5.16.0", "cross-env": "7.0.3", - "cypress": "9.5.0", + "cypress": "9.5.2", "start-server-and-test": "1.14.0", - "typescript": "4.5.5" + "typescript": "4.6.3" } } diff --git a/packages/backend/assets/splash.png b/packages/backend/assets/splash.png new file mode 100644 index 0000000000..3430e6efe7 Binary files /dev/null and b/packages/backend/assets/splash.png differ diff --git a/packages/backend/migration/1646387162108-mute-expires-at.js b/packages/backend/migration/1646387162108-mute-expires-at.js new file mode 100644 index 0000000000..c8be8f3c54 --- /dev/null +++ b/packages/backend/migration/1646387162108-mute-expires-at.js @@ -0,0 +1,13 @@ +export class muteExpiresAt1646387162108 { + name = 'muteExpiresAt1646387162108' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "muting" ADD "expiresAt" TIMESTAMP WITH TIME ZONE`); + await queryRunner.query(`CREATE INDEX "IDX_c1fd1c3dfb0627aa36c253fd14" ON "muting" ("expiresAt") `); + } + + async down(queryRunner) { + await queryRunner.query(`DROP INDEX "public"."IDX_c1fd1c3dfb0627aa36c253fd14"`); + await queryRunner.query(`ALTER TABLE "muting" DROP COLUMN "expiresAt"`); + } +} diff --git a/packages/backend/migration/1646549089451-poll-ended-notification.js b/packages/backend/migration/1646549089451-poll-ended-notification.js new file mode 100644 index 0000000000..38a38ce64d --- /dev/null +++ b/packages/backend/migration/1646549089451-poll-ended-notification.js @@ -0,0 +1,18 @@ + +export class pollEndedNotification1646549089451 { + name = 'pollEndedNotification1646549089451' + + async up(queryRunner) { + await queryRunner.query(`ALTER TYPE "public"."notification_type_enum" RENAME TO "notification_type_enum_old"`); + await queryRunner.query(`CREATE TYPE "public"."notification_type_enum" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app')`); + await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "type" TYPE "public"."notification_type_enum" USING "type"::"text"::"public"."notification_type_enum"`); + await queryRunner.query(`DROP TYPE "public"."notification_type_enum_old"`); + } + + async down(queryRunner) { + await queryRunner.query(`CREATE TYPE "public"."notification_type_enum_old" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app')`); + await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "type" TYPE "public"."notification_type_enum_old" USING "type"::"text"::"public"."notification_type_enum_old"`); + await queryRunner.query(`DROP TYPE "public"."notification_type_enum"`); + await queryRunner.query(`ALTER TYPE "public"."notification_type_enum_old" RENAME TO "notification_type_enum"`); + } +} diff --git a/packages/backend/migration/1646633030285-chart-federation-active.js b/packages/backend/migration/1646633030285-chart-federation-active.js new file mode 100644 index 0000000000..952289c8f8 --- /dev/null +++ b/packages/backend/migration/1646633030285-chart-federation-active.js @@ -0,0 +1,13 @@ +export class chartFederationActive1646633030285 { + name = 'chartFederationActive1646633030285' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "___active" smallint NOT NULL DEFAULT '0'`); + await queryRunner.query(`ALTER TABLE "__chart_day__federation" ADD "___active" smallint NOT NULL DEFAULT '0'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "__chart_day__federation" DROP COLUMN "___active"`); + await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "___active"`); + } +} diff --git a/packages/backend/migration/1646655454495-remove-instance-drive-columns.js b/packages/backend/migration/1646655454495-remove-instance-drive-columns.js new file mode 100644 index 0000000000..a0ee1b2c43 --- /dev/null +++ b/packages/backend/migration/1646655454495-remove-instance-drive-columns.js @@ -0,0 +1,13 @@ +export class removeInstanceDriveColumns1646655454495 { + name = 'removeInstanceDriveColumns1646655454495' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "driveUsage"`); + await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "driveFiles"`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "instance" ADD "driveFiles" integer NOT NULL DEFAULT '0'`); + await queryRunner.query(`ALTER TABLE "instance" ADD "driveUsage" bigint NOT NULL DEFAULT '0'`); + } +} diff --git a/packages/backend/migration/1646732390560-chart-federation-active-sub-pub.js b/packages/backend/migration/1646732390560-chart-federation-active-sub-pub.js new file mode 100644 index 0000000000..c9a847cbcf --- /dev/null +++ b/packages/backend/migration/1646732390560-chart-federation-active-sub-pub.js @@ -0,0 +1,21 @@ +export class chartFederationActiveSubPub1646732390560 { + name = 'chartFederationActiveSubPub1646732390560' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "___active"`); + await queryRunner.query(`ALTER TABLE "__chart_day__federation" DROP COLUMN "___active"`); + await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "___subActive" smallint NOT NULL DEFAULT '0'`); + await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "___pubActive" smallint NOT NULL DEFAULT '0'`); + await queryRunner.query(`ALTER TABLE "__chart_day__federation" ADD "___subActive" smallint NOT NULL DEFAULT '0'`); + await queryRunner.query(`ALTER TABLE "__chart_day__federation" ADD "___pubActive" smallint NOT NULL DEFAULT '0'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "__chart_day__federation" DROP COLUMN "___pubActive"`); + await queryRunner.query(`ALTER TABLE "__chart_day__federation" DROP COLUMN "___subActive"`); + await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "___pubActive"`); + await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "___subActive"`); + await queryRunner.query(`ALTER TABLE "__chart_day__federation" ADD "___active" smallint NOT NULL DEFAULT '0'`); + await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "___active" smallint NOT NULL DEFAULT '0'`); + } +} diff --git a/packages/backend/ormconfig.js b/packages/backend/ormconfig.js index b8150f7014..a4e903abad 100644 --- a/packages/backend/ormconfig.js +++ b/packages/backend/ormconfig.js @@ -1,7 +1,8 @@ +import { DataSource } from 'typeorm'; import config from './built/config/index.js'; import { entities } from './built/db/postgre.js'; -export default { +export default new DataSource({ type: 'postgres', host: config.db.host, port: config.db.port, @@ -11,7 +12,4 @@ export default { extra: config.db.extra, entities: entities, migrations: ['migration/*.js'], - cli: { - migrationsDir: 'migration' - } -}; +}); diff --git a/packages/backend/package.json b/packages/backend/package.json index 3bc7051b1b..91c099e9f2 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,12 +14,12 @@ "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", - "@koa/router": "10.1.1", - "@sinonjs/fake-timers": "9.1.0", + "@koa/router": "9.0.1", + "@sinonjs/fake-timers": "9.1.1", "@syuilo/aiscript": "0.11.1", "@types/bcryptjs": "2.4.2", "@types/bull": "3.15.8", @@ -31,7 +30,7 @@ "@types/jsdom": "16.2.14", "@types/jsonld": "1.5.6", "@types/koa": "2.13.4", - "@types/koa-bodyparser": "4.3.5", + "@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.19", + "@types/node": "17.0.23", "@types/node-fetch": "3.0.3", "@types/nodemailer": "6.4.4", "@types/oauth": "0.9.1", @@ -56,9 +55,8 @@ "@types/redis": "4.0.11", "@types/rename": "1.0.4", "@types/sanitize-html": "2.6.2", - "@types/seedrandom": "3.0.1", - "@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", @@ -66,44 +64,44 @@ "@types/uuid": "8.3.4", "@types/web-push": "3.3.2", "@types/websocket": "1.0.5", - "@types/ws": "8.2.3", - "@typescript-eslint/eslint-plugin": "5.12.1", - "@typescript-eslint/parser": "5.12.1", + "@types/ws": "8.5.3", + "@typescript-eslint/eslint-plugin": "5.16.0", + "@typescript-eslint/parser": "5.16.0", + "@bull-board/koa": "3.10.1", "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.1100.0", "bcryptjs": "2.4.3", "blurhash": "1.1.5", "broadcast-channel": "4.10.0", - "bull": "4.6.2", + "bull": "4.8.1", "cacheable-lookup": "6.0.4", "cafy": "15.2.1", "cbor": "8.1.0", - "chalk": "5.0.0", - "chalk-template": "0.3.1", + "chalk": "5.0.1", + "chalk-template": "0.4.0", "cli-highlight": "2.1.11", "color-convert": "2.0.1", "content-disposition": "0.5.4", "date-fns": "2.28.0", "deep-email-validator": "0.1.21", "escape-regexp": "0.0.1", - "eslint": "8.9.0", + "eslint": "8.12.0", "eslint-plugin-import": "2.25.4", - "eventemitter3": "4.0.7", "feed": "4.2.2", - "file-type": "16.5.3", + "file-type": "17.1.1", "fluent-ffmpeg": "2.1.2", - "got": "11.8.2", + "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", @@ -117,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": "2.6.7", - "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", @@ -147,25 +145,25 @@ "rndstr": "1.0.0", "s-age": "1.1.2", "sanitize-html": "2.7.0", - "seedrandom": "3.0.5", - "sharp": "0.30.1", + "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.4", + "systeminformation": "5.11.9", "throttle-debounce": "3.0.1", "tinycolor2": "1.4.2", "tmp": "0.2.1", - "ts-loader": "9.2.6", - "ts-node": "10.5.0", + "ts-loader": "9.2.8", + "ts-node": "10.7.0", "tsc-alias": "1.4.1", - "tsconfig-paths": "3.12.0", - "twemoji-parser": "13.1.0", - "typeorm": "0.2.44", - "typescript": "4.5.5", + "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", @@ -175,7 +173,7 @@ "xev": "2.0.1" }, "devDependencies": { - "@redocly/openapi-core": "1.0.0-beta.83", + "@redocly/openapi-core": "1.0.0-beta.90", "@types/fluent-ffmpeg": "2.1.20", "cross-env": "7.0.3", "execa": "6.1.0" diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts index 1c909dff13..09d20f9361 100644 --- a/packages/backend/src/boot/master.ts +++ b/packages/backend/src/boot/master.ts @@ -6,7 +6,7 @@ import cluster from 'node:cluster'; import chalk from 'chalk'; import chalkTemplate from 'chalk-template'; import * as portscanner from 'portscanner'; -import { getConnection } from 'typeorm'; +import semver from 'semver'; import Logger from '@/services/logger.js'; import loadConfig from '@/config/load.js'; @@ -14,7 +14,7 @@ import { Config } from '@/config/types.js'; import { lessThan } from '@/prelude/array.js'; import { envOption } from '../env.js'; import { showMachineInfo } from '@/misc/show-machine-info.js'; -import { initDb } from '../db/postgre.js'; +import { db, initDb } from '../db/postgre.js'; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); @@ -88,10 +88,6 @@ export async function masterMain() { } } -const runningNodejsVersion = process.version.slice(1).split('.').map(x => parseInt(x, 10)); -const requiredNodejsVersion = [11, 7, 0]; -const satisfyNodejsVersion = !lessThan(runningNodejsVersion, requiredNodejsVersion); - function showEnvironment(): void { const env = process.env.NODE_ENV; const logger = bootLogger.createSubLogger('env'); @@ -108,10 +104,11 @@ function showEnvironment(): void { function showNodejsVersion(): void { const nodejsLogger = bootLogger.createSubLogger('nodejs'); - nodejsLogger.info(`Version ${runningNodejsVersion.join('.')}`); + nodejsLogger.info(`Version ${process.version} detected.`); - if (!satisfyNodejsVersion) { - nodejsLogger.error(`Node.js version is less than ${requiredNodejsVersion.join('.')}. Please upgrade it.`, null, true); + const minVersion = fs.readFileSync(`${_dirname}/../../../../.node-version`, 'utf-8').trim(); + if (semver.lt(process.version, minVersion)) { + nodejsLogger.error(`At least Node.js ${minVersion} required!`); process.exit(1); } } @@ -146,7 +143,7 @@ async function connectDb(): Promise { try { dbLogger.info('Connecting...'); await initDb(); - const v = await getConnection().query('SHOW server_version').then(x => x[0].server_version); + const v = await db.query('SHOW server_version').then(x => x[0].server_version); dbLogger.succ(`Connected: v${v}`); } catch (e) { dbLogger.error('Cannot connect', null, true); diff --git a/packages/backend/src/config/types.ts b/packages/backend/src/config/types.ts index 7a1b83c99b..58a27803cb 100644 --- a/packages/backend/src/config/types.ts +++ b/packages/backend/src/config/types.ts @@ -6,7 +6,6 @@ export type Source = { feedback_url?: string; url: string; port: number; - https?: { [x: string]: string }; disableHsts?: boolean; db: { host: string; diff --git a/packages/backend/src/db/postgre.ts b/packages/backend/src/db/postgre.ts index c1f7245bc7..491c1a174f 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'; @@ -74,7 +74,7 @@ import { UserPending } from '@/models/entities/user-pending.js'; import { entities as charts } from '@/services/chart/entities.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 +84,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[]) { @@ -176,52 +174,51 @@ export const entities = [ ...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'; +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, + }, + } : false, + logging: log, + logger: log ? new MyCustomLogger() : undefined, + maxQueryExecutionTime: 300, + entities: entities, + migrations: ['../../migration/*.js'], +}); - 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: config.db.extra, - 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, - }); +export async function initDb() { + await db.connect(); } export async function resetDb() { const reset = async () => { - const conn = await getConnection(); - const tables = await conn.query(`SELECT relname AS "table" + const tables = await db.query(`SELECT relname AS "table" FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) WHERE nspname NOT IN ('pg_catalog', 'information_schema') AND C.relkind = 'r' AND nspname !~ '^pg_toast';`); for (const table of tables) { - await conn.query(`DELETE FROM "${table.table}" CASCADE`); + await db.query(`DELETE FROM "${table.table}" CASCADE`); } }; diff --git a/packages/backend/src/misc/cache.ts b/packages/backend/src/misc/cache.ts index 76835b44b1..01bbe98a85 100644 --- a/packages/backend/src/misc/cache.ts +++ b/packages/backend/src/misc/cache.ts @@ -1,5 +1,5 @@ export class Cache { - private cache: Map; + public cache: Map; private lifetime: number; constructor(lifetime: Cache['lifetime']) { @@ -28,16 +28,52 @@ export class Cache { this.cache.delete(key); } - public async fetch(key: string | null, fetcher: () => Promise): Promise { + /** + * キャッシュがあればそれを返し、無ければfetcherを呼び出して結果をキャッシュ&返します + * optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします + */ + public async fetch(key: string | null, fetcher: () => Promise, validator?: (cachedValue: T) => boolean): Promise { const cachedValue = this.get(key); if (cachedValue !== undefined) { - // Cache HIT - return cachedValue; + if (validator) { + if (validator(cachedValue)) { + // Cache HIT + return cachedValue; + } + } else { + // Cache HIT + return cachedValue; + } } // Cache MISS const value = await fetcher(); - this.set(key, value); + return value; + } + + /** + * キャッシュがあればそれを返し、無ければfetcherを呼び出して結果をキャッシュ&返します + * optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします + */ + public async fetchMaybe(key: string | null, fetcher: () => Promise, validator?: (cachedValue: T) => boolean): Promise { + const cachedValue = this.get(key); + if (cachedValue !== undefined) { + if (validator) { + if (validator(cachedValue)) { + // Cache HIT + return cachedValue; + } + } else { + // Cache HIT + return cachedValue; + } + } + + // Cache MISS + const value = await fetcher(); + if (value !== undefined) { + this.set(key, value); + } return value; } } diff --git a/packages/backend/src/misc/check-hit-antenna.ts b/packages/backend/src/misc/check-hit-antenna.ts index ceb74d6904..d9cedee7df 100644 --- a/packages/backend/src/misc/check-hit-antenna.ts +++ b/packages/backend/src/misc/check-hit-antenna.ts @@ -1,17 +1,26 @@ import { Antenna } from '@/models/entities/antenna.js'; import { Note } from '@/models/entities/note.js'; import { User } from '@/models/entities/user.js'; -import { UserListJoinings, UserGroupJoinings } from '@/models/index.js'; +import { UserListJoinings, UserGroupJoinings, Blockings } from '@/models/index.js'; import { getFullApAccount } from './convert-host.js'; import * as Acct from '@/misc/acct.js'; import { Packed } from './schema.js'; +import { Cache } from './cache.js'; + +const blockingCache = new Cache(1000 * 60 * 5); + +// NOTE: フォローしているユーザーのノート、リストのユーザーのノート、グループのユーザーのノート指定はパフォーマンス上の理由で無効になっている /** * noteUserFollowers / antennaUserFollowing はどちらか一方が指定されていればよい */ -export async function checkHitAntenna(antenna: Antenna, note: (Note | Packed<'Note'>), noteUser: { username: string; host: string | null; }, noteUserFollowers?: User['id'][], antennaUserFollowing?: User['id'][]): Promise { +export async function checkHitAntenna(antenna: Antenna, note: (Note | Packed<'Note'>), noteUser: { id: User['id']; username: string; host: string | null; }, noteUserFollowers?: User['id'][], antennaUserFollowing?: User['id'][]): Promise { if (note.visibility === 'specified') return false; + // アンテナ作成者がノート作成者にブロックされていたらスキップ + const blockings = await blockingCache.fetch(noteUser.id, () => Blockings.findBy({ blockerId: noteUser.id }).then(res => res.map(x => x.blockeeId))); + if (blockings.some(blocking => blocking === antenna.userId)) return false; + if (note.visibility === 'followers') { if (noteUserFollowers && !noteUserFollowers.includes(antenna.userId)) return false; if (antennaUserFollowing && !antennaUserFollowing.includes(note.userId)) return false; @@ -23,15 +32,15 @@ export async function checkHitAntenna(antenna: Antenna, note: (Note | Packed<'No if (noteUserFollowers && !noteUserFollowers.includes(antenna.userId)) return false; if (antennaUserFollowing && !antennaUserFollowing.includes(note.userId)) return false; } else if (antenna.src === 'list') { - const listUsers = (await UserListJoinings.find({ + const listUsers = (await UserListJoinings.findBy({ userListId: antenna.userListId!, })).map(x => x.userId); if (!listUsers.includes(note.userId)) return false; } else if (antenna.src === 'group') { - const joining = await UserGroupJoinings.findOneOrFail(antenna.userGroupJoiningId!); + const joining = await UserGroupJoinings.findOneByOrFail({ id: antenna.userGroupJoiningId! }); - const groupUsers = (await UserGroupJoinings.find({ + const groupUsers = (await UserGroupJoinings.findBy({ userGroupId: joining.userGroupId, })).map(x => x.userId); diff --git a/packages/backend/src/misc/download-url.ts b/packages/backend/src/misc/download-url.ts index 21eee57b33..7c57b140ef 100644 --- a/packages/backend/src/misc/download-url.ts +++ b/packages/backend/src/misc/download-url.ts @@ -6,7 +6,7 @@ import { httpAgent, httpsAgent, StatusError } from './fetch.js'; import config from '@/config/index.js'; import chalk from 'chalk'; import Logger from '@/services/logger.js'; -import * as IPCIDR from 'ip-cidr'; +import IPCIDR from 'ip-cidr'; import PrivateIp from 'private-ip'; const pipeline = util.promisify(stream.pipeline); diff --git a/packages/backend/src/misc/fetch-meta.ts b/packages/backend/src/misc/fetch-meta.ts index 9f85d3d1db..5417c10962 100644 --- a/packages/backend/src/misc/fetch-meta.ts +++ b/packages/backend/src/misc/fetch-meta.ts @@ -1,19 +1,21 @@ +import { db } from '@/db/postgre.js'; import { Meta } from '@/models/entities/meta.js'; -import { getConnection } from 'typeorm'; let cache: Meta; export async function fetchMeta(noCache = false): Promise { if (!noCache && cache) return cache; - return await getConnection().transaction(async transactionalEntityManager => { + return await db.transaction(async transactionalEntityManager => { // 過去のバグでレコードが複数出来てしまっている可能性があるので新しいIDを優先する - const meta = await transactionalEntityManager.findOne(Meta, { + const metas = await transactionalEntityManager.find(Meta, { order: { id: 'DESC', }, }); + const meta = metas[0]; + if (meta) { cache = meta; return meta; diff --git a/packages/backend/src/misc/fetch-proxy-account.ts b/packages/backend/src/misc/fetch-proxy-account.ts index ed8a4c794d..b61bba264b 100644 --- a/packages/backend/src/misc/fetch-proxy-account.ts +++ b/packages/backend/src/misc/fetch-proxy-account.ts @@ -5,5 +5,5 @@ import { Users } from '@/models/index.js'; export async function fetchProxyAccount(): Promise { const meta = await fetchMeta(); if (meta.proxyAccountId == null) return null; - return await Users.findOneOrFail(meta.proxyAccountId) as ILocalUser; + return await Users.findOneByOrFail({ id: meta.proxyAccountId }) as ILocalUser; } diff --git a/packages/backend/src/misc/get-file-info.ts b/packages/backend/src/misc/get-file-info.ts index 48a5b40cc4..d70dc3d70c 100644 --- a/packages/backend/src/misc/get-file-info.ts +++ b/packages/backend/src/misc/get-file-info.ts @@ -2,7 +2,7 @@ import * as fs from 'node:fs'; import * as crypto from 'node:crypto'; import * as stream from 'node:stream'; import * as util from 'node:util'; -import fileType from 'file-type'; +import { fileTypeFromFile } from 'file-type'; import isSvg from 'is-svg'; import probeImageSize from 'probe-image-size'; import sharp from 'sharp'; @@ -109,7 +109,7 @@ export async function detectType(path: string): Promise<{ return TYPE_OCTET_STREAM; } - const type = await fileType.fromFile(path); + const type = await fileTypeFromFile(path); if (type) { // XMLはSVGかもしれない diff --git a/packages/backend/src/misc/keypair-store.ts b/packages/backend/src/misc/keypair-store.ts index 3d505c0abb..1183b9a781 100644 --- a/packages/backend/src/misc/keypair-store.ts +++ b/packages/backend/src/misc/keypair-store.ts @@ -6,5 +6,5 @@ import { Cache } from './cache.js'; const cache = new Cache(Infinity); export async function getUserKeypair(userId: User['id']): Promise { - return await cache.fetch(userId, () => UserKeypairs.findOneOrFail(userId)); + return await cache.fetch(userId, () => UserKeypairs.findOneByOrFail({ userId: userId })); } diff --git a/packages/backend/src/misc/populate-emojis.ts b/packages/backend/src/misc/populate-emojis.ts index 4953c6890b..86f1356c31 100644 --- a/packages/backend/src/misc/populate-emojis.ts +++ b/packages/backend/src/misc/populate-emojis.ts @@ -1,4 +1,4 @@ -import { In } from 'typeorm'; +import { In, IsNull } from 'typeorm'; import { Emojis } from '@/models/index.js'; import { Emoji } from '@/models/entities/emoji.js'; import { Note } from '@/models/entities/note.js'; @@ -52,9 +52,9 @@ export async function populateEmoji(emojiName: string, noteUserHost: string | nu const { name, host } = parseEmojiStr(emojiName, noteUserHost); if (name == null) return null; - const queryOrNull = async () => (await Emojis.findOne({ + const queryOrNull = async () => (await Emojis.findOneBy({ name, - host, + host: host ?? IsNull(), })) || null; const emoji = await cache.fetch(`${name} ${host}`, queryOrNull); @@ -112,7 +112,7 @@ export async function prefetchEmojis(emojis: { name: string; host: string | null for (const host of hosts) { emojisQuery.push({ name: In(notCachedEmojis.filter(e => e.host === host).map(e => e.name)), - host: host, + host: host ?? IsNull(), }); } const _emojis = emojisQuery.length > 0 ? await Emojis.find({ diff --git a/packages/backend/src/misc/reaction-lib.ts b/packages/backend/src/misc/reaction-lib.ts index 086944ccfb..fefc2781f3 100644 --- a/packages/backend/src/misc/reaction-lib.ts +++ b/packages/backend/src/misc/reaction-lib.ts @@ -3,6 +3,7 @@ import { emojiRegex } from './emoji-regex.js'; import { fetchMeta } from './fetch-meta.js'; import { Emojis } from '@/models/index.js'; import { toPunyNullable } from './convert-host.js'; +import { IsNull } from 'typeorm'; const legacies: Record = { 'like': '👍', @@ -74,8 +75,8 @@ export async function toDbReaction(reaction?: string | null, reacterHost?: strin const custom = reaction.match(/^:([\w+-]+)(?:@\.)?:$/); if (custom) { const name = custom[1]; - const emoji = await Emojis.findOne({ - host: reacterHost || null, + const emoji = await Emojis.findOneBy({ + host: reacterHost ?? IsNull(), name, }); diff --git a/packages/backend/src/models/entities/instance.ts b/packages/backend/src/models/entities/instance.ts index c15c80ff43..bb24d6b30f 100644 --- a/packages/backend/src/models/entities/instance.ts +++ b/packages/backend/src/models/entities/instance.ts @@ -59,22 +59,6 @@ export class Instance { }) public followersCount: number; - /** - * ドライブ使用量 - */ - @Column('bigint', { - default: 0, - }) - public driveUsage: number; - - /** - * ドライブのファイル数 - */ - @Column('integer', { - default: 0, - }) - public driveFiles: number; - /** * 直近のリクエスト送信日時 */ diff --git a/packages/backend/src/models/entities/muting.ts b/packages/backend/src/models/entities/muting.ts index 8cdd2af9d1..b3a7e7a671 100644 --- a/packages/backend/src/models/entities/muting.ts +++ b/packages/backend/src/models/entities/muting.ts @@ -14,6 +14,13 @@ export class Muting { }) public createdAt: Date; + @Index() + @Column('timestamp with time zone', { + nullable: true, + default: null, + }) + public expiresAt: Date | null; + @Index() @Column({ ...id(), diff --git a/packages/backend/src/models/entities/notification.ts b/packages/backend/src/models/entities/notification.ts index 4a4739b8c5..db3dba3632 100644 --- a/packages/backend/src/models/entities/notification.ts +++ b/packages/backend/src/models/entities/notification.ts @@ -59,7 +59,8 @@ export class Notification { * renote - (自分または自分がWatchしている)投稿がRenoteされた * quote - (自分または自分がWatchしている)投稿が引用Renoteされた * reaction - (自分または自分がWatchしている)投稿にリアクションされた - * pollVote - (自分または自分がWatchしている)投稿の投票に投票された + * pollVote - (自分または自分がWatchしている)投稿のアンケートに投票された + * pollEnded - 自分のアンケートもしくは自分が投票したアンケートが終了した * receiveFollowRequest - フォローリクエストされた * followRequestAccepted - 自分の送ったフォローリクエストが承認された * groupInvited - グループに招待された diff --git a/packages/backend/src/models/entities/user.ts b/packages/backend/src/models/entities/user.ts index 9d5db10eb3..c76824c977 100644 --- a/packages/backend/src/models/entities/user.ts +++ b/packages/backend/src/models/entities/user.ts @@ -234,3 +234,9 @@ export interface ILocalUser extends User { export interface IRemoteUser extends User { host: string; } + +export type CacheableLocalUser = ILocalUser; + +export type CacheableRemoteUser = IRemoteUser; + +export type CacheableUser = CacheableLocalUser | CacheableRemoteUser; diff --git a/packages/backend/src/models/index.ts b/packages/backend/src/models/index.ts index e7b6854886..54582347c7 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'; @@ -63,65 +65,65 @@ import { PasswordResetRequest } from './entities/password-reset-request.js'; import { UserPending } from './entities/user-pending.js'; import { InstanceRepository } from './repositories/instance.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 Ads = db.getRepository(Ad); +export const PasswordResetRequests = db.getRepository(PasswordResetRequest); diff --git a/packages/backend/src/models/repositories/abuse-user-report.ts b/packages/backend/src/models/repositories/abuse-user-report.ts index 348f88b3a2..36d7ab90c5 100644 --- a/packages/backend/src/models/repositories/abuse-user-report.ts +++ b/packages/backend/src/models/repositories/abuse-user-report.ts @@ -1,14 +1,13 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { Users } from '../index.js'; import { AbuseUserReport } from '@/models/entities/abuse-user-report.js'; import { awaitAll } from '@/prelude/await-all.js'; -@EntityRepository(AbuseUserReport) -export class AbuseUserReportRepository extends Repository { - public async pack( +export const AbuseUserReportRepository = db.getRepository(AbuseUserReport).extend({ + async pack( src: AbuseUserReport['id'] | AbuseUserReport, ) { - const report = typeof src === 'object' ? src : await this.findOneOrFail(src); + const report = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); return await awaitAll({ id: report.id, @@ -29,11 +28,11 @@ export class AbuseUserReportRepository extends Repository { }) : null, forwarded: report.forwarded, }); - } + }, - public packMany( + packMany( reports: any[], ) { return Promise.all(reports.map(x => this.pack(x))); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/antenna.ts b/packages/backend/src/models/repositories/antenna.ts index 3440ca1871..70180e2dec 100644 --- a/packages/backend/src/models/repositories/antenna.ts +++ b/packages/backend/src/models/repositories/antenna.ts @@ -1,17 +1,16 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { Antenna } from '@/models/entities/antenna.js'; import { Packed } from '@/misc/schema.js'; import { AntennaNotes, UserGroupJoinings } from '../index.js'; -@EntityRepository(Antenna) -export class AntennaRepository extends Repository { - public async pack( +export const AntennaRepository = db.getRepository(Antenna).extend({ + async pack( src: Antenna['id'] | Antenna, ): Promise> { - const antenna = typeof src === 'object' ? src : await this.findOneOrFail(src); + const antenna = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); - const hasUnreadNote = (await AntennaNotes.findOne({ antennaId: antenna.id, read: false })) != null; - const userGroupJoining = antenna.userGroupJoiningId ? await UserGroupJoinings.findOne(antenna.userGroupJoiningId) : null; + const hasUnreadNote = (await AntennaNotes.findOneBy({ antennaId: antenna.id, read: false })) != null; + const userGroupJoining = antenna.userGroupJoiningId ? await UserGroupJoinings.findOneBy({ id: antenna.userGroupJoiningId }) : null; return { id: antenna.id, @@ -29,5 +28,5 @@ export class AntennaRepository extends Repository { withFile: antenna.withFile, hasUnreadNote, }; - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/app.ts b/packages/backend/src/models/repositories/app.ts index 4c3c488da0..e08dd6f0e3 100644 --- a/packages/backend/src/models/repositories/app.ts +++ b/packages/backend/src/models/repositories/app.ts @@ -1,12 +1,11 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { App } from '@/models/entities/app.js'; import { AccessTokens } from '../index.js'; import { Packed } from '@/misc/schema.js'; import { User } from '../entities/user.js'; -@EntityRepository(App) -export class AppRepository extends Repository { - public async pack( +export const AppRepository = db.getRepository(App).extend({ + async pack( src: App['id'] | App, me?: { id: User['id'] } | null | undefined, options?: { @@ -21,7 +20,7 @@ export class AppRepository extends Repository { includeProfileImageIds: false, }, options); - const app = typeof src === 'object' ? src : await this.findOneOrFail(src); + const app = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); return { id: app.id, @@ -30,11 +29,11 @@ export class AppRepository extends Repository { permission: app.permission, ...(opts.includeSecret ? { secret: app.secret } : {}), ...(me ? { - isAuthorized: await AccessTokens.count({ + isAuthorized: await AccessTokens.countBy({ appId: app.id, userId: me.id, }).then(count => count > 0), } : {}), }; - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/auth-session.ts b/packages/backend/src/models/repositories/auth-session.ts index 7a7bd3a1ed..3f1f6f4897 100644 --- a/packages/backend/src/models/repositories/auth-session.ts +++ b/packages/backend/src/models/repositories/auth-session.ts @@ -1,21 +1,20 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { Apps } from '../index.js'; import { AuthSession } from '@/models/entities/auth-session.js'; import { awaitAll } from '@/prelude/await-all.js'; import { User } from '@/models/entities/user.js'; -@EntityRepository(AuthSession) -export class AuthSessionRepository extends Repository { - public async pack( +export const AuthSessionRepository = db.getRepository(AuthSession).extend({ + async pack( src: AuthSession['id'] | AuthSession, me?: { id: User['id'] } | null | undefined ) { - const session = typeof src === 'object' ? src : await this.findOneOrFail(src); + const session = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); return await awaitAll({ id: session.id, app: Apps.pack(session.appId, me), token: session.token, }); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/blocking.ts b/packages/backend/src/models/repositories/blocking.ts index b155bf944b..1d569fb875 100644 --- a/packages/backend/src/models/repositories/blocking.ts +++ b/packages/backend/src/models/repositories/blocking.ts @@ -1,17 +1,16 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { Users } from '../index.js'; import { Blocking } from '@/models/entities/blocking.js'; import { awaitAll } from '@/prelude/await-all.js'; import { Packed } from '@/misc/schema.js'; import { User } from '@/models/entities/user.js'; -@EntityRepository(Blocking) -export class BlockingRepository extends Repository { - public async pack( +export const BlockingRepository = db.getRepository(Blocking).extend({ + async pack( src: Blocking['id'] | Blocking, me?: { id: User['id'] } | null | undefined ): Promise> { - const blocking = typeof src === 'object' ? src : await this.findOneOrFail(src); + const blocking = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); return await awaitAll({ id: blocking.id, @@ -21,12 +20,12 @@ export class BlockingRepository extends Repository { detail: true, }), }); - } + }, - public packMany( + packMany( blockings: any[], me: { id: User['id'] } ) { return Promise.all(blockings.map(x => this.pack(x, me))); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/channel.ts b/packages/backend/src/models/repositories/channel.ts index cc13d7c1e6..213ac3671a 100644 --- a/packages/backend/src/models/repositories/channel.ts +++ b/packages/backend/src/models/repositories/channel.ts @@ -1,23 +1,22 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { Channel } from '@/models/entities/channel.js'; import { Packed } from '@/misc/schema.js'; import { DriveFiles, ChannelFollowings, NoteUnreads } from '../index.js'; import { User } from '@/models/entities/user.js'; -@EntityRepository(Channel) -export class ChannelRepository extends Repository { - public async pack( +export const ChannelRepository = db.getRepository(Channel).extend({ + async pack( src: Channel['id'] | Channel, me?: { id: User['id'] } | null | undefined, ): Promise> { - const channel = typeof src === 'object' ? src : await this.findOneOrFail(src); + const channel = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); const meId = me ? me.id : null; - const banner = channel.bannerId ? await DriveFiles.findOne(channel.bannerId) : null; + const banner = channel.bannerId ? await DriveFiles.findOneBy({ id: channel.bannerId }) : null; - const hasUnreadNote = meId ? (await NoteUnreads.findOne({ noteChannelId: channel.id, userId: meId })) != null : undefined; + const hasUnreadNote = meId ? (await NoteUnreads.findOneBy({ noteChannelId: channel.id, userId: meId })) != null : undefined; - const following = meId ? await ChannelFollowings.findOne({ + const following = meId ? await ChannelFollowings.findOneBy({ followerId: meId, followeeId: channel.id, }) : null; @@ -38,5 +37,5 @@ export class ChannelRepository extends Repository { hasUnreadNote, } : {}), }; - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/clip.ts b/packages/backend/src/models/repositories/clip.ts index 9e1979729a..b4a342905e 100644 --- a/packages/backend/src/models/repositories/clip.ts +++ b/packages/backend/src/models/repositories/clip.ts @@ -1,15 +1,14 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { Clip } from '@/models/entities/clip.js'; import { Packed } from '@/misc/schema.js'; import { Users } from '../index.js'; import { awaitAll } from '@/prelude/await-all.js'; -@EntityRepository(Clip) -export class ClipRepository extends Repository { - public async pack( +export const ClipRepository = db.getRepository(Clip).extend({ + async pack( src: Clip['id'] | Clip, ): Promise> { - const clip = typeof src === 'object' ? src : await this.findOneOrFail(src); + const clip = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); return await awaitAll({ id: clip.id, @@ -20,12 +19,12 @@ export class ClipRepository extends Repository { description: clip.description, isPublic: clip.isPublic, }); - } + }, - public packMany( + packMany( clips: Clip[], ) { return Promise.all(clips.map(x => this.pack(x))); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/drive-file.ts b/packages/backend/src/models/repositories/drive-file.ts index 6452632db7..69dc1721c2 100644 --- a/packages/backend/src/models/repositories/drive-file.ts +++ b/packages/backend/src/models/repositories/drive-file.ts @@ -1,4 +1,4 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { DriveFile } from '@/models/entities/drive-file.js'; import { Users, DriveFolders } from '../index.js'; import { User } from '@/models/entities/user.js'; @@ -16,9 +16,8 @@ type PackOptions = { withUser?: boolean, }; -@EntityRepository(DriveFile) -export class DriveFileRepository extends Repository { - public validateFileName(name: string): boolean { +export const DriveFileRepository = db.getRepository(DriveFile).extend({ + validateFileName(name: string): boolean { return ( (name.trim().length > 0) && (name.length <= 200) && @@ -26,9 +25,9 @@ export class DriveFileRepository extends Repository { (name.indexOf('/') === -1) && (name.indexOf('..') === -1) ); - } + }, - public getPublicProperties(file: DriveFile): DriveFile['properties'] { + getPublicProperties(file: DriveFile): DriveFile['properties'] { if (file.properties.orientation != null) { const properties = JSON.parse(JSON.stringify(file.properties)); if (file.properties.orientation >= 5) { @@ -39,9 +38,9 @@ export class DriveFileRepository extends Repository { } return file.properties; - } + }, - public getPublicUrl(file: DriveFile, thumbnail = false): string | null { + getPublicUrl(file: DriveFile, thumbnail = false): string | null { // リモートかつメディアプロキシ if (file.uri != null && file.userHost != null && config.mediaProxy != null) { return appendQuery(config.mediaProxy, query({ @@ -62,9 +61,9 @@ export class DriveFileRepository extends Repository { const isImage = file.type && ['image/png', 'image/apng', 'image/gif', 'image/jpeg', 'image/webp', 'image/svg+xml'].includes(file.type); return thumbnail ? (file.thumbnailUrl || (isImage ? (file.webpublicUrl || file.url) : null)) : (file.webpublicUrl || file.url); - } + }, - public async calcDriveUsageOf(user: User['id'] | { id: User['id'] }): Promise { + async calcDriveUsageOf(user: User['id'] | { id: User['id'] }): Promise { const id = typeof user === 'object' ? user.id : user; const { sum } = await this @@ -75,9 +74,9 @@ export class DriveFileRepository extends Repository { .getRawOne(); return parseInt(sum, 10) || 0; - } + }, - public async calcDriveUsageOfHost(host: string): Promise { + async calcDriveUsageOfHost(host: string): Promise { const { sum } = await this .createQueryBuilder('file') .where('file.userHost = :host', { host: toPuny(host) }) @@ -86,9 +85,9 @@ export class DriveFileRepository extends Repository { .getRawOne(); return parseInt(sum, 10) || 0; - } + }, - public async calcDriveUsageOfLocal(): Promise { + async calcDriveUsageOfLocal(): Promise { const { sum } = await this .createQueryBuilder('file') .where('file.userHost IS NULL') @@ -97,9 +96,9 @@ export class DriveFileRepository extends Repository { .getRawOne(); return parseInt(sum, 10) || 0; - } + }, - public async calcDriveUsageOfRemote(): Promise { + async calcDriveUsageOfRemote(): Promise { const { sum } = await this .createQueryBuilder('file') .where('file.userHost IS NOT NULL') @@ -108,11 +107,9 @@ export class DriveFileRepository extends Repository { .getRawOne(); return parseInt(sum, 10) || 0; - } + }, - public async pack(src: DriveFile['id'], options?: PackOptions): Promise | null>; - public async pack(src: DriveFile, options?: PackOptions): Promise>; - public async pack( + async pack( src: DriveFile['id'] | DriveFile, options?: PackOptions ): Promise | null> { @@ -121,11 +118,9 @@ export class DriveFileRepository extends Repository { self: false, }, options); - const file = typeof src === 'object' ? src : await this.findOne(src); + const file = typeof src === 'object' ? src : await this.findOneBy({ id: src }); if (file == null) return null; - const meta = await fetchMeta(); - return await awaitAll>({ id: file.id, createdAt: file.createdAt.toISOString(), @@ -146,13 +141,13 @@ export class DriveFileRepository extends Repository { userId: opts.withUser ? file.userId : null, user: (opts.withUser && file.userId) ? Users.pack(file.userId) : null, }); - } + }, - public async packMany( + async packMany( files: (DriveFile['id'] | DriveFile)[], options?: PackOptions ) { const items = await Promise.all(files.map(f => this.pack(f, options))); return items.filter(x => x != null); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/drive-folder.ts b/packages/backend/src/models/repositories/drive-folder.ts index b0e09eedf5..ab5f3dab63 100644 --- a/packages/backend/src/models/repositories/drive-folder.ts +++ b/packages/backend/src/models/repositories/drive-folder.ts @@ -1,12 +1,11 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { DriveFolders, DriveFiles } from '../index.js'; import { DriveFolder } from '@/models/entities/drive-folder.js'; import { awaitAll } from '@/prelude/await-all.js'; import { Packed } from '@/misc/schema.js'; -@EntityRepository(DriveFolder) -export class DriveFolderRepository extends Repository { - public async pack( +export const DriveFolderRepository = db.getRepository(DriveFolder).extend({ + async pack( src: DriveFolder['id'] | DriveFolder, options?: { detail: boolean @@ -16,7 +15,7 @@ export class DriveFolderRepository extends Repository { detail: false, }, options); - const folder = typeof src === 'object' ? src : await this.findOneOrFail(src); + const folder = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); return await awaitAll({ id: folder.id, @@ -25,10 +24,10 @@ export class DriveFolderRepository extends Repository { parentId: folder.parentId, ...(opts.detail ? { - foldersCount: DriveFolders.count({ + foldersCount: DriveFolders.countBy({ parentId: folder.id, }), - filesCount: DriveFiles.count({ + filesCount: DriveFiles.countBy({ folderId: folder.id, }), @@ -39,5 +38,5 @@ export class DriveFolderRepository extends Repository { } : {}), } : {}), }); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/emoji.ts b/packages/backend/src/models/repositories/emoji.ts index 3b13832a35..a0d390d793 100644 --- a/packages/backend/src/models/repositories/emoji.ts +++ b/packages/backend/src/models/repositories/emoji.ts @@ -1,13 +1,12 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { Emoji } from '@/models/entities/emoji.js'; import { Packed } from '@/misc/schema.js'; -@EntityRepository(Emoji) -export class EmojiRepository extends Repository { - public async pack( +export const EmojiRepository = db.getRepository(Emoji).extend({ + async pack( src: Emoji['id'] | Emoji, ): Promise> { - const emoji = typeof src === 'object' ? src : await this.findOneOrFail(src); + const emoji = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); return { id: emoji.id, @@ -18,11 +17,11 @@ export class EmojiRepository extends Repository { // || emoji.originalUrl してるのは後方互換性のため url: emoji.publicUrl || emoji.originalUrl, }; - } + }, - public packMany( + packMany( emojis: any[], ) { return Promise.all(emojis.map(x => this.pack(x))); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/follow-request.ts b/packages/backend/src/models/repositories/follow-request.ts index 1da1f875ea..c4a7203aa1 100644 --- a/packages/backend/src/models/repositories/follow-request.ts +++ b/packages/backend/src/models/repositories/follow-request.ts @@ -1,20 +1,19 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { FollowRequest } from '@/models/entities/follow-request.js'; import { Users } from '../index.js'; import { User } from '@/models/entities/user.js'; -@EntityRepository(FollowRequest) -export class FollowRequestRepository extends Repository { - public async pack( +export const FollowRequestRepository = db.getRepository(FollowRequest).extend({ + async pack( src: FollowRequest['id'] | FollowRequest, me?: { id: User['id'] } | null | undefined ) { - const request = typeof src === 'object' ? src : await this.findOneOrFail(src); + const request = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); return { id: request.id, follower: await Users.pack(request.followerId, me), followee: await Users.pack(request.followeeId, me), }; - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/following.ts b/packages/backend/src/models/repositories/following.ts index f25289d19c..46109244fa 100644 --- a/packages/backend/src/models/repositories/following.ts +++ b/packages/backend/src/models/repositories/following.ts @@ -1,4 +1,4 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { Users } from '../index.js'; import { Following } from '@/models/entities/following.js'; import { awaitAll } from '@/prelude/await-all.js'; @@ -29,25 +29,24 @@ type RemoteFolloweeFollowing = Following & { followeeSharedInbox: string; }; -@EntityRepository(Following) -export class FollowingRepository extends Repository { - public isLocalFollower(following: Following): following is LocalFollowerFollowing { +export const FollowingRepository = db.getRepository(Following).extend({ + isLocalFollower(following: Following): following is LocalFollowerFollowing { return following.followerHost == null; - } + }, - public isRemoteFollower(following: Following): following is RemoteFollowerFollowing { + isRemoteFollower(following: Following): following is RemoteFollowerFollowing { return following.followerHost != null; - } + }, - public isLocalFollowee(following: Following): following is LocalFolloweeFollowing { + isLocalFollowee(following: Following): following is LocalFolloweeFollowing { return following.followeeHost == null; - } + }, - public isRemoteFollowee(following: Following): following is RemoteFolloweeFollowing { + isRemoteFollowee(following: Following): following is RemoteFolloweeFollowing { return following.followeeHost != null; - } + }, - public async pack( + async pack( src: Following['id'] | Following, me?: { id: User['id'] } | null | undefined, opts?: { @@ -55,7 +54,7 @@ export class FollowingRepository extends Repository { populateFollower?: boolean; } ): Promise> { - const following = typeof src === 'object' ? src : await this.findOneOrFail(src); + const following = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); if (opts == null) opts = {}; @@ -71,9 +70,9 @@ export class FollowingRepository extends Repository { detail: true, }) : undefined, }); - } + }, - public packMany( + packMany( followings: any[], me?: { id: User['id'] } | null | undefined, opts?: { @@ -82,5 +81,5 @@ export class FollowingRepository extends Repository { } ) { return Promise.all(followings.map(x => this.pack(x, me, opts))); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/gallery-like.ts b/packages/backend/src/models/repositories/gallery-like.ts index 545186fa19..08ca4962b8 100644 --- a/packages/backend/src/models/repositories/gallery-like.ts +++ b/packages/backend/src/models/repositories/gallery-like.ts @@ -1,25 +1,24 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { GalleryLike } from '@/models/entities/gallery-like.js'; import { GalleryPosts } from '../index.js'; -@EntityRepository(GalleryLike) -export class GalleryLikeRepository extends Repository { - public async pack( +export const GalleryLikeRepository = db.getRepository(GalleryLike).extend({ + async pack( src: GalleryLike['id'] | GalleryLike, me?: any ) { - const like = typeof src === 'object' ? src : await this.findOneOrFail(src); + const like = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); return { id: like.id, post: await GalleryPosts.pack(like.post || like.postId, me), }; - } + }, - public packMany( + packMany( likes: any[], me: any ) { return Promise.all(likes.map(x => this.pack(x, me))); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/gallery-post.ts b/packages/backend/src/models/repositories/gallery-post.ts index bbb036dd09..bb8d40b75e 100644 --- a/packages/backend/src/models/repositories/gallery-post.ts +++ b/packages/backend/src/models/repositories/gallery-post.ts @@ -1,18 +1,17 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { GalleryPost } from '@/models/entities/gallery-post.js'; import { Packed } from '@/misc/schema.js'; import { Users, DriveFiles, GalleryLikes } from '../index.js'; import { awaitAll } from '@/prelude/await-all.js'; import { User } from '@/models/entities/user.js'; -@EntityRepository(GalleryPost) -export class GalleryPostRepository extends Repository { - public async pack( +export const GalleryPostRepository = db.getRepository(GalleryPost).extend({ + async pack( src: GalleryPost['id'] | GalleryPost, me?: { id: User['id'] } | null | undefined, ): Promise> { const meId = me ? me.id : null; - const post = typeof src === 'object' ? src : await this.findOneOrFail(src); + const post = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); return await awaitAll({ id: post.id, @@ -27,14 +26,14 @@ export class GalleryPostRepository extends Repository { tags: post.tags.length > 0 ? post.tags : undefined, isSensitive: post.isSensitive, likedCount: post.likedCount, - isLiked: meId ? await GalleryLikes.findOne({ postId: post.id, userId: meId }).then(x => x != null) : undefined, + isLiked: meId ? await GalleryLikes.findOneBy({ postId: post.id, userId: meId }).then(x => x != null) : undefined, }); - } + }, - public packMany( + packMany( posts: GalleryPost[], me?: { id: User['id'] } | null | undefined, ) { return Promise.all(posts.map(x => this.pack(x, me))); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/hashtag.ts b/packages/backend/src/models/repositories/hashtag.ts index 0548e19ee3..e6c0e36f00 100644 --- a/packages/backend/src/models/repositories/hashtag.ts +++ b/packages/backend/src/models/repositories/hashtag.ts @@ -1,10 +1,9 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { Hashtag } from '@/models/entities/hashtag.js'; import { Packed } from '@/misc/schema.js'; -@EntityRepository(Hashtag) -export class HashtagRepository extends Repository { - public async pack( +export const HashtagRepository = db.getRepository(Hashtag).extend({ + async pack( src: Hashtag, ): Promise> { return { @@ -16,11 +15,11 @@ export class HashtagRepository extends Repository { attachedLocalUsersCount: src.attachedLocalUsersCount, attachedRemoteUsersCount: src.attachedRemoteUsersCount, }; - } + }, - public packMany( + packMany( hashtags: Hashtag[], ) { return Promise.all(hashtags.map(x => this.pack(x))); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/instance.ts b/packages/backend/src/models/repositories/instance.ts index 358e055aaa..4ddf717098 100644 --- a/packages/backend/src/models/repositories/instance.ts +++ b/packages/backend/src/models/repositories/instance.ts @@ -1,10 +1,9 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { Instance } from '@/models/entities/instance.js'; import { Packed } from '@/misc/schema.js'; -@EntityRepository(Instance) -export class InstanceRepository extends Repository { - public async pack( +export const InstanceRepository = db.getRepository(Instance).extend({ + async pack( instance: Instance, ): Promise> { return { @@ -29,11 +28,11 @@ export class InstanceRepository extends Repository { iconUrl: instance.iconUrl, infoUpdatedAt: instance.infoUpdatedAt ? instance.infoUpdatedAt.toISOString() : null, }; - } + }, - public packMany( + packMany( instances: Instance[], ) { return Promise.all(instances.map(x => this.pack(x))); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/messaging-message.ts b/packages/backend/src/models/repositories/messaging-message.ts index 3f51707008..6c51c93ff7 100644 --- a/packages/backend/src/models/repositories/messaging-message.ts +++ b/packages/backend/src/models/repositories/messaging-message.ts @@ -1,12 +1,11 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { MessagingMessage } from '@/models/entities/messaging-message.js'; import { Users, DriveFiles, UserGroups } from '../index.js'; import { Packed } from '@/misc/schema.js'; import { User } from '@/models/entities/user.js'; -@EntityRepository(MessagingMessage) -export class MessagingMessageRepository extends Repository { - public async pack( +export const MessagingMessageRepository = db.getRepository(MessagingMessage).extend({ + async pack( src: MessagingMessage['id'] | MessagingMessage, me?: { id: User['id'] } | null | undefined, options?: { @@ -19,7 +18,7 @@ export class MessagingMessageRepository extends Repository { populateGroup: true, }; - const message = typeof src === 'object' ? src : await this.findOneOrFail(src); + const message = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); return { id: message.id, @@ -36,5 +35,5 @@ export class MessagingMessageRepository extends Repository { isRead: message.isRead, reads: message.reads, }; - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/moderation-logs.ts b/packages/backend/src/models/repositories/moderation-logs.ts index ea78104960..1488b1eabe 100644 --- a/packages/backend/src/models/repositories/moderation-logs.ts +++ b/packages/backend/src/models/repositories/moderation-logs.ts @@ -1,14 +1,13 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { Users } from '../index.js'; import { ModerationLog } from '@/models/entities/moderation-log.js'; import { awaitAll } from '@/prelude/await-all.js'; -@EntityRepository(ModerationLog) -export class ModerationLogRepository extends Repository { - public async pack( +export const ModerationLogRepository = db.getRepository(ModerationLog).extend({ + async pack( src: ModerationLog['id'] | ModerationLog, ) { - const log = typeof src === 'object' ? src : await this.findOneOrFail(src); + const log = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); return await awaitAll({ id: log.id, @@ -20,11 +19,11 @@ export class ModerationLogRepository extends Repository { detail: true, }), }); - } + }, - public packMany( + packMany( reports: any[], ) { return Promise.all(reports.map(x => this.pack(x))); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/muting.ts b/packages/backend/src/models/repositories/muting.ts index 6ffecc302a..7891b10fb0 100644 --- a/packages/backend/src/models/repositories/muting.ts +++ b/packages/backend/src/models/repositories/muting.ts @@ -1,32 +1,32 @@ -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, createdAt: muting.createdAt.toISOString(), + expiresAt: muting.expiresAt ? muting.expiresAt.toISOString() : null, muteeId: muting.muteeId, mutee: Users.pack(muting.muteeId, me, { detail: true, }), }); - } + }, - public packMany( + packMany( mutings: any[], me: { id: User['id'] } ) { return Promise.all(mutings.map(x => this.pack(x, me))); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/note-favorite.ts b/packages/backend/src/models/repositories/note-favorite.ts index d7a7925ebc..9bd97f9880 100644 --- a/packages/backend/src/models/repositories/note-favorite.ts +++ b/packages/backend/src/models/repositories/note-favorite.ts @@ -1,15 +1,14 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { NoteFavorite } from '@/models/entities/note-favorite.js'; import { Notes } from '../index.js'; import { User } from '@/models/entities/user.js'; -@EntityRepository(NoteFavorite) -export class NoteFavoriteRepository extends Repository { - public async pack( +export const NoteFavoriteRepository = db.getRepository(NoteFavorite).extend({ + async pack( src: NoteFavorite['id'] | NoteFavorite, me?: { id: User['id'] } | null | undefined ) { - const favorite = typeof src === 'object' ? src : await this.findOneOrFail(src); + const favorite = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); return { id: favorite.id, @@ -17,12 +16,12 @@ export class NoteFavoriteRepository extends Repository { noteId: favorite.noteId, note: await Notes.pack(favorite.note || favorite.noteId, me), }; - } + }, - public packMany( + packMany( favorites: any[], me: { id: User['id'] } ) { return Promise.all(favorites.map(x => this.pack(x, me))); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/note-reaction.ts b/packages/backend/src/models/repositories/note-reaction.ts index a212b0d3e9..4deae51c93 100644 --- a/packages/backend/src/models/repositories/note-reaction.ts +++ b/packages/backend/src/models/repositories/note-reaction.ts @@ -1,13 +1,12 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { NoteReaction } from '@/models/entities/note-reaction.js'; import { Notes, Users } from '../index.js'; import { Packed } from '@/misc/schema.js'; import { convertLegacyReaction } from '@/misc/reaction-lib.js'; import { User } from '@/models/entities/user.js'; -@EntityRepository(NoteReaction) -export class NoteReactionRepository extends Repository { - public async pack( +export const NoteReactionRepository = db.getRepository(NoteReaction).extend({ + async pack( src: NoteReaction['id'] | NoteReaction, me?: { id: User['id'] } | null | undefined, options?: { @@ -18,7 +17,7 @@ export class NoteReactionRepository extends Repository { withNote: false, }, options); - const reaction = typeof src === 'object' ? src : await this.findOneOrFail(src); + const reaction = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); return { id: reaction.id, @@ -29,5 +28,5 @@ export class NoteReactionRepository extends Repository { note: await Notes.pack(reaction.note ?? reaction.noteId, me), } : {}), }; - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/note.ts b/packages/backend/src/models/repositories/note.ts index 418d6e2346..cf5fcb1787 100644 --- a/packages/backend/src/models/repositories/note.ts +++ b/packages/backend/src/models/repositories/note.ts @@ -1,4 +1,4 @@ -import { EntityRepository, Repository, In } from 'typeorm'; +import { In } from 'typeorm'; import * as mfm from 'mfm-js'; import { Note } from '@/models/entities/note.js'; import { User } from '@/models/entities/user.js'; @@ -9,10 +9,133 @@ import { awaitAll } from '@/prelude/await-all.js'; import { convertLegacyReaction, convertLegacyReactions, decodeReaction } from '@/misc/reaction-lib.js'; import { NoteReaction } from '@/models/entities/note-reaction.js'; import { aggregateNoteEmojis, populateEmojis, prefetchEmojis } from '@/misc/populate-emojis.js'; +import { db } from '@/db/postgre.js'; -@EntityRepository(Note) -export class NoteRepository extends Repository { - public async isVisibleForMe(note: Note, meId: User['id'] | null): Promise { +async function hideNote(packedNote: Packed<'Note'>, meId: User['id'] | null) { + // TODO: isVisibleForMe を使うようにしても良さそう(型違うけど) + let hide = false; + + // visibility が specified かつ自分が指定されていなかったら非表示 + if (packedNote.visibility === 'specified') { + if (meId == null) { + hide = true; + } else if (meId === packedNote.userId) { + hide = false; + } else { + // 指定されているかどうか + const specified = packedNote.visibleUserIds!.some((id: any) => meId === id); + + if (specified) { + hide = false; + } else { + hide = true; + } + } + } + + // visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示 + if (packedNote.visibility === 'followers') { + if (meId == null) { + hide = true; + } else if (meId === packedNote.userId) { + hide = false; + } else if (packedNote.reply && (meId === packedNote.reply.userId)) { + // 自分の投稿に対するリプライ + hide = false; + } else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) { + // 自分へのメンション + hide = false; + } else { + // フォロワーかどうか + const following = await Followings.findOneBy({ + followeeId: packedNote.userId, + followerId: meId, + }); + + if (following == null) { + hide = true; + } else { + hide = false; + } + } + } + + if (hide) { + packedNote.visibleUserIds = undefined; + packedNote.fileIds = []; + packedNote.files = []; + packedNote.text = null; + packedNote.poll = undefined; + packedNote.cw = null; + packedNote.isHidden = true; + } +} + +async function populatePoll(note: Note, meId: User['id'] | null) { + const poll = await Polls.findOneByOrFail({ noteId: note.id }); + const choices = poll.choices.map(c => ({ + text: c, + votes: poll.votes[poll.choices.indexOf(c)], + isVoted: false, + })); + + if (meId) { + if (poll.multiple) { + const votes = await PollVotes.findBy({ + userId: meId, + noteId: note.id, + }); + + const myChoices = votes.map(v => v.choice); + for (const myChoice of myChoices) { + choices[myChoice].isVoted = true; + } + } else { + const vote = await PollVotes.findOneBy({ + userId: meId, + noteId: note.id, + }); + + if (vote) { + choices[vote.choice].isVoted = true; + } + } + } + + return { + multiple: poll.multiple, + expiresAt: poll.expiresAt, + choices, + }; +} + +async function populateMyReaction(note: Note, meId: User['id'], _hint_?: { + myReactions: Map; +}) { + if (_hint_?.myReactions) { + const reaction = _hint_.myReactions.get(note.id); + if (reaction) { + return convertLegacyReaction(reaction.reaction); + } else if (reaction === null) { + return undefined; + } + // 実装上抜けがあるだけかもしれないので、「ヒントに含まれてなかったら(=undefinedなら)return」のようにはしない + } + + const reaction = await NoteReactions.findOneBy({ + userId: meId, + noteId: note.id, + }); + + if (reaction) { + return convertLegacyReaction(reaction.reaction); + } + + return undefined; +} + +export const NoteRepository = db.getRepository(Note).extend({ + async isVisibleForMe(note: Note, meId: User['id'] | null): Promise { // visibility が specified かつ自分が指定されていなかったら非表示 if (note.visibility === 'specified') { if (meId == null) { @@ -45,7 +168,7 @@ export class NoteRepository extends Repository { return true; } else { // フォロワーかどうか - const following = await Followings.findOne({ + const following = await Followings.findOneBy({ followeeId: note.userId, followerId: meId, }); @@ -59,69 +182,9 @@ export class NoteRepository extends Repository { } return true; - } + }, - private async hideNote(packedNote: Packed<'Note'>, meId: User['id'] | null) { - // TODO: isVisibleForMe を使うようにしても良さそう(型違うけど) - let hide = false; - - // visibility が specified かつ自分が指定されていなかったら非表示 - if (packedNote.visibility === 'specified') { - if (meId == null) { - hide = true; - } else if (meId === packedNote.userId) { - hide = false; - } else { - // 指定されているかどうか - const specified = packedNote.visibleUserIds!.some((id: any) => meId === id); - - if (specified) { - hide = false; - } else { - hide = true; - } - } - } - - // visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示 - if (packedNote.visibility === 'followers') { - if (meId == null) { - hide = true; - } else if (meId === packedNote.userId) { - hide = false; - } else if (packedNote.reply && (meId === packedNote.reply.userId)) { - // 自分の投稿に対するリプライ - hide = false; - } else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) { - // 自分へのメンション - hide = false; - } else { - // フォロワーかどうか - const following = await Followings.findOne({ - followeeId: packedNote.userId, - followerId: meId, - }); - - if (following == null) { - hide = true; - } else { - hide = false; - } - } - } - - if (hide) { - packedNote.visibleUserIds = undefined; - packedNote.fileIds = []; - packedNote.files = []; - packedNote.text = null; - packedNote.poll = undefined; - packedNote.cw = null; - packedNote.isHidden = true; - } - } - - public async pack( + async pack( src: Note['id'] | Note, me?: { id: User['id'] } | null | undefined, options?: { @@ -138,68 +201,9 @@ export class NoteRepository extends Repository { }, options); const meId = me ? me.id : null; - const note = typeof src === 'object' ? src : await this.findOneOrFail(src); + const note = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); const host = note.userHost; - async function populatePoll() { - const poll = await Polls.findOneOrFail(note.id); - const choices = poll.choices.map(c => ({ - text: c, - votes: poll.votes[poll.choices.indexOf(c)], - isVoted: false, - })); - - if (poll.multiple) { - const votes = await PollVotes.find({ - userId: meId!, - noteId: note.id, - }); - - const myChoices = votes.map(v => v.choice); - for (const myChoice of myChoices) { - choices[myChoice].isVoted = true; - } - } else { - const vote = await PollVotes.findOne({ - userId: meId!, - noteId: note.id, - }); - - if (vote) { - choices[vote.choice].isVoted = true; - } - } - - return { - multiple: poll.multiple, - expiresAt: poll.expiresAt, - choices, - }; - } - - async function populateMyReaction() { - if (options?._hint_?.myReactions) { - const reaction = options._hint_.myReactions.get(note.id); - if (reaction) { - return convertLegacyReaction(reaction.reaction); - } else if (reaction === null) { - return undefined; - } - // 実装上抜けがあるだけかもしれないので、「ヒントに含まれてなかったら(=undefinedなら)return」のようにはしない - } - - const reaction = await NoteReactions.findOne({ - userId: meId!, - noteId: note.id, - }); - - if (reaction) { - return convertLegacyReaction(reaction.reaction); - } - - return undefined; - } - let text = note.text; if (note.name && (note.url ?? note.uri)) { @@ -209,7 +213,7 @@ export class NoteRepository extends Repository { const channel = note.channelId ? note.channel ? note.channel - : await Channels.findOne(note.channelId) + : await Channels.findOneBy({ id: note.channelId }) : null; const reactionEmojiNames = Object.keys(note.reactions).filter(x => x?.startsWith(':')).map(x => decodeReaction(x).reaction).map(x => x.replace(/:/g, '')); @@ -255,10 +259,10 @@ export class NoteRepository extends Repository { _hint_: options?._hint_, }) : undefined, - poll: note.hasPoll ? populatePoll() : undefined, + poll: note.hasPoll ? populatePoll(note, meId) : undefined, ...(meId ? { - myReaction: populateMyReaction(), + myReaction: populateMyReaction(note, meId, options?._hint_), } : {}), } : {}), }); @@ -275,13 +279,13 @@ export class NoteRepository extends Repository { } if (!opts.skipHide) { - await this.hideNote(packed, meId); + await hideNote(packed, meId); } return packed; - } + }, - public async packMany( + async packMany( notes: Note[], me?: { id: User['id'] } | null | undefined, options?: { @@ -296,7 +300,7 @@ export class NoteRepository extends Repository { if (meId) { const renoteIds = notes.filter(n => n.renoteId != null).map(n => n.renoteId!); const targets = [...notes.map(n => n.id), ...renoteIds]; - const myReactions = await NoteReactions.find({ + const myReactions = await NoteReactions.findBy({ userId: meId, noteId: In(targets), }); @@ -314,5 +318,5 @@ export class NoteRepository extends Repository { myReactions: myReactionsMap, }, }))); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/notification.ts b/packages/backend/src/models/repositories/notification.ts index 441bb79261..42b47ab150 100644 --- a/packages/backend/src/models/repositories/notification.ts +++ b/packages/backend/src/models/repositories/notification.ts @@ -1,4 +1,4 @@ -import { EntityRepository, In, Repository } from 'typeorm'; +import { In, Repository } from 'typeorm'; import { Users, Notes, UserGroupInvitations, AccessTokens, NoteReactions } from '../index.js'; import { Notification } from '@/models/entities/notification.js'; import { awaitAll } from '@/prelude/await-all.js'; @@ -8,10 +8,10 @@ import { NoteReaction } from '@/models/entities/note-reaction.js'; import { User } from '@/models/entities/user.js'; import { aggregateNoteEmojis, prefetchEmojis } from '@/misc/populate-emojis.js'; import { notificationTypes } from '@/types.js'; +import { db } from '@/db/postgre.js'; -@EntityRepository(Notification) -export class NotificationRepository extends Repository { - public async pack( +export const NotificationRepository = db.getRepository(Notification).extend({ + async pack( src: Notification['id'] | Notification, options: { _hintForEachNotes_?: { @@ -19,8 +19,8 @@ export class NotificationRepository extends Repository { }; } ): Promise> { - const notification = typeof src === 'object' ? src : await this.findOneOrFail(src); - const token = notification.appAccessTokenId ? await AccessTokens.findOneOrFail(notification.appAccessTokenId) : null; + const notification = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); + const token = notification.appAccessTokenId ? await AccessTokens.findOneByOrFail({ id: notification.appAccessTokenId }) : null; return await awaitAll({ id: notification.id, @@ -67,6 +67,12 @@ export class NotificationRepository extends Repository { }), choice: notification.choice, } : {}), + ...(notification.type === 'pollEnded' ? { + note: Notes.pack(notification.note || notification.noteId!, { id: notification.notifieeId }, { + detail: true, + _hint_: options._hintForEachNotes_, + }), + } : {}), ...(notification.type === 'groupInvited' ? { invitation: UserGroupInvitations.pack(notification.userGroupInvitationId!), } : {}), @@ -76,9 +82,9 @@ export class NotificationRepository extends Repository { icon: notification.customIcon || token?.iconUrl, } : {}), }); - } + }, - public async packMany( + async packMany( notifications: Notification[], meId: User['id'] ) { @@ -89,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), }); @@ -105,5 +111,5 @@ export class NotificationRepository extends Repository { myReactions: myReactionsMap, }, }))); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/page-like.ts b/packages/backend/src/models/repositories/page-like.ts index 66d780584f..87d6accc34 100644 --- a/packages/backend/src/models/repositories/page-like.ts +++ b/packages/backend/src/models/repositories/page-like.ts @@ -1,26 +1,25 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { PageLike } from '@/models/entities/page-like.js'; import { Pages } from '../index.js'; import { User } from '@/models/entities/user.js'; -@EntityRepository(PageLike) -export class PageLikeRepository extends Repository { - public async pack( +export const PageLikeRepository = db.getRepository(PageLike).extend({ + async pack( src: PageLike['id'] | PageLike, me?: { id: User['id'] } | null | undefined ) { - const like = typeof src === 'object' ? src : await this.findOneOrFail(src); + const like = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); return { id: like.id, page: await Pages.pack(like.page || like.pageId, me), }; - } + }, - public packMany( + packMany( likes: any[], me: { id: User['id'] } ) { return Promise.all(likes.map(x => this.pack(x, me))); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/page.ts b/packages/backend/src/models/repositories/page.ts index 037c13c434..1bffb23fa2 100644 --- a/packages/backend/src/models/repositories/page.ts +++ b/packages/backend/src/models/repositories/page.ts @@ -1,4 +1,4 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { Page } from '@/models/entities/page.js'; import { Packed } from '@/misc/schema.js'; import { Users, DriveFiles, PageLikes } from '../index.js'; @@ -6,20 +6,19 @@ import { awaitAll } from '@/prelude/await-all.js'; import { DriveFile } from '@/models/entities/drive-file.js'; import { User } from '@/models/entities/user.js'; -@EntityRepository(Page) -export class PageRepository extends Repository { - public async pack( +export const PageRepository = db.getRepository(Page).extend({ + async pack( src: Page['id'] | Page, me?: { id: User['id'] } | null | undefined, ): Promise> { const meId = me ? me.id : null; - const page = typeof src === 'object' ? src : await this.findOneOrFail(src); + const page = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); const attachedFiles: Promise[] = []; const collectFile = (xs: any[]) => { for (const x of xs) { if (x.type === 'image') { - attachedFiles.push(DriveFiles.findOne({ + attachedFiles.push(DriveFiles.findOneBy({ id: x.fileId, userId: page.userId, })); @@ -76,14 +75,14 @@ export class PageRepository extends Repository { eyeCatchingImage: page.eyeCatchingImageId ? await DriveFiles.pack(page.eyeCatchingImageId) : null, attachedFiles: DriveFiles.packMany(await Promise.all(attachedFiles)), likedCount: page.likedCount, - isLiked: meId ? await PageLikes.findOne({ pageId: page.id, userId: meId }).then(x => x != null) : undefined, + isLiked: meId ? await PageLikes.findOneBy({ pageId: page.id, userId: meId }).then(x => x != null) : undefined, }); - } + }, - public packMany( + packMany( pages: Page[], me?: { id: User['id'] } | null | undefined, ) { return Promise.all(pages.map(x => this.pack(x, me))); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/relay.ts b/packages/backend/src/models/repositories/relay.ts index 160ca60f7b..fa1c8f4d8d 100644 --- a/packages/backend/src/models/repositories/relay.ts +++ b/packages/backend/src/models/repositories/relay.ts @@ -1,6 +1,5 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { Relay } from '@/models/entities/relay.js'; -@EntityRepository(Relay) -export class RelayRepository extends Repository { -} +export const RelayRepository = db.getRepository(Relay).extend({ +}); diff --git a/packages/backend/src/models/repositories/signin.ts b/packages/backend/src/models/repositories/signin.ts index a0e2ce1526..94410ec58a 100644 --- a/packages/backend/src/models/repositories/signin.ts +++ b/packages/backend/src/models/repositories/signin.ts @@ -1,11 +1,10 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { Signin } from '@/models/entities/signin.js'; -@EntityRepository(Signin) -export class SigninRepository extends Repository { - public async pack( +export const SigninRepository = db.getRepository(Signin).extend({ + async pack( src: Signin, ) { return src; - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/user-group-invitation.ts b/packages/backend/src/models/repositories/user-group-invitation.ts index e338242c64..79ad019c99 100644 --- a/packages/backend/src/models/repositories/user-group-invitation.ts +++ b/packages/backend/src/models/repositories/user-group-invitation.ts @@ -1,23 +1,22 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { UserGroupInvitation } from '@/models/entities/user-group-invitation.js'; import { UserGroups } from '../index.js'; -@EntityRepository(UserGroupInvitation) -export class UserGroupInvitationRepository extends Repository { - public async pack( +export const UserGroupInvitationRepository = db.getRepository(UserGroupInvitation).extend({ + async pack( src: UserGroupInvitation['id'] | UserGroupInvitation, ) { - const invitation = typeof src === 'object' ? src : await this.findOneOrFail(src); + const invitation = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); return { id: invitation.id, group: await UserGroups.pack(invitation.userGroup || invitation.userGroupId), }; - } + }, - public packMany( + packMany( invitations: any[], ) { return Promise.all(invitations.map(x => this.pack(x))); - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/user-group.ts b/packages/backend/src/models/repositories/user-group.ts index a9ffe7369e..6eb9234244 100644 --- a/packages/backend/src/models/repositories/user-group.ts +++ b/packages/backend/src/models/repositories/user-group.ts @@ -1,16 +1,15 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { UserGroup } from '@/models/entities/user-group.js'; import { UserGroupJoinings } from '../index.js'; import { Packed } from '@/misc/schema.js'; -@EntityRepository(UserGroup) -export class UserGroupRepository extends Repository { - public async pack( +export const UserGroupRepository = db.getRepository(UserGroup).extend({ + async pack( src: UserGroup['id'] | UserGroup, ): Promise> { - const userGroup = typeof src === 'object' ? src : await this.findOneOrFail(src); + const userGroup = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); - const users = await UserGroupJoinings.find({ + const users = await UserGroupJoinings.findBy({ userGroupId: userGroup.id, }); @@ -21,5 +20,5 @@ export class UserGroupRepository extends Repository { ownerId: userGroup.userId, userIds: users.map(x => x.userId), }; - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/user-list.ts b/packages/backend/src/models/repositories/user-list.ts index 0ea26427fe..2b6f411ef6 100644 --- a/packages/backend/src/models/repositories/user-list.ts +++ b/packages/backend/src/models/repositories/user-list.ts @@ -1,16 +1,15 @@ -import { EntityRepository, Repository } from 'typeorm'; +import { db } from '@/db/postgre.js'; import { UserList } from '@/models/entities/user-list.js'; import { UserListJoinings } from '../index.js'; import { Packed } from '@/misc/schema.js'; -@EntityRepository(UserList) -export class UserListRepository extends Repository { - public async pack( +export const UserListRepository = db.getRepository(UserList).extend({ + async pack( src: UserList['id'] | UserList, ): Promise> { - const userList = typeof src === 'object' ? src : await this.findOneOrFail(src); + const userList = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); - const users = await UserListJoinings.find({ + const users = await UserListJoinings.findBy({ userListId: userList.id, }); @@ -20,5 +19,5 @@ export class UserListRepository extends Repository { name: userList.name, userIds: users.map(x => x.userId), }; - } -} + }, +}); diff --git a/packages/backend/src/models/repositories/user.ts b/packages/backend/src/models/repositories/user.ts index a909ab3ba6..2f4b7d6787 100644 --- a/packages/backend/src/models/repositories/user.ts +++ b/packages/backend/src/models/repositories/user.ts @@ -8,6 +8,11 @@ import { awaitAll, Promiseable } from '@/prelude/await-all.js'; import { populateEmojis } from '@/misc/populate-emojis.js'; import { getAntennas } from '@/misc/antenna-cache.js'; import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from '@/const.js'; +import { Cache } from '@/misc/cache.js'; +import { Instance } from '../entities/instance.js'; +import { db } from '@/db/postgre.js'; + +const userInstanceCache = new Cache(1000 * 60 * 60 * 3); type IsUserDetailed = Detailed extends true ? Packed<'UserDetailed'> : Packed<'UserLite'>; type IsMeAndIsUserDetailed = @@ -19,51 +24,69 @@ type IsMeAndIsUserDetailed { - public localUsernameSchema = { type: 'string', pattern: /^\w{1,20}$/.toString().slice(1, -1) } as const; - public passwordSchema = { type: 'string', minLength: 1 } as const; - public nameSchema = { type: 'string', minLength: 1, maxLength: 50 } as const; - public descriptionSchema = { type: 'string', minLength: 1, maxLength: 500 } as const; - public locationSchema = { type: 'string', minLength: 1, maxLength: 50 } as const; - public birthdaySchema = { type: 'string', pattern: /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.toString().slice(1, -1) } as const; +const localUsernameSchema = { type: 'string', pattern: /^\w{1,20}$/.toString().slice(1, -1) } as const; +const passwordSchema = { type: 'string', minLength: 1 } as const; +const nameSchema = { type: 'string', minLength: 1, maxLength: 50 } as const; +const descriptionSchema = { type: 'string', minLength: 1, maxLength: 500 } as const; +const locationSchema = { type: 'string', minLength: 1, maxLength: 50 } as const; +const birthdaySchema = { type: 'string', pattern: /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.toString().slice(1, -1) } as const; + +function isLocalUser(user: User): user is ILocalUser; +function isLocalUser(user: T): user is T & { host: null; }; +function isLocalUser(user: User | { host: User['host'] }): boolean { + return user.host == null; +} + +function isRemoteUser(user: User): user is IRemoteUser; +function isRemoteUser(user: T): user is T & { host: string; }; +function isRemoteUser(user: User | { host: User['host'] }): boolean { + return !isLocalUser(user); +} + +export const UserRepository = db.getRepository(User).extend({ + localUsernameSchema, + passwordSchema, + nameSchema, + descriptionSchema, + locationSchema, + birthdaySchema, //#region Validators - public validateLocalUsername = ajv.compile(this.localUsernameSchema); - public validatePassword = ajv.compile(this.passwordSchema); - public validateName = ajv.compile(this.nameSchema); - public validateDescription = ajv.compile(this.descriptionSchema); - public validateLocation = ajv.compile(this.locationSchema); - public validateBirthday = ajv.compile(this.birthdaySchema); + validateLocalUsername: ajv.compile(localUsernameSchema), + validatePassword: ajv.compile(passwordSchema), + validateName: ajv.compile(nameSchema), + validateDescription: ajv.compile(descriptionSchema), + validateLocation: ajv.compile(locationSchema), + validateBirthday: ajv.compile(birthdaySchema), //#endregion - public async getRelation(me: User['id'], target: User['id']) { + async getRelation(me: User['id'], target: User['id']) { const [following1, following2, followReq1, followReq2, toBlocking, fromBlocked, mute] = await Promise.all([ - Followings.findOne({ + Followings.findOneBy({ followerId: me, followeeId: target, }), - Followings.findOne({ + Followings.findOneBy({ followerId: target, followeeId: me, }), - FollowRequests.findOne({ + FollowRequests.findOneBy({ followerId: me, followeeId: target, }), - FollowRequests.findOne({ + FollowRequests.findOneBy({ followerId: target, followeeId: me, }), - Blockings.findOne({ + Blockings.findOneBy({ blockerId: me, blockeeId: target, }), - Blockings.findOne({ + Blockings.findOneBy({ blockerId: target, blockeeId: me, }), - Mutings.findOne({ + Mutings.findOneBy({ muterId: me, muteeId: target, }), @@ -79,14 +102,14 @@ export class UserRepository extends Repository { isBlocked: fromBlocked != null, isMuted: mute != null, }; - } + }, - public async getHasUnreadMessagingMessage(userId: User['id']): Promise { - const mute = await Mutings.find({ + async getHasUnreadMessagingMessage(userId: User['id']): Promise { + const mute = await Mutings.findBy({ muterId: userId, }); - const joinings = await UserGroupJoinings.find({ userId: userId }); + const joinings = await UserGroupJoinings.findBy({ userId: userId }); const groupQs = Promise.all(joinings.map(j => MessagingMessages.createQueryBuilder('message') .where(`message.groupId = :groupId`, { groupId: j.userGroupId }) @@ -108,44 +131,44 @@ export class UserRepository extends Repository { ]); return withUser || withGroups.some(x => x); - } + }, - public async getHasUnreadAnnouncement(userId: User['id']): Promise { - const reads = await AnnouncementReads.find({ + async getHasUnreadAnnouncement(userId: User['id']): Promise { + const reads = await AnnouncementReads.findBy({ userId: userId, }); - const count = await Announcements.count(reads.length > 0 ? { + const count = await Announcements.countBy(reads.length > 0 ? { id: Not(In(reads.map(read => read.announcementId))), } : {}); return count > 0; - } + }, - public async getHasUnreadAntenna(userId: User['id']): Promise { + async getHasUnreadAntenna(userId: User['id']): Promise { const myAntennas = (await getAntennas()).filter(a => a.userId === userId); - const unread = myAntennas.length > 0 ? await AntennaNotes.findOne({ + const unread = myAntennas.length > 0 ? await AntennaNotes.findOneBy({ antennaId: In(myAntennas.map(x => x.id)), read: false, }) : null; return unread != null; - } + }, - public async getHasUnreadChannel(userId: User['id']): Promise { - const channels = await ChannelFollowings.find({ followerId: userId }); + async getHasUnreadChannel(userId: User['id']): Promise { + const channels = await ChannelFollowings.findBy({ followerId: userId }); - const unread = channels.length > 0 ? await NoteUnreads.findOne({ + const unread = channels.length > 0 ? await NoteUnreads.findOneBy({ userId: userId, noteChannelId: In(channels.map(x => x.followeeId)), }) : null; return unread != null; - } + }, - public async getHasUnreadNotification(userId: User['id']): Promise { - const mute = await Mutings.find({ + async getHasUnreadNotification(userId: User['id']): Promise { + const mute = await Mutings.findBy({ muterId: userId, }); const mutedUserIds = mute.map(m => m.muteeId); @@ -160,17 +183,17 @@ export class UserRepository extends Repository { }); return count > 0; - } + }, - public async getHasPendingReceivedFollowRequest(userId: User['id']): Promise { - const count = await FollowRequests.count({ + async getHasPendingReceivedFollowRequest(userId: User['id']): Promise { + const count = await FollowRequests.countBy({ followeeId: userId, }); return count > 0; - } + }, - public getOnlineStatus(user: User): 'unknown' | 'online' | 'active' | 'offline' { + getOnlineStatus(user: User): 'unknown' | 'online' | 'active' | 'offline' { if (user.hideOnlineStatus) return 'unknown'; if (user.lastActiveDate == null) return 'unknown'; const elapsed = Date.now() - user.lastActiveDate.getTime(); @@ -179,22 +202,22 @@ export class UserRepository extends Repository { elapsed < USER_ACTIVE_THRESHOLD ? 'active' : 'offline' ); - } + }, - public getAvatarUrl(user: User): string { + getAvatarUrl(user: User): string { // TODO: avatarIdがあるがavatarがない(JOINされてない)場合のハンドリング if (user.avatar) { return DriveFiles.getPublicUrl(user.avatar, true) || this.getIdenticonUrl(user.id); } else { return this.getIdenticonUrl(user.id); } - } + }, - public getIdenticonUrl(userId: User['id']): string { + getIdenticonUrl(userId: User['id']): string { return `${config.url}/identicon/${userId}`; - } + }, - public async pack( + async pack( src: User['id'] | User, me?: { id: User['id'] } | null | undefined, options?: { @@ -211,11 +234,15 @@ export class UserRepository extends Repository { if (typeof src === 'object') { user = src; - if (src.avatar === undefined && src.avatarId) src.avatar = await DriveFiles.findOne(src.avatarId) ?? null; - if (src.banner === undefined && src.bannerId) src.banner = await DriveFiles.findOne(src.bannerId) ?? null; + if (src.avatar === undefined && src.avatarId) src.avatar = await DriveFiles.findOneBy({ id: src.avatarId }) ?? null; + if (src.banner === undefined && src.bannerId) src.banner = await DriveFiles.findOneBy({ id: src.bannerId }) ?? null; } else { - user = await this.findOneOrFail(src, { - relations: ['avatar', 'banner'], + user = await this.findOneOrFail({ + where: { id: src }, + relations: { + avatar: true, + banner: true, + }, }); } @@ -228,7 +255,7 @@ export class UserRepository extends Repository { .innerJoinAndSelect('pin.note', 'note') .orderBy('pin.id', 'DESC') .getMany() : []; - const profile = opts.detail ? await UserProfiles.findOneOrFail(user.id) : null; + const profile = opts.detail ? await UserProfiles.findOneByOrFail({ userId: user.id }) : null; const followingCount = profile == null ? null : (profile.ffVisibility === 'public') || isMe ? user.followingCount : @@ -254,8 +281,10 @@ export class UserRepository extends Repository { isModerator: user.isModerator || falsy, isBot: user.isBot || falsy, isCat: user.isCat || falsy, - showTimelineReplies: user.showTimelineReplies || falsy, - instance: user.host ? Instances.findOne({ host: user.host }).then(instance => instance ? { + instance: user.host ? userInstanceCache.fetch(user.host, + () => Instances.findOneBy({ host: user.host! }), + v => v != null + ).then(instance => instance ? { name: instance.name, softwareName: instance.softwareName, softwareVersion: instance.softwareVersion, @@ -297,7 +326,7 @@ export class UserRepository extends Repository { twoFactorEnabled: profile!.twoFactorEnabled, usePasswordLessLogin: profile!.usePasswordLessLogin, securityKeys: profile!.twoFactorEnabled - ? UserSecurityKeys.count({ + ? UserSecurityKeys.countBy({ userId: user.id, }).then(result => result >= 1) : false, @@ -334,6 +363,7 @@ export class UserRepository extends Repository { mutedInstances: profile!.mutedInstances, mutingNotificationTypes: profile!.mutingNotificationTypes, emailNotificationTypes: profile!.emailNotificationTypes, + showTimelineReplies: user.showTimelineReplies || falsy, } : {}), ...(opts.includeSecrets ? { @@ -344,7 +374,11 @@ export class UserRepository extends Repository { where: { userId: user.id, }, - select: ['id', 'name', 'lastUsed'], + select: { + id: true, + name: true, + lastUsed: true, + }, }) : [], } : {}), @@ -361,9 +395,9 @@ export class UserRepository extends Repository { } as Promiseable> as Promiseable>; return await awaitAll(packed); - } + }, - public packMany( + packMany( users: (User['id'] | User)[], me?: { id: User['id'] } | null | undefined, options?: { @@ -372,17 +406,8 @@ export class UserRepository extends Repository { } ): Promise[]> { return Promise.all(users.map(u => this.pack(u, me, options))); - } + }, - public isLocalUser(user: User): user is ILocalUser; - public isLocalUser(user: T): user is T & { host: null; }; - public isLocalUser(user: User | { host: User['host'] }): boolean { - return user.host == null; - } - - public isRemoteUser(user: User): user is IRemoteUser; - public isRemoteUser(user: T): user is T & { host: string; }; - public isRemoteUser(user: User | { host: User['host'] }): boolean { - return !this.isLocalUser(user); - } -} + isLocalUser, + isRemoteUser, +}); diff --git a/packages/backend/src/models/schema/muting.ts b/packages/backend/src/models/schema/muting.ts index d75a4fbfed..3ab99e17e7 100644 --- a/packages/backend/src/models/schema/muting.ts +++ b/packages/backend/src/models/schema/muting.ts @@ -12,6 +12,11 @@ export const packedMutingSchema = { optional: false, nullable: false, format: 'date-time', }, + expiresAt: { + type: 'string', + optional: false, nullable: true, + format: 'date-time', + }, muteeId: { type: 'string', optional: false, nullable: false, diff --git a/packages/backend/src/queue/index.ts b/packages/backend/src/queue/index.ts index 94055e9c5a..b679a552b2 100644 --- a/packages/backend/src/queue/index.ts +++ b/packages/backend/src/queue/index.ts @@ -8,10 +8,11 @@ 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 { 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 } from './queues.js'; +import { systemQueue, dbQueue, deliverQueue, inboxQueue, objectStorageQueue, endedPollNotificationQueue } from './queues.js'; import { ThinUser } from './types.js'; import { IActivity } from '@/remote/activitypub/type.js'; @@ -255,12 +256,14 @@ export default function() { deliverQueue.process(config.deliverJobConcurrency || 128, processDeliver); inboxQueue.process(config.inboxJobConcurrency || 16, processInbox); + endedPollNotificationQueue.process(endedPollNotification); processDb(dbQueue); processObjectStorage(objectStorageQueue); systemQueue.add('tickCharts', { }, { repeat: { cron: '55 * * * *' }, + removeOnComplete: true, }); systemQueue.add('resyncCharts', { @@ -273,6 +276,12 @@ export default function() { repeat: { cron: '0 0 * * *' }, }); + systemQueue.add('checkExpiredMutings', { + }, { + repeat: { cron: '*/5 * * * *' }, + removeOnComplete: true, + }); + processSystemQueue(systemQueue); } diff --git a/packages/backend/src/queue/processors/db/delete-account.ts b/packages/backend/src/queue/processors/db/delete-account.ts index dbc1f16a46..c1657b4be6 100644 --- a/packages/backend/src/queue/processors/db/delete-account.ts +++ b/packages/backend/src/queue/processors/db/delete-account.ts @@ -13,7 +13,7 @@ const logger = queueLogger.createSubLogger('delete-account'); export async function deleteAccount(job: Bull.Job): Promise { logger.info(`Deleting account of ${job.data.user.id} ...`); - const user = await Users.findOne(job.data.user.id); + const user = await Users.findOneBy({ id: job.data.user.id }); if (user == null) { return; } @@ -75,7 +75,7 @@ export async function deleteAccount(job: Bull.Job): Promise } { // Send email notification - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); if (profile.email && profile.emailVerified) { sendEmail(profile.email, 'Account deleted', `Your account has been deleted.`, diff --git a/packages/backend/src/queue/processors/db/delete-drive-files.ts b/packages/backend/src/queue/processors/db/delete-drive-files.ts index f6a8699855..b3832d9f04 100644 --- a/packages/backend/src/queue/processors/db/delete-drive-files.ts +++ b/packages/backend/src/queue/processors/db/delete-drive-files.ts @@ -11,7 +11,7 @@ const logger = queueLogger.createSubLogger('delete-drive-files'); export async function deleteDriveFiles(job: Bull.Job, done: any): Promise { logger.info(`Deleting drive files of ${job.data.user.id} ...`); - const user = await Users.findOne(job.data.user.id); + const user = await Users.findOneBy({ id: job.data.user.id }); if (user == null) { done(); return; @@ -44,7 +44,7 @@ export async function deleteDriveFiles(job: Bull.Job, done: any): deletedCount++; } - const total = await DriveFiles.count({ + const total = await DriveFiles.countBy({ userId: user.id, }); diff --git a/packages/backend/src/queue/processors/db/export-blocking.ts b/packages/backend/src/queue/processors/db/export-blocking.ts index 83f1ec8fd6..166c9e4cd3 100644 --- a/packages/backend/src/queue/processors/db/export-blocking.ts +++ b/packages/backend/src/queue/processors/db/export-blocking.ts @@ -15,7 +15,7 @@ const logger = queueLogger.createSubLogger('export-blocking'); export async function exportBlocking(job: Bull.Job, done: any): Promise { logger.info(`Exporting blocking of ${job.data.user.id} ...`); - const user = await Users.findOne(job.data.user.id); + const user = await Users.findOneBy({ id: job.data.user.id }); if (user == null) { done(); return; @@ -56,7 +56,7 @@ export async function exportBlocking(job: Bull.Job, done: any): P cursor = blockings[blockings.length - 1].id; for (const block of blockings) { - const u = await Users.findOne({ id: block.blockeeId }); + const u = await Users.findOneBy({ id: block.blockeeId }); if (u == null) { exportedCount++; continue; } @@ -75,7 +75,7 @@ export async function exportBlocking(job: Bull.Job, done: any): P exportedCount++; } - const total = await Blockings.count({ + const total = await Blockings.countBy({ blockerId: user.id, }); diff --git a/packages/backend/src/queue/processors/db/export-custom-emojis.ts b/packages/backend/src/queue/processors/db/export-custom-emojis.ts index a65b46cc00..c2467fb5f0 100644 --- a/packages/backend/src/queue/processors/db/export-custom-emojis.ts +++ b/packages/backend/src/queue/processors/db/export-custom-emojis.ts @@ -12,13 +12,14 @@ import { Users, Emojis } from '@/models/index.js'; import { } from '@/queue/types.js'; import { downloadUrl } from '@/misc/download-url.js'; import config from '@/config/index.js'; +import { IsNull } from 'typeorm'; const logger = queueLogger.createSubLogger('export-custom-emojis'); export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promise { logger.info(`Exporting custom emojis ...`); - const user = await Users.findOne(job.data.user.id); + const user = await Users.findOneBy({ id: job.data.user.id }); if (user == null) { done(); return; @@ -57,7 +58,7 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi const customEmojis = await Emojis.find({ where: { - host: null, + host: IsNull(), }, order: { id: 'ASC', diff --git a/packages/backend/src/queue/processors/db/export-following.ts b/packages/backend/src/queue/processors/db/export-following.ts index 162862180b..965500ac27 100644 --- a/packages/backend/src/queue/processors/db/export-following.ts +++ b/packages/backend/src/queue/processors/db/export-following.ts @@ -16,7 +16,7 @@ const logger = queueLogger.createSubLogger('export-following'); export async function exportFollowing(job: Bull.Job, done: () => void): Promise { logger.info(`Exporting following of ${job.data.user.id} ...`); - const user = await Users.findOne(job.data.user.id); + const user = await Users.findOneBy({ id: job.data.user.id }); if (user == null) { done(); return; @@ -36,7 +36,7 @@ export async function exportFollowing(job: Bull.Job, done: () => let cursor: Following['id'] | null = null; - const mutings = job.data.excludeMuting ? await Mutings.find({ + const mutings = job.data.excludeMuting ? await Mutings.findBy({ muterId: user.id, }) : []; @@ -60,7 +60,7 @@ export async function exportFollowing(job: Bull.Job, done: () => cursor = followings[followings.length - 1].id; for (const following of followings) { - const u = await Users.findOne({ id: following.followeeId }); + const u = await Users.findOneBy({ id: following.followeeId }); if (u == null) { continue; } diff --git a/packages/backend/src/queue/processors/db/export-mute.ts b/packages/backend/src/queue/processors/db/export-mute.ts index 8602e00bff..0ef81971f1 100644 --- a/packages/backend/src/queue/processors/db/export-mute.ts +++ b/packages/backend/src/queue/processors/db/export-mute.ts @@ -7,7 +7,7 @@ import { addFile } from '@/services/drive/add-file.js'; import { format as dateFormat } from 'date-fns'; import { getFullApAccount } from '@/misc/convert-host.js'; import { Users, Mutings } from '@/models/index.js'; -import { MoreThan } from 'typeorm'; +import { IsNull, MoreThan } from 'typeorm'; import { DbUserJobData } from '@/queue/types.js'; const logger = queueLogger.createSubLogger('export-mute'); @@ -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; @@ -40,6 +40,7 @@ export async function exportMute(job: Bull.Job, done: any): Promi const mutes = await Mutings.find({ where: { muterId: user.id, + expiresAt: IsNull(), ...(cursor ? { id: MoreThan(cursor) } : {}), }, take: 100, @@ -56,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; } @@ -75,7 +76,7 @@ export async function exportMute(job: Bull.Job, done: any): Promi exportedCount++; } - const total = await Mutings.count({ + const total = await Mutings.countBy({ muterId: user.id, }); diff --git a/packages/backend/src/queue/processors/db/export-notes.ts b/packages/backend/src/queue/processors/db/export-notes.ts index c79679366c..7e12a6fac2 100644 --- a/packages/backend/src/queue/processors/db/export-notes.ts +++ b/packages/backend/src/queue/processors/db/export-notes.ts @@ -16,7 +16,7 @@ const logger = queueLogger.createSubLogger('export-notes'); export async function exportNotes(job: Bull.Job, done: any): Promise { logger.info(`Exporting notes of ${job.data.user.id} ...`); - const user = await Users.findOne(job.data.user.id); + const user = await Users.findOneBy({ id: job.data.user.id }); if (user == null) { done(); return; @@ -74,7 +74,7 @@ export async function exportNotes(job: Bull.Job, done: any): Prom for (const note of notes) { let poll: Poll | undefined; if (note.hasPoll) { - poll = await Polls.findOneOrFail({ noteId: note.id }); + poll = await Polls.findOneByOrFail({ noteId: note.id }); } const content = JSON.stringify(serialize(note, poll)); const isFirst = exportedNotesCount === 0; @@ -82,7 +82,7 @@ export async function exportNotes(job: Bull.Job, done: any): Prom exportedNotesCount++; } - const total = await Notes.count({ + const total = await Notes.countBy({ userId: user.id, }); diff --git a/packages/backend/src/queue/processors/db/export-user-lists.ts b/packages/backend/src/queue/processors/db/export-user-lists.ts index 1c04c36789..45852a6038 100644 --- a/packages/backend/src/queue/processors/db/export-user-lists.ts +++ b/packages/backend/src/queue/processors/db/export-user-lists.ts @@ -15,13 +15,13 @@ const logger = queueLogger.createSubLogger('export-user-lists'); export async function exportUserLists(job: Bull.Job, done: any): Promise { logger.info(`Exporting user lists of ${job.data.user.id} ...`); - const user = await Users.findOne(job.data.user.id); + const user = await Users.findOneBy({ id: job.data.user.id }); if (user == null) { done(); return; } - const lists = await UserLists.find({ + const lists = await UserLists.findBy({ userId: user.id, }); @@ -38,8 +38,8 @@ export async function exportUserLists(job: Bull.Job, done: any): const stream = fs.createWriteStream(path, { flags: 'a' }); for (const list of lists) { - const joinings = await UserListJoinings.find({ userListId: list.id }); - const users = await Users.find({ + const joinings = await UserListJoinings.findBy({ userListId: list.id }); + const users = await Users.findBy({ id: In(joinings.map(j => j.userId)), }); diff --git a/packages/backend/src/queue/processors/db/import-blocking.ts b/packages/backend/src/queue/processors/db/import-blocking.ts index 857c2629e3..8bddf34bc2 100644 --- a/packages/backend/src/queue/processors/db/import-blocking.ts +++ b/packages/backend/src/queue/processors/db/import-blocking.ts @@ -8,19 +8,20 @@ import { isSelfHost, toPuny } from '@/misc/convert-host.js'; import { Users, DriveFiles, Blockings } from '@/models/index.js'; import { DbUserImportJobData } from '@/queue/types.js'; import block from '@/services/blocking/create.js'; +import { IsNull } from 'typeorm'; const logger = queueLogger.createSubLogger('import-blocking'); export async function importBlocking(job: Bull.Job, done: any): Promise { logger.info(`Importing blocking of ${job.data.user.id} ...`); - const user = await Users.findOne(job.data.user.id); + const user = await Users.findOneBy({ id: job.data.user.id }); if (user == null) { done(); return; } - const file = await DriveFiles.findOne({ + const file = await DriveFiles.findOneBy({ id: job.data.fileId, }); if (file == null) { @@ -39,10 +40,10 @@ export async function importBlocking(job: Bull.Job, done: a const acct = line.split(',')[0].trim(); const { username, host } = Acct.parse(acct); - let target = isSelfHost(host!) ? await Users.findOne({ - host: null, + let target = isSelfHost(host!) ? await Users.findOneBy({ + host: IsNull(), usernameLower: username.toLowerCase(), - }) : await Users.findOne({ + }) : await Users.findOneBy({ host: toPuny(host!), usernameLower: username.toLowerCase(), }); diff --git a/packages/backend/src/queue/processors/db/import-custom-emojis.ts b/packages/backend/src/queue/processors/db/import-custom-emojis.ts index f862276b47..28e0b867a4 100644 --- a/packages/backend/src/queue/processors/db/import-custom-emojis.ts +++ b/packages/backend/src/queue/processors/db/import-custom-emojis.ts @@ -2,7 +2,6 @@ import Bull from 'bull'; import * as tmp from 'tmp'; import * as fs from 'node:fs'; import unzipper from 'unzipper'; -import { getConnection } from 'typeorm'; import { queueLogger } from '../../logger.js'; import { downloadUrl } from '@/misc/download-url.js'; @@ -10,6 +9,7 @@ import { DriveFiles, Emojis } from '@/models/index.js'; import { DbUserImportJobData } from '@/queue/types.js'; import { addFile } from '@/services/drive/add-file.js'; import { genId } from '@/misc/gen-id.js'; +import { db } from '@/db/postgre.js'; const logger = queueLogger.createSubLogger('import-custom-emojis'); @@ -17,7 +17,7 @@ const logger = queueLogger.createSubLogger('import-custom-emojis'); export async function importCustomEmojis(job: Bull.Job, done: any): Promise { logger.info(`Importing custom emojis ...`); - const file = await DriveFiles.findOne({ + const file = await DriveFiles.findOneBy({ id: job.data.fileId, }); if (file == null) { @@ -72,10 +72,10 @@ export async function importCustomEmojis(job: Bull.Job, don originalUrl: driveFile.url, publicUrl: driveFile.webpublicUrl ?? driveFile.url, type: driveFile.webpublicType ?? driveFile.type, - }).then(x => Emojis.findOneOrFail(x.identifiers[0])); + }).then(x => Emojis.findOneByOrFail(x.identifiers[0])); } - await getConnection().queryResultCache!.remove(['meta_emojis']); + await db.queryResultCache!.remove(['meta_emojis']); cleanup(); diff --git a/packages/backend/src/queue/processors/db/import-following.ts b/packages/backend/src/queue/processors/db/import-following.ts index 235fc28394..8ce2c367d6 100644 --- a/packages/backend/src/queue/processors/db/import-following.ts +++ b/packages/backend/src/queue/processors/db/import-following.ts @@ -8,19 +8,20 @@ import { downloadTextFile } from '@/misc/download-text-file.js'; import { isSelfHost, toPuny } from '@/misc/convert-host.js'; import { Users, DriveFiles } from '@/models/index.js'; import { DbUserImportJobData } from '@/queue/types.js'; +import { IsNull } from 'typeorm'; const logger = queueLogger.createSubLogger('import-following'); export async function importFollowing(job: Bull.Job, done: any): Promise { logger.info(`Importing following of ${job.data.user.id} ...`); - const user = await Users.findOne(job.data.user.id); + const user = await Users.findOneBy({ id: job.data.user.id }); if (user == null) { done(); return; } - const file = await DriveFiles.findOne({ + const file = await DriveFiles.findOneBy({ id: job.data.fileId, }); if (file == null) { @@ -39,10 +40,10 @@ export async function importFollowing(job: Bull.Job, done: const acct = line.split(',')[0].trim(); const { username, host } = Acct.parse(acct); - let target = isSelfHost(host!) ? await Users.findOne({ - host: null, + let target = isSelfHost(host!) ? await Users.findOneBy({ + host: IsNull(), usernameLower: username.toLowerCase(), - }) : await Users.findOne({ + }) : await Users.findOneBy({ host: toPuny(host!), usernameLower: username.toLowerCase(), }); diff --git a/packages/backend/src/queue/processors/db/import-muting.ts b/packages/backend/src/queue/processors/db/import-muting.ts index 32f5f6bbee..8552b797be 100644 --- a/packages/backend/src/queue/processors/db/import-muting.ts +++ b/packages/backend/src/queue/processors/db/import-muting.ts @@ -9,19 +9,20 @@ import { Users, DriveFiles, Mutings } from '@/models/index.js'; import { DbUserImportJobData } from '@/queue/types.js'; import { User } from '@/models/entities/user.js'; import { genId } from '@/misc/gen-id.js'; +import { IsNull } from 'typeorm'; const logger = queueLogger.createSubLogger('import-muting'); export async function importMuting(job: Bull.Job, done: any): Promise { logger.info(`Importing muting of ${job.data.user.id} ...`); - const user = await Users.findOne(job.data.user.id); + const user = await Users.findOneBy({ id: job.data.user.id }); if (user == null) { done(); return; } - const file = await DriveFiles.findOne({ + const file = await DriveFiles.findOneBy({ id: job.data.fileId, }); if (file == null) { @@ -40,10 +41,10 @@ export async function importMuting(job: Bull.Job, done: any const acct = line.split(',')[0].trim(); const { username, host } = Acct.parse(acct); - let target = isSelfHost(host!) ? await Users.findOne({ - host: null, + let target = isSelfHost(host!) ? await Users.findOneBy({ + host: IsNull(), usernameLower: username.toLowerCase(), - }) : await Users.findOne({ + }) : await Users.findOneBy({ host: toPuny(host!), usernameLower: username.toLowerCase(), }); diff --git a/packages/backend/src/queue/processors/db/import-user-lists.ts b/packages/backend/src/queue/processors/db/import-user-lists.ts index ae263e19b0..9919b7c53c 100644 --- a/packages/backend/src/queue/processors/db/import-user-lists.ts +++ b/packages/backend/src/queue/processors/db/import-user-lists.ts @@ -9,19 +9,20 @@ import { isSelfHost, toPuny } from '@/misc/convert-host.js'; import { DriveFiles, Users, UserLists, UserListJoinings } from '@/models/index.js'; import { genId } from '@/misc/gen-id.js'; import { DbUserImportJobData } from '@/queue/types.js'; +import { IsNull } from 'typeorm'; const logger = queueLogger.createSubLogger('import-user-lists'); export async function importUserLists(job: Bull.Job, done: any): Promise { logger.info(`Importing user lists of ${job.data.user.id} ...`); - const user = await Users.findOne(job.data.user.id); + const user = await Users.findOneBy({ id: job.data.user.id }); if (user == null) { done(); return; } - const file = await DriveFiles.findOne({ + const file = await DriveFiles.findOneBy({ id: job.data.fileId, }); if (file == null) { @@ -40,7 +41,7 @@ export async function importUserLists(job: Bull.Job, done: const listName = line.split(',')[0].trim(); const { username, host } = Acct.parse(line.split(',')[1].trim()); - let list = await UserLists.findOne({ + let list = await UserLists.findOneBy({ userId: user.id, name: listName, }); @@ -51,13 +52,13 @@ export async function importUserLists(job: Bull.Job, done: createdAt: new Date(), userId: user.id, name: listName, - }).then(x => UserLists.findOneOrFail(x.identifiers[0])); + }).then(x => UserLists.findOneByOrFail(x.identifiers[0])); } - let target = isSelfHost(host!) ? await Users.findOne({ - host: null, + let target = isSelfHost(host!) ? await Users.findOneBy({ + host: IsNull(), usernameLower: username.toLowerCase(), - }) : await Users.findOne({ + }) : await Users.findOneBy({ host: toPuny(host!), usernameLower: username.toLowerCase(), }); @@ -66,7 +67,7 @@ export async function importUserLists(job: Bull.Job, done: target = await resolveUser(username, host); } - if (await UserListJoinings.findOne({ userListId: list!.id, userId: target.id }) != null) continue; + if (await UserListJoinings.findOneBy({ userListId: list!.id, userId: target.id }) != null) continue; pushUserToUserList(target, list!); } catch (e) { diff --git a/packages/backend/src/queue/processors/ended-poll-notification.ts b/packages/backend/src/queue/processors/ended-poll-notification.ts new file mode 100644 index 0000000000..6151c96ad6 --- /dev/null +++ b/packages/backend/src/queue/processors/ended-poll-notification.ts @@ -0,0 +1,33 @@ +import Bull from 'bull'; +import { In } from 'typeorm'; +import { Notes, Polls, PollVotes } from '@/models/index.js'; +import { queueLogger } from '../logger.js'; +import { EndedPollNotificationJobData } from '@/queue/types.js'; +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.findOneBy({ id: job.data.noteId }); + if (note == null || !note.hasPoll) { + done(); + return; + } + + const votes = await PollVotes.createQueryBuilder('vote') + .select('vote.userId') + .where('vote.noteId = :noteId', { noteId: note.id }) + .innerJoinAndSelect('vote.user', 'user') + .andWhere('user.host IS NULL') + .getMany(); + + const userIds = [...new Set([note.userId, ...votes.map(v => v.userId)])]; + + for (const userId of userIds) { + createNotification(userId, 'pollEnded', { + noteId: note.id, + }); + } + + done(); +} diff --git a/packages/backend/src/queue/processors/inbox.ts b/packages/backend/src/queue/processors/inbox.ts index 1b3f94b700..4fbfdb234f 100644 --- a/packages/backend/src/queue/processors/inbox.ts +++ b/packages/backend/src/queue/processors/inbox.ts @@ -15,6 +15,8 @@ import DbResolver from '@/remote/activitypub/db-resolver.js'; import { resolvePerson } from '@/remote/activitypub/models/person.js'; import { LdSignature } from '@/remote/activitypub/misc/ld-signature.js'; import { StatusError } from '@/misc/fetch.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { UserPublickey } from '@/models/entities/user-publickey.js'; const logger = new Logger('inbox'); @@ -42,11 +44,13 @@ export default async (job: Bull.Job): Promise => { return `Old keyId is no longer supported. ${keyIdLower}`; } - // TDOO: キャッシュ const dbResolver = new DbResolver(); // HTTP-Signature keyIdを元にDBから取得 - let authUser = await dbResolver.getAuthUserFromKeyId(signature.keyId); + let authUser: { + user: CacheableRemoteUser; + key: UserPublickey | null; + } | null = await dbResolver.getAuthUserFromKeyId(signature.keyId); // keyIdでわからなければ、activity.actorを元にDBから取得 || activity.actorを元にリモートから取得 if (authUser == null) { diff --git a/packages/backend/src/queue/processors/object-storage/clean-remote-files.ts b/packages/backend/src/queue/processors/object-storage/clean-remote-files.ts index 7d71a20adb..77da162f6e 100644 --- a/packages/backend/src/queue/processors/object-storage/clean-remote-files.ts +++ b/packages/backend/src/queue/processors/object-storage/clean-remote-files.ts @@ -37,7 +37,7 @@ export default async function cleanRemoteFiles(job: Bull.Job>, done: any): Promise { + logger.info(`Checking expired mutings...`); + + const expired = await Mutings.createQueryBuilder('muting') + .where('muting.expiresAt IS NOT NULL') + .andWhere('muting.expiresAt < :now', { now: new Date() }) + .innerJoinAndSelect('muting.mutee', 'mutee') + .getMany(); + + if (expired.length > 0) { + await Mutings.delete({ + id: In(expired.map(m => m.id)), + }); + + for (const m of expired) { + publishUserEvent(m.muterId, 'unmute', m.mutee!); + } + } + + logger.succ(`All expired mutings checked.`); + done(); +} diff --git a/packages/backend/src/queue/processors/system/index.ts b/packages/backend/src/queue/processors/system/index.ts index dca3249e82..f90f6efafd 100644 --- a/packages/backend/src/queue/processors/system/index.ts +++ b/packages/backend/src/queue/processors/system/index.ts @@ -2,11 +2,13 @@ import Bull from 'bull'; import { tickCharts } from './tick-charts.js'; import { resyncCharts } from './resync-charts.js'; import { cleanCharts } from './clean-charts.js'; +import { checkExpiredMutings } from './check-expired-mutings.js'; const jobs = { tickCharts, resyncCharts, cleanCharts, + checkExpiredMutings, } as Record> | Bull.ProcessPromiseFunction>>; export default function(dbQueue: Bull.Queue>) { diff --git a/packages/backend/src/queue/queues.ts b/packages/backend/src/queue/queues.ts index 6ac4ec69cf..d612dee450 100644 --- a/packages/backend/src/queue/queues.ts +++ b/packages/backend/src/queue/queues.ts @@ -1,9 +1,19 @@ import config from '@/config/index.js'; import { initialize as initializeQueue } from './initialize.js'; -import { DeliverJobData, InboxJobData, DbJobData, ObjectStorageJobData } from './types.js'; +import { DeliverJobData, InboxJobData, DbJobData, ObjectStorageJobData, EndedPollNotificationJobData } from './types.js'; export const systemQueue = initializeQueue>('system'); +export const endedPollNotificationQueue = initializeQueue('endedPollNotification'); export const deliverQueue = initializeQueue('deliver', config.deliverJobPerSec || 128); export const inboxQueue = initializeQueue('inbox', config.inboxJobPerSec || 16); export const dbQueue = initializeQueue('db'); export const objectStorageQueue = initializeQueue('objectStorage'); + +export const queues = [ + systemQueue, + endedPollNotificationQueue, + deliverQueue, + inboxQueue, + dbQueue, + objectStorageQueue, +]; diff --git a/packages/backend/src/queue/types.ts b/packages/backend/src/queue/types.ts index de5f5d1396..5191caea4c 100644 --- a/packages/backend/src/queue/types.ts +++ b/packages/backend/src/queue/types.ts @@ -1,4 +1,5 @@ import { DriveFile } from '@/models/entities/drive-file.js'; +import { Note } from '@/models/entities/note'; import { User } from '@/models/entities/user.js'; import { IActivity } from '@/remote/activitypub/type.js'; import httpSignature from 'http-signature'; @@ -41,6 +42,10 @@ export type ObjectStorageFileJobData = { key: string; }; +export type EndedPollNotificationJobData = { + noteId: Note['id']; +}; + export type ThinUser = { id: User['id']; }; diff --git a/packages/backend/src/remote/activitypub/audience.ts b/packages/backend/src/remote/activitypub/audience.ts index ba69b11e85..846ccf9c00 100644 --- a/packages/backend/src/remote/activitypub/audience.ts +++ b/packages/backend/src/remote/activitypub/audience.ts @@ -3,26 +3,26 @@ import Resolver from './resolver.js'; import { resolvePerson } from './models/person.js'; import { unique, concat } from '@/prelude/array.js'; import promiseLimit from 'promise-limit'; -import { User, IRemoteUser } from '@/models/entities/user.js'; +import { User, CacheableRemoteUser, CacheableUser } from '@/models/entities/user.js'; type Visibility = 'public' | 'home' | 'followers' | 'specified'; type AudienceInfo = { visibility: Visibility, - mentionedUsers: User[], - visibleUsers: User[], + mentionedUsers: CacheableUser[], + visibleUsers: CacheableUser[], }; -export async function parseAudience(actor: IRemoteUser, to?: ApObject, cc?: ApObject, resolver?: Resolver): Promise { +export async function parseAudience(actor: CacheableRemoteUser, to?: ApObject, cc?: ApObject, resolver?: Resolver): Promise { const toGroups = groupingAudience(getApIds(to), actor); const ccGroups = groupingAudience(getApIds(cc), actor); const others = unique(concat([toGroups.other, ccGroups.other])); - const limit = promiseLimit(2); + const limit = promiseLimit(2); const mentionedUsers = (await Promise.all( others.map(id => limit(() => resolvePerson(id, resolver).catch(() => null))) - )).filter((x): x is User => x != null); + )).filter((x): x is CacheableUser => x != null); if (toGroups.public.length > 0) { return { @@ -55,7 +55,7 @@ export async function parseAudience(actor: IRemoteUser, to?: ApObject, cc?: ApOb }; } -function groupingAudience(ids: string[], actor: IRemoteUser) { +function groupingAudience(ids: string[], actor: CacheableRemoteUser) { const groups = { public: [] as string[], followers: [] as string[], @@ -85,7 +85,7 @@ function isPublic(id: string) { ].includes(id); } -function isFollowers(id: string, actor: IRemoteUser) { +function isFollowers(id: string, actor: CacheableRemoteUser) { return ( id === (actor.followersUri || `${actor.uri}/followers`) ); diff --git a/packages/backend/src/remote/activitypub/db-resolver.ts b/packages/backend/src/remote/activitypub/db-resolver.ts index 9281e494d0..ef07966e42 100644 --- a/packages/backend/src/remote/activitypub/db-resolver.ts +++ b/packages/backend/src/remote/activitypub/db-resolver.ts @@ -1,12 +1,17 @@ +import escapeRegexp from 'escape-regexp'; import config from '@/config/index.js'; import { Note } from '@/models/entities/note.js'; -import { User, IRemoteUser } from '@/models/entities/user.js'; +import { User, IRemoteUser, CacheableRemoteUser, CacheableUser } from '@/models/entities/user.js'; import { UserPublickey } from '@/models/entities/user-publickey.js'; import { MessagingMessage } from '@/models/entities/messaging-message.js'; import { Notes, Users, UserPublickeys, MessagingMessages } from '@/models/index.js'; import { IObject, getApId } from './type.js'; import { resolvePerson } from './models/person.js'; -import escapeRegexp from 'escape-regexp'; +import { Cache } from '@/misc/cache.js'; +import { uriPersonCache, userByIdCache } from '@/services/user-cache.js'; + +const publicKeyCache = new Cache(Infinity); +const publicKeyByUserIdCache = new Cache(Infinity); export default class DbResolver { constructor() { @@ -19,15 +24,15 @@ export default class DbResolver { const parsed = this.parseUri(value); if (parsed.id) { - return (await Notes.findOne({ + return await Notes.findOneBy({ id: parsed.id, - })) || null; + }); } if (parsed.uri) { - return (await Notes.findOne({ + return await Notes.findOneBy({ uri: parsed.uri, - })) || null; + }); } return null; @@ -37,15 +42,15 @@ export default class DbResolver { const parsed = this.parseUri(value); if (parsed.id) { - return (await MessagingMessages.findOne({ + return await MessagingMessages.findOneBy({ id: parsed.id, - })) || null; + }); } if (parsed.uri) { - return (await MessagingMessages.findOne({ + return await MessagingMessages.findOneBy({ uri: parsed.uri, - })) || null; + }); } return null; @@ -54,19 +59,19 @@ export default class DbResolver { /** * AP Person => Misskey User in DB */ - public async getUserFromApId(value: string | IObject): Promise { + public async getUserFromApId(value: string | IObject): Promise { const parsed = this.parseUri(value); if (parsed.id) { - return (await Users.findOne({ + return await userByIdCache.fetchMaybe(parsed.id, () => Users.findOneBy({ id: parsed.id, - })) || null; + }).then(x => x ?? undefined)) ?? null; } if (parsed.uri) { - return (await Users.findOne({ + return await uriPersonCache.fetch(parsed.uri, () => Users.findOneBy({ uri: parsed.uri, - })) || null; + })); } return null; @@ -75,17 +80,24 @@ export default class DbResolver { /** * AP KeyId => Misskey User and Key */ - public async getAuthUserFromKeyId(keyId: string): Promise { - const key = await UserPublickeys.findOne({ - keyId, - }); + public async getAuthUserFromKeyId(keyId: string): Promise<{ + user: CacheableRemoteUser; + key: UserPublickey; + } | null> { + const key = await publicKeyCache.fetch(keyId, async () => { + const key = await UserPublickeys.findOneBy({ + keyId, + }); + + if (key == null) return null; + + return key; + }, key => key != null); if (key == null) return null; - const user = await Users.findOne(key.userId) as IRemoteUser; - return { - user, + user: await userByIdCache.fetch(key.userId, () => Users.findOneByOrFail({ id: key.userId })) as CacheableRemoteUser, key, }; } @@ -93,12 +105,15 @@ export default class DbResolver { /** * AP Actor id => Misskey User and Key */ - public async getAuthUserFromApId(uri: string): Promise { - const user = await resolvePerson(uri) as IRemoteUser; + public async getAuthUserFromApId(uri: string): Promise<{ + user: CacheableRemoteUser; + key: UserPublickey | null; + } | null> { + const user = await resolvePerson(uri) as CacheableRemoteUser; if (user == null) return null; - const key = await UserPublickeys.findOne(user.id); + const key = await publicKeyByUserIdCache.fetch(user.id, () => UserPublickeys.findOneBy({ userId: user.id }), v => v != null); return { user, @@ -125,11 +140,6 @@ export default class DbResolver { } } -export type AuthUser = { - user: IRemoteUser; - key?: UserPublickey; -}; - type UriParseResult = { /** id in DB (local object only) */ id?: string; diff --git a/packages/backend/src/remote/activitypub/deliver-manager.ts b/packages/backend/src/remote/activitypub/deliver-manager.ts index 9c4e3418ff..9f21dc4cc6 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 { @@ -82,15 +83,25 @@ export default class DeliverManager { for (const recipe of this.recipes) { if (isFollowers(recipe)) { // followers deliver + // TODO: SELECT DISTINCT ON ("followerSharedInbox") "followerSharedInbox" みたいな問い合わせにすればよりパフォーマンス向上できそう + // ただ、sharedInboxがnullなリモートユーザーも稀におり、その対応ができなさそう? const followers = await Followings.find({ - followeeId: this.actor.id, - }); + where: { + followeeId: this.actor.id, + followerHost: Not(IsNull()), + }, + select: { + followerSharedInbox: true, + followerInbox: true, + }, + }) as { + followerSharedInbox: string | null; + followerInbox: string; + }[]; for (const following of followers) { - if (Followings.isRemoteFollower(following)) { - const inbox = following.followerSharedInbox || following.followerInbox; - inboxes.add(inbox); - } + const inbox = following.followerSharedInbox || following.followerInbox; + inboxes.add(inbox); } } else if (isDirect(recipe)) { // direct deliver @@ -112,7 +123,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 +134,7 @@ export async function deliverToFollowers(actor: ILocalUser, activity: any) { * @param activity Activity * @param to Target user */ -export async function deliverToUser(actor: ILocalUser, activity: any, to: IRemoteUser) { +export async function deliverToUser(actor: { id: ILocalUser['id']; host: null; }, activity: any, to: IRemoteUser) { const manager = new DeliverManager(actor, activity); manager.addDirectRecipe(to); await manager.execute(); diff --git a/packages/backend/src/remote/activitypub/kernel/accept/follow.ts b/packages/backend/src/remote/activitypub/kernel/accept/follow.ts index 393516addf..4350ef1333 100644 --- a/packages/backend/src/remote/activitypub/kernel/accept/follow.ts +++ b/packages/backend/src/remote/activitypub/kernel/accept/follow.ts @@ -1,10 +1,10 @@ -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import accept from '@/services/following/requests/accept.js'; import { IFollow } from '../../type.js'; import DbResolver from '../../db-resolver.js'; import { relayAccepted } from '@/services/relay.js'; -export default async (actor: IRemoteUser, activity: IFollow): Promise => { +export default async (actor: CacheableRemoteUser, activity: IFollow): Promise => { // ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある const dbResolver = new DbResolver(); diff --git a/packages/backend/src/remote/activitypub/kernel/accept/index.ts b/packages/backend/src/remote/activitypub/kernel/accept/index.ts index 354bd4f6e1..78ef75ade3 100644 --- a/packages/backend/src/remote/activitypub/kernel/accept/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/accept/index.ts @@ -1,12 +1,12 @@ import Resolver from '../../resolver.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import acceptFollow from './follow.js'; import { IAccept, isFollow, getApType } from '../../type.js'; import { apLogger } from '../../logger.js'; const logger = apLogger; -export default async (actor: IRemoteUser, activity: IAccept): Promise => { +export default async (actor: CacheableRemoteUser, activity: IAccept): Promise => { const uri = activity.id || activity; logger.info(`Accept: ${uri}`); diff --git a/packages/backend/src/remote/activitypub/kernel/add/index.ts b/packages/backend/src/remote/activitypub/kernel/add/index.ts index 9a2fac1e74..c813414f93 100644 --- a/packages/backend/src/remote/activitypub/kernel/add/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/add/index.ts @@ -1,9 +1,9 @@ -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { IAdd } from '../../type.js'; import { resolveNote } from '../../models/note.js'; import { addPinned } from '@/services/i/pin.js'; -export default async (actor: IRemoteUser, activity: IAdd): Promise => { +export default async (actor: CacheableRemoteUser, activity: IAdd): Promise => { if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); } diff --git a/packages/backend/src/remote/activitypub/kernel/announce/index.ts b/packages/backend/src/remote/activitypub/kernel/announce/index.ts index 7e2e73bdd5..ae7e507c99 100644 --- a/packages/backend/src/remote/activitypub/kernel/announce/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/announce/index.ts @@ -1,12 +1,12 @@ import Resolver from '../../resolver.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import announceNote from './note.js'; import { IAnnounce, getApId } from '../../type.js'; import { apLogger } from '../../logger.js'; const logger = apLogger; -export default async (actor: IRemoteUser, activity: IAnnounce): Promise => { +export default async (actor: CacheableRemoteUser, activity: IAnnounce): Promise => { const uri = getApId(activity); logger.info(`Announce: ${uri}`); diff --git a/packages/backend/src/remote/activitypub/kernel/announce/note.ts b/packages/backend/src/remote/activitypub/kernel/announce/note.ts index f6068fac79..680749f4d8 100644 --- a/packages/backend/src/remote/activitypub/kernel/announce/note.ts +++ b/packages/backend/src/remote/activitypub/kernel/announce/note.ts @@ -1,6 +1,6 @@ import Resolver from '../../resolver.js'; import post from '@/services/note/create.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { IAnnounce, getApId } from '../../type.js'; import { fetchNote, resolveNote } from '../../models/note.js'; import { apLogger } from '../../logger.js'; @@ -15,10 +15,9 @@ const logger = apLogger; /** * アナウンスアクティビティを捌きます */ -export default async function(resolver: Resolver, actor: IRemoteUser, activity: IAnnounce, targetUri: string): Promise { +export default async function(resolver: Resolver, actor: CacheableRemoteUser, activity: IAnnounce, targetUri: string): Promise { const uri = getApId(activity); - // アナウンサーが凍結されていたらスキップ if (actor.isSuspended) { return; } diff --git a/packages/backend/src/remote/activitypub/kernel/block/index.ts b/packages/backend/src/remote/activitypub/kernel/block/index.ts index 9e4f1b316e..5e230ad7b7 100644 --- a/packages/backend/src/remote/activitypub/kernel/block/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/block/index.ts @@ -1,9 +1,10 @@ import { IBlock } from '../../type.js'; import block from '@/services/blocking/create.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import DbResolver from '../../db-resolver.js'; +import { Users } from '@/models/index.js'; -export default async (actor: IRemoteUser, activity: IBlock): Promise => { +export default async (actor: CacheableRemoteUser, activity: IBlock): Promise => { // ※ activity.objectにブロック対象があり、それは存在するローカルユーザーのはず const dbResolver = new DbResolver(); @@ -17,6 +18,6 @@ export default async (actor: IRemoteUser, activity: IBlock): Promise => return `skip: ブロックしようとしているユーザーはローカルユーザーではありません`; } - await block(actor, blockee); + await block(await Users.findOneByOrFail({ id: actor.id }), await Users.findOneByOrFail({ id: blockee.id })); return `ok`; }; diff --git a/packages/backend/src/remote/activitypub/kernel/create/index.ts b/packages/backend/src/remote/activitypub/kernel/create/index.ts index 1187b95ac6..c253f9f667 100644 --- a/packages/backend/src/remote/activitypub/kernel/create/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/create/index.ts @@ -1,5 +1,5 @@ import Resolver from '../../resolver.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import createNote from './note.js'; import { ICreate, getApId, isPost, getApType } from '../../type.js'; import { apLogger } from '../../logger.js'; @@ -7,7 +7,7 @@ import { toArray, concat, unique } from '@/prelude/array.js'; const logger = apLogger; -export default async (actor: IRemoteUser, activity: ICreate): Promise => { +export default async (actor: CacheableRemoteUser, activity: ICreate): Promise => { const uri = getApId(activity); logger.info(`Create: ${uri}`); diff --git a/packages/backend/src/remote/activitypub/kernel/create/note.ts b/packages/backend/src/remote/activitypub/kernel/create/note.ts index b5c47990aa..f8dabe06e2 100644 --- a/packages/backend/src/remote/activitypub/kernel/create/note.ts +++ b/packages/backend/src/remote/activitypub/kernel/create/note.ts @@ -1,5 +1,5 @@ import Resolver from '../../resolver.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { createNote, fetchNote } from '../../models/note.js'; import { getApId, IObject, ICreate } from '../../type.js'; import { getApLock } from '@/misc/app-lock.js'; @@ -9,7 +9,7 @@ import { StatusError } from '@/misc/fetch.js'; /** * 投稿作成アクティビティを捌きます */ -export default async function(resolver: Resolver, actor: IRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise { +export default async function(resolver: Resolver, actor: CacheableRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise { const uri = getApId(note); if (typeof note === 'object') { diff --git a/packages/backend/src/remote/activitypub/kernel/delete/actor.ts b/packages/backend/src/remote/activitypub/kernel/delete/actor.ts index 2f75841e52..1f94df033d 100644 --- a/packages/backend/src/remote/activitypub/kernel/delete/actor.ts +++ b/packages/backend/src/remote/activitypub/kernel/delete/actor.ts @@ -1,18 +1,19 @@ import { apLogger } from '../../logger.js'; import { createDeleteAccountJob } from '@/queue/index.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { Users } from '@/models/index.js'; const logger = apLogger; -export async function deleteActor(actor: IRemoteUser, uri: string): Promise { +export async function deleteActor(actor: CacheableRemoteUser, uri: string): Promise { logger.info(`Deleting the Actor: ${uri}`); if (actor.uri !== uri) { return `skip: delete actor ${actor.uri} !== ${uri}`; } - if (actor.isDeleted) { + const user = await Users.findOneByOrFail({ id: actor.id }); + if (user.isDeleted) { logger.info(`skip: already deleted`); } diff --git a/packages/backend/src/remote/activitypub/kernel/delete/index.ts b/packages/backend/src/remote/activitypub/kernel/delete/index.ts index b6d5e96d03..4c06a9de0b 100644 --- a/packages/backend/src/remote/activitypub/kernel/delete/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/delete/index.ts @@ -1,5 +1,5 @@ import deleteNote from './note.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { IDelete, getApId, isTombstone, IObject, validPost, validActor } from '../../type.js'; import { toSingle } from '@/prelude/array.js'; import { deleteActor } from './actor.js'; @@ -7,7 +7,7 @@ import { deleteActor } from './actor.js'; /** * 削除アクティビティを捌きます */ -export default async (actor: IRemoteUser, activity: IDelete): Promise => { +export default async (actor: CacheableRemoteUser, activity: IDelete): Promise => { if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); } diff --git a/packages/backend/src/remote/activitypub/kernel/delete/note.ts b/packages/backend/src/remote/activitypub/kernel/delete/note.ts index ad5e1a2edc..1f44c35562 100644 --- a/packages/backend/src/remote/activitypub/kernel/delete/note.ts +++ b/packages/backend/src/remote/activitypub/kernel/delete/note.ts @@ -1,4 +1,4 @@ -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import deleteNode from '@/services/note/delete.js'; import { apLogger } from '../../logger.js'; import DbResolver from '../../db-resolver.js'; @@ -7,7 +7,7 @@ import { deleteMessage } from '@/services/messages/delete.js'; const logger = apLogger; -export default async function(actor: IRemoteUser, uri: string): Promise { +export default async function(actor: CacheableRemoteUser, uri: string): Promise { logger.info(`Deleting the Note: ${uri}`); const unlock = await getApLock(uri); diff --git a/packages/backend/src/remote/activitypub/kernel/flag/index.ts b/packages/backend/src/remote/activitypub/kernel/flag/index.ts index e80e632786..aa2f1f5362 100644 --- a/packages/backend/src/remote/activitypub/kernel/flag/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/flag/index.ts @@ -1,17 +1,17 @@ -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import config from '@/config/index.js'; import { IFlag, getApIds } from '../../type.js'; import { AbuseUserReports, Users } from '@/models/index.js'; import { In } from 'typeorm'; import { genId } from '@/misc/gen-id.js'; -export default async (actor: IRemoteUser, activity: IFlag): Promise => { +export default async (actor: CacheableRemoteUser, activity: IFlag): Promise => { // objectは `(User|Note) | (User|Note)[]` だけど、全パターンDBスキーマと対応させられないので // 対象ユーザーは一番最初のユーザー として あとはコメントとして格納する const uris = getApIds(activity.object); const userIds = uris.filter(uri => uri.startsWith(config.url + '/users/')).map(uri => uri.split('/').pop()!); - const users = await Users.find({ + const users = await Users.findBy({ id: In(userIds), }); if (users.length < 1) return `skip`; diff --git a/packages/backend/src/remote/activitypub/kernel/follow.ts b/packages/backend/src/remote/activitypub/kernel/follow.ts index 49c1a7ee01..a9e92fa229 100644 --- a/packages/backend/src/remote/activitypub/kernel/follow.ts +++ b/packages/backend/src/remote/activitypub/kernel/follow.ts @@ -1,9 +1,9 @@ -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import follow from '@/services/following/create.js'; import { IFollow } from '../type.js'; import DbResolver from '../db-resolver.js'; -export default async (actor: IRemoteUser, activity: IFollow): Promise => { +export default async (actor: CacheableRemoteUser, activity: IFollow): Promise => { const dbResolver = new DbResolver(); const followee = await dbResolver.getUserFromApId(activity.object); diff --git a/packages/backend/src/remote/activitypub/kernel/index.ts b/packages/backend/src/remote/activitypub/kernel/index.ts index 6aea8e57cf..254a121605 100644 --- a/packages/backend/src/remote/activitypub/kernel/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/index.ts @@ -1,5 +1,5 @@ import { IObject, isCreate, isDelete, isUpdate, isRead, isFollow, isAccept, isReject, isAdd, isRemove, isAnnounce, isLike, isUndo, isBlock, isCollectionOrOrderedCollection, isCollection, isFlag } from '../type.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import create from './create/index.js'; import performDeleteActivity from './delete/index.js'; import performUpdateActivity from './update/index.js'; @@ -17,8 +17,9 @@ import flag from './flag/index.js'; import { apLogger } from '../logger.js'; import Resolver from '../resolver.js'; import { toArray } from '@/prelude/array.js'; +import { Users } from '@/models/index.js'; -export async function performActivity(actor: IRemoteUser, activity: IObject) { +export async function performActivity(actor: CacheableRemoteUser, activity: IObject) { if (isCollectionOrOrderedCollection(activity)) { const resolver = new Resolver(); for (const item of toArray(isCollection(activity) ? activity.items : activity.orderedItems)) { @@ -36,7 +37,7 @@ export async function performActivity(actor: IRemoteUser, activity: IObject) { } } -async function performOneActivity(actor: IRemoteUser, activity: IObject): Promise { +async function performOneActivity(actor: CacheableRemoteUser, activity: IObject): Promise { if (actor.isSuspended) return; if (isCreate(activity)) { diff --git a/packages/backend/src/remote/activitypub/kernel/like.ts b/packages/backend/src/remote/activitypub/kernel/like.ts index 715cc379b9..2b65ff7383 100644 --- a/packages/backend/src/remote/activitypub/kernel/like.ts +++ b/packages/backend/src/remote/activitypub/kernel/like.ts @@ -1,9 +1,9 @@ -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { ILike, getApId } from '../type.js'; import create from '@/services/note/reaction/create.js'; import { fetchNote, extractEmojis } from '../models/note.js'; -export default async (actor: IRemoteUser, activity: ILike) => { +export default async (actor: CacheableRemoteUser, activity: ILike) => { const targetUri = getApId(activity.object); const note = await fetchNote(targetUri); diff --git a/packages/backend/src/remote/activitypub/kernel/read.ts b/packages/backend/src/remote/activitypub/kernel/read.ts index 93cc36ec46..7f1519ac2e 100644 --- a/packages/backend/src/remote/activitypub/kernel/read.ts +++ b/packages/backend/src/remote/activitypub/kernel/read.ts @@ -1,10 +1,10 @@ -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { IRead, getApId } from '../type.js'; import { isSelfHost, extractDbHost } from '@/misc/convert-host.js'; import { MessagingMessages } from '@/models/index.js'; import { readUserMessagingMessage } from '../../../server/api/common/read-messaging-message.js'; -export const performReadActivity = async (actor: IRemoteUser, activity: IRead): Promise => { +export const performReadActivity = async (actor: CacheableRemoteUser, activity: IRead): Promise => { const id = await getApId(activity.object); if (!isSelfHost(extractDbHost(id))) { @@ -13,7 +13,7 @@ export const performReadActivity = async (actor: IRemoteUser, activity: IRead): const messageId = id.split('/').pop(); - const message = await MessagingMessages.findOne(messageId); + const message = await MessagingMessages.findOneBy({ id: messageId }); if (message == null) { return `skip: message not found`; } diff --git a/packages/backend/src/remote/activitypub/kernel/reject/follow.ts b/packages/backend/src/remote/activitypub/kernel/reject/follow.ts index 72751e83c0..824ac69d70 100644 --- a/packages/backend/src/remote/activitypub/kernel/reject/follow.ts +++ b/packages/backend/src/remote/activitypub/kernel/reject/follow.ts @@ -1,11 +1,11 @@ -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { remoteReject } from '@/services/following/reject.js'; import { IFollow } from '../../type.js'; import DbResolver from '../../db-resolver.js'; import { relayRejected } from '@/services/relay.js'; import { Users } from '@/models/index.js'; -export default async (actor: IRemoteUser, activity: IFollow): Promise => { +export default async (actor: CacheableRemoteUser, activity: IFollow): Promise => { // ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある const dbResolver = new DbResolver(); diff --git a/packages/backend/src/remote/activitypub/kernel/reject/index.ts b/packages/backend/src/remote/activitypub/kernel/reject/index.ts index ed86a4aa2f..00f08842f4 100644 --- a/packages/backend/src/remote/activitypub/kernel/reject/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/reject/index.ts @@ -1,12 +1,12 @@ import Resolver from '../../resolver.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import rejectFollow from './follow.js'; import { IReject, isFollow, getApType } from '../../type.js'; import { apLogger } from '../../logger.js'; const logger = apLogger; -export default async (actor: IRemoteUser, activity: IReject): Promise => { +export default async (actor: CacheableRemoteUser, activity: IReject): Promise => { const uri = activity.id || activity; logger.info(`Reject: ${uri}`); diff --git a/packages/backend/src/remote/activitypub/kernel/remove/index.ts b/packages/backend/src/remote/activitypub/kernel/remove/index.ts index 7d7b3386c0..11a994a83b 100644 --- a/packages/backend/src/remote/activitypub/kernel/remove/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/remove/index.ts @@ -1,9 +1,9 @@ -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { IRemove } from '../../type.js'; import { resolveNote } from '../../models/note.js'; import { removePinned } from '@/services/i/pin.js'; -export default async (actor: IRemoteUser, activity: IRemove): Promise => { +export default async (actor: CacheableRemoteUser, activity: IRemove): Promise => { if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); } diff --git a/packages/backend/src/remote/activitypub/kernel/undo/accept.ts b/packages/backend/src/remote/activitypub/kernel/undo/accept.ts index 2383eea5bd..8f6eab6858 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/accept.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/accept.ts @@ -1,11 +1,11 @@ import unfollow from '@/services/following/delete.js'; import cancelRequest from '@/services/following/requests/cancel.js'; import {IAccept} from '../../type.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { Followings } from '@/models/index.js'; import DbResolver from '../../db-resolver.js'; -export default async (actor: IRemoteUser, activity: IAccept): Promise => { +export default async (actor: CacheableRemoteUser, activity: IAccept): Promise => { const dbResolver = new DbResolver(); const follower = await dbResolver.getUserFromApId(activity.object); @@ -13,7 +13,7 @@ export default async (actor: IRemoteUser, activity: IAccept): Promise => return `skip: follower not found`; } - const following = await Followings.findOne({ + const following = await Followings.findOneBy({ followerId: follower.id, followeeId: actor.id, }); diff --git a/packages/backend/src/remote/activitypub/kernel/undo/announce.ts b/packages/backend/src/remote/activitypub/kernel/undo/announce.ts index 822c1e4948..c2ac31bf8d 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/announce.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/announce.ts @@ -1,12 +1,12 @@ import { Notes } from '@/models/index.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { IAnnounce, getApId } from '../../type.js'; import deleteNote from '@/services/note/delete.js'; -export const undoAnnounce = async (actor: IRemoteUser, activity: IAnnounce): Promise => { +export const undoAnnounce = async (actor: CacheableRemoteUser, activity: IAnnounce): Promise => { const uri = getApId(activity); - const note = await Notes.findOne({ + const note = await Notes.findOneBy({ uri, }); diff --git a/packages/backend/src/remote/activitypub/kernel/undo/block.ts b/packages/backend/src/remote/activitypub/kernel/undo/block.ts index 844b067e2b..4ac6698578 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/block.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/block.ts @@ -1,9 +1,10 @@ import { IBlock } from '../../type.js'; import unblock from '@/services/blocking/delete.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import DbResolver from '../../db-resolver.js'; +import { Users } from '@/models/index.js'; -export default async (actor: IRemoteUser, activity: IBlock): Promise => { +export default async (actor: CacheableRemoteUser, activity: IBlock): Promise => { const dbResolver = new DbResolver(); const blockee = await dbResolver.getUserFromApId(activity.object); @@ -15,6 +16,6 @@ export default async (actor: IRemoteUser, activity: IBlock): Promise => return `skip: ブロック解除しようとしているユーザーはローカルユーザーではありません`; } - await unblock(actor, blockee); + await unblock(await Users.findOneByOrFail({ id: actor.id }), blockee); return `ok`; }; diff --git a/packages/backend/src/remote/activitypub/kernel/undo/follow.ts b/packages/backend/src/remote/activitypub/kernel/undo/follow.ts index 6715adcf76..6a43c1444a 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/follow.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/follow.ts @@ -1,11 +1,11 @@ import unfollow from '@/services/following/delete.js'; import cancelRequest from '@/services/following/requests/cancel.js'; import { IFollow } from '../../type.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { FollowRequests, Followings } from '@/models/index.js'; import DbResolver from '../../db-resolver.js'; -export default async (actor: IRemoteUser, activity: IFollow): Promise => { +export default async (actor: CacheableRemoteUser, activity: IFollow): Promise => { const dbResolver = new DbResolver(); const followee = await dbResolver.getUserFromApId(activity.object); @@ -17,12 +17,12 @@ export default async (actor: IRemoteUser, activity: IFollow): Promise => return `skip: フォロー解除しようとしているユーザーはローカルユーザーではありません`; } - const req = await FollowRequests.findOne({ + const req = await FollowRequests.findOneBy({ followerId: actor.id, followeeId: followee.id, }); - const following = await Followings.findOne({ + const following = await Followings.findOneBy({ followerId: actor.id, followeeId: followee.id, }); diff --git a/packages/backend/src/remote/activitypub/kernel/undo/index.ts b/packages/backend/src/remote/activitypub/kernel/undo/index.ts index 05937c6855..27d433eb33 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/index.ts @@ -1,5 +1,5 @@ -import { IRemoteUser } from '@/models/entities/user.js'; -import {IUndo, isFollow, isBlock, isLike, isAnnounce, getApType, isAccept} from '../../type.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { IUndo, isFollow, isBlock, isLike, isAnnounce, getApType, isAccept } from '../../type.js'; import unfollow from './follow.js'; import unblock from './block.js'; import undoLike from './like.js'; @@ -10,7 +10,7 @@ import { apLogger } from '../../logger.js'; const logger = apLogger; -export default async (actor: IRemoteUser, activity: IUndo): Promise => { +export default async (actor: CacheableRemoteUser, activity: IUndo): Promise => { if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); } diff --git a/packages/backend/src/remote/activitypub/kernel/undo/like.ts b/packages/backend/src/remote/activitypub/kernel/undo/like.ts index 08ac630351..01aeba1fb7 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/like.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/like.ts @@ -1,4 +1,4 @@ -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { ILike, getApId } from '../../type.js'; import deleteReaction from '@/services/note/reaction/delete.js'; import { fetchNote } from '../../models/note.js'; @@ -6,7 +6,7 @@ import { fetchNote } from '../../models/note.js'; /** * Process Undo.Like activity */ -export default async (actor: IRemoteUser, activity: ILike) => { +export default async (actor: CacheableRemoteUser, activity: ILike) => { const targetUri = getApId(activity.object); const note = await fetchNote(targetUri); diff --git a/packages/backend/src/remote/activitypub/kernel/update/index.ts b/packages/backend/src/remote/activitypub/kernel/update/index.ts index 7888c698e3..9e8a81bb39 100644 --- a/packages/backend/src/remote/activitypub/kernel/update/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/update/index.ts @@ -1,4 +1,4 @@ -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { getApType, IUpdate, isActor } from '../../type.js'; import { apLogger } from '../../logger.js'; import { updateQuestion } from '../../models/question.js'; @@ -8,7 +8,7 @@ import { updatePerson } from '../../models/person.js'; /** * Updateアクティビティを捌きます */ -export default async (actor: IRemoteUser, activity: IUpdate): Promise => { +export default async (actor: CacheableRemoteUser, activity: IUpdate): Promise => { if ('actor' in activity && actor.uri !== activity.actor) { return `skip: invalid actor`; } diff --git a/packages/backend/src/remote/activitypub/misc/ld-signature.ts b/packages/backend/src/remote/activitypub/misc/ld-signature.ts index 34294c935d..5132c6ef96 100644 --- a/packages/backend/src/remote/activitypub/misc/ld-signature.ts +++ b/packages/backend/src/remote/activitypub/misc/ld-signature.ts @@ -1,5 +1,5 @@ import * as crypto from 'node:crypto'; -import * as jsonld from 'jsonld'; +import jsonld from 'jsonld'; import { CONTEXTS } from './contexts.js'; import fetch from 'node-fetch'; import { httpAgent, httpsAgent } from '@/misc/fetch.js'; diff --git a/packages/backend/src/remote/activitypub/models/image.ts b/packages/backend/src/remote/activitypub/models/image.ts index b5e9181d30..102b7b1346 100644 --- a/packages/backend/src/remote/activitypub/models/image.ts +++ b/packages/backend/src/remote/activitypub/models/image.ts @@ -1,10 +1,10 @@ import { uploadFromUrl } from '@/services/drive/upload-from-url.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser, IRemoteUser } from '@/models/entities/user.js'; import Resolver from '../resolver.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { apLogger } from '../logger.js'; import { DriveFile } from '@/models/entities/drive-file.js'; -import { DriveFiles } from '@/models/index.js'; +import { DriveFiles, Users } from '@/models/index.js'; import { truncate } from '@/misc/truncate.js'; import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js'; @@ -13,7 +13,7 @@ const logger = apLogger; /** * Imageを作成します。 */ -export async function createImage(actor: IRemoteUser, value: any): Promise { +export async function createImage(actor: CacheableRemoteUser, value: any): Promise { // 投稿者が凍結されていたらスキップ if (actor.isSuspended) { throw new Error('actor has been suspended'); @@ -47,7 +47,7 @@ export async function createImage(actor: IRemoteUser, value: any): Promise { +export async function resolveImage(actor: CacheableRemoteUser, value: any): Promise { // TODO // リモートサーバーからフェッチしてきて登録 diff --git a/packages/backend/src/remote/activitypub/models/mention.ts b/packages/backend/src/remote/activitypub/models/mention.ts index c5b0ea53ce..a160092969 100644 --- a/packages/backend/src/remote/activitypub/models/mention.ts +++ b/packages/backend/src/remote/activitypub/models/mention.ts @@ -3,17 +3,17 @@ import { IObject, isMention, IApMention } from '../type.js'; import { resolvePerson } from './person.js'; import promiseLimit from 'promise-limit'; import Resolver from '../resolver.js'; -import { User } from '@/models/entities/user.js'; +import { CacheableUser, User } from '@/models/entities/user.js'; export async function extractApMentions(tags: IObject | IObject[] | null | undefined) { const hrefs = unique(extractApMentionObjects(tags).map(x => x.href as string)); const resolver = new Resolver(); - const limit = promiseLimit(2); + const limit = promiseLimit(2); const mentionedUsers = (await Promise.all( hrefs.map(x => limit(() => resolvePerson(x, resolver).catch(() => null))) - )).filter((x): x is User => x != null); + )).filter((x): x is CacheableUser => x != null); return mentionedUsers; } diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts index dca64d0a60..097a716614 100644 --- a/packages/backend/src/remote/activitypub/models/note.ts +++ b/packages/backend/src/remote/activitypub/models/note.ts @@ -5,7 +5,7 @@ import Resolver from '../resolver.js'; import post from '@/services/note/create.js'; import { resolvePerson, updatePerson } from './person.js'; import { resolveImage } from './image.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser, IRemoteUser } from '@/models/entities/user.js'; import { htmlToMfm } from '../misc/html-to-mfm.js'; import { extractApHashtags } from './tag.js'; import { unique, toArray, toSingle } from '@/prelude/array.js'; @@ -15,7 +15,7 @@ import { apLogger } from '../logger.js'; import { DriveFile } from '@/models/entities/drive-file.js'; import { deliverQuestionUpdate } from '@/services/note/polls/update.js'; import { extractDbHost, toPuny } from '@/misc/convert-host.js'; -import { Emojis, Polls, MessagingMessages } from '@/models/index.js'; +import { Emojis, Polls, MessagingMessages, Users } from '@/models/index.js'; import { Note } from '@/models/entities/note.js'; import { IObject, getOneApId, getApId, getOneApHrefNullable, validPost, IPost, isEmoji, getApType } from '../type.js'; import { Emoji } from '@/models/entities/emoji.js'; @@ -90,7 +90,7 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s logger.info(`Creating the Note: ${note.id}`); // 投稿者をフェッチ - const actor = await resolvePerson(getOneApId(note.attributedTo), resolver) as IRemoteUser; + const actor = await resolvePerson(getOneApId(note.attributedTo), resolver) as CacheableRemoteUser; // 投稿者が凍結されていたらスキップ if (actor.isSuspended) { @@ -141,7 +141,7 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s const uri = getApId(note.inReplyTo); if (uri.startsWith(config.url + '/')) { const id = uri.split('/').pop(); - const talk = await MessagingMessages.findOne(id); + const talk = await MessagingMessages.findOneBy({ id }); if (talk) { isTalk = true; return null; @@ -197,11 +197,11 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s const cw = note.summary === '' ? null : note.summary; // テキストのパース - const text = note._misskey_content || (note.content ? htmlToMfm(note.content, note.tag) : null); + const text = typeof note._misskey_content !== 'undefined' ? note._misskey_content : (note.content ? htmlToMfm(note.content, note.tag) : null); // vote if (reply && reply.hasPoll) { - const poll = await Polls.findOneOrFail(reply.id); + const poll = await Polls.findOneByOrFail({ noteId: reply.id }); const tryCreateVote = async (name: string, index: number): Promise => { if (poll.expiresAt && Date.now() > new Date(poll.expiresAt).getTime()) { @@ -230,11 +230,6 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s const poll = await extractPollFromQuestion(note, resolver).catch(() => undefined); - // ユーザーの情報が古かったらついでに更新しておく - if (actor.lastFetchedAt == null || Date.now() - actor.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { - if (actor.uri) updatePerson(actor.uri); - } - if (isTalk) { for (const recipient of visibleUsers) { await createMessage(actor, recipient, undefined, text || undefined, (files && files.length > 0) ? files[0] : null, object.id); @@ -311,7 +306,7 @@ export async function extractEmojis(tags: IObject | IObject[], host: string): Pr const name = tag.name!.replace(/^:/, '').replace(/:$/, ''); tag.icon = toSingle(tag.icon); - const exists = await Emojis.findOne({ + const exists = await Emojis.findOneBy({ host, name, }); @@ -332,7 +327,7 @@ export async function extractEmojis(tags: IObject | IObject[], host: string): Pr updatedAt: new Date(), }); - return await Emojis.findOne({ + return await Emojis.findOneBy({ host, name, }) as Emoji; @@ -352,6 +347,6 @@ export async function extractEmojis(tags: IObject | IObject[], host: string): Pr publicUrl: tag.icon!.url, updatedAt: new Date(), aliases: [], - } as Partial).then(x => Emojis.findOneOrFail(x.identifiers[0])); + } as Partial).then(x => Emojis.findOneByOrFail(x.identifiers[0])); })); } diff --git a/packages/backend/src/remote/activitypub/models/person.ts b/packages/backend/src/remote/activitypub/models/person.ts index 659d3ac9a2..88661865da 100644 --- a/packages/backend/src/remote/activitypub/models/person.ts +++ b/packages/backend/src/remote/activitypub/models/person.ts @@ -15,7 +15,7 @@ import { apLogger } from '../logger.js'; import { Note } from '@/models/entities/note.js'; import { updateUsertags } from '@/services/update-hashtag.js'; import { Users, Instances, DriveFiles, Followings, UserProfiles, UserPublickeys } from '@/models/index.js'; -import { User, IRemoteUser } from '@/models/entities/user.js'; +import { User, IRemoteUser, CacheableUser } from '@/models/entities/user.js'; import { Emoji } from '@/models/entities/emoji.js'; import { UserNotePining } from '@/models/entities/user-note-pining.js'; import { genId } from '@/misc/gen-id.js'; @@ -24,12 +24,14 @@ import { UserPublickey } from '@/models/entities/user-publickey.js'; import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; import { toPuny } from '@/misc/convert-host.js'; import { UserProfile } from '@/models/entities/user-profile.js'; -import { getConnection } from 'typeorm'; import { toArray } from '@/prelude/array.js'; import { fetchInstanceMetadata } from '@/services/fetch-instance-metadata.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { truncate } from '@/misc/truncate.js'; import { StatusError } from '@/misc/fetch.js'; +import { uriPersonCache } from '@/services/user-cache.js'; +import { publishInternalEvent } from '@/services/stream.js'; +import { db } from '@/db/postgre.js'; const logger = apLogger; @@ -91,19 +93,25 @@ function validateActor(x: IObject, uri: string): IActor { * * Misskeyに対象のPersonが登録されていればそれを返します。 */ -export async function fetchPerson(uri: string, resolver?: Resolver): Promise { +export async function fetchPerson(uri: string, resolver?: Resolver): Promise { if (typeof uri !== 'string') throw new Error('uri is not string'); + const cached = uriPersonCache.get(uri); + if (cached) return cached; + // URIがこのサーバーを指しているならデータベースからフェッチ if (uri.startsWith(config.url + '/')) { const id = uri.split('/').pop(); - return await Users.findOne(id).then(x => x || null); + const u = await Users.findOneBy({ id }); + if (u) uriPersonCache.set(uri, u); + return u; } //#region このサーバーに既に登録されていたらそれを返す - const exist = await Users.findOne({ uri }); + const exist = await Users.findOneBy({ uri }); if (exist) { + uriPersonCache.set(uri, exist); return exist; } //#endregion @@ -143,7 +151,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise { + await db.transaction(async transactionalEntityManager => { user = await transactionalEntityManager.save(new User({ id: genId(), avatarId: null, @@ -189,7 +197,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise /users/:id のように入力がaliasなときにエラーになることがあるのを対応 - const u = await Users.findOne({ + const u = await Users.findOneBy({ uri: person.id, }); @@ -272,7 +280,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint } //#region このサーバーに既に登録されているか - const exist = await Users.findOne({ uri }) as IRemoteUser; + const exist = await Users.findOneBy({ uri }) as IRemoteUser; if (exist == null) { return; @@ -352,6 +360,8 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint location: person['vcard:Address'] || null, }); + publishInternalEvent('remoteUserUpdated', { id: exist.id }); + // ハッシュタグ更新 updateUsertags(exist, tags); @@ -371,7 +381,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint * Misskeyに対象のPersonが登録されていればそれを返し、そうでなければ * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 */ -export async function resolvePerson(uri: string, resolver?: Resolver): Promise { +export async function resolvePerson(uri: string, resolver?: Resolver): Promise { if (typeof uri !== 'string') throw new Error('uri is not string'); //#region このサーバーに既に登録されていたらそれを返す @@ -441,7 +451,7 @@ export function analyzeAttachments(attachments: IObject | IObject[] | undefined) } export async function updateFeatured(userId: User['id']) { - const user = await Users.findOneOrFail(userId); + const user = await Users.findOneByOrFail({ id: userId }); if (!Users.isRemoteUser(user)) return; if (!user.featured) return; @@ -464,7 +474,7 @@ export async function updateFeatured(userId: User['id']) { .slice(0, 5) .map(item => limit(() => resolveNote(item, resolver)))); - await getConnection().transaction(async transactionalEntityManager => { + await db.transaction(async transactionalEntityManager => { await transactionalEntityManager.delete(UserNotePining, { userId: user.id }); // とりあえずidを別の時間で生成して順番を維持 diff --git a/packages/backend/src/remote/activitypub/models/question.ts b/packages/backend/src/remote/activitypub/models/question.ts index 0a77465e31..9e75864c63 100644 --- a/packages/backend/src/remote/activitypub/models/question.ts +++ b/packages/backend/src/remote/activitypub/models/question.ts @@ -47,10 +47,10 @@ export async function updateQuestion(value: any) { if (uri.startsWith(config.url + '/')) throw new Error('uri points local'); //#region このサーバーに既に登録されているか - const note = await Notes.findOne({ uri }); + const note = await Notes.findOneBy({ uri }); if (note == null) throw new Error('Question is not registed'); - const poll = await Polls.findOne({ noteId: note.id }); + const poll = await Polls.findOneBy({ noteId: note.id }); if (poll == null) throw new Error('Question is not registed'); //#endregion diff --git a/packages/backend/src/remote/activitypub/perform.ts b/packages/backend/src/remote/activitypub/perform.ts index 3e18815586..a3c10ba945 100644 --- a/packages/backend/src/remote/activitypub/perform.ts +++ b/packages/backend/src/remote/activitypub/perform.ts @@ -1,7 +1,17 @@ import { IObject } from './type.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { performActivity } from './kernel/index.js'; +import { updatePerson } from './models/person.js'; -export default async (actor: IRemoteUser, activity: IObject): Promise => { +export default async (actor: CacheableRemoteUser, activity: IObject): Promise => { await performActivity(actor, activity); + + // ついでにリモートユーザーの情報が古かったら更新しておく + if (actor.uri) { + if (actor.lastFetchedAt == null || Date.now() - actor.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { + setImmediate(() => { + updatePerson(actor.uri!); + }); + } + } }; diff --git a/packages/backend/src/remote/activitypub/renderer/follow-user.ts b/packages/backend/src/remote/activitypub/renderer/follow-user.ts index ad1d63b933..9a8a16d749 100644 --- a/packages/backend/src/remote/activitypub/renderer/follow-user.ts +++ b/packages/backend/src/remote/activitypub/renderer/follow-user.ts @@ -7,6 +7,6 @@ import { User } from '@/models/entities/user.js'; * @param id Follower|Followee ID */ export default async function renderFollowUser(id: User['id']): Promise { - const user = await Users.findOneOrFail(id); + const user = await Users.findOneByOrFail({ id: id }); return Users.isLocalUser(user) ? `${config.url}/users/${user.id}` : user.uri; } diff --git a/packages/backend/src/remote/activitypub/renderer/like.ts b/packages/backend/src/remote/activitypub/renderer/like.ts index 1bf36d4708..da1bfe6e8e 100644 --- a/packages/backend/src/remote/activitypub/renderer/like.ts +++ b/packages/backend/src/remote/activitypub/renderer/like.ts @@ -2,6 +2,7 @@ import config from '@/config/index.js'; import { NoteReaction } from '@/models/entities/note-reaction.js'; import { Note } from '@/models/entities/note.js'; import { Emojis } from '@/models/index.js'; +import { IsNull } from 'typeorm'; import renderEmoji from './emoji.js'; export const renderLike = async (noteReaction: NoteReaction, note: Note) => { @@ -18,9 +19,9 @@ export const renderLike = async (noteReaction: NoteReaction, note: Note) => { if (reaction.startsWith(':')) { const name = reaction.replace(/:/g, ''); - const emoji = await Emojis.findOne({ + const emoji = await Emojis.findOneBy({ name, - host: null, + host: IsNull(), }); if (emoji) object.tag = [ renderEmoji(emoji) ]; diff --git a/packages/backend/src/remote/activitypub/renderer/note.ts b/packages/backend/src/remote/activitypub/renderer/note.ts index c3d9e120d6..679c8bbfe4 100644 --- a/packages/backend/src/remote/activitypub/renderer/note.ts +++ b/packages/backend/src/remote/activitypub/renderer/note.ts @@ -7,25 +7,25 @@ import toHtml from '../misc/get-note-html.js'; import { Note, IMentionedRemoteUsers } from '@/models/entities/note.js'; import { DriveFile } from '@/models/entities/drive-file.js'; import { DriveFiles, Notes, Users, Emojis, Polls } from '@/models/index.js'; -import { In } from 'typeorm'; +import { In, IsNull } from 'typeorm'; import { Emoji } from '@/models/entities/emoji.js'; import { Poll } from '@/models/entities/poll.js'; export default async function renderNote(note: Note, dive = true, isTalk = false): Promise> { const getPromisedFiles = async (ids: string[]) => { if (!ids || ids.length === 0) return []; - const items = await DriveFiles.find({ id: In(ids) }); + const items = await DriveFiles.findBy({ id: In(ids) }); return ids.map(id => items.find(item => item.id === id)).filter(item => item != null) as DriveFile[]; }; let inReplyTo; - let inReplyToNote: Note | undefined; + let inReplyToNote: Note | null; if (note.replyId) { - inReplyToNote = await Notes.findOne(note.replyId); + inReplyToNote = await Notes.findOneBy({ id: note.replyId }); if (inReplyToNote != null) { - const inReplyToUser = await Users.findOne(inReplyToNote.userId); + const inReplyToUser = await Users.findOneBy({ id: inReplyToNote.userId }); if (inReplyToUser != null) { if (inReplyToNote.uri) { @@ -46,16 +46,14 @@ export default async function renderNote(note: Note, dive = true, isTalk = false let quote; if (note.renoteId) { - const renote = await Notes.findOne(note.renoteId); + const renote = await Notes.findOneBy({ id: note.renoteId }); if (renote) { quote = renote.uri ? renote.uri : `${config.url}/notes/${renote.id}`; } } - const user = await Users.findOneOrFail(note.userId); - - const attributedTo = `${config.url}/users/${user.id}`; + const attributedTo = `${config.url}/users/${note.userId}`; const mentions = (JSON.parse(note.mentionedRemoteUsers) as IMentionedRemoteUsers).map(x => x.uri); @@ -75,7 +73,7 @@ export default async function renderNote(note: Note, dive = true, isTalk = false to = mentions; } - const mentionedUsers = note.mentions.length > 0 ? await Users.find({ + const mentionedUsers = note.mentions.length > 0 ? await Users.findBy({ id: In(note.mentions), }) : []; @@ -85,10 +83,10 @@ export default async function renderNote(note: Note, dive = true, isTalk = false const files = await getPromisedFiles(note.fileIds); const text = note.text; - let poll: Poll | undefined; + let poll: Poll | null; if (note.hasPoll) { - poll = await Polls.findOne({ noteId: note.id }); + poll = await Polls.findOneBy({ noteId: note.id }); } let apText = text; @@ -158,9 +156,9 @@ export async function getEmojis(names: string[]): Promise { if (names == null || names.length === 0) return []; const emojis = await Promise.all( - names.map(name => Emojis.findOne({ + names.map(name => Emojis.findOneBy({ name, - host: null, + host: IsNull(), })) ); diff --git a/packages/backend/src/remote/activitypub/renderer/person.ts b/packages/backend/src/remote/activitypub/renderer/person.ts index 3d86e37cca..cd2fd74d47 100644 --- a/packages/backend/src/remote/activitypub/renderer/person.ts +++ b/packages/backend/src/remote/activitypub/renderer/person.ts @@ -17,9 +17,9 @@ export async function renderPerson(user: ILocalUser) { const isSystem = !!user.username.match(/\./); const [avatar, banner, profile] = await Promise.all([ - user.avatarId ? DriveFiles.findOne(user.avatarId) : Promise.resolve(undefined), - user.bannerId ? DriveFiles.findOne(user.bannerId) : Promise.resolve(undefined), - UserProfiles.findOneOrFail(user.id), + user.avatarId ? DriveFiles.findOneBy({ id: user.avatarId }) : Promise.resolve(undefined), + user.bannerId ? DriveFiles.findOneBy({ id: user.bannerId }) : Promise.resolve(undefined), + UserProfiles.findOneByOrFail({ userId: user.id }), ]); const attachment: { diff --git a/packages/backend/src/remote/activitypub/renderer/undo.ts b/packages/backend/src/remote/activitypub/renderer/undo.ts index d28778e22e..46631df9ea 100644 --- a/packages/backend/src/remote/activitypub/renderer/undo.ts +++ b/packages/backend/src/remote/activitypub/renderer/undo.ts @@ -3,9 +3,11 @@ import { ILocalUser, User } from '@/models/entities/user.js'; export default (object: any, user: { id: User['id'] }) => { if (object == null) return null; + const id = typeof object.id === 'string' && object.id.startsWith(config.url) ? `${object.id}/undo` : undefined; return { type: 'Undo', + ...(id ? { id } : {}), actor: `${config.url}/users/${user.id}`, object, published: new Date().toISOString(), diff --git a/packages/backend/src/remote/resolve-user.ts b/packages/backend/src/remote/resolve-user.ts index aa37013c4a..6fc6f2c4d3 100644 --- a/packages/backend/src/remote/resolve-user.ts +++ b/packages/backend/src/remote/resolve-user.ts @@ -7,15 +7,16 @@ import chalk from 'chalk'; import { User, IRemoteUser } from '@/models/entities/user.js'; import { Users } from '@/models/index.js'; import { toPuny } from '@/misc/convert-host.js'; +import { IsNull } from 'typeorm'; const logger = remoteLogger.createSubLogger('resolve-user'); -export async function resolveUser(username: string, host: string | null, option?: any, resync = false): Promise { +export async function resolveUser(username: string, host: string | null): Promise { const usernameLower = username.toLowerCase(); if (host == null) { logger.info(`return local user: ${usernameLower}`); - return await Users.findOne({ usernameLower, host: null }).then(u => { + return await Users.findOneBy({ usernameLower, host: IsNull() }).then(u => { if (u == null) { throw new Error('user not found'); } else { @@ -28,7 +29,7 @@ export async function resolveUser(username: string, host: string | null, option? if (config.host === host) { logger.info(`return local user: ${usernameLower}`); - return await Users.findOne({ usernameLower, host: null }).then(u => { + return await Users.findOneBy({ usernameLower, host: IsNull() }).then(u => { if (u == null) { throw new Error('user not found'); } else { @@ -37,7 +38,7 @@ export async function resolveUser(username: string, host: string | null, option? }); } - const user = await Users.findOne({ usernameLower, host }, option) as IRemoteUser | null; + const user = await Users.findOneBy({ usernameLower, host }) as IRemoteUser | null; const acctLower = `${usernameLower}@${host}`; @@ -48,8 +49,8 @@ export async function resolveUser(username: string, host: string | null, option? return await createPerson(self.href); } - // resyncオプション OR ユーザー情報が古い場合は、WebFilgerからやりなおして返す - if (resync || user.lastFetchedAt == null || Date.now() - user.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { + // ユーザー情報が古い場合は、WebFilgerからやりなおして返す + if (user.lastFetchedAt == null || Date.now() - user.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { // 繋がらないインスタンスに何回も試行するのを防ぐ, 後続の同様処理の連続試行を防ぐ ため 試行前にも更新する await Users.update(user.id, { lastFetchedAt: new Date(), @@ -82,7 +83,7 @@ export async function resolveUser(username: string, host: string | null, option? await updatePerson(self.href); logger.info(`return resynced remote user: ${acctLower}`); - return await Users.findOne({ uri: self.href }).then(u => { + return await Users.findOneBy({ uri: self.href }).then(u => { if (u == null) { throw new Error('user not found'); } else { diff --git a/packages/backend/src/server/activitypub.ts b/packages/backend/src/server/activitypub.ts index 21be0a2517..133dd36066 100644 --- a/packages/backend/src/server/activitypub.ts +++ b/packages/backend/src/server/activitypub.ts @@ -15,7 +15,7 @@ import { inbox as processInbox } from '@/queue/index.js'; import { isSelfHost } from '@/misc/convert-host.js'; import { Notes, Users, Emojis, NoteReactions } from '@/models/index.js'; import { ILocalUser, User } from '@/models/entities/user.js'; -import { In } from 'typeorm'; +import { In, IsNull } from 'typeorm'; import { renderLike } from '@/remote/activitypub/renderer/like.js'; import { getUserKeypair } from '@/misc/keypair-store.js'; @@ -65,7 +65,7 @@ router.post('/users/:user/inbox', json(), inbox); router.get('/notes/:note', async (ctx, next) => { if (!isActivityPubReq(ctx)) return await next(); - const note = await Notes.findOne({ + const note = await Notes.findOneBy({ id: ctx.params.note, visibility: In(['public' as const, 'home' as const]), localOnly: false, @@ -93,9 +93,9 @@ router.get('/notes/:note', async (ctx, next) => { // note activity router.get('/notes/:note/activity', async ctx => { - const note = await Notes.findOne({ + const note = await Notes.findOneBy({ id: ctx.params.note, - userHost: null, + userHost: IsNull(), visibility: In(['public' as const, 'home' as const]), localOnly: false, }); @@ -126,9 +126,9 @@ router.get('/users/:user/collections/featured', Featured); router.get('/users/:user/publickey', async ctx => { const userId = ctx.params.user; - const user = await Users.findOne({ + const user = await Users.findOneBy({ id: userId, - host: null, + host: IsNull(), }); if (user == null) { @@ -148,7 +148,7 @@ router.get('/users/:user/publickey', async ctx => { }); // user -async function userInfo(ctx: Router.RouterContext, user: User | undefined) { +async function userInfo(ctx: Router.RouterContext, user: User | null) { if (user == null) { ctx.status = 404; return; @@ -164,9 +164,9 @@ router.get('/users/:user', async (ctx, next) => { const userId = ctx.params.user; - const user = await Users.findOne({ + const user = await Users.findOneBy({ id: userId, - host: null, + host: IsNull(), isSuspended: false, }); @@ -176,9 +176,9 @@ router.get('/users/:user', async (ctx, next) => { router.get('/@:user', async (ctx, next) => { if (!isActivityPubReq(ctx)) return await next(); - const user = await Users.findOne({ + const user = await Users.findOneBy({ usernameLower: ctx.params.user.toLowerCase(), - host: null, + host: IsNull(), isSuspended: false, }); @@ -188,8 +188,8 @@ router.get('/@:user', async (ctx, next) => { // emoji router.get('/emojis/:emoji', async ctx => { - const emoji = await Emojis.findOne({ - host: null, + const emoji = await Emojis.findOneBy({ + host: IsNull(), name: ctx.params.emoji, }); @@ -205,14 +205,14 @@ router.get('/emojis/:emoji', async ctx => { // like router.get('/likes/:like', async ctx => { - const reaction = await NoteReactions.findOne(ctx.params.like); + const reaction = await NoteReactions.findOneBy({ id: ctx.params.like }); if (reaction == null) { ctx.status = 404; return; } - const note = await Notes.findOne(reaction.noteId); + const note = await Notes.findOneBy({ id: reaction.noteId }); if (note == null) { ctx.status = 404; diff --git a/packages/backend/src/server/activitypub/featured.ts b/packages/backend/src/server/activitypub/featured.ts index 129881a718..c03fd1049f 100644 --- a/packages/backend/src/server/activitypub/featured.ts +++ b/packages/backend/src/server/activitypub/featured.ts @@ -5,14 +5,14 @@ import renderOrderedCollection from '@/remote/activitypub/renderer/ordered-colle import { setResponseType } from '../activitypub.js'; import renderNote from '@/remote/activitypub/renderer/note.js'; import { Users, Notes, UserNotePinings } from '@/models/index.js'; +import { IsNull } from 'typeorm'; export default async (ctx: Router.RouterContext) => { const userId = ctx.params.user; - // Verify user - const user = await Users.findOne({ + const user = await Users.findOneBy({ id: userId, - host: null, + host: IsNull(), }); if (user == null) { @@ -26,7 +26,7 @@ export default async (ctx: Router.RouterContext) => { }); const pinnedNotes = await Promise.all(pinings.map(pining => - Notes.findOneOrFail(pining.noteId))); + Notes.findOneByOrFail({ id: pining.noteId }))); const renderedNotes = await Promise.all(pinnedNotes.map(note => renderNote(note))); diff --git a/packages/backend/src/server/activitypub/followers.ts b/packages/backend/src/server/activitypub/followers.ts index 5d1d7c59eb..4d4f733162 100644 --- a/packages/backend/src/server/activitypub/followers.ts +++ b/packages/backend/src/server/activitypub/followers.ts @@ -9,7 +9,7 @@ import renderOrderedCollectionPage from '@/remote/activitypub/renderer/ordered-c import renderFollowUser from '@/remote/activitypub/renderer/follow-user.js'; import { setResponseType } from '../activitypub.js'; import { Users, Followings, UserProfiles } from '@/models/index.js'; -import { LessThan } from 'typeorm'; +import { IsNull, LessThan } from 'typeorm'; export default async (ctx: Router.RouterContext) => { const userId = ctx.params.user; @@ -27,10 +27,9 @@ export default async (ctx: Router.RouterContext) => { return; } - // Verify user - const user = await Users.findOne({ + const user = await Users.findOneBy({ id: userId, - host: null, + host: IsNull(), }); if (user == null) { @@ -39,7 +38,7 @@ export default async (ctx: Router.RouterContext) => { } //#region Check ff visibility - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); if (profile.ffVisibility === 'private') { ctx.status = 403; diff --git a/packages/backend/src/server/activitypub/following.ts b/packages/backend/src/server/activitypub/following.ts index 23110ce873..0af1f424f9 100644 --- a/packages/backend/src/server/activitypub/following.ts +++ b/packages/backend/src/server/activitypub/following.ts @@ -9,7 +9,7 @@ import renderOrderedCollectionPage from '@/remote/activitypub/renderer/ordered-c import renderFollowUser from '@/remote/activitypub/renderer/follow-user.js'; import { setResponseType } from '../activitypub.js'; import { Users, Followings, UserProfiles } from '@/models/index.js'; -import { LessThan, FindConditions } from 'typeorm'; +import { LessThan, IsNull, FindOptionsWhere } from 'typeorm'; import { Following } from '@/models/entities/following.js'; export default async (ctx: Router.RouterContext) => { @@ -28,10 +28,9 @@ export default async (ctx: Router.RouterContext) => { return; } - // Verify user - const user = await Users.findOne({ + const user = await Users.findOneBy({ id: userId, - host: null, + host: IsNull(), }); if (user == null) { @@ -40,7 +39,7 @@ export default async (ctx: Router.RouterContext) => { } //#region Check ff visibility - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); if (profile.ffVisibility === 'private') { ctx.status = 403; @@ -59,7 +58,7 @@ export default async (ctx: Router.RouterContext) => { if (page) { const query = { followerId: user.id, - } as FindConditions; + } as FindOptionsWhere; // カーソルが指定されている場合 if (cursor) { diff --git a/packages/backend/src/server/activitypub/outbox.ts b/packages/backend/src/server/activitypub/outbox.ts index 57c126752a..6b9592bcf3 100644 --- a/packages/backend/src/server/activitypub/outbox.ts +++ b/packages/backend/src/server/activitypub/outbox.ts @@ -13,7 +13,7 @@ import { countIf } from '@/prelude/array.js'; import * as url from '@/prelude/url.js'; import { Users, Notes } from '@/models/index.js'; import { makePaginationQuery } from '../api/common/make-pagination-query.js'; -import { Brackets } from 'typeorm'; +import { Brackets, IsNull } from 'typeorm'; import { Note } from '@/models/entities/note.js'; export default async (ctx: Router.RouterContext) => { @@ -35,10 +35,9 @@ export default async (ctx: Router.RouterContext) => { return; } - // Verify user - const user = await Users.findOne({ + const user = await Users.findOneBy({ id: userId, - host: null, + host: IsNull(), }); if (user == null) { @@ -100,7 +99,7 @@ export default async (ctx: Router.RouterContext) => { */ export async function packActivity(note: Note): Promise { if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length === 0)) { - const renote = await Notes.findOneOrFail(note.renoteId); + const renote = await Notes.findOneByOrFail({ id: note.renoteId }); return renderAnnounce(renote.uri ? renote.uri : `${config.url}/notes/${renote.id}`, note); } diff --git a/packages/backend/src/server/api/authenticate.ts b/packages/backend/src/server/api/authenticate.ts index 7fdf14666e..65ccfcf551 100644 --- a/packages/backend/src/server/api/authenticate.ts +++ b/packages/backend/src/server/api/authenticate.ts @@ -1,7 +1,12 @@ import isNativeToken from './common/is-native-token.js'; -import { User } from '@/models/entities/user.js'; +import { CacheableLocalUser, ILocalUser } from '@/models/entities/user.js'; import { Users, AccessTokens, Apps } from '@/models/index.js'; import { AccessToken } from '@/models/entities/access-token.js'; +import { Cache } from '@/misc/cache.js'; +import { App } from '@/models/entities/app.js'; +import { localUserByIdCache, localUserByNativeTokenCache } from '@/services/user-cache.js'; + +const appCache = new Cache(Infinity); export class AuthenticationError extends Error { constructor(message: string) { @@ -10,15 +15,14 @@ export class AuthenticationError extends Error { } } -export default async (token: string | null): Promise<[User | null | undefined, AccessToken | null | undefined]> => { +export default async (token: string | null): Promise<[CacheableLocalUser | null | undefined, AccessToken | null | undefined]> => { if (token == null) { return [null, null]; } if (isNativeToken(token)) { - // Fetch user - const user = await Users - .findOne({ token }); + const user = await localUserByNativeTokenCache.fetch(token, + () => Users.findOneBy({ token }) as Promise); if (user == null) { throw new AuthenticationError('user not found'); @@ -42,14 +46,14 @@ export default async (token: string | null): Promise<[User | null | undefined, A lastUsedAt: new Date(), }); - const user = await Users - .findOne({ - id: accessToken.userId, // findOne(accessToken.userId) のように書かないのは後方互換性のため - }); + const user = await localUserByIdCache.fetch(accessToken.userId, + () => Users.findOneBy({ + id: accessToken.userId, + }) as Promise); if (accessToken.appId) { - const app = await Apps - .findOneOrFail(accessToken.appId); + const app = await appCache.fetch(accessToken.appId, + () => Apps.findOneByOrFail({ id: accessToken.appId! })); return [user, { id: accessToken.id, diff --git a/packages/backend/src/server/api/call.ts b/packages/backend/src/server/api/call.ts index 5c5ef66019..9a85e4565b 100644 --- a/packages/backend/src/server/api/call.ts +++ b/packages/backend/src/server/api/call.ts @@ -1,7 +1,7 @@ import Koa from 'koa'; import { performance } from 'perf_hooks'; import { limiter } from './limiter.js'; -import { User } from '@/models/entities/user.js'; +import { CacheableLocalUser, User } from '@/models/entities/user.js'; import endpoints, { IEndpoint } from './endpoints.js'; import { ApiError } from './error.js'; import { apiLogger } from './logger.js'; @@ -13,7 +13,7 @@ const accessDenied = { id: '56f35758-7dd5-468b-8439-5d6fb8ec9b8e', }; -export default async (endpoint: string, user: User | null | undefined, token: AccessToken | null | undefined, data: any, ctx?: Koa.Context) => { +export default async (endpoint: string, user: CacheableLocalUser | null | undefined, token: AccessToken | null | undefined, data: any, ctx?: Koa.Context) => { const isSecure = user != null && token == null; const ep = endpoints.find(e => e.name === endpoint); diff --git a/packages/backend/src/server/api/common/getters.ts b/packages/backend/src/server/api/common/getters.ts index c5a47876d0..783ea9ef75 100644 --- a/packages/backend/src/server/api/common/getters.ts +++ b/packages/backend/src/server/api/common/getters.ts @@ -7,7 +7,7 @@ import { Notes, Users } from '@/models/index.js'; * Get note for API processing */ export async function getNote(noteId: Note['id']) { - const note = await Notes.findOne(noteId); + const note = await Notes.findOneBy({ id: noteId }); if (note == null) { throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.'); @@ -20,7 +20,7 @@ export async function getNote(noteId: Note['id']) { * Get user for API processing */ export async function getUser(userId: User['id']) { - const user = await Users.findOne(userId); + const user = await Users.findOneBy({ id: userId }); if (user == null) { throw new IdentifiableError('15348ddd-432d-49c2-8a5a-8069753becff', 'No such user.'); diff --git a/packages/backend/src/server/api/common/inject-featured.ts b/packages/backend/src/server/api/common/inject-featured.ts index b7dd8028b5..f7cdd365ed 100644 --- a/packages/backend/src/server/api/common/inject-featured.ts +++ b/packages/backend/src/server/api/common/inject-featured.ts @@ -11,7 +11,7 @@ export async function injectFeatured(timeline: Note[], user?: User | null) { if (timeline.length < 5) return; if (user) { - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); if (!profile.injectFeaturedNote) return; } diff --git a/packages/backend/src/server/api/common/inject-promo.ts b/packages/backend/src/server/api/common/inject-promo.ts index b467b7b70b..b0da8118b4 100644 --- a/packages/backend/src/server/api/common/inject-promo.ts +++ b/packages/backend/src/server/api/common/inject-promo.ts @@ -8,7 +8,7 @@ export async function injectPromo(timeline: Note[], user?: User | null) { // TODO: readやexpireフィルタはクエリ側でやる - const reads = user ? await PromoReads.find({ + const reads = user ? await PromoReads.findBy({ userId: user.id, }) : []; @@ -22,10 +22,10 @@ export async function injectPromo(timeline: Note[], user?: User | null) { // Pick random promo const promo = promos[Math.floor(Math.random() * promos.length)]; - const note = await Notes.findOneOrFail(promo.noteId); + const note = await Notes.findOneByOrFail({ id: promo.noteId }); // Join - note.user = await Users.findOneOrFail(note.userId); + note.user = await Users.findOneByOrFail({ id: note.userId }); (note as any)._prId_ = rndstr('a-z0-9', 8); diff --git a/packages/backend/src/server/api/common/read-messaging-message.ts b/packages/backend/src/server/api/common/read-messaging-message.ts index b0ce54d370..3638518e67 100644 --- a/packages/backend/src/server/api/common/read-messaging-message.ts +++ b/packages/backend/src/server/api/common/read-messaging-message.ts @@ -23,7 +23,7 @@ export async function readUserMessagingMessage( ) { if (messageIds.length === 0) return; - const messages = await MessagingMessages.find({ + const messages = await MessagingMessages.findBy({ id: In(messageIds), }); @@ -64,7 +64,7 @@ export async function readGroupMessagingMessage( if (messageIds.length === 0) return; // check joined - const joining = await UserGroupJoinings.findOne({ + const joining = await UserGroupJoinings.findOneBy({ userId: userId, userGroupId: groupId, }); @@ -73,7 +73,7 @@ export async function readGroupMessagingMessage( throw new IdentifiableError('930a270c-714a-46b2-b776-ad27276dc569', 'Access denied (group).'); } - const messages = await MessagingMessages.find({ + const messages = await MessagingMessages.findBy({ id: In(messageIds), }); diff --git a/packages/backend/src/server/api/common/signin.ts b/packages/backend/src/server/api/common/signin.ts index 163f132a44..f1dccee2ce 100644 --- a/packages/backend/src/server/api/common/signin.ts +++ b/packages/backend/src/server/api/common/signin.ts @@ -36,7 +36,7 @@ export default function(ctx: Koa.Context, user: ILocalUser, redirect = false) { ip: ctx.ip, headers: ctx.headers, success: true, - }).then(x => Signins.findOneOrFail(x.identifiers[0])); + }).then(x => Signins.findOneByOrFail(x.identifiers[0])); // Publish signin event publishMainStream(user.id, 'signin', await Signins.pack(record)); diff --git a/packages/backend/src/server/api/common/signup.ts b/packages/backend/src/server/api/common/signup.ts index 7689e8233f..abc142472a 100644 --- a/packages/backend/src/server/api/common/signup.ts +++ b/packages/backend/src/server/api/common/signup.ts @@ -4,12 +4,13 @@ import generateUserToken from './generate-native-user-token.js'; import { User } from '@/models/entities/user.js'; import { Users, UsedUsernames } from '@/models/index.js'; import { UserProfile } from '@/models/entities/user-profile.js'; -import { getConnection } from 'typeorm'; +import { IsNull } from 'typeorm'; import { genId } from '@/misc/gen-id.js'; import { toPunyNullable } from '@/misc/convert-host.js'; import { UserKeypair } from '@/models/entities/user-keypair.js'; import { usersChart } from '@/services/chart/index.js'; import { UsedUsername } from '@/models/entities/used-username.js'; +import { db } from '@/db/postgre.js'; export async function signup(opts: { username: User['username']; @@ -40,12 +41,12 @@ export async function signup(opts: { const secret = generateUserToken(); // Check username duplication - if (await Users.findOne({ usernameLower: username.toLowerCase(), host: null })) { + if (await Users.findOneBy({ usernameLower: username.toLowerCase(), host: IsNull() })) { throw new Error('DUPLICATED_USERNAME'); } // Check deleted username duplication - if (await UsedUsernames.findOne({ username: username.toLowerCase() })) { + if (await UsedUsernames.findOneBy({ username: username.toLowerCase() })) { throw new Error('USED_USERNAME'); } @@ -69,10 +70,10 @@ export async function signup(opts: { let account!: User; // Start transaction - await getConnection().transaction(async transactionalEntityManager => { - const exist = await transactionalEntityManager.findOne(User, { + await db.transaction(async transactionalEntityManager => { + const exist = await transactionalEntityManager.findOneBy(User, { usernameLower: username.toLowerCase(), - host: null, + host: IsNull(), }); if (exist) throw new Error(' the username is already used'); @@ -84,8 +85,8 @@ export async function signup(opts: { usernameLower: username.toLowerCase(), host: toPunyNullable(host), token: secret, - isAdmin: (await Users.count({ - host: null, + isAdmin: (await Users.countBy({ + host: IsNull(), })) === 0, })); diff --git a/packages/backend/src/server/api/define.ts b/packages/backend/src/server/api/define.ts index 9094fcffc6..1529894341 100644 --- a/packages/backend/src/server/api/define.ts +++ b/packages/backend/src/server/api/define.ts @@ -1,44 +1,30 @@ 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({ useDefaults: true, }); -ajv.addFormat('misskey:id', /^[a-z0-9]+$/); +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/admin/accounts/create.ts b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts index 2820c7993d..5f89219991 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts @@ -1,6 +1,7 @@ import define from '../../../define.js'; import { Users } from '@/models/index.js'; import { signup } from '../../../common/signup.js'; +import { IsNull } from 'typeorm'; export const meta = { tags: ['admin'], @@ -29,9 +30,9 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, _me) => { - const me = _me ? await Users.findOneOrFail(_me.id) : null; - const noUsers = (await Users.count({ - host: null, + const me = _me ? await Users.findOneByOrFail({ id: _me.id }) : null; + const noUsers = (await Users.countBy({ + host: IsNull(), })) === 0; if (!noUsers && !me?.isAdmin) throw new Error('access denied'); diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts index 01754ec8f3..629d700582 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts @@ -21,7 +21,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOne(ps.userId); + const user = await Users.findOneBy({ id: ps.userId }); if (user == null) { throw new Error('user not found'); diff --git a/packages/backend/src/server/api/endpoints/admin/ad/delete.ts b/packages/backend/src/server/api/endpoints/admin/ad/delete.ts index 3663d974c5..0ead2be005 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/delete.ts @@ -27,7 +27,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const ad = await Ads.findOne(ps.id); + const ad = await Ads.findOneBy({ id: ps.id }); if (ad == null) throw new ApiError(meta.errors.noSuchAd); diff --git a/packages/backend/src/server/api/endpoints/admin/ad/update.ts b/packages/backend/src/server/api/endpoints/admin/ad/update.ts index 89c421db66..650f8670e3 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/update.ts @@ -34,7 +34,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const ad = await Ads.findOne(ps.id); + const ad = await Ads.findOneBy({ id: ps.id }); if (ad == null) throw new ApiError(meta.errors.noSuchAd); diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts index 41570078d4..33076b6d30 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts @@ -63,7 +63,7 @@ export default define(meta, paramDef, async (ps) => { title: ps.title, text: ps.text, imageUrl: ps.imageUrl, - }).then(x => Announcements.findOneOrFail(x.identifiers[0])); + }).then(x => Announcements.findOneByOrFail(x.identifiers[0])); return Object.assign({}, announcement, { createdAt: announcement.createdAt.toISOString(), updatedAt: null }); }); diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts b/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts index 4871dc4e12..c17765f4fc 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts @@ -27,7 +27,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const announcement = await Announcements.findOne(ps.id); + const announcement = await Announcements.findOneBy({ id: ps.id }); if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement); diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts index 0ba0a8ee08..1d8eb1d618 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts @@ -69,7 +69,7 @@ export default define(meta, paramDef, async (ps) => { const announcements = await query.take(ps.limit).getMany(); for (const announcement of announcements) { - (announcement as any).reads = await AnnouncementReads.count({ + (announcement as any).reads = await AnnouncementReads.countBy({ announcementId: announcement.id, }); } diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts index 138337ef5b..61ce106d88 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts @@ -30,7 +30,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const announcement = await Announcements.findOne(ps.id); + const announcement = await Announcements.findOneBy({ id: ps.id }); if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement); diff --git a/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts b/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts index 90e65ec4cd..dc1976624d 100644 --- a/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts @@ -19,7 +19,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const files = await DriveFiles.find({ + const files = await DriveFiles.findBy({ userId: ps.userId, }); diff --git a/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts b/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts index 3e7d43fb0b..3db942e6cd 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts @@ -18,7 +18,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const files = await DriveFiles.find({ + const files = await DriveFiles.findBy({ userId: IsNull(), }); diff --git a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts index e821160095..4b27fc0188 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 @@ -160,7 +160,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const file = ps.fileId ? await DriveFiles.findOne(ps.fileId) : await DriveFiles.findOne({ + const file = ps.fileId ? await DriveFiles.findOneBy({ id: ps.fileId }) : await DriveFiles.findOne({ where: [{ url: ps.url, }, { diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts index 77a4adea61..232fbbd573 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts @@ -1,7 +1,8 @@ import define from '../../../define.js'; import { Emojis } from '@/models/index.js'; -import { getConnection, In } from 'typeorm'; +import { In } from 'typeorm'; import { ApiError } from '../../../error.js'; +import { db } from '@/db/postgre.js'; export const meta = { tags: ['admin'], @@ -25,7 +26,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps) => { - const emojis = await Emojis.find({ + const emojis = await Emojis.findBy({ id: In(ps.ids), }); @@ -36,5 +37,5 @@ export default define(meta, paramDef, async (ps) => { }); } - await getConnection().queryResultCache!.remove(['meta_emojis']); + await db.queryResultCache!.remove(['meta_emojis']); }); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts index c5787d59dc..67349c24e0 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts @@ -1,11 +1,11 @@ import define from '../../../define.js'; import { Emojis, DriveFiles } from '@/models/index.js'; import { genId } from '@/misc/gen-id.js'; -import { getConnection } from 'typeorm'; import { insertModerationLog } from '@/services/insert-moderation-log.js'; import { ApiError } from '../../../error.js'; import rndstr from 'rndstr'; import { publishBroadcastStream } from '@/services/stream.js'; +import { db } from '@/db/postgre.js'; export const meta = { tags: ['admin'], @@ -32,7 +32,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const file = await DriveFiles.findOne(ps.fileId); + const file = await DriveFiles.findOneBy({ id: ps.fileId }); if (file == null) throw new ApiError(meta.errors.noSuchFile); @@ -48,9 +48,9 @@ export default define(meta, paramDef, async (ps, me) => { originalUrl: file.url, publicUrl: file.webpublicUrl ?? file.url, type: file.webpublicType ?? file.type, - }).then(x => Emojis.findOneOrFail(x.identifiers[0])); + }).then(x => Emojis.findOneByOrFail(x.identifiers[0])); - await getConnection().queryResultCache!.remove(['meta_emojis']); + await db.queryResultCache!.remove(['meta_emojis']); publishBroadcastStream('emojiAdded', { emoji: await Emojis.pack(emoji.id), diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts index a0eaa61258..7010ade0d8 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts @@ -1,11 +1,11 @@ import define from '../../../define.js'; import { Emojis } from '@/models/index.js'; import { genId } from '@/misc/gen-id.js'; -import { getConnection } from 'typeorm'; import { ApiError } from '../../../error.js'; import { DriveFile } from '@/models/entities/drive-file.js'; import { uploadFromUrl } from '@/services/drive/upload-from-url.js'; import { publishBroadcastStream } from '@/services/stream.js'; +import { db } from '@/db/postgre.js'; export const meta = { tags: ['admin'], @@ -44,7 +44,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const emoji = await Emojis.findOne(ps.emojiId); + const emoji = await Emojis.findOneBy({ id: ps.emojiId }); if (emoji == null) { throw new ApiError(meta.errors.noSuchEmoji); @@ -68,9 +68,9 @@ export default define(meta, paramDef, async (ps, me) => { originalUrl: driveFile.url, publicUrl: driveFile.webpublicUrl ?? driveFile.url, type: driveFile.webpublicType ?? driveFile.type, - }).then(x => Emojis.findOneOrFail(x.identifiers[0])); + }).then(x => Emojis.findOneByOrFail(x.identifiers[0])); - await getConnection().queryResultCache!.remove(['meta_emojis']); + await db.queryResultCache!.remove(['meta_emojis']); publishBroadcastStream('emojiAdded', { emoji: await Emojis.pack(copied.id), diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts index 38a2d65cf6..93a6c4e4e2 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts @@ -1,8 +1,9 @@ import define from '../../../define.js'; import { Emojis } from '@/models/index.js'; -import { getConnection, In } from 'typeorm'; +import { In } from 'typeorm'; import { insertModerationLog } from '@/services/insert-moderation-log.js'; import { ApiError } from '../../../error.js'; +import { db } from '@/db/postgre.js'; export const meta = { tags: ['admin'], @@ -23,14 +24,14 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const emojis = await Emojis.find({ + const emojis = await Emojis.findBy({ id: In(ps.ids), }); for (const emoji of emojis) { await Emojis.delete(emoji.id); - await getConnection().queryResultCache!.remove(['meta_emojis']); + await db.queryResultCache!.remove(['meta_emojis']); insertModerationLog(me, 'deleteEmoji', { emoji: emoji, diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts index a0cffb47f8..67dbf28d85 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts @@ -1,8 +1,8 @@ import define from '../../../define.js'; import { Emojis } from '@/models/index.js'; -import { getConnection } from 'typeorm'; import { insertModerationLog } from '@/services/insert-moderation-log.js'; import { ApiError } from '../../../error.js'; +import { db } from '@/db/postgre.js'; export const meta = { tags: ['admin'], @@ -29,13 +29,13 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const emoji = await Emojis.findOne(ps.id); + const emoji = await Emojis.findOneBy({ id: ps.id }); if (emoji == null) throw new ApiError(meta.errors.noSuchEmoji); await Emojis.delete(emoji.id); - await getConnection().queryResultCache!.remove(['meta_emojis']); + await db.queryResultCache!.remove(['meta_emojis']); insertModerationLog(me, 'deleteEmoji', { emoji: emoji, diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts index dbad93d336..a4da40fffd 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts @@ -1,7 +1,8 @@ import define from '../../../define.js'; import { Emojis } from '@/models/index.js'; -import { getConnection, In } from 'typeorm'; +import { In } from 'typeorm'; import { ApiError } from '../../../error.js'; +import { db } from '@/db/postgre.js'; export const meta = { tags: ['admin'], @@ -25,7 +26,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps) => { - const emojis = await Emojis.find({ + const emojis = await Emojis.findBy({ id: In(ps.ids), }); @@ -36,5 +37,5 @@ export default define(meta, paramDef, async (ps) => { }); } - await getConnection().queryResultCache!.remove(['meta_emojis']); + await db.queryResultCache!.remove(['meta_emojis']); }); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts index 470b9bef08..ae3b190f40 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts @@ -1,7 +1,8 @@ import define from '../../../define.js'; import { Emojis } from '@/models/index.js'; -import { getConnection, In } from 'typeorm'; +import { In } from 'typeorm'; import { ApiError } from '../../../error.js'; +import { db } from '@/db/postgre.js'; export const meta = { tags: ['admin'], @@ -32,5 +33,5 @@ export default define(meta, paramDef, async (ps) => { aliases: ps.aliases, }); - await getConnection().queryResultCache!.remove(['meta_emojis']); + await db.queryResultCache!.remove(['meta_emojis']); }); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts index 40e4c0199e..6063f3e3be 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'], @@ -30,5 +31,5 @@ export default define(meta, paramDef, async (ps) => { category: ps.category, }); - await getConnection().queryResultCache!.remove(['meta_emojis']); + await db.queryResultCache!.remove(['meta_emojis']); }); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts index c6d07e16fa..e26514e0ca 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'], @@ -33,7 +33,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 +44,5 @@ export default define(meta, paramDef, async (ps) => { aliases: ps.aliases, }); - await getConnection().queryResultCache!.remove(['meta_emojis']); + await db.queryResultCache!.remove(['meta_emojis']); }); diff --git a/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts b/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts index d4251f2feb..da54201473 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts @@ -19,7 +19,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const files = await DriveFiles.find({ + const files = await DriveFiles.findBy({ userHost: ps.host, }); diff --git a/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts b/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts index 86978cc309..cb2be5ab37 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts @@ -20,7 +20,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const instance = await Instances.findOne({ host: toPuny(ps.host) }); + const instance = await Instances.findOneBy({ host: toPuny(ps.host) }); if (instance == null) { throw new Error('instance not found'); diff --git a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts index ccd07489cb..b7ee27db64 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts @@ -19,13 +19,13 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const followings = await Followings.find({ + const followings = await Followings.findBy({ followerHost: ps.host, }); const pairs = await Promise.all(followings.map(f => Promise.all([ - Users.findOneOrFail(f.followerId), - Users.findOneOrFail(f.followeeId), + Users.findOneByOrFail({ id: f.followerId }), + Users.findOneByOrFail({ id: f.followeeId }), ]))); for (const pair of pairs) { diff --git a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts index 1981082428..278131fb37 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts @@ -20,7 +20,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const instance = await Instances.findOne({ host: toPuny(ps.host) }); + const instance = await Instances.findOneBy({ host: toPuny(ps.host) }); if (instance == null) { throw new Error('instance not found'); diff --git a/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts b/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts index 37878c4143..dd16473f30 100644 --- a/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts +++ b/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts @@ -1,5 +1,5 @@ import define from '../../define.js'; -import { getConnection } from 'typeorm'; +import { db } from '@/db/postgre.js'; export const meta = { requireCredential: true, @@ -16,15 +16,13 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async () => { - const stats = await - getConnection().query(`SELECT * FROM pg_indexes;`) - .then(recs => { - const res = [] as { tablename: string; indexname: string; }[]; - for (const rec of recs) { - res.push(rec); - } - return res; - }); + const stats = await db.query(`SELECT * FROM pg_indexes;`).then(recs => { + const res = [] as { tablename: string; indexname: string; }[]; + for (const rec of recs) { + res.push(rec); + } + return res; + }); return stats; }); diff --git a/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts b/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts index 7cf2d5ffd4..aca2540fd5 100644 --- a/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts +++ b/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts @@ -1,5 +1,5 @@ +import { db } from '@/db/postgre.js'; import define from '../../define.js'; -import { getConnection } from 'typeorm'; export const meta = { requireCredential: true, @@ -28,7 +28,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async () => { const sizes = await - getConnection().query(` + db.query(` SELECT relname AS "table", reltuples as "count", pg_total_relation_size(C.oid) AS "size" FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) WHERE nspname NOT IN ('pg_catalog', 'information_schema') diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts new file mode 100644 index 0000000000..8d50486ef6 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -0,0 +1,401 @@ +import config from '@/config/index.js'; +import define from '../../define.js'; +import { fetchMeta } from '@/misc/fetch-meta.js'; +import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; + +export const meta = { + tags: ['meta'], + + requireCredential: true, + requireAdmin: true, + + res: { + type: 'object', + optional: false, nullable: false, + properties: { + driveCapacityPerLocalUserMb: { + type: 'number', + optional: false, nullable: false, + }, + driveCapacityPerRemoteUserMb: { + type: 'number', + optional: false, nullable: false, + }, + cacheRemoteFiles: { + type: 'boolean', + optional: false, nullable: false, + }, + emailRequiredForSignup: { + type: 'boolean', + optional: false, nullable: false, + }, + enableHcaptcha: { + type: 'boolean', + optional: false, nullable: false, + }, + hcaptchaSiteKey: { + type: 'string', + optional: false, nullable: true, + }, + enableRecaptcha: { + type: 'boolean', + optional: false, nullable: false, + }, + recaptchaSiteKey: { + type: 'string', + optional: false, nullable: true, + }, + swPublickey: { + type: 'string', + optional: false, nullable: true, + }, + mascotImageUrl: { + type: 'string', + optional: false, nullable: false, + default: '/assets/ai.png', + }, + bannerUrl: { + type: 'string', + optional: false, nullable: false, + }, + errorImageUrl: { + type: 'string', + optional: false, nullable: false, + default: 'https://xn--931a.moe/aiart/yubitun.png', + }, + iconUrl: { + type: 'string', + optional: false, nullable: true, + }, + maxNoteTextLength: { + type: 'number', + optional: false, nullable: false, + }, + emojis: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + aliases: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, + category: { + type: 'string', + optional: false, nullable: true, + }, + host: { + type: 'string', + optional: false, nullable: true, + }, + url: { + type: 'string', + optional: false, nullable: false, + format: 'url', + }, + }, + }, + }, + ads: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + properties: { + place: { + type: 'string', + optional: false, nullable: false, + }, + url: { + type: 'string', + optional: false, nullable: false, + format: 'url', + }, + imageUrl: { + type: 'string', + optional: false, nullable: false, + format: 'url', + }, + }, + }, + }, + enableEmail: { + type: 'boolean', + optional: false, nullable: false, + }, + enableTwitterIntegration: { + type: 'boolean', + optional: false, nullable: false, + }, + enableGithubIntegration: { + type: 'boolean', + optional: false, nullable: false, + }, + enableDiscordIntegration: { + type: 'boolean', + optional: false, nullable: false, + }, + enableServiceWorker: { + type: 'boolean', + optional: false, nullable: false, + }, + translatorAvailable: { + type: 'boolean', + optional: false, nullable: false, + }, + proxyAccountName: { + type: 'string', + optional: false, nullable: true, + }, + userStarForReactionFallback: { + type: 'boolean', + optional: true, nullable: false, + }, + pinnedUsers: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, + hiddenTags: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, + blockedHosts: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, + hcaptchaSecretKey: { + type: 'string', + optional: true, nullable: true, + }, + recaptchaSecretKey: { + type: 'string', + optional: true, nullable: true, + }, + proxyAccountId: { + type: 'string', + optional: true, nullable: true, + format: 'id', + }, + twitterConsumerKey: { + type: 'string', + optional: true, nullable: true, + }, + twitterConsumerSecret: { + type: 'string', + optional: true, nullable: true, + }, + githubClientId: { + type: 'string', + optional: true, nullable: true, + }, + githubClientSecret: { + type: 'string', + optional: true, nullable: true, + }, + discordClientId: { + type: 'string', + optional: true, nullable: true, + }, + discordClientSecret: { + type: 'string', + optional: true, nullable: true, + }, + summaryProxy: { + type: 'string', + optional: true, nullable: true, + }, + email: { + type: 'string', + optional: true, nullable: true, + }, + smtpSecure: { + type: 'boolean', + optional: true, nullable: false, + }, + smtpHost: { + type: 'string', + optional: true, nullable: true, + }, + smtpPort: { + type: 'string', + optional: true, nullable: true, + }, + smtpUser: { + type: 'string', + optional: true, nullable: true, + }, + smtpPass: { + type: 'string', + optional: true, nullable: true, + }, + swPrivateKey: { + type: 'string', + optional: true, nullable: true, + }, + useObjectStorage: { + type: 'boolean', + optional: true, nullable: false, + }, + objectStorageBaseUrl: { + type: 'string', + optional: true, nullable: true, + }, + objectStorageBucket: { + type: 'string', + optional: true, nullable: true, + }, + objectStoragePrefix: { + type: 'string', + optional: true, nullable: true, + }, + objectStorageEndpoint: { + type: 'string', + optional: true, nullable: true, + }, + objectStorageRegion: { + type: 'string', + optional: true, nullable: true, + }, + objectStoragePort: { + type: 'number', + optional: true, nullable: true, + }, + objectStorageAccessKey: { + type: 'string', + optional: true, nullable: true, + }, + objectStorageSecretKey: { + type: 'string', + optional: true, nullable: true, + }, + objectStorageUseSSL: { + type: 'boolean', + optional: true, nullable: false, + }, + objectStorageUseProxy: { + type: 'boolean', + optional: true, nullable: false, + }, + objectStorageSetPublicRead: { + type: 'boolean', + optional: true, nullable: false, + }, + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + }, + required: [], +} as const; + +// eslint-disable-next-line import/no-default-export +export default define(meta, paramDef, async (ps, me) => { + const instance = await fetchMeta(true); + + return { + maintainerName: instance.maintainerName, + maintainerEmail: instance.maintainerEmail, + version: config.version, + name: instance.name, + uri: config.url, + description: instance.description, + langs: instance.langs, + tosUrl: instance.ToSUrl, + repositoryUrl: instance.repositoryUrl, + feedbackUrl: instance.feedbackUrl, + disableRegistration: instance.disableRegistration, + disableLocalTimeline: instance.disableLocalTimeline, + disableGlobalTimeline: instance.disableGlobalTimeline, + driveCapacityPerLocalUserMb: instance.localDriveCapacityMb, + driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb, + emailRequiredForSignup: instance.emailRequiredForSignup, + enableHcaptcha: instance.enableHcaptcha, + hcaptchaSiteKey: instance.hcaptchaSiteKey, + enableRecaptcha: instance.enableRecaptcha, + recaptchaSiteKey: instance.recaptchaSiteKey, + swPublickey: instance.swPublicKey, + themeColor: instance.themeColor, + mascotImageUrl: instance.mascotImageUrl, + bannerUrl: instance.bannerUrl, + errorImageUrl: instance.errorImageUrl, + iconUrl: instance.iconUrl, + backgroundImageUrl: instance.backgroundImageUrl, + logoImageUrl: instance.logoImageUrl, + maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため + defaultLightTheme: instance.defaultLightTheme, + defaultDarkTheme: instance.defaultDarkTheme, + enableEmail: instance.enableEmail, + enableTwitterIntegration: instance.enableTwitterIntegration, + enableGithubIntegration: instance.enableGithubIntegration, + enableDiscordIntegration: instance.enableDiscordIntegration, + enableServiceWorker: instance.enableServiceWorker, + translatorAvailable: instance.deeplAuthKey != null, + pinnedPages: instance.pinnedPages, + pinnedClipId: instance.pinnedClipId, + cacheRemoteFiles: instance.cacheRemoteFiles, + + useStarForReactionFallback: instance.useStarForReactionFallback, + pinnedUsers: instance.pinnedUsers, + hiddenTags: instance.hiddenTags, + blockedHosts: instance.blockedHosts, + hcaptchaSecretKey: instance.hcaptchaSecretKey, + recaptchaSecretKey: instance.recaptchaSecretKey, + proxyAccountId: instance.proxyAccountId, + twitterConsumerKey: instance.twitterConsumerKey, + twitterConsumerSecret: instance.twitterConsumerSecret, + githubClientId: instance.githubClientId, + githubClientSecret: instance.githubClientSecret, + discordClientId: instance.discordClientId, + discordClientSecret: instance.discordClientSecret, + summalyProxy: instance.summalyProxy, + email: instance.email, + smtpSecure: instance.smtpSecure, + smtpHost: instance.smtpHost, + smtpPort: instance.smtpPort, + smtpUser: instance.smtpUser, + smtpPass: instance.smtpPass, + swPrivateKey: instance.swPrivateKey, + useObjectStorage: instance.useObjectStorage, + objectStorageBaseUrl: instance.objectStorageBaseUrl, + objectStorageBucket: instance.objectStorageBucket, + objectStoragePrefix: instance.objectStoragePrefix, + objectStorageEndpoint: instance.objectStorageEndpoint, + objectStorageRegion: instance.objectStorageRegion, + objectStoragePort: instance.objectStoragePort, + objectStorageAccessKey: instance.objectStorageAccessKey, + objectStorageSecretKey: instance.objectStorageSecretKey, + objectStorageUseSSL: instance.objectStorageUseSSL, + objectStorageUseProxy: instance.objectStorageUseProxy, + objectStorageSetPublicRead: instance.objectStorageSetPublicRead, + objectStorageS3ForcePathStyle: instance.objectStorageS3ForcePathStyle, + deeplAuthKey: instance.deeplAuthKey, + deeplIsPro: instance.deeplIsPro, + }; +}); diff --git a/packages/backend/src/server/api/endpoints/admin/moderators/add.ts b/packages/backend/src/server/api/endpoints/admin/moderators/add.ts index 4206e3a3c2..7b209c2d99 100644 --- a/packages/backend/src/server/api/endpoints/admin/moderators/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/moderators/add.ts @@ -1,5 +1,6 @@ import define from '../../../define.js'; import { Users } from '@/models/index.js'; +import { publishInternalEvent } from '@/services/stream.js'; export const meta = { tags: ['admin'], @@ -18,7 +19,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps) => { - const user = await Users.findOne(ps.userId as string); + const user = await Users.findOneBy({ id: ps.userId }); if (user == null) { throw new Error('user not found'); @@ -31,4 +32,6 @@ export default define(meta, paramDef, async (ps) => { await Users.update(user.id, { isModerator: true, }); + + publishInternalEvent('userChangeModeratorState', { id: user.id, isModerator: true }); }); diff --git a/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts b/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts index 143119bfe4..b85a677e81 100644 --- a/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts +++ b/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts @@ -18,7 +18,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 +27,6 @@ export default define(meta, paramDef, async (ps) => { await Users.update(user.id, { isModerator: false, }); + + publishInternalEvent('userChangeModeratorState', { id: user.id, isModerator: false }); }); diff --git a/packages/backend/src/server/api/endpoints/admin/promo/create.ts b/packages/backend/src/server/api/endpoints/admin/promo/create.ts index 2eec5bf0db..68a17867b2 100644 --- a/packages/backend/src/server/api/endpoints/admin/promo/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/promo/create.ts @@ -40,7 +40,7 @@ export default define(meta, paramDef, async (ps, user) => { throw e; }); - const exist = await PromoNotes.findOne(note.id); + const exist = await PromoNotes.findOneBy({ noteId: note.id }); if (exist != null) { throw new ApiError(meta.errors.alreadyPromoted); diff --git a/packages/backend/src/server/api/endpoints/admin/reset-password.ts b/packages/backend/src/server/api/endpoints/admin/reset-password.ts index 1fd5c8d5a5..be4c2dceed 100644 --- a/packages/backend/src/server/api/endpoints/admin/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/admin/reset-password.ts @@ -33,7 +33,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps) => { - const user = await Users.findOne(ps.userId as string); + const user = await Users.findOneBy({ id: ps.userId }); if (user == null) { throw new Error('user not found'); diff --git a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts index a9e5658413..3edae4a85f 100644 --- a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts +++ b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts @@ -23,7 +23,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const report = await AbuseUserReports.findOne(ps.reportId); + const report = await AbuseUserReports.findOneByOrFail({ id: ps.reportId }); if (report == null) { throw new Error('report not found'); @@ -31,7 +31,7 @@ export default define(meta, paramDef, async (ps, me) => { if (ps.forward && report.targetUserHost != null) { const actor = await getInstanceActor(); - const targetUser = await Users.findOneOrFail(report.targetUserId); + const targetUser = await Users.findOneByOrFail({ id: report.targetUserId }); deliver(actor, renderActivity(renderFlag(actor, [targetUser.uri!], report.comment)), targetUser.inbox); } diff --git a/packages/backend/src/server/api/endpoints/admin/server-info.ts b/packages/backend/src/server/api/endpoints/admin/server-info.ts index 8bf1c4341c..9c150420b1 100644 --- a/packages/backend/src/server/api/endpoints/admin/server-info.ts +++ b/packages/backend/src/server/api/endpoints/admin/server-info.ts @@ -1,8 +1,8 @@ import * as os from 'node:os'; import si from 'systeminformation'; -import { getConnection } from 'typeorm'; import define from '../../define.js'; import { redisClient } from '../../../../db/redis.js'; +import { db } from '@/db/postgre.js'; export const meta = { requireCredential: true, @@ -103,7 +103,7 @@ export default define(meta, paramDef, async () => { machine: os.hostname(), os: os.platform(), node: process.version, - psql: await getConnection().query('SHOW server_version').then(x => x[0].server_version), + psql: await db.query('SHOW server_version').then(x => x[0].server_version), redis: redisClient.server_info.redis_version, cpu: { model: os.cpus()[0].model, diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts index a435dcc288..bf6cc16532 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts @@ -23,13 +23,14 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOne(ps.userId as string); + const user = await Users.findOneBy({ id: ps.userId }); if (user == null) { throw new Error('user not found'); } - if ((me.isModerator && !me.isAdmin) && user.isAdmin) { + const _me = await Users.findOneByOrFail({ id: me.id }); + if ((_me.isModerator && !_me.isAdmin) && user.isAdmin) { throw new Error('cannot show info of admin'); } diff --git a/packages/backend/src/server/api/endpoints/admin/silence-user.ts b/packages/backend/src/server/api/endpoints/admin/silence-user.ts index 4a74c3fb00..17b9f3b5a0 100644 --- a/packages/backend/src/server/api/endpoints/admin/silence-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/silence-user.ts @@ -1,6 +1,7 @@ import define from '../../define.js'; import { Users } from '@/models/index.js'; import { insertModerationLog } from '@/services/insert-moderation-log.js'; +import { publishInternalEvent } from '@/services/stream.js'; export const meta = { tags: ['admin'], @@ -19,7 +20,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOne(ps.userId as string); + const user = await Users.findOneBy({ id: ps.userId }); if (user == null) { throw new Error('user not found'); @@ -33,6 +34,8 @@ export default define(meta, paramDef, async (ps, me) => { isSilenced: true, }); + publishInternalEvent('userChangeSilencedState', { id: user.id, isSilenced: true }); + insertModerationLog(me, 'silence', { targetId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts index adaa7b86ce..ed513eda08 100644 --- a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts @@ -23,7 +23,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOne(ps.userId as string); + const user = await Users.findOneBy({ id: ps.userId }); if (user == null) { throw new Error('user not found'); @@ -58,12 +58,12 @@ export default define(meta, paramDef, async (ps, me) => { }); async function unFollowAll(follower: User) { - const followings = await Followings.find({ + const followings = await Followings.findBy({ followerId: follower.id, }); for (const following of followings) { - const followee = await Users.findOne({ + const followee = await Users.findOneBy({ id: following.followeeId, }); diff --git a/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts b/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts index 4e6366aa18..a4b373f5c7 100644 --- a/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts @@ -1,6 +1,7 @@ import define from '../../define.js'; import { Users } from '@/models/index.js'; import { insertModerationLog } from '@/services/insert-moderation-log.js'; +import { publishInternalEvent } from '@/services/stream.js'; export const meta = { tags: ['admin'], @@ -19,7 +20,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOne(ps.userId as string); + const user = await Users.findOneBy({ id: ps.userId }); if (user == null) { throw new Error('user not found'); @@ -29,6 +30,8 @@ export default define(meta, paramDef, async (ps, me) => { isSilenced: false, }); + publishInternalEvent('userChangeSilencedState', { id: user.id, isSilenced: false }); + insertModerationLog(me, 'unsilence', { targetId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts index 3b9e0a94e0..5cf26251be 100644 --- a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts @@ -20,7 +20,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOne(ps.userId as string); + const user = await Users.findOneBy({ id: ps.userId }); if (user == null) { throw new Error('user not found'); diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index 66b634c877..3c39bf0f30 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -1,8 +1,8 @@ import define from '../../define.js'; -import { getConnection } from 'typeorm'; import { Meta } from '@/models/entities/meta.js'; import { insertModerationLog } from '@/services/insert-moderation-log.js'; import { DB_MAX_NOTE_TEXT_LENGTH } from '@/misc/hard-limits.js'; +import { db } from '@/db/postgre.js'; export const meta = { tags: ['admin'], @@ -396,7 +396,7 @@ export default define(meta, paramDef, async (ps, me) => { set.deeplIsPro = ps.deeplIsPro; } - await getConnection().transaction(async transactionalEntityManager => { + await db.transaction(async transactionalEntityManager => { const meta = await transactionalEntityManager.findOne(Meta, { order: { id: 'DESC', diff --git a/packages/backend/src/server/api/endpoints/admin/vacuum.ts b/packages/backend/src/server/api/endpoints/admin/vacuum.ts index 4c04e019da..0546acfacb 100644 --- a/packages/backend/src/server/api/endpoints/admin/vacuum.ts +++ b/packages/backend/src/server/api/endpoints/admin/vacuum.ts @@ -1,6 +1,6 @@ import define from '../../define.js'; -import { getConnection } from 'typeorm'; import { insertModerationLog } from '@/services/insert-moderation-log.js'; +import { db } from '@/db/postgre.js'; export const meta = { tags: ['admin'], @@ -30,7 +30,7 @@ export default define(meta, paramDef, async (ps, me) => { params.push('ANALYZE'); } - getConnection().query('VACUUM ' + params.join(' ')); + db.query('VACUUM ' + params.join(' ')); insertModerationLog(me, 'vacuum', ps); }); diff --git a/packages/backend/src/server/api/endpoints/announcements.ts b/packages/backend/src/server/api/endpoints/announcements.ts index bba66e98cf..222efdcef0 100644 --- a/packages/backend/src/server/api/endpoints/announcements.ts +++ b/packages/backend/src/server/api/endpoints/announcements.ts @@ -69,7 +69,7 @@ export default define(meta, paramDef, async (ps, user) => { const announcements = await query.take(ps.limit).getMany(); if (user) { - const reads = (await AnnouncementReads.find({ + const reads = (await AnnouncementReads.findBy({ userId: user.id, })).map(x => x.announcementId); diff --git a/packages/backend/src/server/api/endpoints/antennas/create.ts b/packages/backend/src/server/api/endpoints/antennas/create.ts index 92cbba817e..7a4923b944 100644 --- a/packages/backend/src/server/api/endpoints/antennas/create.ts +++ b/packages/backend/src/server/api/endpoints/antennas/create.ts @@ -66,7 +66,7 @@ export default define(meta, paramDef, async (ps, user) => { let userGroupJoining; if (ps.src === 'list' && ps.userListId) { - userList = await UserLists.findOne({ + userList = await UserLists.findOneBy({ id: ps.userListId, userId: user.id, }); @@ -75,7 +75,7 @@ export default define(meta, paramDef, async (ps, user) => { throw new ApiError(meta.errors.noSuchUserList); } } else if (ps.src === 'group' && ps.userGroupId) { - userGroupJoining = await UserGroupJoinings.findOne({ + userGroupJoining = await UserGroupJoinings.findOneBy({ userGroupId: ps.userGroupId, userId: user.id, }); @@ -100,7 +100,7 @@ export default define(meta, paramDef, async (ps, user) => { withReplies: ps.withReplies, withFile: ps.withFile, notify: ps.notify, - }).then(x => Antennas.findOneOrFail(x.identifiers[0])); + }).then(x => Antennas.findOneByOrFail(x.identifiers[0])); publishInternalEvent('antennaCreated', antenna); diff --git a/packages/backend/src/server/api/endpoints/antennas/delete.ts b/packages/backend/src/server/api/endpoints/antennas/delete.ts index 4e6b8b3d2e..ced34ba313 100644 --- a/packages/backend/src/server/api/endpoints/antennas/delete.ts +++ b/packages/backend/src/server/api/endpoints/antennas/delete.ts @@ -29,7 +29,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const antenna = await Antennas.findOne({ + const antenna = await Antennas.findOneBy({ id: ps.antennaId, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/antennas/list.ts b/packages/backend/src/server/api/endpoints/antennas/list.ts index accca5de76..c519b452ef 100644 --- a/packages/backend/src/server/api/endpoints/antennas/list.ts +++ b/packages/backend/src/server/api/endpoints/antennas/list.ts @@ -27,7 +27,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const antennas = await Antennas.find({ + const antennas = await Antennas.findBy({ userId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts index f0cb2ba3c0..004e4c131d 100644 --- a/packages/backend/src/server/api/endpoints/antennas/notes.ts +++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts @@ -48,7 +48,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const antenna = await Antennas.findOne({ + const antenna = await Antennas.findOneBy({ id: ps.antennaId, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/antennas/show.ts b/packages/backend/src/server/api/endpoints/antennas/show.ts index 36c4da81b7..dd693789cb 100644 --- a/packages/backend/src/server/api/endpoints/antennas/show.ts +++ b/packages/backend/src/server/api/endpoints/antennas/show.ts @@ -35,7 +35,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { // Fetch the antenna - const antenna = await Antennas.findOne({ + const antenna = await Antennas.findOneBy({ id: ps.antennaId, userId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/antennas/update.ts b/packages/backend/src/server/api/endpoints/antennas/update.ts index a99964555b..edfedc1752 100644 --- a/packages/backend/src/server/api/endpoints/antennas/update.ts +++ b/packages/backend/src/server/api/endpoints/antennas/update.ts @@ -69,7 +69,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { // Fetch the antenna - const antenna = await Antennas.findOne({ + const antenna = await Antennas.findOneBy({ id: ps.antennaId, userId: user.id, }); @@ -82,7 +82,7 @@ export default define(meta, paramDef, async (ps, user) => { let userGroupJoining; if (ps.src === 'list' && ps.userListId) { - userList = await UserLists.findOne({ + userList = await UserLists.findOneBy({ id: ps.userListId, userId: user.id, }); @@ -91,7 +91,7 @@ export default define(meta, paramDef, async (ps, user) => { throw new ApiError(meta.errors.noSuchUserList); } } else if (ps.src === 'group' && ps.userGroupId) { - userGroupJoining = await UserGroupJoinings.findOne({ + userGroupJoining = await UserGroupJoinings.findOneBy({ userGroupId: ps.userGroupId, userId: user.id, }); @@ -115,7 +115,7 @@ export default define(meta, paramDef, async (ps, user) => { notify: ps.notify, }); - publishInternalEvent('antennaUpdated', await Antennas.findOneOrFail(antenna.id)); + publishInternalEvent('antennaUpdated', await Antennas.findOneByOrFail({ id: antenna.id })); return await Antennas.pack(antenna.id); }); diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index 7595c38e8a..3c0c0642e3 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -97,7 +97,7 @@ async function fetchAny(uri: string): Promise | n const type = parts.pop(); if (type === 'notes') { - const note = await Notes.findOne(id); + const note = await Notes.findOneBy({ id }); if (note) { return { @@ -106,7 +106,7 @@ async function fetchAny(uri: string): Promise | n }; } } else if (type === 'users') { - const user = await Users.findOne(id); + const user = await Users.findOneBy({ id }); if (user) { return { @@ -124,8 +124,8 @@ async function fetchAny(uri: string): Promise | n // URI(AP Object id)としてDB検索 { const [user, note] = await Promise.all([ - Users.findOne({ uri: uri }), - Notes.findOne({ uri: uri }), + Users.findOneBy({ uri: uri }), + Notes.findOneBy({ uri: uri }), ]); const packed = await mergePack(user, note); @@ -145,7 +145,7 @@ async function fetchAny(uri: string): Promise | n const type = parts.pop(); if (type === 'notes') { - const note = await Notes.findOne(id); + const note = await Notes.findOneBy({ id }); if (note) { return { @@ -154,7 +154,7 @@ async function fetchAny(uri: string): Promise | n }; } } else if (type === 'users') { - const user = await Users.findOne(id); + const user = await Users.findOneBy({ id }); if (user) { return { @@ -166,8 +166,8 @@ async function fetchAny(uri: string): Promise | n } const [user, note] = await Promise.all([ - Users.findOne({ uri: object.id }), - Notes.findOne({ uri: object.id }), + Users.findOneBy({ uri: object.id }), + Notes.findOneBy({ uri: object.id }), ]); const packed = await mergePack(user, note); diff --git a/packages/backend/src/server/api/endpoints/app/create.ts b/packages/backend/src/server/api/endpoints/app/create.ts index e0cf8632fb..a0a7350822 100644 --- a/packages/backend/src/server/api/endpoints/app/create.ts +++ b/packages/backend/src/server/api/endpoints/app/create.ts @@ -47,7 +47,7 @@ export default define(meta, paramDef, async (ps, user) => { permission, callbackUrl: ps.callbackUrl, secret: secret, - }).then(x => Apps.findOneOrFail(x.identifiers[0])); + }).then(x => Apps.findOneByOrFail(x.identifiers[0])); return await Apps.pack(app, null, { detail: true, diff --git a/packages/backend/src/server/api/endpoints/app/show.ts b/packages/backend/src/server/api/endpoints/app/show.ts index 54e714e193..451969d971 100644 --- a/packages/backend/src/server/api/endpoints/app/show.ts +++ b/packages/backend/src/server/api/endpoints/app/show.ts @@ -33,7 +33,7 @@ export default define(meta, paramDef, async (ps, user, token) => { const isSecure = user != null && token == null; // Lookup app - const ap = await Apps.findOne(ps.appId); + const ap = await Apps.findOneBy({ id: ps.appId }); if (ap == null) { throw new ApiError(meta.errors.noSuchApp); diff --git a/packages/backend/src/server/api/endpoints/auth/accept.ts b/packages/backend/src/server/api/endpoints/auth/accept.ts index 0760eef52b..b5c06792bb 100644 --- a/packages/backend/src/server/api/endpoints/auth/accept.ts +++ b/packages/backend/src/server/api/endpoints/auth/accept.ts @@ -33,7 +33,7 @@ export const paramDef = { export default define(meta, paramDef, async (ps, user) => { // Fetch token const session = await AuthSessions - .findOne({ token: ps.token }); + .findOneBy({ token: ps.token }); if (session == null) { throw new ApiError(meta.errors.noSuchSession); @@ -43,14 +43,14 @@ export default define(meta, paramDef, async (ps, user) => { const accessToken = secureRndstr(32, true); // Fetch exist access token - const exist = await AccessTokens.findOne({ + const exist = await AccessTokens.findOneBy({ appId: session.appId, userId: user.id, }); if (exist == null) { // Lookup app - const app = await Apps.findOneOrFail(session.appId); + const app = await Apps.findOneByOrFail({ id: session.appId }); // Generate Hash const sha256 = crypto.createHash('sha256'); diff --git a/packages/backend/src/server/api/endpoints/auth/session/generate.ts b/packages/backend/src/server/api/endpoints/auth/session/generate.ts index bd571327d2..717c3e5086 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/generate.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/generate.ts @@ -46,7 +46,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps) => { // Lookup app - const app = await Apps.findOne({ + const app = await Apps.findOneBy({ secret: ps.appSecret, }); @@ -63,7 +63,7 @@ export default define(meta, paramDef, async (ps) => { createdAt: new Date(), appId: app.id, token: token, - }).then(x => AuthSessions.findOneOrFail(x.identifiers[0])); + }).then(x => AuthSessions.findOneByOrFail(x.identifiers[0])); return { token: doc.token, diff --git a/packages/backend/src/server/api/endpoints/auth/session/show.ts b/packages/backend/src/server/api/endpoints/auth/session/show.ts index d40c9363c6..3f3a4d1427 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/show.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/show.ts @@ -48,7 +48,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { // Lookup session - const session = await AuthSessions.findOne({ + const session = await AuthSessions.findOneBy({ token: ps.token, }); diff --git a/packages/backend/src/server/api/endpoints/auth/session/userkey.ts b/packages/backend/src/server/api/endpoints/auth/session/userkey.ts index b699c6fa25..89884ed38a 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/userkey.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/userkey.ts @@ -57,7 +57,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps) => { // Lookup app - const app = await Apps.findOne({ + const app = await Apps.findOneBy({ secret: ps.appSecret, }); @@ -66,7 +66,7 @@ export default define(meta, paramDef, async (ps) => { } // Fetch token - const session = await AuthSessions.findOne({ + const session = await AuthSessions.findOneBy({ token: ps.token, appId: app.id, }); @@ -80,7 +80,7 @@ export default define(meta, paramDef, async (ps) => { } // Lookup access token - const accessToken = await AccessTokens.findOneOrFail({ + const accessToken = await AccessTokens.findOneByOrFail({ appId: app.id, userId: session.userId, }); diff --git a/packages/backend/src/server/api/endpoints/blocking/create.ts b/packages/backend/src/server/api/endpoints/blocking/create.ts index c5e73c0131..0540e6ab0f 100644 --- a/packages/backend/src/server/api/endpoints/blocking/create.ts +++ b/packages/backend/src/server/api/endpoints/blocking/create.ts @@ -54,7 +54,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const blocker = await Users.findOneOrFail(user.id); + const blocker = await Users.findOneByOrFail({ id: user.id }); // 自分自身 if (user.id === ps.userId) { @@ -68,7 +68,7 @@ export default define(meta, paramDef, async (ps, user) => { }); // Check if already blocking - const exist = await Blockings.findOne({ + const exist = await Blockings.findOneBy({ blockerId: blocker.id, blockeeId: blockee.id, }); diff --git a/packages/backend/src/server/api/endpoints/blocking/delete.ts b/packages/backend/src/server/api/endpoints/blocking/delete.ts index a45547290c..77e17b3ba9 100644 --- a/packages/backend/src/server/api/endpoints/blocking/delete.ts +++ b/packages/backend/src/server/api/endpoints/blocking/delete.ts @@ -54,7 +54,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const blocker = await Users.findOneOrFail(user.id); + const blocker = await Users.findOneByOrFail({ id: user.id }); // Check if the blockee is yourself if (user.id === ps.userId) { @@ -68,7 +68,7 @@ export default define(meta, paramDef, async (ps, user) => { }); // Check not blocking - const exist = await Blockings.findOne({ + const exist = await Blockings.findOneBy({ blockerId: blocker.id, blockeeId: blockee.id, }); diff --git a/packages/backend/src/server/api/endpoints/channels/create.ts b/packages/backend/src/server/api/endpoints/channels/create.ts index 16456b9c01..94dcfe5023 100644 --- a/packages/backend/src/server/api/endpoints/channels/create.ts +++ b/packages/backend/src/server/api/endpoints/channels/create.ts @@ -40,7 +40,7 @@ export const paramDef = { export default define(meta, paramDef, async (ps, user) => { let banner = null; if (ps.bannerId != null) { - banner = await DriveFiles.findOne({ + banner = await DriveFiles.findOneBy({ id: ps.bannerId, userId: user.id, }); @@ -57,7 +57,7 @@ export default define(meta, paramDef, async (ps, user) => { name: ps.name, description: ps.description || null, bannerId: banner ? banner.id : null, - } as Channel).then(x => Channels.findOneOrFail(x.identifiers[0])); + } as Channel).then(x => Channels.findOneByOrFail(x.identifiers[0])); return await Channels.pack(channel, user); }); diff --git a/packages/backend/src/server/api/endpoints/channels/follow.ts b/packages/backend/src/server/api/endpoints/channels/follow.ts index 4372c283cb..895ffed0bd 100644 --- a/packages/backend/src/server/api/endpoints/channels/follow.ts +++ b/packages/backend/src/server/api/endpoints/channels/follow.ts @@ -30,7 +30,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const channel = await Channels.findOne({ + const channel = await Channels.findOneBy({ id: ps.channelId, }); diff --git a/packages/backend/src/server/api/endpoints/channels/show.ts b/packages/backend/src/server/api/endpoints/channels/show.ts index ea4e013073..87665a9865 100644 --- a/packages/backend/src/server/api/endpoints/channels/show.ts +++ b/packages/backend/src/server/api/endpoints/channels/show.ts @@ -32,7 +32,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const channel = await Channels.findOne({ + const channel = await Channels.findOneBy({ id: ps.channelId, }); diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts index 57a9fa44b8..deaa299013 100644 --- a/packages/backend/src/server/api/endpoints/channels/timeline.ts +++ b/packages/backend/src/server/api/endpoints/channels/timeline.ts @@ -43,7 +43,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const channel = await Channels.findOne({ + const channel = await Channels.findOneBy({ id: ps.channelId, }); diff --git a/packages/backend/src/server/api/endpoints/channels/unfollow.ts b/packages/backend/src/server/api/endpoints/channels/unfollow.ts index 32beb24d6f..e065d897a5 100644 --- a/packages/backend/src/server/api/endpoints/channels/unfollow.ts +++ b/packages/backend/src/server/api/endpoints/channels/unfollow.ts @@ -29,7 +29,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const channel = await Channels.findOne({ + const channel = await Channels.findOneBy({ id: ps.channelId, }); diff --git a/packages/backend/src/server/api/endpoints/channels/update.ts b/packages/backend/src/server/api/endpoints/channels/update.ts index 2f2b4aeeb2..13104f324f 100644 --- a/packages/backend/src/server/api/endpoints/channels/update.ts +++ b/packages/backend/src/server/api/endpoints/channels/update.ts @@ -49,7 +49,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const channel = await Channels.findOne({ + const channel = await Channels.findOneBy({ id: ps.channelId, }); @@ -64,7 +64,7 @@ export default define(meta, paramDef, async (ps, me) => { // eslint:disable-next-line:no-unnecessary-initializer let banner = undefined; if (ps.bannerId != null) { - banner = await DriveFiles.findOne({ + banner = await DriveFiles.findOneBy({ id: ps.bannerId, userId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/clips/add-note.ts b/packages/backend/src/server/api/endpoints/clips/add-note.ts index c630302b98..5d72f5c1bf 100644 --- a/packages/backend/src/server/api/endpoints/clips/add-note.ts +++ b/packages/backend/src/server/api/endpoints/clips/add-note.ts @@ -43,7 +43,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const clip = await Clips.findOne({ + const clip = await Clips.findOneBy({ id: ps.clipId, userId: user.id, }); @@ -57,7 +57,7 @@ export default define(meta, paramDef, async (ps, user) => { throw e; }); - const exist = await ClipNotes.findOne({ + const exist = await ClipNotes.findOneBy({ noteId: note.id, clipId: clip.id, }); diff --git a/packages/backend/src/server/api/endpoints/clips/create.ts b/packages/backend/src/server/api/endpoints/clips/create.ts index 531847d15c..a2dbef12e0 100644 --- a/packages/backend/src/server/api/endpoints/clips/create.ts +++ b/packages/backend/src/server/api/endpoints/clips/create.ts @@ -35,7 +35,7 @@ export default define(meta, paramDef, async (ps, user) => { name: ps.name, isPublic: ps.isPublic, description: ps.description, - }).then(x => Clips.findOneOrFail(x.identifiers[0])); + }).then(x => Clips.findOneByOrFail(x.identifiers[0])); return await Clips.pack(clip); }); diff --git a/packages/backend/src/server/api/endpoints/clips/delete.ts b/packages/backend/src/server/api/endpoints/clips/delete.ts index 675db1d57f..b6c0eb702a 100644 --- a/packages/backend/src/server/api/endpoints/clips/delete.ts +++ b/packages/backend/src/server/api/endpoints/clips/delete.ts @@ -28,7 +28,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const clip = await Clips.findOne({ + const clip = await Clips.findOneBy({ id: ps.clipId, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/clips/list.ts b/packages/backend/src/server/api/endpoints/clips/list.ts index 1c955d64fc..378811eba0 100644 --- a/packages/backend/src/server/api/endpoints/clips/list.ts +++ b/packages/backend/src/server/api/endpoints/clips/list.ts @@ -27,7 +27,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const clips = await Clips.find({ + const clips = await Clips.findBy({ userId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/clips/notes.ts b/packages/backend/src/server/api/endpoints/clips/notes.ts index 2627884ee1..4b6782fca0 100644 --- a/packages/backend/src/server/api/endpoints/clips/notes.ts +++ b/packages/backend/src/server/api/endpoints/clips/notes.ts @@ -45,7 +45,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const clip = await Clips.findOne({ + const clip = await Clips.findOneBy({ id: ps.clipId, }); diff --git a/packages/backend/src/server/api/endpoints/clips/show.ts b/packages/backend/src/server/api/endpoints/clips/show.ts index 0a3b25c94e..c3d73c168d 100644 --- a/packages/backend/src/server/api/endpoints/clips/show.ts +++ b/packages/backend/src/server/api/endpoints/clips/show.ts @@ -35,7 +35,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { // Fetch the clip - const clip = await Clips.findOne({ + const clip = await Clips.findOneBy({ id: ps.clipId, }); diff --git a/packages/backend/src/server/api/endpoints/clips/update.ts b/packages/backend/src/server/api/endpoints/clips/update.ts index 0ac5ccd047..b67d844f6e 100644 --- a/packages/backend/src/server/api/endpoints/clips/update.ts +++ b/packages/backend/src/server/api/endpoints/clips/update.ts @@ -38,7 +38,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { // Fetch the clip - const clip = await Clips.findOne({ + const clip = await Clips.findOneBy({ id: ps.clipId, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts index 3c68beee17..7ffe89a1e5 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts @@ -39,7 +39,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { // Fetch file - const file = await DriveFiles.findOne({ + const file = await DriveFiles.findOneBy({ id: ps.fileId, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts index 7e5cb2498e..80293df5d9 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts @@ -24,7 +24,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const file = await DriveFiles.findOne({ + const file = await DriveFiles.findOneBy({ md5: ps.md5, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/drive/files/delete.ts b/packages/backend/src/server/api/endpoints/drive/files/delete.ts index 5f565a63fb..61c56e6314 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/delete.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/delete.ts @@ -2,7 +2,7 @@ import { deleteFile } from '@/services/drive/delete-file.js'; import { publishDriveStream } from '@/services/stream.js'; import define from '../../../define.js'; import { ApiError } from '../../../error.js'; -import { DriveFiles } from '@/models/index.js'; +import { DriveFiles, Users } from '@/models/index.js'; export const meta = { tags: ['drive'], @@ -36,13 +36,13 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const file = await DriveFiles.findOne(ps.fileId); + const file = await DriveFiles.findOneBy({ id: ps.fileId }); if (file == null) { throw new ApiError(meta.errors.noSuchFile); } - if (!user.isAdmin && !user.isModerator && (file.userId !== user.id)) { + if ((!user.isAdmin && !user.isModerator) && (file.userId !== user.id)) { throw new ApiError(meta.errors.accessDenied); } diff --git a/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts index e45ec633d3..f9b4ea89ea 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts @@ -29,7 +29,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const files = await DriveFiles.find({ + const files = await DriveFiles.findBy({ md5: ps.md5, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/drive/files/find.ts b/packages/backend/src/server/api/endpoints/drive/files/find.ts index 974fc9fbad..4938a69d11 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/find.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/find.ts @@ -1,5 +1,6 @@ import define from '../../../define.js'; import { DriveFiles } from '@/models/index.js'; +import { IsNull } from 'typeorm'; export const meta = { requireCredential: true, @@ -30,10 +31,10 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const files = await DriveFiles.find({ + const files = await DriveFiles.findBy({ name: ps.name, userId: user.id, - folderId: ps.folderId, + folderId: ps.folderId ?? IsNull(), }); return await Promise.all(files.map(file => DriveFiles.pack(file, { self: true }))); diff --git a/packages/backend/src/server/api/endpoints/drive/files/show.ts b/packages/backend/src/server/api/endpoints/drive/files/show.ts index 181365c7e6..c8e9d3dd93 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'], @@ -51,7 +51,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: [{ @@ -70,7 +70,7 @@ export default define(meta, paramDef, async (ps, user) => { throw new ApiError(meta.errors.noSuchFile); } - if (!user.isAdmin && !user.isModerator && (file.userId !== user.id)) { + if ((!user.isAdmin && !user.isModerator) && (file.userId !== user.id)) { throw new ApiError(meta.errors.accessDenied); } diff --git a/packages/backend/src/server/api/endpoints/drive/files/update.ts b/packages/backend/src/server/api/endpoints/drive/files/update.ts index ab8e4aeeb2..4b3f5f2dc9 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/update.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/update.ts @@ -1,7 +1,7 @@ import { publishDriveStream } from '@/services/stream.js'; import define from '../../../define.js'; import { ApiError } from '../../../error.js'; -import { DriveFiles, DriveFolders } from '@/models/index.js'; +import { DriveFiles, DriveFolders, Users } from '@/models/index.js'; import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js'; export const meta = { @@ -58,13 +58,13 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const file = await DriveFiles.findOne(ps.fileId); + const file = await DriveFiles.findOneBy({ id: ps.fileId }); if (file == null) { throw new ApiError(meta.errors.noSuchFile); } - if (!user.isAdmin && !user.isModerator && (file.userId !== user.id)) { + if ((!user.isAdmin && !user.isModerator) && (file.userId !== user.id)) { throw new ApiError(meta.errors.accessDenied); } @@ -81,7 +81,7 @@ export default define(meta, paramDef, async (ps, user) => { if (ps.folderId === null) { file.folderId = null; } else { - const folder = await DriveFolders.findOne({ + const folder = await DriveFolders.findOneBy({ id: ps.folderId, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/drive/folders/create.ts b/packages/backend/src/server/api/endpoints/drive/folders/create.ts index 4ae10f0621..3d7f514c85 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/create.ts @@ -41,7 +41,7 @@ export default define(meta, paramDef, async (ps, user) => { let parent = null; if (ps.parentId) { // Fetch parent folder - parent = await DriveFolders.findOne({ + parent = await DriveFolders.findOneBy({ id: ps.parentId, userId: user.id, }); @@ -58,7 +58,7 @@ export default define(meta, paramDef, async (ps, user) => { name: ps.name, parentId: parent !== null ? parent.id : null, userId: user.id, - }).then(x => DriveFolders.findOneOrFail(x.identifiers[0])); + }).then(x => DriveFolders.findOneByOrFail(x.identifiers[0])); const folderObj = await DriveFolders.pack(folder); diff --git a/packages/backend/src/server/api/endpoints/drive/folders/delete.ts b/packages/backend/src/server/api/endpoints/drive/folders/delete.ts index 4994615cc6..ab9d411ec0 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/delete.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/delete.ts @@ -36,7 +36,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { // Get folder - const folder = await DriveFolders.findOne({ + const folder = await DriveFolders.findOneBy({ id: ps.folderId, userId: user.id, }); @@ -46,8 +46,8 @@ export default define(meta, paramDef, async (ps, user) => { } const [childFoldersCount, childFilesCount] = await Promise.all([ - DriveFolders.count({ parentId: folder.id }), - DriveFiles.count({ folderId: folder.id }), + DriveFolders.countBy({ parentId: folder.id }), + DriveFiles.countBy({ folderId: folder.id }), ]); if (childFoldersCount !== 0 || childFilesCount !== 0) { diff --git a/packages/backend/src/server/api/endpoints/drive/folders/find.ts b/packages/backend/src/server/api/endpoints/drive/folders/find.ts index 9bf0e3d61b..1feab273a1 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/find.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/find.ts @@ -1,5 +1,6 @@ import define from '../../../define.js'; import { DriveFolders } from '@/models/index.js'; +import { IsNull } from 'typeorm'; export const meta = { tags: ['drive'], @@ -30,10 +31,10 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const folders = await DriveFolders.find({ + const folders = await DriveFolders.findBy({ name: ps.name, userId: user.id, - parentId: ps.parentId, + parentId: ps.parentId ?? IsNull(), }); return await Promise.all(folders.map(folder => DriveFolders.pack(folder))); diff --git a/packages/backend/src/server/api/endpoints/drive/folders/show.ts b/packages/backend/src/server/api/endpoints/drive/folders/show.ts index f09816d57a..1e7aa2b16c 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/show.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/show.ts @@ -35,7 +35,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { // Get folder - const folder = await DriveFolders.findOne({ + const folder = await DriveFolders.findOneBy({ id: ps.folderId, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/drive/folders/update.ts b/packages/backend/src/server/api/endpoints/drive/folders/update.ts index c020b243ef..1aa2e84292 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/update.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/update.ts @@ -50,7 +50,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { // Fetch folder - const folder = await DriveFolders.findOne({ + const folder = await DriveFolders.findOneBy({ id: ps.folderId, userId: user.id, }); @@ -68,7 +68,7 @@ export default define(meta, paramDef, async (ps, user) => { folder.parentId = null; } else { // Get parent folder - const parent = await DriveFolders.findOne({ + const parent = await DriveFolders.findOneBy({ id: ps.parentId, userId: user.id, }); @@ -78,9 +78,9 @@ export default define(meta, paramDef, async (ps, user) => { } // Check if the circular reference will occur - async function checkCircle(folderId: any): Promise { + async function checkCircle(folderId: string): Promise { // Fetch folder - const folder2 = await DriveFolders.findOne({ + const folder2 = await DriveFolders.findOneBy({ id: folderId, }); diff --git a/packages/backend/src/server/api/endpoints/endpoint.ts b/packages/backend/src/server/api/endpoints/endpoint.ts index 9db140183c..c174126779 100644 --- a/packages/backend/src/server/api/endpoints/endpoint.ts +++ b/packages/backend/src/server/api/endpoints/endpoint.ts @@ -20,9 +20,9 @@ export default define(meta, paramDef, async (ps) => { const ep = endpoints.find(x => x.name === ps.endpoint); if (ep == null) return null; return { - params: Object.entries(ep.meta.params || {}).map(([k, v]) => ({ + params: Object.entries(ep.params.properties || {}).map(([k, v]) => ({ name: k, - type: v.validator.name === 'ID' ? 'String' : v.validator.name, + type: v.type.charAt(0).toUpperCase() + v.type.slice(1), })), }; }); diff --git a/packages/backend/src/server/api/endpoints/federation/instances.ts b/packages/backend/src/server/api/endpoints/federation/instances.ts index 70fef051ba..e272971763 100644 --- a/packages/backend/src/server/api/endpoints/federation/instances.ts +++ b/packages/backend/src/server/api/endpoints/federation/instances.ts @@ -55,10 +55,6 @@ export default define(meta, paramDef, async (ps, me) => { case '-caughtAt': query.orderBy('instance.caughtAt', 'ASC'); break; case '+lastCommunicatedAt': query.orderBy('instance.lastCommunicatedAt', 'DESC'); break; case '-lastCommunicatedAt': query.orderBy('instance.lastCommunicatedAt', 'ASC'); break; - case '+driveUsage': query.orderBy('instance.driveUsage', 'DESC'); break; - case '-driveUsage': query.orderBy('instance.driveUsage', 'ASC'); break; - case '+driveFiles': query.orderBy('instance.driveFiles', 'DESC'); break; - case '-driveFiles': query.orderBy('instance.driveFiles', 'ASC'); break; default: query.orderBy('instance.id', 'DESC'); break; } diff --git a/packages/backend/src/server/api/endpoints/federation/show-instance.ts b/packages/backend/src/server/api/endpoints/federation/show-instance.ts index 5bfe43fc9c..2fbb8a15cb 100644 --- a/packages/backend/src/server/api/endpoints/federation/show-instance.ts +++ b/packages/backend/src/server/api/endpoints/federation/show-instance.ts @@ -28,7 +28,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { const instance = await Instances - .findOne({ host: toPuny(ps.host) }); + .findOneBy({ host: toPuny(ps.host) }); return instance ? await Instances.pack(instance) : null; }); diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts index 8758a64a39..02a030cd5e 100644 --- a/packages/backend/src/server/api/endpoints/following/create.ts +++ b/packages/backend/src/server/api/endpoints/following/create.ts @@ -81,7 +81,7 @@ export default define(meta, paramDef, async (ps, user) => { }); // Check if already following - const exist = await Followings.findOne({ + const exist = await Followings.findOneBy({ followerId: follower.id, followeeId: followee.id, }); diff --git a/packages/backend/src/server/api/endpoints/following/delete.ts b/packages/backend/src/server/api/endpoints/following/delete.ts index 47efc59b81..2f41b16e9a 100644 --- a/packages/backend/src/server/api/endpoints/following/delete.ts +++ b/packages/backend/src/server/api/endpoints/following/delete.ts @@ -68,7 +68,7 @@ export default define(meta, paramDef, async (ps, user) => { }); // Check not following - const exist = await Followings.findOne({ + const exist = await Followings.findOneBy({ followerId: follower.id, followeeId: followee.id, }); diff --git a/packages/backend/src/server/api/endpoints/following/invalidate.ts b/packages/backend/src/server/api/endpoints/following/invalidate.ts index 24d8256ca6..18ec5affe8 100644 --- a/packages/backend/src/server/api/endpoints/following/invalidate.ts +++ b/packages/backend/src/server/api/endpoints/following/invalidate.ts @@ -68,7 +68,7 @@ export default define(meta, paramDef, async (ps, user) => { }); // Check not following - const exist = await Followings.findOne({ + const exist = await Followings.findOneBy({ followerId: follower.id, followeeId: followee.id, }); diff --git a/packages/backend/src/server/api/endpoints/following/requests/list.ts b/packages/backend/src/server/api/endpoints/following/requests/list.ts index 3b60b89b3c..a8f42c481d 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/list.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/list.ts @@ -43,7 +43,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const reqs = await FollowRequests.find({ + const reqs = await FollowRequests.findBy({ followeeId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts index eb6c0f3eb1..8074a3b34f 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts @@ -45,7 +45,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { const files = (await Promise.all(ps.fileIds.map(fileId => - DriveFiles.findOne({ + DriveFiles.findOneBy({ id: fileId, userId: user.id, }) @@ -64,7 +64,7 @@ export default define(meta, paramDef, async (ps, user) => { userId: user.id, isSensitive: ps.isSensitive, fileIds: files.map(file => file.id), - })).then(x => GalleryPosts.findOneOrFail(x.identifiers[0])); + })).then(x => GalleryPosts.findOneByOrFail(x.identifiers[0])); return await GalleryPosts.pack(post, user); }); diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts b/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts index f8bf785ee6..b00ee0e2ae 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts @@ -28,7 +28,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const post = await GalleryPosts.findOne({ + const post = await GalleryPosts.findOneBy({ id: ps.postId, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts index d154bfc3c6..b858114aec 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts @@ -41,7 +41,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const post = await GalleryPosts.findOne(ps.postId); + const post = await GalleryPosts.findOneBy({ id: ps.postId }); if (post == null) { throw new ApiError(meta.errors.noSuchPost); } @@ -51,7 +51,7 @@ export default define(meta, paramDef, async (ps, user) => { } // if already liked - const exist = await GalleryLikes.findOne({ + const exist = await GalleryLikes.findOneBy({ postId: post.id, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/show.ts b/packages/backend/src/server/api/endpoints/gallery/posts/show.ts index 5b4594070c..4f6dafd7cb 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/show.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/show.ts @@ -32,7 +32,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const post = await GalleryPosts.findOne({ + const post = await GalleryPosts.findOneBy({ id: ps.postId, }); diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts b/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts index b00008a864..d136239e5e 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts @@ -34,12 +34,12 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const post = await GalleryPosts.findOne(ps.postId); + const post = await GalleryPosts.findOneBy({ id: ps.postId }); if (post == null) { throw new ApiError(meta.errors.noSuchPost); } - const exist = await GalleryLikes.findOne({ + const exist = await GalleryLikes.findOneBy({ postId: post.id, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts index 123794d08c..82fe38078e 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts @@ -45,7 +45,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { const files = (await Promise.all(ps.fileIds.map(fileId => - DriveFiles.findOne({ + DriveFiles.findOneBy({ id: fileId, userId: user.id, }) @@ -66,7 +66,7 @@ export default define(meta, paramDef, async (ps, user) => { fileIds: files.map(file => file.id), }); - const post = await GalleryPosts.findOneOrFail(ps.postId); + const post = await GalleryPosts.findOneByOrFail({ id: ps.postId }); return await GalleryPosts.pack(post, user); }); diff --git a/packages/backend/src/server/api/endpoints/get-online-users-count.ts b/packages/backend/src/server/api/endpoints/get-online-users-count.ts index 80a2334cfa..b0c1225bee 100644 --- a/packages/backend/src/server/api/endpoints/get-online-users-count.ts +++ b/packages/backend/src/server/api/endpoints/get-online-users-count.ts @@ -17,7 +17,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async () => { - const count = await Users.count({ + const count = await Users.countBy({ lastActiveDate: MoreThan(new Date(Date.now() - USER_ONLINE_THRESHOLD)), }); diff --git a/packages/backend/src/server/api/endpoints/hashtags/show.ts b/packages/backend/src/server/api/endpoints/hashtags/show.ts index 6e6afa4f13..5b78f6ac7f 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/show.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/show.ts @@ -33,7 +33,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const hashtag = await Hashtags.findOne({ name: normalizeForSearch(ps.tag) }); + const hashtag = await Hashtags.findOneBy({ name: normalizeForSearch(ps.tag) }); if (hashtag == null) { throw new ApiError(meta.errors.noSuchHashtag); } diff --git a/packages/backend/src/server/api/endpoints/i/2fa/done.ts b/packages/backend/src/server/api/endpoints/i/2fa/done.ts index 70478430db..35806b2bc3 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/done.ts @@ -20,7 +20,7 @@ export const paramDef = { export default define(meta, paramDef, async (ps, user) => { const token = ps.token.replace(/\s/g, ''); - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); if (profile.twoFactorTempSecret == null) { throw new Error('二段階認証の設定が開始されていません'); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts index f33237c8bf..0116a55fb7 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts @@ -35,7 +35,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); // Compare password const same = await bcrypt.compare(ps.password, profile.password!); @@ -96,7 +96,7 @@ export default define(meta, paramDef, async (ps, user) => { }); if (!verificationData.valid) throw new Error('signature invalid'); - const attestationChallenge = await AttestationChallenges.findOne({ + const attestationChallenge = await AttestationChallenges.findOneBy({ userId: user.id, id: ps.challengeId, registrationChallenge: true, diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts index 0c4c99271e..e906b82043 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts @@ -24,7 +24,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); // Compare password const same = await bcrypt.compare(ps.password, profile.password!); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register.ts b/packages/backend/src/server/api/endpoints/i/2fa/register.ts index 7951e393b8..d5e1b19e54 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register.ts @@ -21,7 +21,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); // Compare password const same = await bcrypt.compare(ps.password, profile.password!); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts index 2b69b1f8c3..eb2f75308d 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts @@ -20,7 +20,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); // Compare password const same = await bcrypt.compare(ps.password, profile.password!); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts index c5633f68b1..45e7a98639 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts @@ -18,7 +18,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); // Compare password const same = await bcrypt.compare(ps.password, profile.password!); diff --git a/packages/backend/src/server/api/endpoints/i/change-password.ts b/packages/backend/src/server/api/endpoints/i/change-password.ts index 16509d2dcf..f9f6a33a80 100644 --- a/packages/backend/src/server/api/endpoints/i/change-password.ts +++ b/packages/backend/src/server/api/endpoints/i/change-password.ts @@ -19,7 +19,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); // Compare password const same = await bcrypt.compare(ps.currentPassword, profile.password!); diff --git a/packages/backend/src/server/api/endpoints/i/delete-account.ts b/packages/backend/src/server/api/endpoints/i/delete-account.ts index 8cb6b6a631..184005eb53 100644 --- a/packages/backend/src/server/api/endpoints/i/delete-account.ts +++ b/packages/backend/src/server/api/endpoints/i/delete-account.ts @@ -21,8 +21,8 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const profile = await UserProfiles.findOneOrFail(user.id); - const userDetailed = await Users.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); + const userDetailed = await Users.findOneByOrFail({ id: user.id }); if (userDetailed.isDeleted) { return; } diff --git a/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts b/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts index bc3e0aff47..e7d7518c5b 100644 --- a/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts +++ b/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts @@ -29,7 +29,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { return { - count: await MutedNotes.count({ + count: await MutedNotes.countBy({ userId: user.id, reason: 'word', }), diff --git a/packages/backend/src/server/api/endpoints/i/import-blocking.ts b/packages/backend/src/server/api/endpoints/i/import-blocking.ts index c70704f9a8..0bcbf37ddd 100644 --- a/packages/backend/src/server/api/endpoints/i/import-blocking.ts +++ b/packages/backend/src/server/api/endpoints/i/import-blocking.ts @@ -50,7 +50,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const file = await DriveFiles.findOne(ps.fileId); + const file = await DriveFiles.findOneBy({ id: ps.fileId }); if (file == null) throw new ApiError(meta.errors.noSuchFile); //if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType); diff --git a/packages/backend/src/server/api/endpoints/i/import-following.ts b/packages/backend/src/server/api/endpoints/i/import-following.ts index 7e9175cbf8..ee2abbea19 100644 --- a/packages/backend/src/server/api/endpoints/i/import-following.ts +++ b/packages/backend/src/server/api/endpoints/i/import-following.ts @@ -49,7 +49,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const file = await DriveFiles.findOne(ps.fileId); + const file = await DriveFiles.findOneBy({ id: ps.fileId }); if (file == null) throw new ApiError(meta.errors.noSuchFile); //if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType); diff --git a/packages/backend/src/server/api/endpoints/i/import-muting.ts b/packages/backend/src/server/api/endpoints/i/import-muting.ts index abbf07212e..b3b3b39238 100644 --- a/packages/backend/src/server/api/endpoints/i/import-muting.ts +++ b/packages/backend/src/server/api/endpoints/i/import-muting.ts @@ -50,7 +50,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const file = await DriveFiles.findOne(ps.fileId); + const file = await DriveFiles.findOneBy({ id: ps.fileId }); if (file == null) throw new ApiError(meta.errors.noSuchFile); //if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType); diff --git a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts index be162817f1..64f5ec05fd 100644 --- a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts +++ b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts @@ -49,7 +49,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const file = await DriveFiles.findOne(ps.fileId); + const file = await DriveFiles.findOneBy({ id: ps.fileId }); if (file == null) throw new ApiError(meta.errors.noSuchFile); //if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType); diff --git a/packages/backend/src/server/api/endpoints/i/notifications.ts b/packages/backend/src/server/api/endpoints/i/notifications.ts index 7d9bd44d1d..6ea8cb3574 100644 --- a/packages/backend/src/server/api/endpoints/i/notifications.ts +++ b/packages/backend/src/server/api/endpoints/i/notifications.ts @@ -70,6 +70,8 @@ export default define(meta, paramDef, async (ps, user) => { .andWhere(`notification.notifieeId = :meId`, { meId: user.id }) .leftJoinAndSelect('notification.notifier', 'notifier') .leftJoinAndSelect('notification.note', 'note') + .leftJoinAndSelect('notifier.avatar', 'notifierAvatar') + .leftJoinAndSelect('notifier.banner', 'notifierBanner') .leftJoinAndSelect('note.user', 'user') .leftJoinAndSelect('user.avatar', 'avatar') .leftJoinAndSelect('user.banner', 'banner') diff --git a/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts b/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts index 2e291a34a0..7ff6409caf 100644 --- a/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts +++ b/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts @@ -26,7 +26,7 @@ export default define(meta, paramDef, async (ps, user) => { isRead: true, }); - const joinings = await UserGroupJoinings.find({ userId: user.id }); + const joinings = await UserGroupJoinings.findBy({ userId: user.id }); await Promise.all(joinings.map(j => MessagingMessages.createQueryBuilder().update() .set({ diff --git a/packages/backend/src/server/api/endpoints/i/read-announcement.ts b/packages/backend/src/server/api/endpoints/i/read-announcement.ts index 647fa77fa4..45b6e98c86 100644 --- a/packages/backend/src/server/api/endpoints/i/read-announcement.ts +++ b/packages/backend/src/server/api/endpoints/i/read-announcement.ts @@ -31,14 +31,14 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { // Check if announcement exists - const announcement = await Announcements.findOne(ps.announcementId); + const announcement = await Announcements.findOneBy({ id: ps.announcementId }); if (announcement == null) { throw new ApiError(meta.errors.noSuchAnnouncement); } // Check if already read - const read = await AnnouncementReads.findOne({ + const read = await AnnouncementReads.findOneBy({ announcementId: ps.announcementId, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts index 771c98b212..af929b04e8 100644 --- a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts +++ b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts @@ -1,5 +1,5 @@ import bcrypt from 'bcryptjs'; -import { publishMainStream, publishUserEvent } from '@/services/stream.js'; +import { publishInternalEvent, publishMainStream, publishUserEvent } from '@/services/stream.js'; import generateUserToken from '../../common/generate-native-user-token.js'; import define from '../../define.js'; import { Users, UserProfiles } from '@/models/index.js'; @@ -20,7 +20,10 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const profile = await UserProfiles.findOneOrFail(user.id); + const freshUser = await Users.findOneByOrFail({ id: user.id }); + const oldToken = freshUser.token; + + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); // Compare password const same = await bcrypt.compare(ps.password, profile.password!); @@ -29,14 +32,14 @@ export default define(meta, paramDef, async (ps, user) => { throw new Error('incorrect password'); } - // Generate secret - const secret = generateUserToken(); + const newToken = generateUserToken(); await Users.update(user.id, { - token: secret, + token: newToken, }); // Publish event + publishInternalEvent('userTokenRegenerated', { id: user.id, oldToken, newToken }); publishMainStream(user.id, 'myTokenRegenerated'); // Terminate streaming diff --git a/packages/backend/src/server/api/endpoints/i/revoke-token.ts b/packages/backend/src/server/api/endpoints/i/revoke-token.ts index b957fd0796..c692453794 100644 --- a/packages/backend/src/server/api/endpoints/i/revoke-token.ts +++ b/packages/backend/src/server/api/endpoints/i/revoke-token.ts @@ -18,7 +18,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const token = await AccessTokens.findOne(ps.tokenId); + const token = await AccessTokens.findOneBy({ id: ps.tokenId }); if (token) { await AccessTokens.delete({ diff --git a/packages/backend/src/server/api/endpoints/i/update-email.ts b/packages/backend/src/server/api/endpoints/i/update-email.ts index 389ff1b81d..3318078523 100644 --- a/packages/backend/src/server/api/endpoints/i/update-email.ts +++ b/packages/backend/src/server/api/endpoints/i/update-email.ts @@ -45,7 +45,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); // Compare password const same = await bcrypt.compare(ps.password, profile.password!); diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index 85d0a62548..b2964e68c7 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -121,13 +121,13 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, _user, token) => { - const user = await Users.findOneOrFail(_user.id); + const user = await Users.findOneByOrFail({ id: _user.id }); const isSecure = token == null; const updates = {} as Partial; const profileUpdates = {} as Partial; - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); if (ps.name !== undefined) updates.name = ps.name; if (ps.description !== undefined) profileUpdates.description = ps.description; @@ -171,21 +171,21 @@ export default define(meta, paramDef, async (ps, _user, token) => { if (ps.emailNotificationTypes !== undefined) profileUpdates.emailNotificationTypes = ps.emailNotificationTypes; if (ps.avatarId) { - const avatar = await DriveFiles.findOne(ps.avatarId); + const avatar = await DriveFiles.findOneBy({ id: ps.avatarId }); if (avatar == null || avatar.userId !== user.id) throw new ApiError(meta.errors.noSuchAvatar); if (!avatar.type.startsWith('image/')) throw new ApiError(meta.errors.avatarNotAnImage); } if (ps.bannerId) { - const banner = await DriveFiles.findOne(ps.bannerId); + const banner = await DriveFiles.findOneBy({ id: ps.bannerId }); if (banner == null || banner.userId !== user.id) throw new ApiError(meta.errors.noSuchBanner); if (!banner.type.startsWith('image/')) throw new ApiError(meta.errors.bannerNotAnImage); } if (ps.pinnedPageId) { - const page = await Pages.findOne(ps.pinnedPageId); + const page = await Pages.findOneBy({ id: ps.pinnedPageId }); if (page == null || page.userId !== user.id) throw new ApiError(meta.errors.noSuchPage); @@ -238,7 +238,7 @@ export default define(meta, paramDef, async (ps, _user, token) => { // Publish meUpdated event publishMainStream(user.id, 'meUpdated', iObj); - publishUserEvent(user.id, 'updateUserProfile', await UserProfiles.findOne(user.id)); + publishUserEvent(user.id, 'updateUserProfile', await UserProfiles.findOneBy({ userId: user.id })); // 鍵垢を解除したとき、溜まっていたフォローリクエストがあるならすべて承認 if (user.isLocked && ps.isLocked === false) { diff --git a/packages/backend/src/server/api/endpoints/messaging/history.ts b/packages/backend/src/server/api/endpoints/messaging/history.ts index 14de4e1028..ea0600d0e4 100644 --- a/packages/backend/src/server/api/endpoints/messaging/history.ts +++ b/packages/backend/src/server/api/endpoints/messaging/history.ts @@ -32,11 +32,11 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const mute = await Mutings.find({ + const mute = await Mutings.findBy({ muterId: user.id, }); - const groups = ps.group ? await UserGroupJoinings.find({ + const groups = ps.group ? await UserGroupJoinings.findBy({ userId: user.id, }).then(xs => xs.map(x => x.userGroupId)) : []; diff --git a/packages/backend/src/server/api/endpoints/messaging/messages.ts b/packages/backend/src/server/api/endpoints/messaging/messages.ts index 49ace21600..9760709c29 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages.ts @@ -97,14 +97,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, }); diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/create.ts b/packages/backend/src/server/api/endpoints/messaging/messages/create.ts index a9b926c4fb..8c1226b0f4 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/create.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/create.ts @@ -77,8 +77,8 @@ export const paramDef = { // 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 +93,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 +102,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 +121,7 @@ export default define(meta, paramDef, async (ps, user) => { let file = null; if (ps.fileId != null) { - file = await DriveFiles.findOne({ + file = await DriveFiles.findOneBy({ id: ps.fileId, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts b/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts index a0945af510..f66d75873c 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts @@ -36,7 +36,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const message = await MessagingMessages.findOne({ + const message = await MessagingMessages.findOneBy({ id: ps.messageId, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/read.ts b/packages/backend/src/server/api/endpoints/messaging/messages/read.ts index 8d38e509ac..db12ae922c 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/read.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/read.ts @@ -29,7 +29,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const message = await MessagingMessages.findOne(ps.messageId); + const message = await MessagingMessages.findOneBy({ id: ps.messageId }); if (message == null) { throw new ApiError(meta.errors.noSuchMessage); diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index 6231c35ab9..057d22f33b 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 = { @@ -64,11 +64,6 @@ export const meta = { optional: false, nullable: false, default: 'https://github.com/misskey-dev/misskey/issues/new', }, - secure: { - type: 'boolean', - optional: false, nullable: false, - default: false, - }, defaultDarkTheme: { type: 'string', optional: false, nullable: true, @@ -295,151 +290,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; @@ -458,7 +308,7 @@ export default define(meta, paramDef, async (ps, me) => { const emojis = await Emojis.find({ where: { - host: null, + host: IsNull(), }, order: { category: 'ASC', @@ -489,9 +339,6 @@ export default define(meta, paramDef, async (ps, me) => { tosUrl: instance.ToSUrl, repositoryUrl: instance.repositoryUrl, feedbackUrl: instance.feedbackUrl, - - secure: config.https != null, - disableRegistration: instance.disableRegistration, disableLocalTimeline: instance.disableLocalTimeline, disableGlobalTimeline: instance.disableGlobalTimeline, @@ -535,8 +382,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, } : {}), }; @@ -560,45 +407,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 0178aab143..0c3a3453f1 100644 --- a/packages/backend/src/server/api/endpoints/mute/create.ts +++ b/packages/backend/src/server/api/endpoints/mute/create.ts @@ -38,6 +38,7 @@ export const paramDef = { type: 'object', properties: { userId: { type: 'string', format: 'misskey:id' }, + expiresAt: { type: 'integer', nullable: true }, }, required: ['userId'], } as const; @@ -58,7 +59,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, }); @@ -67,10 +68,15 @@ export default define(meta, paramDef, async (ps, user) => { throw new ApiError(meta.errors.alreadyMuting); } + if (ps.expiresAt && ps.expiresAt <= Date.now()) { + return; + } + // Create mute await Mutings.insert({ id: genId(), createdAt: new Date(), + expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : null, muterId: muter.id, muteeId: mutee.id, } as Muting); diff --git a/packages/backend/src/server/api/endpoints/mute/delete.ts b/packages/backend/src/server/api/endpoints/mute/delete.ts index a8cf2a6667..0b173dbe24 100644 --- a/packages/backend/src/server/api/endpoints/mute/delete.ts +++ b/packages/backend/src/server/api/endpoints/mute/delete.ts @@ -56,7 +56,7 @@ export default define(meta, paramDef, async (ps, user) => { }); // Check not muting - const exist = await Mutings.findOne({ + const exist = await Mutings.findOneBy({ muterId: muter.id, muteeId: mutee.id, }); diff --git a/packages/backend/src/server/api/endpoints/notes/clips.ts b/packages/backend/src/server/api/endpoints/notes/clips.ts index 9a863b7148..8683a7f75a 100644 --- a/packages/backend/src/server/api/endpoints/notes/clips.ts +++ b/packages/backend/src/server/api/endpoints/notes/clips.ts @@ -43,11 +43,11 @@ export default define(meta, paramDef, async (ps, me) => { throw e; }); - const clipNotes = await ClipNotes.find({ + const clipNotes = await ClipNotes.findBy({ noteId: note.id, }); - const clips = await Clips.find({ + const clips = await Clips.findBy({ id: In(clipNotes.map(x => x.clipId)), isPublic: true, }); diff --git a/packages/backend/src/server/api/endpoints/notes/conversation.ts b/packages/backend/src/server/api/endpoints/notes/conversation.ts index 2552c0f99d..8f5d21db60 100644 --- a/packages/backend/src/server/api/endpoints/notes/conversation.ts +++ b/packages/backend/src/server/api/endpoints/notes/conversation.ts @@ -50,7 +50,7 @@ export default define(meta, paramDef, async (ps, user) => { async function get(id: any) { i++; - const p = await Notes.findOne(id); + const p = await Notes.findOneBy({ id }); if (p == null) return; if (i > ps.offset!) { diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index e4a9b28891..961983f5f4 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -130,7 +130,7 @@ export const paramDef = { 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 +138,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 +158,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 +168,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 +184,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, }); @@ -211,7 +211,7 @@ export default define(meta, paramDef, async (ps, user) => { let channel: Channel | undefined; if (ps.channelId != null) { - channel = await Channels.findOne(ps.channelId); + channel = await Channels.findOneBy({ id: ps.channelId }); if (channel == null) { throw new ApiError(meta.errors.noSuchChannel); diff --git a/packages/backend/src/server/api/endpoints/notes/delete.ts b/packages/backend/src/server/api/endpoints/notes/delete.ts index 22ff2275ca..804e146fa4 100644 --- a/packages/backend/src/server/api/endpoints/notes/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/delete.ts @@ -48,10 +48,10 @@ export default define(meta, paramDef, async (ps, user) => { throw e; }); - if (!user.isAdmin && !user.isModerator && (note.userId !== user.id)) { + if ((!user.isAdmin && !user.isModerator) && (note.userId !== user.id)) { throw new ApiError(meta.errors.accessDenied); } // この操作を行うのが投稿者とは限らない(例えばモデレーター)ため - await deleteNote(await Users.findOneOrFail(note.userId), note); + await deleteNote(await Users.findOneByOrFail({ id: note.userId }), note); }); diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts index bcc2c44c02..41dc5ac8e1 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts @@ -43,7 +43,7 @@ export default define(meta, paramDef, async (ps, user) => { }); // if already favorited - const exist = await NoteFavorites.findOne({ + const exist = await NoteFavorites.findOneBy({ noteId: note.id, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts index d41fab22d3..a48f7a0aa8 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts @@ -42,7 +42,7 @@ export default define(meta, paramDef, async (ps, user) => { }); // if already favorited - const exist = await NoteFavorites.findOne({ + const exist = await NoteFavorites.findOneBy({ noteId: note.id, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts index 26aaa0919c..09a8194665 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'; diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts index 9bcb64b656..7c9c122963 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'; @@ -56,7 +56,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { const m = await fetchMeta(); - if (m.disableLocalTimeline && !user.isAdmin && !user.isModerator) { + if (m.disableLocalTimeline && (!user.isAdmin && !user.isModerator)) { throw new ApiError(meta.errors.stlDisabled); } diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts index 12fc88b1fd..bb0bbe2a20 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'; diff --git a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts index bdd1aeecd4..28bfade2f0 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts @@ -64,7 +64,7 @@ export default define(meta, paramDef, async (ps, user) => { if (polls.length === 0) return []; - const notes = await Notes.find({ + const notes = await Notes.findBy({ id: In(polls.map(poll => poll.noteId)), }); diff --git a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts index ef52d03664..6380b331f2 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts @@ -83,7 +83,7 @@ export default define(meta, paramDef, async (ps, user) => { // Check blocking if (note.userId !== user.id) { - const block = await Blockings.findOne({ + const block = await Blockings.findOneBy({ blockerId: note.userId, blockeeId: user.id, }); @@ -92,7 +92,7 @@ export default define(meta, paramDef, async (ps, user) => { } } - const poll = await Polls.findOneOrFail({ noteId: note.id }); + const poll = await Polls.findOneByOrFail({ noteId: note.id }); if (poll.expiresAt && poll.expiresAt < createdAt) { throw new ApiError(meta.errors.alreadyExpired); @@ -103,7 +103,7 @@ export default define(meta, paramDef, async (ps, user) => { } // if already voted - const exist = await PollVotes.find({ + const exist = await PollVotes.findBy({ noteId: note.id, userId: user.id, }); @@ -125,7 +125,7 @@ export default define(meta, paramDef, async (ps, user) => { noteId: note.id, userId: user.id, choice: ps.choice, - }).then(x => PollVotes.findOneOrFail(x.identifiers[0])); + }).then(x => PollVotes.findOneByOrFail(x.identifiers[0])); // Increment votes count const index = ps.choice + 1; // In SQL, array index is 1 based @@ -144,7 +144,7 @@ export default define(meta, paramDef, async (ps, user) => { }); // Fetch watchers - NoteWatchings.find({ + NoteWatchings.findBy({ noteId: note.id, userId: Not(user.id), }).then(watchers => { @@ -159,7 +159,7 @@ export default define(meta, paramDef, async (ps, user) => { // リモート投票の場合リプライ送信 if (note.userHost != null) { - const pollOwner = await Users.findOneOrFail(note.userId) as IRemoteUser; + const pollOwner = await Users.findOneByOrFail({ id: note.userId }) as IRemoteUser; deliver(user, renderActivity(await renderVote(user, vote, note, poll, pollOwner)), pollOwner.inbox); } diff --git a/packages/backend/src/server/api/endpoints/notes/reactions.ts b/packages/backend/src/server/api/endpoints/notes/reactions.ts index 43e5d1ef6f..3555424fa6 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions.ts @@ -1,5 +1,4 @@ import define from '../../define.js'; -import { getNote } from '../../common/getters.js'; import { ApiError } from '../../error.js'; import { NoteReactions } from '@/models/index.js'; import { DeepPartial } from 'typeorm'; @@ -44,13 +43,8 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; - }); - const query = { - noteId: note.id, + noteId: ps.noteId, } as DeepPartial; if (ps.type) { diff --git a/packages/backend/src/server/api/endpoints/notes/state.ts b/packages/backend/src/server/api/endpoints/notes/state.ts index 6fdb8e88fb..069f11fa4a 100644 --- a/packages/backend/src/server/api/endpoints/notes/state.ts +++ b/packages/backend/src/server/api/endpoints/notes/state.ts @@ -36,7 +36,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const note = await Notes.findOneOrFail(ps.noteId); + const note = await Notes.findOneByOrFail({ id: ps.noteId }); const [favorite, watching, threadMuting] = await Promise.all([ NoteFavorites.count({ diff --git a/packages/backend/src/server/api/endpoints/notes/unrenote.ts b/packages/backend/src/server/api/endpoints/notes/unrenote.ts index a9aadba338..5e8c31eaf8 100644 --- a/packages/backend/src/server/api/endpoints/notes/unrenote.ts +++ b/packages/backend/src/server/api/endpoints/notes/unrenote.ts @@ -42,12 +42,12 @@ export default define(meta, paramDef, async (ps, user) => { throw e; }); - const renotes = await Notes.find({ + const renotes = await Notes.findBy({ userId: user.id, renoteId: note.id, }); for (const note of renotes) { - deleteNote(await Users.findOneOrFail(user.id), note); + deleteNote(await Users.findOneByOrFail({ id: user.id }), note); } }); diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts index 0829d0e4c1..866e306d8d 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 @@ -49,7 +49,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const list = await UserLists.findOne({ + const list = await UserLists.findOneBy({ id: ps.listId, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/notifications/read.ts b/packages/backend/src/server/api/endpoints/notifications/read.ts index 34f4c155fa..c7bc5dc0a5 100644 --- a/packages/backend/src/server/api/endpoints/notifications/read.ts +++ b/packages/backend/src/server/api/endpoints/notifications/read.ts @@ -30,7 +30,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const notification = await Notifications.findOne({ + const notification = await Notifications.findOneBy({ notifieeId: user.id, id: ps.notificationId, }); diff --git a/packages/backend/src/server/api/endpoints/page-push.ts b/packages/backend/src/server/api/endpoints/page-push.ts index acaa118470..7096aaa3d3 100644 --- a/packages/backend/src/server/api/endpoints/page-push.ts +++ b/packages/backend/src/server/api/endpoints/page-push.ts @@ -28,7 +28,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const page = await Pages.findOne(ps.pageId); + const page = await Pages.findOneBy({ id: ps.pageId }); if (page == null) { throw new ApiError(meta.errors.noSuchPage); } diff --git a/packages/backend/src/server/api/endpoints/pages/create.ts b/packages/backend/src/server/api/endpoints/pages/create.ts index 7cac530606..c171cd39f5 100644 --- a/packages/backend/src/server/api/endpoints/pages/create.ts +++ b/packages/backend/src/server/api/endpoints/pages/create.ts @@ -62,7 +62,7 @@ export const paramDef = { export default define(meta, paramDef, async (ps, user) => { let eyeCatchingImage = null; if (ps.eyeCatchingImageId != null) { - eyeCatchingImage = await DriveFiles.findOne({ + eyeCatchingImage = await DriveFiles.findOneBy({ id: ps.eyeCatchingImageId, userId: user.id, }); @@ -72,7 +72,7 @@ export default define(meta, paramDef, async (ps, user) => { } } - await Pages.find({ + await Pages.findBy({ userId: user.id, name: ps.name, }).then(result => { @@ -97,7 +97,7 @@ export default define(meta, paramDef, async (ps, user) => { alignCenter: ps.alignCenter, hideTitleWhenPinned: ps.hideTitleWhenPinned, font: ps.font, - })).then(x => Pages.findOneOrFail(x.identifiers[0])); + })).then(x => Pages.findOneByOrFail(x.identifiers[0])); return await Pages.pack(page); }); diff --git a/packages/backend/src/server/api/endpoints/pages/delete.ts b/packages/backend/src/server/api/endpoints/pages/delete.ts index ddf691f53c..e35ad9ebf2 100644 --- a/packages/backend/src/server/api/endpoints/pages/delete.ts +++ b/packages/backend/src/server/api/endpoints/pages/delete.ts @@ -34,7 +34,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const page = await Pages.findOne(ps.pageId); + const page = await Pages.findOneBy({ id: ps.pageId }); if (page == null) { throw new ApiError(meta.errors.noSuchPage); } diff --git a/packages/backend/src/server/api/endpoints/pages/like.ts b/packages/backend/src/server/api/endpoints/pages/like.ts index cab78e576c..20793db988 100644 --- a/packages/backend/src/server/api/endpoints/pages/like.ts +++ b/packages/backend/src/server/api/endpoints/pages/like.ts @@ -41,7 +41,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const page = await Pages.findOne(ps.pageId); + const page = await Pages.findOneBy({ id: ps.pageId }); if (page == null) { throw new ApiError(meta.errors.noSuchPage); } @@ -51,7 +51,7 @@ export default define(meta, paramDef, async (ps, user) => { } // if already liked - const exist = await PageLikes.findOne({ + const exist = await PageLikes.findOneBy({ pageId: page.id, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/pages/show.ts b/packages/backend/src/server/api/endpoints/pages/show.ts index 4e3facae5b..1c218acfd4 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'], @@ -38,14 +39,14 @@ export default define(meta, paramDef, async (ps, user) => { let page: Page | undefined; if (ps.pageId) { - page = await Pages.findOne(ps.pageId); + page = await Pages.findOneBy({ id: ps.pageId }); } else if (ps.name && ps.username) { - const author = await Users.findOne({ - host: null, + const author = await Users.findOneBy({ + host: IsNull(), usernameLower: ps.username.toLowerCase(), }); if (author) { - page = await Pages.findOne({ + page = await Pages.findOneBy({ name: ps.name, userId: author.id, }); diff --git a/packages/backend/src/server/api/endpoints/pages/unlike.ts b/packages/backend/src/server/api/endpoints/pages/unlike.ts index 31cd1a3359..636f3c7149 100644 --- a/packages/backend/src/server/api/endpoints/pages/unlike.ts +++ b/packages/backend/src/server/api/endpoints/pages/unlike.ts @@ -34,12 +34,12 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const page = await Pages.findOne(ps.pageId); + const page = await Pages.findOneBy({ id: ps.pageId }); if (page == null) { throw new ApiError(meta.errors.noSuchPage); } - const exist = await PageLikes.findOne({ + const exist = await PageLikes.findOneBy({ pageId: page.id, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/pages/update.ts b/packages/backend/src/server/api/endpoints/pages/update.ts index 24c8f467e6..bf95ab36f2 100644 --- a/packages/backend/src/server/api/endpoints/pages/update.ts +++ b/packages/backend/src/server/api/endpoints/pages/update.ts @@ -66,7 +66,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const page = await Pages.findOne(ps.pageId); + const page = await Pages.findOneBy({ id: ps.pageId }); if (page == null) { throw new ApiError(meta.errors.noSuchPage); } @@ -76,7 +76,7 @@ export default define(meta, paramDef, async (ps, user) => { let eyeCatchingImage = null; if (ps.eyeCatchingImageId != null) { - eyeCatchingImage = await DriveFiles.findOne({ + eyeCatchingImage = await DriveFiles.findOneBy({ id: ps.eyeCatchingImageId, userId: user.id, }); @@ -86,7 +86,7 @@ export default define(meta, paramDef, async (ps, user) => { } } - await Pages.find({ + await Pages.findBy({ id: Not(ps.pageId), userId: user.id, name: ps.name, diff --git a/packages/backend/src/server/api/endpoints/pinned-users.ts b/packages/backend/src/server/api/endpoints/pinned-users.ts index 1d26ab266e..8d253c1f33 100644 --- a/packages/backend/src/server/api/endpoints/pinned-users.ts +++ b/packages/backend/src/server/api/endpoints/pinned-users.ts @@ -3,6 +3,7 @@ import { Users } from '@/models/index.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import * as Acct from '@/misc/acct.js'; import { User } from '@/models/entities/user.js'; +import { IsNull } from 'typeorm'; export const meta = { tags: ['users'], @@ -30,7 +31,10 @@ export const paramDef = { export default define(meta, paramDef, async (ps, me) => { const meta = await fetchMeta(); - const users = await Promise.all(meta.pinnedUsers.map(acct => Users.findOne(Acct.parse(acct)))); + const users = await Promise.all(meta.pinnedUsers.map(acct => Acct.parse(acct)).map(acct => Users.findOneBy({ + usernameLower: acct.username.toLowerCase(), + host: acct.host ?? IsNull(), + }))); return await Users.packMany(users.filter(x => x !== undefined) as User[], me, { detail: true }); }); diff --git a/packages/backend/src/server/api/endpoints/promo/read.ts b/packages/backend/src/server/api/endpoints/promo/read.ts index ea34ca3aad..cc602857de 100644 --- a/packages/backend/src/server/api/endpoints/promo/read.ts +++ b/packages/backend/src/server/api/endpoints/promo/read.ts @@ -33,7 +33,7 @@ export default define(meta, paramDef, async (ps, user) => { throw e; }); - const exist = await PromoReads.findOne({ + const exist = await PromoReads.findOneBy({ noteId: note.id, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/request-reset-password.ts b/packages/backend/src/server/api/endpoints/request-reset-password.ts index 18cd98b164..046337f040 100644 --- a/packages/backend/src/server/api/endpoints/request-reset-password.ts +++ b/packages/backend/src/server/api/endpoints/request-reset-password.ts @@ -33,7 +33,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps) => { - const user = await Users.findOne({ + const user = await Users.findOneBy({ usernameLower: ps.username.toLowerCase(), host: IsNull(), }); @@ -43,7 +43,7 @@ export default define(meta, paramDef, async (ps) => { return; } - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); // 合致するメアドが登録されていなかったら無視 if (profile.email !== ps.email) { diff --git a/packages/backend/src/server/api/endpoints/reset-password.ts b/packages/backend/src/server/api/endpoints/reset-password.ts index 3abf232af0..7acc545c40 100644 --- a/packages/backend/src/server/api/endpoints/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/reset-password.ts @@ -23,7 +23,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const req = await PasswordResetRequests.findOneOrFail({ + const req = await PasswordResetRequests.findOneByOrFail({ token: ps.token, }); diff --git a/packages/backend/src/server/api/endpoints/stats.ts b/packages/backend/src/server/api/endpoints/stats.ts index 92fea4de6a..f8a1ee29de 100644 --- a/packages/backend/src/server/api/endpoints/stats.ts +++ b/packages/backend/src/server/api/endpoints/stats.ts @@ -1,6 +1,7 @@ import define from '../define.js'; import { Instances, NoteReactions, Notes, Users } from '@/models/index.js'; import { } from '@/services/chart/index.js'; +import { IsNull } from 'typeorm'; export const meta = { requireCredential: false, @@ -61,11 +62,11 @@ export default define(meta, paramDef, async () => { instances, ] = await Promise.all([ Notes.count({ cache: 3600000 }), // 1 hour - Notes.count({ where: { userHost: null }, cache: 3600000 }), + Notes.count({ where: { userHost: IsNull() }, cache: 3600000 }), Users.count({ cache: 3600000 }), - Users.count({ where: { host: null }, cache: 3600000 }), + Users.count({ where: { host: IsNull() }, cache: 3600000 }), NoteReactions.count({ cache: 3600000 }), // 1 hour - //NoteReactions.count({ where: { userHost: null }, cache: 3600000 }), + //NoteReactions.count({ where: { userHost: IsNull() }, cache: 3600000 }), Instances.count({ cache: 3600000 }), ]); diff --git a/packages/backend/src/server/api/endpoints/sw/register.ts b/packages/backend/src/server/api/endpoints/sw/register.ts index 6c7714e19b..a48973a0df 100644 --- a/packages/backend/src/server/api/endpoints/sw/register.ts +++ b/packages/backend/src/server/api/endpoints/sw/register.ts @@ -38,7 +38,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { // if already subscribed - const exist = await SwSubscriptions.findOne({ + const exist = await SwSubscriptions.findOneBy({ userId: user.id, endpoint: ps.endpoint, auth: ps.auth, diff --git a/packages/backend/src/server/api/endpoints/username/available.ts b/packages/backend/src/server/api/endpoints/username/available.ts index 5a1c4128ab..04b754f4ad 100644 --- a/packages/backend/src/server/api/endpoints/username/available.ts +++ b/packages/backend/src/server/api/endpoints/username/available.ts @@ -1,5 +1,6 @@ import define from '../../define.js'; import { Users, UsedUsernames } from '@/models/index.js'; +import { IsNull } from 'typeorm'; export const meta = { tags: ['users'], @@ -29,12 +30,12 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps) => { // Get exist - const exist = await Users.count({ - host: null, + const exist = await Users.countBy({ + host: IsNull(), usernameLower: ps.username.toLowerCase(), }); - const exist2 = await UsedUsernames.count({ username: ps.username.toLowerCase() }); + const exist2 = await UsedUsernames.countBy({ username: ps.username.toLowerCase() }); return { available: exist === 0 && exist2 === 0, diff --git a/packages/backend/src/server/api/endpoints/users/followers.ts b/packages/backend/src/server/api/endpoints/users/followers.ts index 1e104b6bcc..5de624312a 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'], @@ -49,15 +50,15 @@ 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 != 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 +68,7 @@ export default define(meta, paramDef, async (ps, me) => { if (me == null) { throw new ApiError(meta.errors.forbidden); } else if (me.id !== user.id) { - const following = await Followings.findOne({ + const following = await Followings.findOneBy({ followeeId: user.id, followerId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/users/following.ts b/packages/backend/src/server/api/endpoints/users/following.ts index b0a1036c76..55460f7c67 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'], @@ -49,15 +50,15 @@ 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 != 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 +68,7 @@ export default define(meta, paramDef, async (ps, me) => { if (me == null) { throw new ApiError(meta.errors.forbidden); } else if (me.id !== user.id) { - const following = await Followings.findOne({ + const following = await Followings.findOneBy({ followeeId: user.id, followerId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/users/groups/create.ts b/packages/backend/src/server/api/endpoints/users/groups/create.ts index 9f6d8464d8..fc775d7cc1 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/create.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/create.ts @@ -33,7 +33,7 @@ export default define(meta, paramDef, async (ps, user) => { createdAt: new Date(), userId: user.id, name: ps.name, - } as UserGroup).then(x => UserGroups.findOneOrFail(x.identifiers[0])); + } as UserGroup).then(x => UserGroups.findOneByOrFail(x.identifiers[0])); // Push the owner await UserGroupJoinings.insert({ diff --git a/packages/backend/src/server/api/endpoints/users/groups/delete.ts b/packages/backend/src/server/api/endpoints/users/groups/delete.ts index f4898a3c7c..f68006994c 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/delete.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/delete.ts @@ -28,7 +28,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const userGroup = await UserGroups.findOne({ + const userGroup = await UserGroups.findOneBy({ id: ps.groupId, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts index efbdf968f6..75c1acc302 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts @@ -31,7 +31,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { // Fetch the invitation - const invitation = await UserGroupInvitations.findOne({ + const invitation = await UserGroupInvitations.findOneBy({ id: ps.invitationId, }); diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts index fe5d431eab..46bc780ab0 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts @@ -29,7 +29,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { // Fetch the invitation - const invitation = await UserGroupInvitations.findOne({ + const invitation = await UserGroupInvitations.findOneBy({ id: ps.invitationId, }); diff --git a/packages/backend/src/server/api/endpoints/users/groups/invite.ts b/packages/backend/src/server/api/endpoints/users/groups/invite.ts index 10bfb7eca1..30a5beb1d9 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invite.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invite.ts @@ -52,7 +52,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { // Fetch the group - const userGroup = await UserGroups.findOne({ + const userGroup = await UserGroups.findOneBy({ id: ps.groupId, userId: me.id, }); @@ -67,7 +67,7 @@ export default define(meta, paramDef, async (ps, me) => { throw e; }); - const joining = await UserGroupJoinings.findOne({ + const joining = await UserGroupJoinings.findOneBy({ userGroupId: userGroup.id, userId: user.id, }); @@ -76,7 +76,7 @@ export default define(meta, paramDef, async (ps, me) => { throw new ApiError(meta.errors.alreadyAdded); } - const existInvitation = await UserGroupInvitations.findOne({ + const existInvitation = await UserGroupInvitations.findOneBy({ userGroupId: userGroup.id, userId: user.id, }); @@ -90,7 +90,7 @@ export default define(meta, paramDef, async (ps, me) => { createdAt: new Date(), userId: user.id, userGroupId: userGroup.id, - } as UserGroupInvitation).then(x => UserGroupInvitations.findOneOrFail(x.identifiers[0])); + } as UserGroupInvitation).then(x => UserGroupInvitations.findOneByOrFail(x.identifiers[0])); // 通知を作成 createNotification(user.id, 'groupInvited', { diff --git a/packages/backend/src/server/api/endpoints/users/groups/joined.ts b/packages/backend/src/server/api/endpoints/users/groups/joined.ts index e52de78595..77dc59d3e5 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/joined.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/joined.ts @@ -28,11 +28,11 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const ownedGroups = await UserGroups.find({ + const ownedGroups = await UserGroups.findBy({ userId: me.id, }); - const joinings = await UserGroupJoinings.find({ + const joinings = await UserGroupJoinings.findBy({ userId: me.id, ...(ownedGroups.length > 0 ? { userGroupId: Not(In(ownedGroups.map(x => x.id))), diff --git a/packages/backend/src/server/api/endpoints/users/groups/leave.ts b/packages/backend/src/server/api/endpoints/users/groups/leave.ts index c1a8c2c024..33abd5439f 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/leave.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/leave.ts @@ -35,7 +35,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { // Fetch the group - const userGroup = await UserGroups.findOne({ + const userGroup = await UserGroups.findOneBy({ id: ps.groupId, }); diff --git a/packages/backend/src/server/api/endpoints/users/groups/owned.ts b/packages/backend/src/server/api/endpoints/users/groups/owned.ts index 11aad0f73c..b1289e601f 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/owned.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/owned.ts @@ -27,7 +27,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const userGroups = await UserGroups.find({ + const userGroups = await UserGroups.findBy({ userId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/users/groups/pull.ts b/packages/backend/src/server/api/endpoints/users/groups/pull.ts index 55ec9f915b..b31990b2e3 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/pull.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/pull.ts @@ -43,7 +43,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { // Fetch the group - const userGroup = await UserGroups.findOne({ + const userGroup = await UserGroups.findOneBy({ id: ps.groupId, userId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/users/groups/show.ts b/packages/backend/src/server/api/endpoints/users/groups/show.ts index 28ca1162c8..3ffb0f5ba9 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/show.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/show.ts @@ -35,7 +35,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { // Fetch the group - const userGroup = await UserGroups.findOne({ + const userGroup = await UserGroups.findOneBy({ id: ps.groupId, }); @@ -43,7 +43,7 @@ export default define(meta, paramDef, async (ps, me) => { throw new ApiError(meta.errors.noSuchGroup); } - const joining = await UserGroupJoinings.findOne({ + const joining = await UserGroupJoinings.findOneBy({ userId: me.id, userGroupId: userGroup.id, }); diff --git a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts index f48e1ddbf0..41ceee3b2e 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts @@ -49,7 +49,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { // Fetch the group - const userGroup = await UserGroups.findOne({ + const userGroup = await UserGroups.findOneBy({ id: ps.groupId, userId: me.id, }); @@ -64,7 +64,7 @@ export default define(meta, paramDef, async (ps, me) => { throw e; }); - const joining = await UserGroupJoinings.findOne({ + const joining = await UserGroupJoinings.findOneBy({ userGroupId: userGroup.id, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/users/groups/update.ts b/packages/backend/src/server/api/endpoints/users/groups/update.ts index b3e17dfd9e..1016aa8926 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/update.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/update.ts @@ -36,7 +36,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { // Fetch the group - const userGroup = await UserGroups.findOne({ + const userGroup = await UserGroups.findOneBy({ id: ps.groupId, userId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/users/lists/create.ts b/packages/backend/src/server/api/endpoints/users/lists/create.ts index 1a0599f9e7..d5260256d5 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/create.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/create.ts @@ -32,7 +32,7 @@ export default define(meta, paramDef, async (ps, user) => { createdAt: new Date(), userId: user.id, name: ps.name, - } as UserList).then(x => UserLists.findOneOrFail(x.identifiers[0])); + } as UserList).then(x => UserLists.findOneByOrFail(x.identifiers[0])); return await UserLists.pack(userList); }); diff --git a/packages/backend/src/server/api/endpoints/users/lists/delete.ts b/packages/backend/src/server/api/endpoints/users/lists/delete.ts index aeefb98c83..b7ad96eef0 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/delete.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/delete.ts @@ -28,7 +28,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const userList = await UserLists.findOne({ + const userList = await UserLists.findOneBy({ id: ps.listId, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/users/lists/list.ts b/packages/backend/src/server/api/endpoints/users/lists/list.ts index a8663ada8a..78311292cb 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/list.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/list.ts @@ -27,7 +27,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const userLists = await UserLists.find({ + const userLists = await UserLists.findBy({ userId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/users/lists/pull.ts b/packages/backend/src/server/api/endpoints/users/lists/pull.ts index 2c4c61d51e..76863f07d1 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/pull.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/pull.ts @@ -38,7 +38,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { // Fetch the list - const userList = await UserLists.findOne({ + const userList = await UserLists.findOneBy({ id: ps.listId, userId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/users/lists/push.ts b/packages/backend/src/server/api/endpoints/users/lists/push.ts index 034a9d2db6..260665c63a 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/push.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts @@ -50,7 +50,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { // Fetch the list - const userList = await UserLists.findOne({ + const userList = await UserLists.findOneBy({ id: ps.listId, userId: me.id, }); @@ -67,7 +67,7 @@ export default define(meta, paramDef, async (ps, me) => { // Check blocking if (user.id !== me.id) { - const block = await Blockings.findOne({ + const block = await Blockings.findOneBy({ blockerId: user.id, blockeeId: me.id, }); @@ -76,7 +76,7 @@ export default define(meta, paramDef, async (ps, me) => { } } - const exist = await UserListJoinings.findOne({ + const exist = await UserListJoinings.findOneBy({ userListId: userList.id, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/users/lists/show.ts b/packages/backend/src/server/api/endpoints/users/lists/show.ts index fadb94c90e..5f51980e95 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/show.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/show.ts @@ -35,7 +35,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { // Fetch the list - const userList = await UserLists.findOne({ + const userList = await UserLists.findOneBy({ id: ps.listId, userId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/users/lists/update.ts b/packages/backend/src/server/api/endpoints/users/lists/update.ts index 5ec99031e1..52353a14cc 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/update.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/update.ts @@ -36,7 +36,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { // Fetch the list - const userList = await UserLists.findOne({ + const userList = await UserLists.findOneBy({ id: ps.listId, userId: user.id, }); diff --git a/packages/backend/src/server/api/endpoints/users/reactions.ts b/packages/backend/src/server/api/endpoints/users/reactions.ts index 7b55a16711..c2d1994343 100644 --- a/packages/backend/src/server/api/endpoints/users/reactions.ts +++ b/packages/backend/src/server/api/endpoints/users/reactions.ts @@ -43,7 +43,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const profile = await UserProfiles.findOneOrFail(ps.userId); + const profile = await UserProfiles.findOneByOrFail({ userId: ps.userId }); if (me == null || (me.id !== ps.userId && !profile.publicReactions)) { throw new ApiError(meta.errors.reactionsNotPublic); diff --git a/packages/backend/src/server/api/endpoints/users/report-abuse.ts b/packages/backend/src/server/api/endpoints/users/report-abuse.ts index e091b8e1b1..0be385dbbf 100644 --- a/packages/backend/src/server/api/endpoints/users/report-abuse.ts +++ b/packages/backend/src/server/api/endpoints/users/report-abuse.ts @@ -67,10 +67,10 @@ export default define(meta, paramDef, async (ps, me) => { reporterId: me.id, reporterHost: null, comment: ps.comment, - }).then(x => AbuseUserReports.findOneOrFail(x.identifiers[0])); + }).then(x => AbuseUserReports.findOneByOrFail(x.identifiers[0])); // Publish event to moderators - setTimeout(async () => { + setImmediate(async () => { const moderators = await Users.find({ where: [{ isAdmin: true, @@ -94,5 +94,5 @@ export default define(meta, paramDef, async (ps, me) => { sanitizeHtml(ps.comment), sanitizeHtml(ps.comment)); } - }, 1); + }); }); diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts index 263c102a7a..775a4b29ff 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 = { @@ -68,7 +68,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 +92,11 @@ export default define(meta, paramDef, async (ps, me) => { throw new ApiError(meta.errors.failedToResolveRemoteUser); }); } else { - const q: any = ps.userId != null + const q: FindOptionsWhere = ps.userId != null ? { id: ps.userId } - : { usernameLower: ps.username!.toLowerCase(), host: null }; + : { usernameLower: ps.username!.toLowerCase(), host: IsNull() }; - user = await Users.findOne(q); + user = await Users.findOneBy(q); } if (user == null || (!isAdminOrModerator && user.isSuspended)) { diff --git a/packages/backend/src/server/api/endpoints/users/stats.ts b/packages/backend/src/server/api/endpoints/users/stats.ts index 180a9386d3..d138019a72 100644 --- a/packages/backend/src/server/api/endpoints/users/stats.ts +++ b/packages/backend/src/server/api/endpoints/users/stats.ts @@ -26,7 +26,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOne(ps.userId); + const user = await Users.findOneBy({ id: ps.userId }); if (user == null) { throw new ApiError(meta.errors.noSuchUser); } diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts index ba2a71951c..02bec31b17 100644 --- a/packages/backend/src/server/api/index.ts +++ b/packages/backend/src/server/api/index.ts @@ -81,7 +81,7 @@ router.get('/v1/instance/peers', async ctx => { }); router.post('/miauth/:session/check', async ctx => { - const token = await AccessTokens.findOne({ + const token = await AccessTokens.findOneBy({ session: ctx.params.session, }); diff --git a/packages/backend/src/server/api/limiter.ts b/packages/backend/src/server/api/limiter.ts index 7e6b93b39f..e74db8466e 100644 --- a/packages/backend/src/server/api/limiter.ts +++ b/packages/backend/src/server/api/limiter.ts @@ -2,12 +2,12 @@ import Limiter from 'ratelimiter'; import { redisClient } from '../../db/redis.js'; import { IEndpoint } from './endpoints.js'; import * as Acct from '@/misc/acct.js'; -import { User } from '@/models/entities/user.js'; +import { CacheableLocalUser, User } from '@/models/entities/user.js'; import Logger from '@/services/logger.js'; const logger = new Logger('limiter'); -export const limiter = (endpoint: IEndpoint & { meta: { limit: NonNullable } }, user: User) => new Promise((ok, reject) => { +export const limiter = (endpoint: IEndpoint & { meta: { limit: NonNullable } }, user: CacheableLocalUser) => new Promise((ok, reject) => { const limitation = endpoint.meta.limit; const key = Object.prototype.hasOwnProperty.call(limitation, 'key') diff --git a/packages/backend/src/server/api/private/signin.ts b/packages/backend/src/server/api/private/signin.ts index b0f88948a0..3f7118ad22 100644 --- a/packages/backend/src/server/api/private/signin.ts +++ b/packages/backend/src/server/api/private/signin.ts @@ -8,6 +8,7 @@ import { ILocalUser } from '@/models/entities/user.js'; import { genId } from '@/misc/gen-id.js'; import { verifyLogin, hash } from '../2fa.js'; import { randomBytes } from 'node:crypto'; +import { IsNull } from 'typeorm'; export default async (ctx: Koa.Context) => { ctx.set('Access-Control-Allow-Origin', config.url); @@ -39,9 +40,9 @@ export default async (ctx: Koa.Context) => { } // Fetch user - const user = await Users.findOne({ + const user = await Users.findOneBy({ usernameLower: username.toLowerCase(), - host: null, + host: IsNull(), }) as ILocalUser; if (user == null) { @@ -58,7 +59,7 @@ export default async (ctx: Koa.Context) => { return; } - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); // Compare password const same = await bcrypt.compare(password, profile.password!); @@ -123,7 +124,7 @@ export default async (ctx: Koa.Context) => { const clientDataJSON = Buffer.from(body.clientDataJSON, 'hex'); const clientData = JSON.parse(clientDataJSON.toString('utf-8')); - const challenge = await AttestationChallenges.findOne({ + const challenge = await AttestationChallenges.findOneBy({ userId: user.id, id: body.challengeId, registrationChallenge: false, @@ -149,7 +150,7 @@ export default async (ctx: Koa.Context) => { return; } - const securityKey = await UserSecurityKeys.findOne({ + const securityKey = await UserSecurityKeys.findOneBy({ id: Buffer.from( body.credentialId .replace(/-/g, '+') @@ -191,7 +192,7 @@ export default async (ctx: Koa.Context) => { return; } - const keys = await UserSecurityKeys.find({ + const keys = await UserSecurityKeys.findBy({ userId: user.id, }); diff --git a/packages/backend/src/server/api/private/signup-pending.ts b/packages/backend/src/server/api/private/signup-pending.ts index 1a667ddb43..e5e39ba00d 100644 --- a/packages/backend/src/server/api/private/signup-pending.ts +++ b/packages/backend/src/server/api/private/signup-pending.ts @@ -9,7 +9,7 @@ export default async (ctx: Koa.Context) => { const code = body['code']; try { - const pendingUser = await UserPendings.findOneOrFail({ code }); + const pendingUser = await UserPendings.findOneByOrFail({ code }); const { account, secret } = await signup({ username: pendingUser.username, @@ -20,7 +20,7 @@ export default async (ctx: Koa.Context) => { id: pendingUser.id, }); - const profile = await UserProfiles.findOneOrFail(account.id); + const profile = await UserProfiles.findOneByOrFail({ userId: account.id }); await UserProfiles.update({ userId: profile.userId }, { email: pendingUser.email, diff --git a/packages/backend/src/server/api/private/signup.ts b/packages/backend/src/server/api/private/signup.ts index 01f284a57f..26f172637c 100644 --- a/packages/backend/src/server/api/private/signup.ts +++ b/packages/backend/src/server/api/private/signup.ts @@ -56,7 +56,7 @@ export default async (ctx: Koa.Context) => { return; } - const ticket = await RegistrationTickets.findOne({ + const ticket = await RegistrationTickets.findOneBy({ code: invitationCode, }); diff --git a/packages/backend/src/server/api/service/discord.ts b/packages/backend/src/server/api/service/discord.ts index 089f7de0cd..04197574c2 100644 --- a/packages/backend/src/server/api/service/discord.ts +++ b/packages/backend/src/server/api/service/discord.ts @@ -10,6 +10,7 @@ import signin from '../common/signin.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { Users, UserProfiles } from '@/models/index.js'; import { ILocalUser } from '@/models/entities/user.js'; +import { IsNull } from 'typeorm'; function getUserToken(ctx: Koa.BaseContext): string | null { return ((ctx.headers['cookie'] || '').match(/igi=(\w+)/) || [null, null])[1]; @@ -40,12 +41,12 @@ router.get('/disconnect/discord', async ctx => { return; } - const user = await Users.findOneOrFail({ - host: null, + const user = await Users.findOneByOrFail({ + host: IsNull(), token: userToken, }); - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); delete profile.integrations.discord; @@ -206,7 +207,7 @@ router.get('/dc/cb', async ctx => { }, }); - signin(ctx, await Users.findOne(profile.userId) as ILocalUser, true); + signin(ctx, await Users.findOneBy({ id: profile.userId }) as ILocalUser, true); } else { const code = ctx.query.code; @@ -252,12 +253,12 @@ router.get('/dc/cb', async ctx => { return; } - const user = await Users.findOneOrFail({ - host: null, + const user = await Users.findOneByOrFail({ + host: IsNull(), token: userToken, }); - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); await UserProfiles.update(user.id, { integrations: { diff --git a/packages/backend/src/server/api/service/github.ts b/packages/backend/src/server/api/service/github.ts index ce032db181..61bb768a63 100644 --- a/packages/backend/src/server/api/service/github.ts +++ b/packages/backend/src/server/api/service/github.ts @@ -10,6 +10,7 @@ import signin from '../common/signin.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { Users, UserProfiles } from '@/models/index.js'; import { ILocalUser } from '@/models/entities/user.js'; +import { IsNull } from 'typeorm'; function getUserToken(ctx: Koa.BaseContext): string | null { return ((ctx.headers['cookie'] || '').match(/igi=(\w+)/) || [null, null])[1]; @@ -40,12 +41,12 @@ router.get('/disconnect/github', async ctx => { return; } - const user = await Users.findOneOrFail({ - host: null, + const user = await Users.findOneByOrFail({ + host: IsNull(), token: userToken, }); - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); delete profile.integrations.github; @@ -184,7 +185,7 @@ router.get('/gh/cb', async ctx => { return; } - signin(ctx, await Users.findOne(link.userId) as ILocalUser, true); + signin(ctx, await Users.findOneBy({ id: link.userId }) as ILocalUser, true); } else { const code = ctx.query.code; @@ -227,12 +228,12 @@ router.get('/gh/cb', async ctx => { return; } - const user = await Users.findOneOrFail({ - host: null, + const user = await Users.findOneByOrFail({ + host: IsNull(), token: userToken, }); - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); await UserProfiles.update(user.id, { integrations: { diff --git a/packages/backend/src/server/api/service/twitter.ts b/packages/backend/src/server/api/service/twitter.ts index e6e4398fa2..e72b71e2f7 100644 --- a/packages/backend/src/server/api/service/twitter.ts +++ b/packages/backend/src/server/api/service/twitter.ts @@ -9,6 +9,7 @@ import signin from '../common/signin.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { Users, UserProfiles } from '@/models/index.js'; import { ILocalUser } from '@/models/entities/user.js'; +import { IsNull } from 'typeorm'; function getUserToken(ctx: Koa.BaseContext): string | null { return ((ctx.headers['cookie'] || '').match(/igi=(\w+)/) || [null, null])[1]; @@ -39,12 +40,12 @@ router.get('/disconnect/twitter', async ctx => { return; } - const user = await Users.findOneOrFail({ - host: null, + const user = await Users.findOneByOrFail({ + host: IsNull(), token: userToken, }); - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); delete profile.integrations.twitter; @@ -143,7 +144,7 @@ router.get('/tw/cb', async ctx => { return; } - signin(ctx, await Users.findOne(link.userId) as ILocalUser, true); + signin(ctx, await Users.findOneBy({ id: link.userId }) as ILocalUser, true); } else { const verifier = ctx.query.oauth_verifier; @@ -162,12 +163,12 @@ router.get('/tw/cb', async ctx => { const result = await twAuth!.done(JSON.parse(twCtx), verifier); - const user = await Users.findOneOrFail({ - host: null, + const user = await Users.findOneByOrFail({ + host: IsNull(), token: userToken, }); - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); await UserProfiles.update(user.id, { integrations: { diff --git a/packages/backend/src/server/api/stream/channels/messaging.ts b/packages/backend/src/server/api/stream/channels/messaging.ts index 94bbdeca52..877d44c38e 100644 --- a/packages/backend/src/server/api/stream/channels/messaging.ts +++ b/packages/backend/src/server/api/stream/channels/messaging.ts @@ -26,12 +26,12 @@ export default class extends Channel { public async init(params: any) { this.otherpartyId = params.otherparty; - this.otherparty = this.otherpartyId ? await Users.findOneOrFail({ id: this.otherpartyId }) : null; + this.otherparty = this.otherpartyId ? await Users.findOneByOrFail({ id: this.otherpartyId }) : null; this.groupId = params.group; // Check joining if (this.groupId) { - const joining = await UserGroupJoinings.findOne({ + const joining = await UserGroupJoinings.findOneBy({ userId: this.user!.id, userGroupId: this.groupId, }); @@ -72,7 +72,7 @@ export default class extends Channel { // リモートユーザーからのメッセージだったら既読配信 if (Users.isLocalUser(this.user!) && Users.isRemoteUser(this.otherparty!)) { - MessagingMessages.findOne(body.id).then(message => { + MessagingMessages.findOneBy({ id: body.id }).then(message => { if (message) deliverReadActivity(this.user as ILocalUser, this.otherparty as IRemoteUser, message); }); } diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts index 57523c8488..d8034e83fe 100644 --- a/packages/backend/src/server/api/stream/channels/user-list.ts +++ b/packages/backend/src/server/api/stream/channels/user-list.ts @@ -23,7 +23,7 @@ export default class extends Channel { this.listId = params.listId as string; // Check existence and owner - const list = await UserLists.findOne({ + const list = await UserLists.findOneBy({ id: this.listId, userId: this.user!.id, }); diff --git a/packages/backend/src/server/api/stream/index.ts b/packages/backend/src/server/api/stream/index.ts index 0cb38e2a99..b803478281 100644 --- a/packages/backend/src/server/api/stream/index.ts +++ b/packages/backend/src/server/api/stream/index.ts @@ -188,7 +188,7 @@ export default class Connection { */ private async onApiRequest(payload: any) { // 新鮮なデータを利用するためにユーザーをフェッチ - const user = this.user ? await Users.findOne(this.user.id) : null; + const user = this.user ? await Users.findOneBy({ id: this.user.id }) : null; const endpoint = payload.endpoint || payload.ep; // alias @@ -386,7 +386,7 @@ export default class Connection { } private async updateUserProfile() { - this.userProfile = await UserProfiles.findOne({ + this.userProfile = await UserProfiles.findOneBy({ userId: this.user!.id, }); } diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts index 90cf59038d..bea863eb7c 100644 --- a/packages/backend/src/server/api/stream/types.ts +++ b/packages/backend/src/server/api/stream/types.ts @@ -18,6 +18,11 @@ import { Packed } from '@/misc/schema.js'; //#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']; }; antennaCreated: Antenna; antennaDeleted: Antenna; antennaUpdated: Antenna; diff --git a/packages/backend/src/server/file/send-drive-file.ts b/packages/backend/src/server/file/send-drive-file.ts index 4e2bba0e20..6bc220b362 100644 --- a/packages/backend/src/server/file/send-drive-file.ts +++ b/packages/backend/src/server/file/send-drive-file.ts @@ -2,7 +2,7 @@ import * as fs from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname } from 'node:path'; import Koa from 'koa'; -import * as send from 'koa-send'; +import send from 'koa-send'; import rename from 'rename'; import * as tmp from 'tmp'; import { serverLogger } from '../index.js'; diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts index 80130e8843..a68cebfeb2 100644 --- a/packages/backend/src/server/index.ts +++ b/packages/backend/src/server/index.ts @@ -4,8 +4,6 @@ import * as fs from 'node:fs'; import * as http from 'http'; -import * as http2 from 'http2'; -import * as https from 'https'; import Koa from 'koa'; import Router from '@koa/router'; import mount from 'koa-mount'; @@ -28,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); @@ -73,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'], }); @@ -95,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, }); @@ -123,16 +123,7 @@ app.use(router.routes()); app.use(mount(webServer)); function createServer() { - if (config.https) { - const certs: any = {}; - for (const k of Object.keys(config.https)) { - certs[k] = fs.readFileSync(config.https[k]); - } - certs['allowHTTP1'] = true; - return http2.createSecureServer(certs, app.callback()) as https.Server; - } else { - return http.createServer(app.callback()); - } + return http.createServer(app.callback()); } // For testing diff --git a/packages/backend/src/server/nodeinfo.ts b/packages/backend/src/server/nodeinfo.ts index f4b56fc8a5..13a362a75f 100644 --- a/packages/backend/src/server/nodeinfo.ts +++ b/packages/backend/src/server/nodeinfo.ts @@ -2,8 +2,9 @@ import Router from '@koa/router'; import config from '@/config/index.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { Users, Notes } from '@/models/index.js'; -import { MoreThan } from 'typeorm'; +import { IsNull, MoreThan } from 'typeorm'; import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; +import { Cache } from '@/misc/cache.js'; const router = new Router(); @@ -28,10 +29,10 @@ const nodeinfo2 = async () => { localPosts, ] = await Promise.all([ fetchMeta(true), - Users.count({ where: { host: null } }), - Users.count({ where: { host: null, lastActiveDate: MoreThan(new Date(now - 15552000000)) } }), - Users.count({ where: { host: null, lastActiveDate: MoreThan(new Date(now - 2592000000)) } }), - Notes.count({ where: { userHost: null } }), + Users.count({ where: { host: IsNull() } }), + Users.count({ where: { host: IsNull(), lastActiveDate: MoreThan(new Date(now - 15552000000)) } }), + Users.count({ where: { host: IsNull(), lastActiveDate: MoreThan(new Date(now - 2592000000)) } }), + Notes.count({ where: { userHost: IsNull() } }), ]); const proxyAccount = meta.proxyAccountId ? await Users.pack(meta.proxyAccountId).catch(() => null) : null; @@ -81,15 +82,17 @@ const nodeinfo2 = async () => { }; }; +const cache = new Cache>>(1000 * 60 * 10); + router.get(nodeinfo2_1path, async ctx => { - const base = await nodeinfo2(); + const base = await cache.fetch(null, () => nodeinfo2()); ctx.body = { version: '2.1', ...base }; ctx.set('Cache-Control', 'public, max-age=600'); }); router.get(nodeinfo2_0path, async ctx => { - const base = await nodeinfo2(); + const base = await cache.fetch(null, () => nodeinfo2()); delete base.software.repository; diff --git a/packages/backend/src/server/web/feed.ts b/packages/backend/src/server/web/feed.ts index b98e3f8bf6..eba8dc58d4 100644 --- a/packages/backend/src/server/web/feed.ts +++ b/packages/backend/src/server/web/feed.ts @@ -2,7 +2,7 @@ import { Feed } from 'feed'; import config from '@/config/index.js'; import { User } from '@/models/entities/user.js'; import { Notes, DriveFiles, UserProfiles } from '@/models/index.js'; -import { In } from 'typeorm'; +import { In, IsNull } from 'typeorm'; export default async function(user: User) { const author = { @@ -10,12 +10,12 @@ export default async function(user: User) { name: user.name || user.username, }; - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); const notes = await Notes.find({ where: { userId: user.id, - renoteId: null, + renoteId: IsNull(), visibility: In(['public', 'home']), }, order: { createdAt: -1 }, @@ -39,7 +39,7 @@ export default async function(user: User) { }); for (const note of notes) { - const files = note.fileIds.length > 0 ? await DriveFiles.find({ + const files = note.fileIds.length > 0 ? await DriveFiles.findBy({ id: In(note.fileIds), }) : []; const file = files.find(file => file.type.startsWith('image/')); diff --git a/packages/backend/src/server/web/index.ts b/packages/backend/src/server/web/index.ts index cc4c2cc9ca..48bf6f7338 100644 --- a/packages/backend/src/server/web/index.ts +++ b/packages/backend/src/server/web/index.ts @@ -10,6 +10,9 @@ import Router from '@koa/router'; import send from 'koa-send'; import favicon from 'koa-favicon'; import views from 'koa-views'; +import { createBullBoard } from '@bull-board/api'; +import { BullAdapter } from '@bull-board/api/bullAdapter.js'; +import { KoaAdapter } from '@bull-board/koa'; import packFeed from './feed.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; @@ -20,6 +23,8 @@ import * as Acct from '@/misc/acct.js'; import { getNoteSummary } from '@/misc/get-note-summary.js'; import { urlPreviewHandler } from './url-preview.js'; import { manifestHandler } from './manifest.js'; +import { queues } from '@/queue/queues.js'; +import { IsNull } from 'typeorm'; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); @@ -31,6 +36,37 @@ const assets = `${_dirname}/../../../../../built/_client_dist_/`; // Init app const app = new Koa(); +//#region Bull Dashboard +const bullBoardPath = '/queue'; + +// Authenticate +app.use(async (ctx, next) => { + if (ctx.path === bullBoardPath || ctx.path.startsWith(bullBoardPath + '/')) { + const token = ctx.cookies.get('token'); + if (token == null) { + ctx.status = 401; + return; + } + const user = await Users.findOneBy({ token }); + if (user == null || !(user.isAdmin || user.isModerator)) { + ctx.status = 403; + return; + } + } + await next(); +}); + +const serverAdapter = new KoaAdapter(); + +createBullBoard({ + queues: queues.map(q => new BullAdapter(q)), + serverAdapter, +}); + +serverAdapter.setBasePath(bullBoardPath); +app.use(serverAdapter.registerPlugin()); +//#endregion + // Init renderer app.use(views(_dirname + '/views', { extension: 'pug', @@ -133,9 +169,9 @@ router.get('/api.json', async ctx => { const getFeed = async (acct: string) => { const { username, host } = Acct.parse(acct); - const user = await Users.findOne({ + const user = await Users.findOneBy({ usernameLower: username.toLowerCase(), - host, + host: host ?? IsNull(), isSuspended: false, }); @@ -182,14 +218,14 @@ router.get('/@:user.json', async ctx => { // User router.get(['/@:user', '/@:user/:sub'], async (ctx, next) => { const { username, host } = Acct.parse(ctx.params.user); - const user = await Users.findOne({ + const user = await Users.findOneBy({ usernameLower: username.toLowerCase(), - host, + host: host ?? IsNull(), isSuspended: false, }); if (user != null) { - const profile = await UserProfiles.findOneOrFail(user.id); + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); const meta = await fetchMeta(); const me = profile.fields ? profile.fields @@ -213,9 +249,9 @@ router.get(['/@:user', '/@:user/:sub'], async (ctx, next) => { }); router.get('/users/:user', async ctx => { - const user = await Users.findOne({ + const user = await Users.findOneBy({ id: ctx.params.user, - host: null, + host: IsNull(), isSuspended: false, }); @@ -229,11 +265,11 @@ router.get('/users/:user', async ctx => { // Note router.get('/notes/:note', async (ctx, next) => { - const note = await Notes.findOne(ctx.params.note); + const note = await Notes.findOneBy({ id: ctx.params.note }); if (note) { const _note = await Notes.pack(note); - const profile = await UserProfiles.findOneOrFail(note.userId); + const profile = await UserProfiles.findOneByOrFail({ userId: note.userId }); const meta = await fetchMeta(); await ctx.render('note', { note: _note, @@ -260,21 +296,21 @@ router.get('/notes/:note', async (ctx, next) => { // Page router.get('/@:user/pages/:page', async (ctx, next) => { const { username, host } = Acct.parse(ctx.params.user); - const user = await Users.findOne({ + const user = await Users.findOneBy({ usernameLower: username.toLowerCase(), - host, + host: host ?? IsNull(), }); if (user == null) return; - const page = await Pages.findOne({ + const page = await Pages.findOneBy({ name: ctx.params.page, userId: user.id, }); if (page) { const _page = await Pages.pack(page); - const profile = await UserProfiles.findOneOrFail(page.userId); + const profile = await UserProfiles.findOneByOrFail({ userId: page.userId }); const meta = await fetchMeta(); await ctx.render('page', { page: _page, @@ -299,13 +335,13 @@ router.get('/@:user/pages/:page', async (ctx, next) => { // Clip // TODO: 非publicなclipのハンドリング router.get('/clips/:clip', async (ctx, next) => { - const clip = await Clips.findOne({ + const clip = await Clips.findOneBy({ id: ctx.params.clip, }); if (clip) { const _clip = await Clips.pack(clip); - const profile = await UserProfiles.findOneOrFail(clip.userId); + const profile = await UserProfiles.findOneByOrFail({ userId: clip.userId }); const meta = await fetchMeta(); await ctx.render('clip', { clip: _clip, @@ -325,11 +361,11 @@ router.get('/clips/:clip', async (ctx, next) => { // Gallery post router.get('/gallery/:post', async (ctx, next) => { - const post = await GalleryPosts.findOne(ctx.params.post); + const post = await GalleryPosts.findOneBy({ id: ctx.params.post }); if (post) { const _post = await GalleryPosts.pack(post); - const profile = await UserProfiles.findOneOrFail(post.userId); + const profile = await UserProfiles.findOneByOrFail({ userId: post.userId }); const meta = await fetchMeta(); await ctx.render('gallery-post', { post: _post, @@ -349,7 +385,7 @@ router.get('/gallery/:post', async (ctx, next) => { // Channel router.get('/channels/:channel', async (ctx, next) => { - const channel = await Channels.findOne({ + const channel = await Channels.findOneBy({ id: ctx.params.channel, }); @@ -381,8 +417,8 @@ router.get('/_info_card_', async ctx => { version: config.version, host: config.host, meta: meta, - originalUsersCount: await Users.count({ host: null }), - originalNotesCount: await Notes.count({ userHost: null }), + originalUsersCount: await Users.countBy({ host: IsNull() }), + originalNotesCount: await Notes.countBy({ userHost: IsNull() }), }); }); diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug index e1cb2cfa93..abacb1ccfc 100644 --- a/packages/backend/src/server/web/views/base.pug +++ b/packages/backend/src/server/web/views/base.pug @@ -59,5 +59,5 @@ html br | Please turn on your JavaScript div#splash - img(src='/favicon.ico') + img(src= icon || '/static-assets/splash.png') block content diff --git a/packages/backend/src/server/well-known.ts b/packages/backend/src/server/well-known.ts index 7a5d085413..7530b4e0ba 100644 --- a/packages/backend/src/server/well-known.ts +++ b/packages/backend/src/server/well-known.ts @@ -6,6 +6,7 @@ import { links } from './nodeinfo.js'; import { escapeAttribute, escapeValue } from '@/prelude/xml.js'; import { Users } from '@/models/index.js'; import { User } from '@/models/entities/user.js'; +import { FindOptionsWhere, IsNull } from 'typeorm'; // Init router const router = new Router(); @@ -66,13 +67,13 @@ router.get('/.well-known/change-password', async ctx => { */ router.get(webFingerPath, async ctx => { - const fromId = (id: User['id']): Record => ({ + const fromId = (id: User['id']): FindOptionsWhere => ({ id, - host: null, + host: IsNull(), isSuspended: false, }); - const generateQuery = (resource: string) => + const generateQuery = (resource: string): FindOptionsWhere | number => resource.startsWith(`${config.url.toLowerCase()}/users/`) ? fromId(resource.split('/').pop()!) : fromAcct(Acct.parse( @@ -80,10 +81,10 @@ router.get(webFingerPath, async ctx => { resource.startsWith('acct:') ? resource.slice('acct:'.length) : resource)); - const fromAcct = (acct: Acct.Acct): Record | number => + const fromAcct = (acct: Acct.Acct): FindOptionsWhere | number => !acct.host || acct.host === config.host.toLowerCase() ? { usernameLower: acct.username, - host: null, + host: IsNull(), isSuspended: false, } : 422; @@ -99,7 +100,7 @@ router.get(webFingerPath, async ctx => { return; } - const user = await Users.findOne(query); + const user = await Users.findOneBy(query); if (user == null) { ctx.status = 404; diff --git a/packages/backend/src/services/add-note-to-antenna.ts b/packages/backend/src/services/add-note-to-antenna.ts index e88c387234..f86f394f80 100644 --- a/packages/backend/src/services/add-note-to-antenna.ts +++ b/packages/backend/src/services/add-note-to-antenna.ts @@ -33,10 +33,10 @@ export async function addNoteToAntenna(antenna: Antenna, note: Note, noteUser: { }; if (note.replyId != null) { - _note.reply = await Notes.findOneOrFail(note.replyId); + _note.reply = await Notes.findOneByOrFail({ id: note.replyId }); } if (note.renoteId != null) { - _note.renote = await Notes.findOneOrFail(note.renoteId); + _note.renote = await Notes.findOneByOrFail({ id: note.renoteId }); } if (isMutedUserRelated(_note, new Set(mutings.map(x => x.muteeId)))) { @@ -45,7 +45,7 @@ export async function addNoteToAntenna(antenna: Antenna, note: Note, noteUser: { // 2秒経っても既読にならなかったら通知 setTimeout(async () => { - const unread = await AntennaNotes.findOne({ antennaId: antenna.id, read: false }); + const unread = await AntennaNotes.findOneBy({ antennaId: antenna.id, read: false }); if (unread) { publishMainStream(antenna.userId, 'unreadAntenna', antenna); } diff --git a/packages/backend/src/services/blocking/create.ts b/packages/backend/src/services/blocking/create.ts index 198d28705e..86c7d7967b 100644 --- a/packages/backend/src/services/blocking/create.ts +++ b/packages/backend/src/services/blocking/create.ts @@ -34,7 +34,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, }); @@ -77,7 +77,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, }); @@ -116,7 +116,7 @@ async function unFollow(follower: User, followee: User) { } async function removeFromList(listOwner: User, user: User) { - const userLists = await UserLists.find({ + const userLists = await UserLists.findBy({ userId: listOwner.id, }); diff --git a/packages/backend/src/services/blocking/delete.ts b/packages/backend/src/services/blocking/delete.ts index c4f3784b05..d7b5ddd5ff 100644 --- a/packages/backend/src/services/blocking/delete.ts +++ b/packages/backend/src/services/blocking/delete.ts @@ -3,13 +3,13 @@ import renderBlock from '@/remote/activitypub/renderer/block.js'; import renderUndo from '@/remote/activitypub/renderer/undo.js'; import { deliver } from '@/queue/index.js'; import Logger from '../logger.js'; -import { User } from '@/models/entities/user.js'; +import { CacheableUser, User } from '@/models/entities/user.js'; import { Blockings, Users } from '@/models/index.js'; const logger = new Logger('blocking/delete'); -export default async function(blocker: User, blockee: User) { - const blocking = await Blockings.findOne({ +export default async function(blocker: CacheableUser, blockee: CacheableUser) { + const blocking = await Blockings.findOneBy({ blockerId: blocker.id, blockeeId: blockee.id, }); diff --git a/packages/backend/src/services/chart/charts/entities/federation.ts b/packages/backend/src/services/chart/charts/entities/federation.ts index 9d2b860b10..a8466b0b4c 100644 --- a/packages/backend/src/services/chart/charts/entities/federation.ts +++ b/packages/backend/src/services/chart/charts/entities/federation.ts @@ -9,6 +9,8 @@ export const schema = { 'sub': { accumulate: true, range: 'small' }, 'pub': { accumulate: true, range: 'small' }, 'pubsub': { accumulate: true, range: 'small' }, + 'subActive': { accumulate: true, range: 'small' }, + 'pubActive': { accumulate: true, range: 'small' }, } as const; export const entity = Chart.schemaToEntity(name, schema); diff --git a/packages/backend/src/services/chart/charts/federation.ts b/packages/backend/src/services/chart/charts/federation.ts index 4fbd297dbf..10221ee1e3 100644 --- a/packages/backend/src/services/chart/charts/federation.ts +++ b/packages/backend/src/services/chart/charts/federation.ts @@ -1,6 +1,7 @@ import Chart, { KVs } from '../core.js'; -import { Followings } from '@/models/index.js'; +import { Followings, Instances } from '@/models/index.js'; import { name, schema } from './entities/federation.js'; +import { fetchMeta } from '@/misc/fetch-meta.js'; /** * フェデレーションに関するチャート @@ -17,34 +18,72 @@ export default class FederationChart extends Chart { } protected async tickMinor(): Promise>> { + const meta = await fetchMeta(); + + const suspendedInstancesQuery = Instances.createQueryBuilder('instance') + .select('instance.host') + .where('instance.isSuspended = true'); + const pubsubSubQuery = Followings.createQueryBuilder('f') .select('f.followerHost') .where('f.followerHost IS NOT NULL'); - const [sub, pub, pubsub] = await Promise.all([ + const subInstancesQuery = Followings.createQueryBuilder('f') + .select('f.followeeHost') + .where('f.followeeHost IS NOT NULL'); + + const pubInstancesQuery = Followings.createQueryBuilder('f') + .select('f.followerHost') + .where('f.followerHost IS NOT NULL'); + + const [sub, pub, pubsub, subActive, pubActive] = await Promise.all([ Followings.createQueryBuilder('following') .select('COUNT(DISTINCT following.followeeHost)') .where('following.followeeHost IS NOT NULL') + .andWhere(meta.blockedHosts.length === 0 ? '1=1' : `following.followeeHost NOT IN (:...blocked)`, { blocked: meta.blockedHosts }) + .andWhere(`following.followeeHost NOT IN (${ suspendedInstancesQuery.getQuery() })`) .getRawOne() .then(x => parseInt(x.count, 10)), Followings.createQueryBuilder('following') .select('COUNT(DISTINCT following.followerHost)') .where('following.followerHost IS NOT NULL') + .andWhere(meta.blockedHosts.length === 0 ? '1=1' : `following.followerHost NOT IN (:...blocked)`, { blocked: meta.blockedHosts }) + .andWhere(`following.followerHost NOT IN (${ suspendedInstancesQuery.getQuery() })`) .getRawOne() .then(x => parseInt(x.count, 10)), Followings.createQueryBuilder('following') .select('COUNT(DISTINCT following.followeeHost)') .where('following.followeeHost IS NOT NULL') + .andWhere(meta.blockedHosts.length === 0 ? '1=1' : `following.followeeHost NOT IN (:...blocked)`, { blocked: meta.blockedHosts }) + .andWhere(`following.followeeHost NOT IN (${ suspendedInstancesQuery.getQuery() })`) .andWhere(`following.followeeHost IN (${ pubsubSubQuery.getQuery() })`) .setParameters(pubsubSubQuery.getParameters()) .getRawOne() .then(x => parseInt(x.count, 10)), + Instances.createQueryBuilder('instance') + .select('COUNT(instance.id)') + .where(`instance.host IN (${ subInstancesQuery.getQuery() })`) + .andWhere(meta.blockedHosts.length === 0 ? '1=1' : `instance.host NOT IN (:...blocked)`, { blocked: meta.blockedHosts }) + .andWhere(`instance.isSuspended = false`) + .andWhere(`instance.lastCommunicatedAt > :gt`, { gt: new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)) }) + .getRawOne() + .then(x => parseInt(x.count, 10)), + Instances.createQueryBuilder('instance') + .select('COUNT(instance.id)') + .where(`instance.host IN (${ pubInstancesQuery.getQuery() })`) + .andWhere(meta.blockedHosts.length === 0 ? '1=1' : `instance.host NOT IN (:...blocked)`, { blocked: meta.blockedHosts }) + .andWhere(`instance.isSuspended = false`) + .andWhere(`instance.lastCommunicatedAt > :gt`, { gt: new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)) }) + .getRawOne() + .then(x => parseInt(x.count, 10)), ]); return { 'sub': sub, 'pub': pub, 'pubsub': pubsub, + 'subActive': subActive, + 'pubActive': pubActive, }; } diff --git a/packages/backend/src/services/chart/charts/instance.ts b/packages/backend/src/services/chart/charts/instance.ts index 593430f281..fe29ba5228 100644 --- a/packages/backend/src/services/chart/charts/instance.ts +++ b/packages/backend/src/services/chart/charts/instance.ts @@ -21,14 +21,12 @@ export default class InstanceChart extends Chart { followingCount, followersCount, driveFiles, - //driveUsage, ] = await Promise.all([ - Notes.count({ userHost: group }), - Users.count({ host: group }), - Followings.count({ followerHost: group }), - Followings.count({ followeeHost: group }), - DriveFiles.count({ userHost: group }), - //DriveFiles.calcDriveUsageOfHost(group), + Notes.countBy({ userHost: group }), + Users.countBy({ host: group }), + Followings.countBy({ followerHost: group }), + Followings.countBy({ followeeHost: group }), + DriveFiles.countBy({ userHost: group }), ]); return { diff --git a/packages/backend/src/services/chart/charts/notes.ts b/packages/backend/src/services/chart/charts/notes.ts index ab6a37e3c1..bb14b62f3c 100644 --- a/packages/backend/src/services/chart/charts/notes.ts +++ b/packages/backend/src/services/chart/charts/notes.ts @@ -15,8 +15,8 @@ export default class NotesChart extends Chart { protected async tickMajor(): Promise>> { const [localCount, remoteCount] = await Promise.all([ - Notes.count({ userHost: null }), - Notes.count({ userHost: Not(IsNull()) }), + Notes.countBy({ userHost: IsNull() }), + Notes.countBy({ userHost: Not(IsNull()) }), ]); return { diff --git a/packages/backend/src/services/chart/charts/per-user-drive.ts b/packages/backend/src/services/chart/charts/per-user-drive.ts index 131befa396..5f75dc6887 100644 --- a/packages/backend/src/services/chart/charts/per-user-drive.ts +++ b/packages/backend/src/services/chart/charts/per-user-drive.ts @@ -14,7 +14,7 @@ export default class PerUserDriveChart extends Chart { protected async tickMajor(group: string): Promise>> { const [count, size] = await Promise.all([ - DriveFiles.count({ userId: group }), + DriveFiles.countBy({ userId: group }), DriveFiles.calcDriveUsageOf(group), ]); diff --git a/packages/backend/src/services/chart/charts/per-user-following.ts b/packages/backend/src/services/chart/charts/per-user-following.ts index 5d5dd1fa1e..02b149f52a 100644 --- a/packages/backend/src/services/chart/charts/per-user-following.ts +++ b/packages/backend/src/services/chart/charts/per-user-following.ts @@ -20,10 +20,10 @@ export default class PerUserFollowingChart extends Chart { remoteFollowingsCount, remoteFollowersCount, ] = await Promise.all([ - Followings.count({ followerId: group, followeeHost: null }), - Followings.count({ followeeId: group, followerHost: null }), - Followings.count({ followerId: group, followeeHost: Not(IsNull()) }), - Followings.count({ followeeId: group, followerHost: Not(IsNull()) }), + Followings.countBy({ followerId: group, followeeHost: IsNull() }), + Followings.countBy({ followeeId: group, followerHost: IsNull() }), + Followings.countBy({ followerId: group, followeeHost: Not(IsNull()) }), + Followings.countBy({ followeeId: group, followerHost: Not(IsNull()) }), ]); return { diff --git a/packages/backend/src/services/chart/charts/per-user-notes.ts b/packages/backend/src/services/chart/charts/per-user-notes.ts index 9c5dea1aa9..b9191dd088 100644 --- a/packages/backend/src/services/chart/charts/per-user-notes.ts +++ b/packages/backend/src/services/chart/charts/per-user-notes.ts @@ -15,7 +15,7 @@ export default class PerUserNotesChart extends Chart { protected async tickMajor(group: string): Promise>> { const [count] = await Promise.all([ - Notes.count({ userId: group }), + Notes.countBy({ userId: group }), ]); return { diff --git a/packages/backend/src/services/chart/charts/users.ts b/packages/backend/src/services/chart/charts/users.ts index fb9d5e15fb..acb16ead87 100644 --- a/packages/backend/src/services/chart/charts/users.ts +++ b/packages/backend/src/services/chart/charts/users.ts @@ -15,8 +15,8 @@ export default class UsersChart extends Chart { protected async tickMajor(): Promise>> { const [localCount, remoteCount] = await Promise.all([ - Users.count({ host: null }), - Users.count({ host: Not(IsNull()) }), + Users.countBy({ host: IsNull() }), + Users.countBy({ host: Not(IsNull()) }), ]); return { diff --git a/packages/backend/src/services/chart/core.ts b/packages/backend/src/services/chart/core.ts index 39fad71dd7..cf69e2194d 100644 --- a/packages/backend/src/services/chart/core.ts +++ b/packages/backend/src/services/chart/core.ts @@ -6,9 +6,10 @@ import * as nestedProperty from 'nested-property'; import Logger from '../logger.js'; -import { EntitySchema, getRepository, Repository, LessThan, Between } from 'typeorm'; +import { EntitySchema, Repository, LessThan, Between } from 'typeorm'; import { dateUTC, isTimeSame, isTimeBefore, subtractTime, addTime } from '@/prelude/time.js'; import { getChartInsertLock } from '@/misc/app-lock.js'; +import { db } from '@/db/postgre.js'; const logger = new Logger('chart', 'white', process.env.NODE_ENV !== 'test'); @@ -241,8 +242,8 @@ export default abstract class Chart { this.schema = schema; const { hour, day } = Chart.schemaToEntity(name, schema, grouped); - this.repositoryForHour = getRepository<{ id: number; group?: string | null; date: number; }>(hour); - this.repositoryForDay = getRepository<{ id: number; group?: string | null; date: number; }>(day); + this.repositoryForHour = db.getRepository<{ id: number; group?: string | null; date: number; }>(hour); + this.repositoryForDay = db.getRepository<{ id: number; group?: string | null; date: number; }>(day); } private convertRawRecord(x: RawRecord): KVs { @@ -271,9 +272,10 @@ export default abstract class Chart { span === 'day' ? this.repositoryForDay : new Error('not happen') as never; - return repository.findOne(group ? { - group: group, - } : {}, { + return repository.findOne({ + where: group ? { + group: group, + } : {}, order: { date: -1, }, @@ -297,7 +299,7 @@ export default abstract class Chart { new Error('not happen') as never; // 現在(=今のHour or Day)のログ - const currentLog = await repository.findOne({ + const currentLog = await repository.findOneBy({ date: Chart.dateToTimestamp(current), ...(group ? { group: group } : {}), }) as RawRecord | undefined; @@ -337,7 +339,7 @@ export default abstract class Chart { const unlock = await getChartInsertLock(lockKey); try { // ロック内でもう1回チェックする - const currentLog = await repository.findOne({ + const currentLog = await repository.findOneBy({ date: date, ...(group ? { group: group } : {}), }) as RawRecord | undefined; @@ -356,7 +358,7 @@ export default abstract class Chart { date: date, ...(group ? { group: group } : {}), ...columns, - }).then(x => repository.findOneOrFail(x.identifiers[0])) as RawRecord; + }).then(x => repository.findOneByOrFail(x.identifiers[0])) as RawRecord; logger.info(`${this.name + (group ? `:${group}` : '')}(${span}): New commit created`); @@ -598,9 +600,10 @@ export default abstract class Chart { if (logs.length === 0) { // もっとも新しいログを持ってくる // (すくなくともひとつログが無いと隙間埋めできないため) - const recentLog = await repository.findOne(group ? { - group: group, - } : {}, { + const recentLog = await repository.findOne({ + where: group ? { + group: group, + } : {}, order: { date: -1, }, @@ -615,9 +618,10 @@ export default abstract class Chart { // 要求された範囲の最も古い箇所時点での最も新しいログを持ってきて末尾に追加する // (隙間埋めできないため) const outdatedLog = await repository.findOne({ - date: LessThan(Chart.dateToTimestamp(gt)), - ...(group ? { group: group } : {}), - }, { + where: { + date: LessThan(Chart.dateToTimestamp(gt)), + ...(group ? { group: group } : {}), + }, order: { date: -1, }, diff --git a/packages/backend/src/services/create-notification.ts b/packages/backend/src/services/create-notification.ts index d78e707ecf..9a53db1f38 100644 --- a/packages/backend/src/services/create-notification.ts +++ b/packages/backend/src/services/create-notification.ts @@ -15,7 +15,7 @@ export async function createNotification( return null; } - const profile = await UserProfiles.findOne({ userId: notifieeId }); + const profile = await UserProfiles.findOneBy({ userId: notifieeId }); const isMuted = profile?.mutingNotificationTypes.includes(type); @@ -29,7 +29,7 @@ export async function createNotification( isRead: isMuted, ...data, } as Partial) - .then(x => Notifications.findOneOrFail(x.identifiers[0])); + .then(x => Notifications.findOneByOrFail(x.identifiers[0])); const packed = await Notifications.pack(notification, {}); @@ -38,12 +38,12 @@ export async function createNotification( // 2秒経っても(今回作成した)通知が既読にならなかったら「未読の通知がありますよ」イベントを発行する setTimeout(async () => { - const fresh = await Notifications.findOne(notification.id); + const fresh = await Notifications.findOneBy({ id: notification.id }); if (fresh == null) return; // 既に削除されているかもしれない if (fresh.isRead) return; //#region ただしミュートしているユーザーからの通知なら無視 - const mutings = await Mutings.find({ + const mutings = await Mutings.findBy({ muterId: notifieeId, }); if (data.notifierId && mutings.map(m => m.muteeId).includes(data.notifierId)) { @@ -54,8 +54,8 @@ export async function createNotification( publishMainStream(notifieeId, 'unreadNotification', packed); pushSw(notifieeId, 'notification', packed); - if (type === 'follow') sendEmailNotification.follow(notifieeId, await Users.findOneOrFail(data.notifierId!)); - if (type === 'receiveFollowRequest') sendEmailNotification.receiveFollowRequest(notifieeId, await Users.findOneOrFail(data.notifierId!)); + if (type === 'follow') sendEmailNotification.follow(notifieeId, await Users.findOneByOrFail({ id: data.notifierId! })); + if (type === 'receiveFollowRequest') sendEmailNotification.receiveFollowRequest(notifieeId, await Users.findOneByOrFail({ id: data.notifierId! })); }, 2000); return notification; diff --git a/packages/backend/src/services/create-system-user.ts b/packages/backend/src/services/create-system-user.ts index 781e0560d1..bae91ec4c3 100644 --- a/packages/backend/src/services/create-system-user.ts +++ b/packages/backend/src/services/create-system-user.ts @@ -4,10 +4,11 @@ import generateNativeUserToken from '../server/api/common/generate-native-user-t import { genRsaKeyPair } from '@/misc/gen-key-pair.js'; import { User } from '@/models/entities/user.js'; import { UserProfile } from '@/models/entities/user-profile.js'; -import { getConnection, ObjectLiteral } from 'typeorm'; +import { IsNull } from 'typeorm'; import { genId } from '@/misc/gen-id.js'; import { UserKeypair } from '@/models/entities/user-keypair.js'; import { UsedUsername } from '@/models/entities/used-username.js'; +import { db } from '@/db/postgre.js'; export async function createSystemUser(username: string) { const password = uuid(); @@ -21,13 +22,13 @@ export async function createSystemUser(username: string) { const keyPair = await genRsaKeyPair(4096); - let account!: User | ObjectLiteral; + let account!: User; // Start transaction - await getConnection().transaction(async transactionalEntityManager => { - const exist = await transactionalEntityManager.findOne(User, { + await db.transaction(async transactionalEntityManager => { + const exist = await transactionalEntityManager.findOneBy(User, { usernameLower: username.toLowerCase(), - host: null, + host: IsNull(), }); if (exist) throw new Error('the user is already exists'); @@ -43,7 +44,7 @@ export async function createSystemUser(username: string) { isLocked: true, isExplorable: false, isBot: true, - }).then(x => transactionalEntityManager.findOneOrFail(User, x.identifiers[0])); + }).then(x => transactionalEntityManager.findOneByOrFail(User, x.identifiers[0])); await transactionalEntityManager.insert(UserKeypair, { publicKey: keyPair.publicKey, diff --git a/packages/backend/src/services/drive/add-file.ts b/packages/backend/src/services/drive/add-file.ts index 9f1980bff1..549b11c9fe 100644 --- a/packages/backend/src/services/drive/add-file.ts +++ b/packages/backend/src/services/drive/add-file.ts @@ -21,6 +21,7 @@ import S3 from 'aws-sdk/clients/s3.js'; import { getS3 } from './s3.js'; import sharp from 'sharp'; import { FILE_TYPE_BROWSERSAFE } from '@/const.js'; +import { IsNull } from 'typeorm'; const logger = driveLogger.createSubLogger('register', 'yellow'); @@ -108,7 +109,7 @@ async function save(file: DriveFile, path: string, name: string, type: string, h file.size = size; file.storedInternal = false; - return await DriveFiles.insert(file).then(x => DriveFiles.findOneOrFail(x.identifiers[0])); + return await DriveFiles.insert(file).then(x => DriveFiles.findOneByOrFail(x.identifiers[0])); } else { // use internal storage const accessKey = uuid(); const thumbnailAccessKey = 'thumbnail-' + uuid(); @@ -142,7 +143,7 @@ async function save(file: DriveFile, path: string, name: string, type: string, h file.md5 = hash; file.size = size; - return await DriveFiles.insert(file).then(x => DriveFiles.findOneOrFail(x.identifiers[0])); + return await DriveFiles.insert(file).then(x => DriveFiles.findOneByOrFail(x.identifiers[0])); } } @@ -344,7 +345,7 @@ export async function addFile({ if (user && !force) { // Check if there is a file with the same hash - const much = await DriveFiles.findOne({ + const much = await DriveFiles.findOneBy({ md5: info.md5, userId: user.id, }); @@ -370,7 +371,7 @@ export async function addFile({ throw new Error('no-free-space'); } else { // (アバターまたはバナーを含まず)最も古いファイルを削除する - deleteOldFile(await Users.findOneOrFail(user.id) as IRemoteUser); + deleteOldFile(await Users.findOneByOrFail({ id: user.id }) as IRemoteUser); } } } @@ -381,9 +382,9 @@ export async function addFile({ return null; } - const driveFolder = await DriveFolders.findOne({ + const driveFolder = await DriveFolders.findOneBy({ id: folderId, - userId: user ? user.id : null, + userId: user ? user.id : IsNull(), }); if (driveFolder == null) throw new Error('folder-not-found'); @@ -405,7 +406,7 @@ export async function addFile({ properties['orientation'] = info.orientation; } - const profile = user ? await UserProfiles.findOne(user.id) : null; + const profile = user ? await UserProfiles.findOneBy({ userId: user.id }) : null; const folder = await fetchFolder(); @@ -450,15 +451,15 @@ export async function addFile({ file.type = info.type.mime; file.storedInternal = false; - file = await DriveFiles.insert(file).then(x => DriveFiles.findOneOrFail(x.identifiers[0])); + file = await DriveFiles.insert(file).then(x => DriveFiles.findOneByOrFail(x.identifiers[0])); } catch (err) { // duplicate key error (when already registered) if (isDuplicateKeyValueError(err)) { logger.info(`already registered ${file.uri}`); - file = await DriveFiles.findOne({ - uri: file.uri, - userId: user ? user.id : null, + file = await DriveFiles.findOneBy({ + uri: file.uri!, + userId: user ? user.id : IsNull(), }) as DriveFile; } else { logger.error(err as Error); @@ -484,8 +485,6 @@ export async function addFile({ perUserDriveChart.update(file, true); if (file.userHost !== null) { instanceChart.updateDrive(file, true); - Instances.increment({ host: file.userHost }, 'driveUsage', file.size); - Instances.increment({ host: file.userHost }, 'driveFiles', 1); } return file; diff --git a/packages/backend/src/services/drive/delete-file.ts b/packages/backend/src/services/drive/delete-file.ts index 18f1dc970b..4816a3a31b 100644 --- a/packages/backend/src/services/drive/delete-file.ts +++ b/packages/backend/src/services/drive/delete-file.ts @@ -86,8 +86,6 @@ async function postProcess(file: DriveFile, isExpired = false) { perUserDriveChart.update(file, false); if (file.userHost !== null) { instanceChart.updateDrive(file, false); - Instances.decrement({ host: file.userHost }, 'driveUsage', file.size); - Instances.decrement({ host: file.userHost }, 'driveFiles', 1); } } diff --git a/packages/backend/src/services/fetch-instance-metadata.ts b/packages/backend/src/services/fetch-instance-metadata.ts index f3a0424abd..2b6f82a910 100644 --- a/packages/backend/src/services/fetch-instance-metadata.ts +++ b/packages/backend/src/services/fetch-instance-metadata.ts @@ -13,7 +13,7 @@ export async function fetchInstanceMetadata(instance: Instance, force = false): const unlock = await getFetchInstanceMetadataLock(instance.host); if (!force) { - const _instance = await Instances.findOne({ host: instance.host }); + const _instance = await Instances.findOneBy({ host: instance.host }); const now = Date.now(); if (_instance && _instance.infoUpdatedAt && (now - _instance.infoUpdatedAt.getTime() < 1000 * 60 * 60 * 24)) { unlock(); diff --git a/packages/backend/src/services/following/create.ts b/packages/backend/src/services/following/create.ts index a416412131..0daf30ddad 100644 --- a/packages/backend/src/services/following/create.ts +++ b/packages/backend/src/services/following/create.ts @@ -45,7 +45,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, }); @@ -108,17 +108,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 +138,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 +148,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 +158,7 @@ export default async function(_follower: { id: User['id'] }, _followee: { id: Us // フォローしているユーザーは自動承認オプション if (!autoAccept && (Users.isLocalUser(followee) && followeeProfile.autoAcceptFollowed)) { - const followed = await Followings.findOne({ + const followed = await Followings.findOneBy({ followerId: followee.id, followeeId: follower.id, }); diff --git a/packages/backend/src/services/following/delete.ts b/packages/backend/src/services/following/delete.ts index d82c0be52d..35fd664b55 100644 --- a/packages/backend/src/services/following/delete.ts +++ b/packages/backend/src/services/following/delete.ts @@ -13,7 +13,7 @@ import { instanceChart, perUserFollowingChart } from '@/services/chart/index.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, }); diff --git a/packages/backend/src/services/following/reject.ts b/packages/backend/src/services/following/reject.ts index 3b0cb2ba88..2d1db3c342 100644 --- a/packages/backend/src/services/following/reject.ts +++ b/packages/backend/src/services/following/reject.ts @@ -7,8 +7,17 @@ import { User, ILocalUser, IRemoteUser } from '@/models/entities/user.js'; import { Users, FollowRequests, Followings } from '@/models/index.js'; import { decrementFollowing } from './delete.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 +63,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 +77,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 +92,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, }); diff --git a/packages/backend/src/services/following/requests/accept-all.ts b/packages/backend/src/services/following/requests/accept-all.ts index a240bec8f4..5fbb549e01 100644 --- a/packages/backend/src/services/following/requests/accept-all.ts +++ b/packages/backend/src/services/following/requests/accept-all.ts @@ -7,12 +7,12 @@ import { FollowRequests, Users } from '@/models/index.js'; * @param user ユーザー */ export default async function(user: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']; }) { - const requests = await FollowRequests.find({ + const requests = await FollowRequests.findBy({ followeeId: user.id, }); for (const request of requests) { - const follower = await Users.findOneOrFail(request.followerId); + const follower = await Users.findOneByOrFail({ id: request.followerId }); accept(user, follower); } } diff --git a/packages/backend/src/services/following/requests/accept.ts b/packages/backend/src/services/following/requests/accept.ts index b8113cd1b1..20829f70c7 100644 --- a/packages/backend/src/services/following/requests/accept.ts +++ b/packages/backend/src/services/following/requests/accept.ts @@ -4,12 +4,12 @@ import renderAccept from '@/remote/activitypub/renderer/accept.js'; import { deliver } from '@/queue/index.js'; import { publishMainStream } from '@/services/stream.js'; import { insertFollowingDoc } from '../create.js'; -import { User, ILocalUser } from '@/models/entities/user.js'; +import { User, ILocalUser, CacheableUser } from '@/models/entities/user.js'; import { FollowRequests, Users } from '@/models/index.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; -export default async function(followee: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']; }, follower: User) { - const request = await FollowRequests.findOne({ +export default async function(followee: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']; }, follower: CacheableUser) { + const request = await FollowRequests.findOneBy({ followeeId: followee.id, followerId: follower.id, }); diff --git a/packages/backend/src/services/following/requests/cancel.ts b/packages/backend/src/services/following/requests/cancel.ts index ca9777d38b..56531fa1fd 100644 --- a/packages/backend/src/services/following/requests/cancel.ts +++ b/packages/backend/src/services/following/requests/cancel.ts @@ -16,7 +16,7 @@ export default async function(followee: { id: User['id']; host: User['host']; ur } } - const request = await FollowRequests.findOne({ + const request = await FollowRequests.findOneBy({ followeeId: followee.id, followerId: follower.id, }); diff --git a/packages/backend/src/services/following/requests/create.ts b/packages/backend/src/services/following/requests/create.ts index bca607d7e4..bda2f8f92d 100644 --- a/packages/backend/src/services/following/requests/create.ts +++ b/packages/backend/src/services/following/requests/create.ts @@ -12,11 +12,11 @@ export default async function(follower: { id: User['id']; host: User['host']; ur // check blocking const [blocking, blocked] = await Promise.all([ - Blockings.findOne({ + Blockings.findOneBy({ blockerId: follower.id, blockeeId: followee.id, }), - Blockings.findOne({ + Blockings.findOneBy({ blockerId: followee.id, blockeeId: follower.id, }), @@ -39,7 +39,7 @@ export default async function(follower: { id: User['id']; host: User['host']; ur followeeHost: followee.host, followeeInbox: Users.isRemoteUser(followee) ? followee.inbox : undefined, followeeSharedInbox: Users.isRemoteUser(followee) ? followee.sharedInbox : undefined, - }).then(x => FollowRequests.findOneOrFail(x.identifiers[0])); + }).then(x => FollowRequests.findOneByOrFail(x.identifiers[0])); // Publish receiveRequest event if (Users.isLocalUser(followee)) { diff --git a/packages/backend/src/services/i/pin.ts b/packages/backend/src/services/i/pin.ts index 06d7e79e89..f35392a34b 100644 --- a/packages/backend/src/services/i/pin.ts +++ b/packages/backend/src/services/i/pin.ts @@ -18,7 +18,7 @@ import { deliverToRelays } from '../relay.js'; */ export async function addPinned(user: { id: User['id']; host: User['host']; }, noteId: Note['id']) { // Fetch pinee - const note = await Notes.findOne({ + const note = await Notes.findOneBy({ id: noteId, userId: user.id, }); @@ -27,7 +27,7 @@ export async function addPinned(user: { id: User['id']; host: User['host']; }, n throw new IdentifiableError('70c4e51f-5bea-449c-a030-53bee3cce202', 'No such note.'); } - const pinings = await UserNotePinings.find({ userId: user.id }); + const pinings = await UserNotePinings.findBy({ userId: user.id }); if (pinings.length >= 5) { throw new IdentifiableError('15a018eb-58e5-4da1-93be-330fcc5e4e1a', 'You can not pin notes any more.'); @@ -57,7 +57,7 @@ export async function addPinned(user: { id: User['id']; host: User['host']; }, n */ export async function removePinned(user: { id: User['id']; host: User['host']; }, noteId: Note['id']) { // Fetch unpinee - const note = await Notes.findOne({ + const note = await Notes.findOneBy({ id: noteId, userId: user.id, }); @@ -78,7 +78,7 @@ export async function removePinned(user: { id: User['id']; host: User['host']; } } export async function deliverPinnedChange(userId: User['id'], noteId: Note['id'], isAddition: boolean) { - const user = await Users.findOne(userId); + const user = await Users.findOneBy({ id: userId }); if (user == null) throw new Error('user not found'); if (!Users.isLocalUser(user)) return; diff --git a/packages/backend/src/services/i/update.ts b/packages/backend/src/services/i/update.ts index 1fbaf40df1..27bd38bd39 100644 --- a/packages/backend/src/services/i/update.ts +++ b/packages/backend/src/services/i/update.ts @@ -7,7 +7,7 @@ import { deliverToFollowers } from '@/remote/activitypub/deliver-manager.js'; import { deliverToRelays } from '../relay.js'; export async function publishToFollowers(userId: User['id']) { - const user = await Users.findOne(userId); + const user = await Users.findOneBy({ id: userId }); if (user == null) throw new Error('user not found'); // フォロワーがリモートユーザーかつ投稿者がローカルユーザーならUpdateを配信 diff --git a/packages/backend/src/services/instance-actor.ts b/packages/backend/src/services/instance-actor.ts index e271710488..bddd0355a2 100644 --- a/packages/backend/src/services/instance-actor.ts +++ b/packages/backend/src/services/instance-actor.ts @@ -2,6 +2,7 @@ import { createSystemUser } from './create-system-user.js'; import { ILocalUser } from '@/models/entities/user.js'; import { Users } from '@/models/index.js'; import { Cache } from '@/misc/cache.js'; +import { IsNull } from 'typeorm'; const ACTOR_USERNAME = 'instance.actor' as const; @@ -11,8 +12,8 @@ export async function getInstanceActor(): Promise { const cached = cache.get(null); if (cached) return cached; - const user = await Users.findOne({ - host: null, + const user = await Users.findOneBy({ + host: IsNull(), username: ACTOR_USERNAME, }) as ILocalUser | undefined; diff --git a/packages/backend/src/services/messages/create.ts b/packages/backend/src/services/messages/create.ts index c3908b2552..e5cd5a30d2 100644 --- a/packages/backend/src/services/messages/create.ts +++ b/packages/backend/src/services/messages/create.ts @@ -1,4 +1,4 @@ -import { User } from '@/models/entities/user.js'; +import { CacheableUser, User } from '@/models/entities/user.js'; import { UserGroup } from '@/models/entities/user-group.js'; import { DriveFile } from '@/models/entities/drive-file.js'; import { MessagingMessages, UserGroupJoinings, Mutings, Users } from '@/models/index.js'; @@ -13,7 +13,7 @@ import renderCreate from '@/remote/activitypub/renderer/create.js'; import { renderActivity } from '@/remote/activitypub/renderer/index.js'; import { deliver } from '@/queue/index.js'; -export async function createMessage(user: { id: User['id']; host: User['host']; }, recipientUser: User | undefined, recipientGroup: UserGroup | undefined, text: string | null | undefined, file: DriveFile | null, uri?: string) { +export async function createMessage(user: { id: User['id']; host: User['host']; }, recipientUser: CacheableUser | undefined, recipientGroup: UserGroup | undefined, text: string | null | undefined, file: DriveFile | null, uri?: string) { const message = { id: genId(), createdAt: new Date(), @@ -50,7 +50,7 @@ export async function createMessage(user: { id: User['id']; host: User['host']; publishGroupMessagingStream(recipientGroup.id, 'message', messageObj); // メンバーのストリーム - const joinings = await UserGroupJoinings.find({ userGroupId: recipientGroup.id }); + const joinings = await UserGroupJoinings.findBy({ userGroupId: recipientGroup.id }); for (const joining of joinings) { publishMessagingIndexStream(joining.userId, 'message', messageObj); publishMainStream(joining.userId, 'messagingMessage', messageObj); @@ -59,14 +59,14 @@ export async function createMessage(user: { id: User['id']; host: User['host']; // 2秒経っても(今回作成した)メッセージが既読にならなかったら「未読のメッセージがありますよ」イベントを発行する setTimeout(async () => { - const freshMessage = await MessagingMessages.findOne(message.id); + const freshMessage = await MessagingMessages.findOneBy({ id: message.id }); if (freshMessage == null) return; // メッセージが削除されている場合もある if (recipientUser && Users.isLocalUser(recipientUser)) { if (freshMessage.isRead) return; // 既読 //#region ただしミュートされているなら発行しない - const mute = await Mutings.find({ + const mute = await Mutings.findBy({ muterId: recipientUser.id, }); if (mute.map(m => m.muteeId).includes(user.id)) return; @@ -75,7 +75,7 @@ export async function createMessage(user: { id: User['id']; host: User['host']; publishMainStream(recipientUser.id, 'unreadMessagingMessage', messageObj); pushNotification(recipientUser.id, 'unreadMessagingMessage', messageObj); } else if (recipientGroup) { - const joinings = await UserGroupJoinings.find({ userGroupId: recipientGroup.id, userId: Not(user.id) }); + const joinings = await UserGroupJoinings.findBy({ userGroupId: recipientGroup.id, userId: Not(user.id) }); for (const joining of joinings) { if (freshMessage.reads.includes(joining.userId)) return; // 既読 publishMainStream(joining.userId, 'unreadMessagingMessage', messageObj); diff --git a/packages/backend/src/services/messages/delete.ts b/packages/backend/src/services/messages/delete.ts index 82eb6cb21c..1e7ce1981c 100644 --- a/packages/backend/src/services/messages/delete.ts +++ b/packages/backend/src/services/messages/delete.ts @@ -14,8 +14,8 @@ export async function deleteMessage(message: MessagingMessage) { async function postDeleteMessage(message: MessagingMessage) { if (message.recipientId) { - const user = await Users.findOneOrFail(message.userId); - const recipient = await Users.findOneOrFail(message.recipientId); + const user = await Users.findOneByOrFail({ id: message.userId }); + const recipient = await Users.findOneByOrFail({ id: message.recipientId }); if (Users.isLocalUser(user)) publishMessagingStream(message.userId, message.recipientId, 'deleted', message.id); if (Users.isLocalUser(recipient)) publishMessagingStream(message.recipientId, message.userId, 'deleted', message.id); diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts index ed242a0b59..2ed194b7e9 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'; @@ -34,6 +34,12 @@ import { deliverToRelays } from '../relay.js'; 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 { Cache } from '@/misc/cache.js'; +import { UserProfile } from '@/models/entities/user-profile.js'; +import { db } from '@/db/postgre.js'; + +const mutedWordsCache = new Cache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(1000 * 60 * 5); type NotificationType = 'reply' | 'renote' | 'quote' | 'mention'; @@ -73,7 +79,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, }); @@ -90,6 +96,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; @@ -101,9 +114,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; @@ -116,7 +129,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; } @@ -125,7 +138,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(); @@ -198,7 +211,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') { @@ -211,7 +224,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 })); } } @@ -240,10 +253,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) { @@ -259,25 +274,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, @@ -296,6 +303,15 @@ export default async (user: { id: User['id']; username: User['username']; host: incRenoteCount(data.renote); } + if (data.poll && data.poll.expiresAt) { + const delay = data.poll.expiresAt.getTime() - Date.now(); + endedPollNotificationQueue.add({ + noteId: note.id, + }, { + delay + }); + } + if (!silent) { if (Users.isLocalUser(user)) activeUsersChart.write(user); @@ -341,7 +357,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, }); @@ -388,13 +404,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); } @@ -419,7 +435,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 => { @@ -455,7 +471,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!, @@ -499,7 +515,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; @@ -516,7 +532,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({ @@ -566,7 +582,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), }); @@ -577,7 +593,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), }); @@ -587,9 +603,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, }); diff --git a/packages/backend/src/services/note/delete.ts b/packages/backend/src/services/note/delete.ts index cf23656f8f..ffd609dd84 100644 --- a/packages/backend/src/services/note/delete.ts +++ b/packages/backend/src/services/note/delete.ts @@ -20,7 +20,7 @@ import { Brackets, In } from 'typeorm'; * @param user 投稿者 * @param note 投稿 */ -export default async function(user: User, note: Note, quiet = false) { +export default async function(user: { id: User['id']; uri: User['uri']; host: User['host']; }, note: Note, quiet = false) { const deletedAt = new Date(); // この投稿を除く指定したユーザーによる指定したノートのリノートが存在しないとき @@ -29,6 +29,10 @@ export default async function(user: User, note: Note, quiet = false) { Notes.decrement({ id: note.renoteId }, 'score', 1); } + if (note.replyId) { + await Notes.decrement({ id: note.replyId }, 'repliesCount', 1); + } + if (!quiet) { publishNoteStream(note.id, 'deleted', { deletedAt: deletedAt, @@ -36,11 +40,11 @@ export default async function(user: User, note: Note, quiet = false) { //#region ローカルの投稿なら削除アクティビティを配送 if (Users.isLocalUser(user) && !note.localOnly) { - let renote: Note | undefined; + let renote: Note | null; // if deletd note is renote if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length === 0)) { - renote = await Notes.findOne({ + renote = await Notes.findOneBy({ id: note.renoteId, }); } @@ -127,7 +131,7 @@ async function getMentionedRemoteUsers(note: Note) { }) as IRemoteUser[]; } -async function deliverToConcerned(user: ILocalUser, note: Note, content: any) { +async function deliverToConcerned(user: { id: ILocalUser['id']; host: null; }, note: Note, content: any) { deliverToFollowers(user, content); deliverToRelays(user, content); const remoteUsers = await getMentionedRemoteUsers(note); diff --git a/packages/backend/src/services/note/polls/update.ts b/packages/backend/src/services/note/polls/update.ts index 88baf16b64..43ca3eff4d 100644 --- a/packages/backend/src/services/note/polls/update.ts +++ b/packages/backend/src/services/note/polls/update.ts @@ -7,10 +7,10 @@ import { deliverToFollowers } from '@/remote/activitypub/deliver-manager.js'; import { deliverToRelays } from '../../relay.js'; export async function deliverQuestionUpdate(noteId: Note['id']) { - const note = await Notes.findOne(noteId); + const note = await Notes.findOneBy({ id: noteId }); if (note == null) throw new Error('note not found'); - const user = await Users.findOne(note.userId); + const user = await Users.findOneBy({ id: note.userId }); if (user == null) throw new Error('note not found'); if (Users.isLocalUser(user)) { diff --git a/packages/backend/src/services/note/polls/vote.ts b/packages/backend/src/services/note/polls/vote.ts index 9b83b1953f..84d98769d9 100644 --- a/packages/backend/src/services/note/polls/vote.ts +++ b/packages/backend/src/services/note/polls/vote.ts @@ -1,13 +1,13 @@ import { publishNoteStream } from '@/services/stream.js'; -import { User } from '@/models/entities/user.js'; +import { CacheableUser, User } from '@/models/entities/user.js'; import { Note } from '@/models/entities/note.js'; import { PollVotes, NoteWatchings, Polls, Blockings } from '@/models/index.js'; import { Not } from 'typeorm'; import { genId } from '@/misc/gen-id.js'; import { createNotification } from '../../create-notification.js'; -export default async function(user: User, note: Note, choice: number) { - const poll = await Polls.findOne(note.id); +export default async function(user: CacheableUser, note: Note, choice: number) { + const poll = await Polls.findOneBy({ noteId: note.id }); if (poll == null) throw new Error('poll not found'); @@ -16,7 +16,7 @@ export default async function(user: User, note: Note, choice: number) { // Check blocking if (note.userId !== user.id) { - const block = await Blockings.findOne({ + const block = await Blockings.findOneBy({ blockerId: note.userId, blockeeId: user.id, }); @@ -26,7 +26,7 @@ export default async function(user: User, note: Note, choice: number) { } // if already voted - const exist = await PollVotes.find({ + const exist = await PollVotes.findBy({ noteId: note.id, userId: user.id, }); @@ -65,7 +65,7 @@ export default async function(user: User, note: Note, choice: number) { }); // Fetch watchers - NoteWatchings.find({ + NoteWatchings.findBy({ noteId: note.id, userId: Not(user.id), }) diff --git a/packages/backend/src/services/note/reaction/create.ts b/packages/backend/src/services/note/reaction/create.ts index 236aa79938..5a0948bca9 100644 --- a/packages/backend/src/services/note/reaction/create.ts +++ b/packages/backend/src/services/note/reaction/create.ts @@ -6,7 +6,7 @@ import { toDbReaction, decodeReaction } from '@/misc/reaction-lib.js'; import { User, IRemoteUser } from '@/models/entities/user.js'; import { Note } from '@/models/entities/note.js'; import { NoteReactions, Users, NoteWatchings, Notes, Emojis, Blockings } from '@/models/index.js'; -import { Not } from 'typeorm'; +import { IsNull, Not } from 'typeorm'; import { perUserReactionsChart } from '@/services/chart/index.js'; import { genId } from '@/misc/gen-id.js'; import { createNotification } from '../../create-notification.js'; @@ -18,7 +18,7 @@ import { IdentifiableError } from '@/misc/identifiable-error.js'; export default async (user: { id: User['id']; host: User['host']; }, note: Note, reaction?: string) => { // Check blocking if (note.userId !== user.id) { - const block = await Blockings.findOne({ + const block = await Blockings.findOneBy({ blockerId: note.userId, blockeeId: user.id, }); @@ -43,7 +43,7 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note, await NoteReactions.insert(record); } catch (e) { if (isDuplicateKeyValueError(e)) { - const exists = await NoteReactions.findOneOrFail({ + const exists = await NoteReactions.findOneByOrFail({ noteId: note.id, userId: user.id, }); @@ -79,7 +79,7 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note, const emoji = await Emojis.findOne({ where: { name: decodedReaction.name, - host: decodedReaction.host, + host: decodedReaction.host ?? IsNull(), }, select: ['name', 'host', 'originalUrl', 'publicUrl'], }); @@ -103,7 +103,7 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note, } // Fetch watchers - NoteWatchings.find({ + NoteWatchings.findBy({ noteId: note.id, userId: Not(user.id), }).then(watchers => { @@ -121,10 +121,19 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note, const content = renderActivity(await renderLike(record, note)); const dm = new DeliverManager(user, content); if (note.userHost !== null) { - const reactee = await Users.findOne(note.userId); + const reactee = await Users.findOneBy({ id: note.userId }); dm.addDirectRecipe(reactee as IRemoteUser); } - dm.addFollowersRecipe(); + + if (['public', 'home', 'followers'].includes(note.visibility)) { + dm.addFollowersRecipe(); + } else if (note.visibility === 'specified') { + const visibleUsers = await Promise.all(note.visibleUserIds.map(id => Users.findOneBy({ id }))); + for (const u of visibleUsers.filter(u => u && Users.isRemoteUser(u))) { + dm.addDirectRecipe(u as IRemoteUser); + } + } + dm.execute(); } //#endregion diff --git a/packages/backend/src/services/note/reaction/delete.ts b/packages/backend/src/services/note/reaction/delete.ts index 62b00f56fd..a7cbcb1c17 100644 --- a/packages/backend/src/services/note/reaction/delete.ts +++ b/packages/backend/src/services/note/reaction/delete.ts @@ -11,7 +11,7 @@ import { decodeReaction } from '@/misc/reaction-lib.js'; export default async (user: { id: User['id']; host: User['host']; }, note: Note) => { // if already unreacted - const exist = await NoteReactions.findOne({ + const exist = await NoteReactions.findOneBy({ noteId: note.id, userId: user.id, }); @@ -48,7 +48,7 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note) const content = renderActivity(renderUndo(await renderLike(exist, note), user)); const dm = new DeliverManager(user, content); if (note.userHost !== null) { - const reactee = await Users.findOne(note.userId); + const reactee = await Users.findOneBy({ id: note.userId }); dm.addDirectRecipe(reactee as IRemoteUser); } dm.addFollowersRecipe(); diff --git a/packages/backend/src/services/note/read.ts b/packages/backend/src/services/note/read.ts index 28827c5965..915a9e9eef 100644 --- a/packages/backend/src/services/note/read.ts +++ b/packages/backend/src/services/note/read.ts @@ -68,7 +68,7 @@ export default async function( // TODO: ↓まとめてクエリしたい - NoteUnreads.count({ + NoteUnreads.countBy({ userId: userId, isMentioned: true, }).then(mentionsCount => { @@ -78,7 +78,7 @@ export default async function( } }); - NoteUnreads.count({ + NoteUnreads.countBy({ userId: userId, isSpecified: true, }).then(specifiedCount => { @@ -88,7 +88,7 @@ export default async function( } }); - NoteUnreads.count({ + NoteUnreads.countBy({ userId: userId, noteChannelId: Not(IsNull()), }).then(channelNoteCount => { @@ -113,7 +113,7 @@ export default async function( // TODO: まとめてクエリしたい for (const antenna of myAntennas) { - const count = await AntennaNotes.count({ + const count = await AntennaNotes.countBy({ antennaId: antenna.id, read: false, }); diff --git a/packages/backend/src/services/note/unread.ts b/packages/backend/src/services/note/unread.ts index ef95dc7e8c..d9ed711e03 100644 --- a/packages/backend/src/services/note/unread.ts +++ b/packages/backend/src/services/note/unread.ts @@ -11,14 +11,14 @@ export async function insertNoteUnread(userId: User['id'], note: Note, params: { }) { //#region ミュートしているなら無視 // TODO: 現在の仕様ではChannelにミュートは適用されないのでよしなにケアする - const mute = await Mutings.find({ + const mute = await Mutings.findBy({ muterId: userId, }); if (mute.map(m => m.muteeId).includes(note.userId)) return; //#endregion // スレッドミュート - const threadMute = await NoteThreadMutings.findOne({ + const threadMute = await NoteThreadMutings.findOneBy({ userId: userId, threadId: note.threadId || note.id, }); @@ -38,7 +38,7 @@ export async function insertNoteUnread(userId: User['id'], note: Note, params: { // 2秒経っても既読にならなかったら「未読の投稿がありますよ」イベントを発行する setTimeout(async () => { - const exist = await NoteUnreads.findOne(unread.id); + const exist = await NoteUnreads.findOneBy({ id: unread.id }); if (exist == null) return; diff --git a/packages/backend/src/services/push-notification.ts b/packages/backend/src/services/push-notification.ts index 8d5c09e6ae..41122c92e8 100644 --- a/packages/backend/src/services/push-notification.ts +++ b/packages/backend/src/services/push-notification.ts @@ -41,7 +41,7 @@ export default async function(userId: string, type: notificationType, body: noti meta.swPrivateKey); // Fetch - const subscriptions = await SwSubscriptions.find({ + const subscriptions = await SwSubscriptions.findBy({ userId: userId, }); diff --git a/packages/backend/src/services/register-or-fetch-instance-doc.ts b/packages/backend/src/services/register-or-fetch-instance-doc.ts index 152930dbd9..df7d125d0b 100644 --- a/packages/backend/src/services/register-or-fetch-instance-doc.ts +++ b/packages/backend/src/services/register-or-fetch-instance-doc.ts @@ -12,7 +12,7 @@ export async function registerOrFetchInstanceDoc(host: string): Promise Instances.findOneOrFail(x.identifiers[0])); + }).then(x => Instances.findOneByOrFail(x.identifiers[0])); cache.set(host, i); return i; diff --git a/packages/backend/src/services/relay.ts b/packages/backend/src/services/relay.ts index 6f0da503fc..1ab45588da 100644 --- a/packages/backend/src/services/relay.ts +++ b/packages/backend/src/services/relay.ts @@ -6,12 +6,17 @@ import { deliver } from '@/queue/index.js'; import { ILocalUser, User } from '@/models/entities/user.js'; import { Users, Relays } from '@/models/index.js'; import { genId } from '@/misc/gen-id.js'; +import { Cache } from '@/misc/cache.js'; +import { Relay } from '@/models/entities/relay.js'; +import { IsNull } from 'typeorm'; const ACTOR_USERNAME = 'relay.actor' as const; +const relaysCache = new Cache(1000 * 60 * 10); + export async function getRelayActor(): Promise { - const user = await Users.findOne({ - host: null, + const user = await Users.findOneBy({ + host: IsNull(), username: ACTOR_USERNAME, }); @@ -26,7 +31,7 @@ export async function addRelay(inbox: string) { id: genId(), inbox, status: 'requesting', - }).then(x => Relays.findOneOrFail(x.identifiers[0])); + }).then(x => Relays.findOneByOrFail(x.identifiers[0])); const relayActor = await getRelayActor(); const follow = await renderFollowRelay(relay, relayActor); @@ -37,7 +42,7 @@ export async function addRelay(inbox: string) { } export async function removeRelay(inbox: string) { - const relay = await Relays.findOne({ + const relay = await Relays.findOneBy({ inbox, }); @@ -78,9 +83,9 @@ export async function relayRejected(id: string) { export async function deliverToRelays(user: { id: User['id']; host: null; }, activity: any) { if (activity == null) return; - const relays = await Relays.find({ + const relays = await relaysCache.fetch(null, () => Relays.findBy({ status: 'accepted', - }); + })); if (relays.length === 0) return; const copy = JSON.parse(JSON.stringify(activity)); diff --git a/packages/backend/src/services/send-email-notification.ts b/packages/backend/src/services/send-email-notification.ts index debaf3476d..4a2f94b425 100644 --- a/packages/backend/src/services/send-email-notification.ts +++ b/packages/backend/src/services/send-email-notification.ts @@ -10,7 +10,7 @@ import * as Acct from '@/misc/acct.js'; async function follow(userId: User['id'], follower: User) { /* - const userProfile = await UserProfiles.findOneOrFail({ userId: userId }); + const userProfile = await UserProfiles.findOneByOrFail({ userId: userId }); if (!userProfile.email || !userProfile.emailNotificationTypes.includes('follow')) return; const locale = locales[userProfile.lang || 'ja-JP']; const i18n = new I18n(locale); @@ -21,7 +21,7 @@ async function follow(userId: User['id'], follower: User) { async function receiveFollowRequest(userId: User['id'], follower: User) { /* - const userProfile = await UserProfiles.findOneOrFail({ userId: userId }); + const userProfile = await UserProfiles.findOneByOrFail({ userId: userId }); if (!userProfile.email || !userProfile.emailNotificationTypes.includes('receiveFollowRequest')) return; const locale = locales[userProfile.lang || 'ja-JP']; const i18n = new I18n(locale); diff --git a/packages/backend/src/services/suspend-user.ts b/packages/backend/src/services/suspend-user.ts index 033311a3cc..e96b06a351 100644 --- a/packages/backend/src/services/suspend-user.ts +++ b/packages/backend/src/services/suspend-user.ts @@ -5,8 +5,11 @@ import config from '@/config/index.js'; import { User } from '@/models/entities/user.js'; import { Users, Followings } from '@/models/index.js'; import { Not, IsNull } from 'typeorm'; +import { publishInternalEvent } from '@/services/stream.js'; export async function doPostSuspend(user: { id: User['id']; host: User['host'] }) { + publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: true }); + if (Users.isLocalUser(user)) { // 知り得る全SharedInboxにDelete配信 const content = renderActivity(renderDelete(`${config.url}/users/${user.id}`, user)); diff --git a/packages/backend/src/services/unsuspend-user.ts b/packages/backend/src/services/unsuspend-user.ts index 3be081d0ed..44a0d01ca2 100644 --- a/packages/backend/src/services/unsuspend-user.ts +++ b/packages/backend/src/services/unsuspend-user.ts @@ -6,8 +6,11 @@ import config from '@/config/index.js'; import { User } from '@/models/entities/user.js'; import { Users, Followings } from '@/models/index.js'; import { Not, IsNull } from 'typeorm'; +import { publishInternalEvent } from '@/services/stream.js'; export async function doPostUnsuspend(user: User) { + publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: false }); + if (Users.isLocalUser(user)) { // 知り得る全SharedInboxにUndo Delete配信 const content = renderActivity(renderUndo(renderDelete(`${config.url}/users/${user.id}`, user), user)); diff --git a/packages/backend/src/services/update-hashtag.ts b/packages/backend/src/services/update-hashtag.ts index b6fb38bc5a..23b210b7a9 100644 --- a/packages/backend/src/services/update-hashtag.ts +++ b/packages/backend/src/services/update-hashtag.ts @@ -24,7 +24,7 @@ export async function updateUsertags(user: User, tags: string[]) { export async function updateHashtag(user: { id: User['id']; host: User['host']; }, tag: string, isUserAttached = false, inc = true) { tag = normalizeForSearch(tag); - const index = await Hashtags.findOne({ name: tag }); + const index = await Hashtags.findOneBy({ name: tag }); if (index == null && !inc) return; diff --git a/packages/backend/src/services/user-cache.ts b/packages/backend/src/services/user-cache.ts new file mode 100644 index 0000000000..407301f2fd --- /dev/null +++ b/packages/backend/src/services/user-cache.ts @@ -0,0 +1,44 @@ +import { CacheableLocalUser, CacheableUser, ILocalUser, User } from '@/models/entities/user.js'; +import { Users } from '@/models/index.js'; +import { Cache } from '@/misc/cache.js'; +import { subsdcriber } from '@/db/redis.js'; + +export const userByIdCache = new Cache(Infinity); +export const localUserByNativeTokenCache = new Cache(Infinity); +export const localUserByIdCache = new Cache(Infinity); +export const uriPersonCache = new Cache(Infinity); + +subsdcriber.on('message', async (_, data) => { + const obj = JSON.parse(data); + + if (obj.channel === 'internal') { + const { type, body } = obj.message; + switch (type) { + case 'userChangeSuspendedState': + case 'userChangeSilencedState': + case 'userChangeModeratorState': + case 'remoteUserUpdated': { + const user = await Users.findOneByOrFail({ id: body.id }); + userByIdCache.set(user.id, user); + for (const [k, v] of uriPersonCache.cache.entries()) { + if (v.value?.id === user.id) { + uriPersonCache.set(k, user); + } + } + if (Users.isLocalUser(user)) { + localUserByNativeTokenCache.set(user.token, user); + localUserByIdCache.set(user.id, user); + } + break; + } + case 'userTokenRegenerated': { + const user = await Users.findOneByOrFail({ id: body.id }) as ILocalUser; + localUserByNativeTokenCache.delete(body.oldToken); + localUserByNativeTokenCache.set(body.newToken, user); + break; + } + default: + break; + } + } +}); diff --git a/packages/backend/src/services/validate-email-for-account.ts b/packages/backend/src/services/validate-email-for-account.ts index 3c49d37eef..132168fb31 100644 --- a/packages/backend/src/services/validate-email-for-account.ts +++ b/packages/backend/src/services/validate-email-for-account.ts @@ -1,11 +1,11 @@ -import validateEmail from 'deep-email-validator'; +import { validate as validateEmail } from 'deep-email-validator'; import { UserProfiles } from '@/models/index.js'; export async function validateEmailForAccount(emailAddress: string): Promise<{ available: boolean; reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp'; }> { - const exist = await UserProfiles.count({ + const exist = await UserProfiles.countBy({ emailVerified: true, email: emailAddress, }); diff --git a/packages/backend/src/tools/accept-migration.ts b/packages/backend/src/tools/accept-migration.ts deleted file mode 100644 index adbfcdadf7..0000000000 --- a/packages/backend/src/tools/accept-migration.ts +++ /dev/null @@ -1,25 +0,0 @@ -// ex) node built/tools/accept-migration Yo 1000000000001 - -import { createConnection } from 'typeorm'; -import config from '@/config/index.js'; - -createConnection({ - type: 'postgres', - host: config.db.host, - port: config.db.port, - username: config.db.user, - password: config.db.pass, - database: config.db.db, - extra: config.db.extra, - synchronize: false, - dropSchema: false, -}).then(c => { - c.query(`INSERT INTO migrations(timestamp,name) VALUES (${process.argv[3]}, '${process.argv[2]}${process.argv[3]}');`).then(() => { - console.log('done'); - process.exit(0); - }).catch(e => { - console.log('ERROR:'); - console.log(e); - process.exit(1); - }); -}); diff --git a/packages/backend/src/tools/demote-admin.ts b/packages/backend/src/tools/demote-admin.ts deleted file mode 100644 index 7f67222473..0000000000 --- a/packages/backend/src/tools/demote-admin.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { initDb } from '../db/postgre.js'; - -async function main(username: string) { - if (!username) throw `username required`; - username = username.replace(/^@/, ''); - - await initDb(); - const { Users } = await import('@/models/index'); - - const res = await Users.update({ - usernameLower: username.toLowerCase(), - host: null, - }, { - isAdmin: false, - }); - - if (res.affected !== 1) { - throw 'Failed'; - } -} - -const args = process.argv.slice(2); - -main(args[0]).then(() => { - console.log('Success'); - process.exit(0); -}).catch(e => { - console.error(`Error: ${e.message || e}`); - process.exit(1); -}); diff --git a/packages/backend/src/tools/mark-admin.ts b/packages/backend/src/tools/mark-admin.ts deleted file mode 100644 index 630179e7ab..0000000000 --- a/packages/backend/src/tools/mark-admin.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { initDb } from '../db/postgre.js'; - -async function main(username: string) { - if (!username) throw `username required`; - username = username.replace(/^@/, ''); - - await initDb(); - const { Users } = await import('@/models/index'); - - const res = await Users.update({ - usernameLower: username.toLowerCase(), - host: null, - }, { - isAdmin: true, - }); - - if (res.affected !== 1) { - throw 'Failed'; - } -} - -const args = process.argv.slice(2); - -main(args[0]).then(() => { - console.log('Success'); - process.exit(0); -}).catch(e => { - console.error(`Error: ${e.message || e}`); - process.exit(1); -}); diff --git a/packages/backend/src/tools/refresh-question.ts b/packages/backend/src/tools/refresh-question.ts deleted file mode 100644 index 0111a2257a..0000000000 --- a/packages/backend/src/tools/refresh-question.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { initDb } from '@/db/postgre.js'; - -async function main(uri: string): Promise { - await initDb(); - const { updateQuestion } = await import('@/remote/activitypub/models/question'); - - return await updateQuestion(uri); -} - -const args = process.argv.slice(2); -const uri = args[0]; - -main(uri).then(result => { - console.log(`Done: ${result}`); -}).catch(e => { - console.warn(e); -}); diff --git a/packages/backend/src/tools/resync-remote-user.ts b/packages/backend/src/tools/resync-remote-user.ts deleted file mode 100644 index 8c02ef7efc..0000000000 --- a/packages/backend/src/tools/resync-remote-user.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { initDb } from '@/db/postgre.js'; -import * as Acct from '@/misc/acct.js'; - -async function main(acct: string): Promise { - await initDb(); - const { resolveUser } = await import('@/remote/resolve-user'); - - const { username, host } = Acct.parse(acct); - await resolveUser(username, host, {}, true); -} - -// get args -const args = process.argv.slice(2); -let acct = args[0]; - -// normalize args -acct = acct.replace(/^@/, ''); - -// check args -if (!acct.match(/^\w+@\w/)) { - throw `Invalid acct format. Valid format are user@host`; -} - -console.log(`resync ${acct}`); - -main(acct).then(() => { - console.log('Done'); -}).catch(e => { - console.warn(e); -}); diff --git a/packages/backend/src/tools/show-signin-history.ts b/packages/backend/src/tools/show-signin-history.ts deleted file mode 100644 index c3388fd1b6..0000000000 --- a/packages/backend/src/tools/show-signin-history.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { initDb } from '@/db/postgre.js'; - -// node built/tools/show-signin-history username -// => {Success} {Date} {IPAddrsss} - -// node built/tools/show-signin-history username user-agent,x-forwarded-for -// with user-agent and x-forwarded-for - -// node built/tools/show-signin-history username all -// with full request headers - -async function main(username: string, headers?: string[]) { - await initDb(); - const { Users, Signins } = await import('@/models/index'); - - const user = await Users.findOne({ - host: null, - usernameLower: username.toLowerCase(), - }); - - if (user == null) throw new Error('User not found'); - - const history = await Signins.find({ - userId: user.id, - }); - - for (const signin of history) { - console.log(`${signin.success ? 'OK' : 'NG'} ${signin.createdAt ? signin.createdAt.toISOString() : 'Unknown'} ${signin.ip}`); - - // headers - if (headers != null) { - for (const key of Object.keys(signin.headers)) { - if (headers.includes('all') || headers.includes(key)) { - console.log(` ${key}: ${signin.headers[key]}`); - } - } - } - } -} - -// get args -const args = process.argv.slice(2); - -let username = args[0]; -let headers: string[] | undefined; - -if (args[1] != null) { - headers = args[1].split(/,/).map(header => header.toLowerCase()); -} - -// normalize args -username = username.replace(/^@/, ''); - -main(username, headers).then(() => { - process.exit(0); -}).catch(e => { - console.warn(e); - process.exit(1); -}); diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index 20f6f8bb88..573e2faf87 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -1,4 +1,4 @@ -export const notificationTypes = ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app'] as const; +export const notificationTypes = ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app'] as const; export const noteVisibilities = ['public', 'home', 'followers', 'specified'] as const; diff --git a/packages/backend/test/note.ts b/packages/backend/test/note.ts index 62cea5208b..942b2709df 100644 --- a/packages/backend/test/note.ts +++ b/packages/backend/test/note.ts @@ -333,4 +333,36 @@ describe('Note', () => { assert.strictEqual(res.status, 400); })); }); + + describe('notes/delete', () => { + it('delete a reply', async(async () => { + const mainNoteRes = await request('/notes/create', { + text: 'main post', + }, alice); + const replyOneRes = await request('/notes/create', { + text: 'reply one', + replyId: mainNoteRes.body.createdNote.id + }, alice); + const replyTwoRes = await request('/notes/create', { + text: 'reply two', + replyId: mainNoteRes.body.createdNote.id + }, alice); + + const deleteOneRes = await request('/notes/delete', { + noteId: replyOneRes.body.createdNote.id, + }, alice); + + assert.strictEqual(deleteOneRes.status, 204); + let mainNote = await Notes.findOne({id: mainNoteRes.body.createdNote.id}); + assert.strictEqual(mainNote.repliesCount, 1); + + const deleteTwoRes = await request('/notes/delete', { + noteId: replyTwoRes.body.createdNote.id, + }, alice); + + assert.strictEqual(deleteTwoRes.status, 204); + mainNote = await Notes.findOne({id: mainNoteRes.body.createdNote.id}); + assert.strictEqual(mainNote.repliesCount, 0); + })); + }); }); diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts index 994c098b7b..32a030f933 100644 --- a/packages/backend/test/utils.ts +++ b/packages/backend/test/utils.ts @@ -7,7 +7,6 @@ import * as childProcess from 'child_process'; import * as http from 'http'; import loadConfig from '../src/config/load.js'; import { SIGKILL } from 'constants'; -import { createConnection, getConnection } from 'typeorm'; import { entities } from '../src/db/postgre.js'; const config = loadConfig(); diff --git a/packages/backend/yarn.lock b/packages/backend/yarn.lock index fea3b2e010..3d5d38d695 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.1": + version "3.10.1" + resolved "https://registry.yarnpkg.com/@bull-board/api/-/api-3.10.1.tgz#c9608d501c887abcfa8f1907bc3dedee179bdea3" + integrity sha512-ZYjNBdoBQu+UVbLAHQuEhJL96C+i7vYioc2n7FL/XoVea44XIw2WiKFcFxq0LnActPErja26QyZBQht23ph1lg== + dependencies: + redis-info "^3.0.8" + +"@bull-board/koa@3.10.1": + version "3.10.1" + resolved "https://registry.yarnpkg.com/@bull-board/koa/-/koa-3.10.1.tgz#205641ae9721ec71303c4f16dc27eca1f71ca131" + integrity sha512-+mxdnu7idjd75WqUklJbPzrQU6NJzgQCT+BLKCyqOBsWzpfEwaac6QaIXOiuPwgwG2VjH90HWIcWr+2BQB9c1w== + dependencies: + "@bull-board/api" "3.10.1" + "@bull-board/ui" "3.10.1" + 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.1": + version "3.10.1" + resolved "https://registry.yarnpkg.com/@bull-board/ui/-/ui-3.10.1.tgz#edf7c7752a78d9829f7a944bb87a0e70812b749f" + integrity sha512-K2qEAvTuyHZxUdK31HaBb9sdTFSOSKAZkxsl/LeiT4FGNF/h54iYGmWF9+HSFytggcnGdM0XnK3wLihCaIQAOQ== + dependencies: + "@bull-board/api" "3.10.1" + "@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.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.1.0.tgz#583d12dbec5d4f22f333f9669f7d0b7c7815b4d3" - integrity sha512-C1DfL7XX4nPqGd6jcP01W9pVM1HYCuUkFk1432D7F0v3JSlUIeOYn9oCoi3eoLZ+iwBSb29BMFxxny0YrrEZqg== +"@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" @@ -128,10 +156,10 @@ resolved "https://registry.yarnpkg.com/@koa/multer/-/multer-3.0.0.tgz#439777949f28097d7b329c0b4ce3048074c862f8" integrity sha512-y+OQBmex5D1jIl723gAEUYcAWPEicIXppaAKw/zCMfpllQ08ZNweDPwoCLxEoatqd5pCu2XG6V8dl67JRq3RJw== -"@koa/router@10.1.1": - version "10.1.1" - resolved "https://registry.yarnpkg.com/@koa/router/-/router-10.1.1.tgz#8e5a85c9b243e0bc776802c0de564561e57a5f78" - integrity sha512-ORNjq5z4EmQPriKbR0ER3k4Gh7YGNhWDL7JBW+8wXDrHLbWYKYSJaOJ9aN06npF5tbTxe2JBOsurpJDAvjiXKw== +"@koa/router@9.0.1": + version "9.0.1" + resolved "https://registry.yarnpkg.com/@koa/router/-/router-9.0.1.tgz#4090a14223ea7e78aa13b632761209cba69acd95" + integrity sha512-OI+OU49CJV4px0WkIMmayBeqVXB/JS1ZMq7UoGlTZt6Y7ijK7kdeQ18+SEHHJPytmtI1y6Hf8XLrpxva3mhv5Q== dependencies: debug "^4.1.1" http-errors "^1.7.3" @@ -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.90": + version "1.0.0-beta.90" + resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.0.0-beta.90.tgz#edf53b23314368e190b005e1958c1f4a7dfaa2c3" + integrity sha512-MvkME+AWCBexyJyNp/sVFRUBjxCSk5CQ+CAozkwm0t/HusXp9G+kH26+e9giD6Fms129smr1qp3pCAUbwJZzZA== dependencies: "@redocly/ajv" "^8.6.4" "@types/node" "^14.11.8" @@ -237,10 +265,10 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-3.1.2.tgz#548650de521b344e3781fbdb0ece4aa6f729afb8" integrity sha512-JiX9vxoKMmu8Y3Zr2RVathBL1Cdu4Nt4MuNWemt1Nc06A0RAin9c5FArkhGsyMBWfCu4zj+9b+GxtjAnE4qqLQ== -"@sindresorhus/is@^4.0.0": - version "4.4.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.4.0.tgz#e277e5bdbdf7cb1e20d320f02f5e2ed113cd3185" - integrity sha512-QppPM/8l3Mawvh4rn9CNEYIU9bxpXUCRMaX9yUpvBk1nMKusLKpfXGDEKExKaPhLzcn3lzil7pR6rnJ11HgeRQ== +"@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== "@sinonjs/commons@^1.7.0": version "1.7.2" @@ -249,10 +277,10 @@ dependencies: type-detect "4.0.8" -"@sinonjs/fake-timers@9.1.0": - version "9.1.0" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-9.1.0.tgz#8c92c56f195e0bed4c893ba59c8e3d55831ca0df" - integrity sha512-M8vapsv9qQupMdzrVzkn5rb9jG7aUTEPAZdMtME2PuBaefksFZVE2C1g4LBRTkF/k3nRDNbDc5tp5NFC1PEYxA== +"@sinonjs/fake-timers@9.1.1": + version "9.1.1" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-9.1.1.tgz#7b698e0b9d12d93611f06ee143c30ced848e2840" + integrity sha512-Wp5vwlZ0lOqpSYGKqr53INws9HLkt6JDc/pDZcPf7bchQnrXJMXPns8CXx0hFikMSGSWfvtvvpb2gtMVfkWagA== dependencies: "@sinonjs/commons" "^1.7.0" @@ -279,6 +307,13 @@ dependencies: defer-to-connect "^2.0.0" +"@szmarczak/http-timer@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-5.0.1.tgz#c7c1bf1141cdd4751b0399c8fc7b8b664cd5be3a" + integrity sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw== + dependencies: + defer-to-connect "^2.0.1" + "@tokenizer/token@^0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276" @@ -352,6 +387,16 @@ "@types/node" "*" "@types/responselike" "*" +"@types/cacheable-request@^6.0.2": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.2.tgz#c324da0197de0a98a2312156536ae262429ff6b9" + integrity sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA== + dependencies: + "@types/http-cache-semantics" "*" + "@types/keyv" "*" + "@types/node" "*" + "@types/responselike" "*" + "@types/cbor@6.0.0": version "6.0.0" resolved "https://registry.yarnpkg.com/@types/cbor/-/cbor-6.0.0.tgz#ddead015e14ef4463287d40cd92a6297a34dac8d" @@ -494,10 +539,10 @@ dependencies: "@types/node" "*" -"@types/koa-bodyparser@4.3.5": - version "4.3.5" - resolved "https://registry.yarnpkg.com/@types/koa-bodyparser/-/koa-bodyparser-4.3.5.tgz#0c5fa44d7150202ffc16b89bd730ce1b6c7bc250" - integrity sha512-NRqqoTtt7cfdDk/KNo+EwCIKRuzPAu/wsaZ7tgIvSIBtNfxuZHYueaLoWdxX3ZftWavQv07NE46TcpyoZGqpgQ== +"@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" "*" @@ -621,10 +666,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.6.2.tgz#331b7b9f8621c638284787c5559423822fdffc50" integrity sha512-LSw8TZt12ZudbpHc6EkIyDM3nHVWKYrAvGy6EAJfNfjusbwnThqjqxUKKRwuV3iWYeW/LYMzNgaq3MaLffQ2xA== -"@types/node@17.0.19": - version "17.0.19" - resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.19.tgz#726171367f404bfbe8512ba608a09ebad810c7e6" - integrity sha512-PfeQhvcMR4cPFVuYfBN4ifG7p9c+Dlh3yUZR6k+5yQK7wX3gDgVxBly4/WkBRs9x4dmcy1TVl08SY67wwtEvmA== +"@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" @@ -699,14 +744,6 @@ dependencies: "@types/redis" "^2.8.0" -"@types/readable-stream@^2.3.9": - version "2.3.9" - resolved "https://registry.yarnpkg.com/@types/readable-stream/-/readable-stream-2.3.9.tgz#40a8349e6ace3afd2dd1b6d8e9b02945de4566a9" - integrity sha512-sqsgQqFT7HmQz/V5jH1O0fvQQnXAJO46Gg9LRO/JPfjmVmGUlcx831TZZO3Y3HtWhIkzf3kTsNT0Z0kzIhIvZw== - dependencies: - "@types/node" "*" - safe-buffer "*" - "@types/redis@4.0.11": version "4.0.11" resolved "https://registry.yarnpkg.com/@types/redis/-/redis-4.0.11.tgz#0bb4c11ac9900a21ad40d2a6768ec6aaf651c0e1" @@ -740,11 +777,6 @@ dependencies: htmlparser2 "^6.0.0" -"@types/seedrandom@3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/seedrandom/-/seedrandom-3.0.1.tgz#1254750a4fec4aff2ebec088ccd0bb02e91fedb4" - integrity sha512-giB9gzDeiCeloIXDgzFBCgjj1k4WxcDrZtGl6h1IqmUPlxF+Nx8Ve+96QCyDZ/HseB/uvDsKbpib9hU5cU53pw== - "@types/serve-static@*": version "1.13.3" resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.3.tgz#eb7e1c41c4468272557e897e9171ded5e2ded9d1" @@ -753,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" @@ -811,26 +843,21 @@ dependencies: "@types/node" "*" -"@types/ws@8.2.3": - version "8.2.3" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.2.3.tgz#0bca6b03ba2f41e0fab782d4a573fe284aa907ae" - integrity sha512-ahRJZquUYCdOZf/rCsWg88S0/+cb9wazUBHv6HZEe3XdYaBe2zr/slM8J28X07Hn88Pnm4ezo7N8/ofnOgrPVQ== +"@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.12.1": - version "5.12.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.12.1.tgz#b2cd3e288f250ce8332d5035a2ff65aba3374ac4" - integrity sha512-M499lqa8rnNK7mUv74lSFFttuUsubIRdAbHcVaP93oFcKkEmHmLqy2n7jM9C8DVmFMYK61ExrZU6dLYhQZmUpw== +"@typescript-eslint/eslint-plugin@5.16.0": + version "5.16.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.16.0.tgz#78f246dd8d1b528fc5bfca99a8a64d4023a3d86d" + integrity sha512-SJoba1edXvQRMmNI505Uo4XmGbxCK9ARQpkvOd00anxzri9RNQk0DDCxD+LIl+jYhkzOJiOMMKYEHnHEODjdCw== dependencies: - "@typescript-eslint/scope-manager" "5.12.1" - "@typescript-eslint/type-utils" "5.12.1" - "@typescript-eslint/utils" "5.12.1" + "@typescript-eslint/scope-manager" "5.16.0" + "@typescript-eslint/type-utils" "5.16.0" + "@typescript-eslint/utils" "5.16.0" debug "^4.3.2" functional-red-black-tree "^1.0.1" ignore "^5.1.8" @@ -838,69 +865,69 @@ semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/parser@5.12.1": - version "5.12.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.12.1.tgz#b090289b553b8aa0899740d799d0f96e6f49771b" - integrity sha512-6LuVUbe7oSdHxUWoX/m40Ni8gsZMKCi31rlawBHt7VtW15iHzjbpj2WLiToG2758KjtCCiLRKZqfrOdl3cNKuw== +"@typescript-eslint/parser@5.16.0": + version "5.16.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.16.0.tgz#e4de1bde4b4dad5b6124d3da227347616ed55508" + integrity sha512-fkDq86F0zl8FicnJtdXakFs4lnuebH6ZADDw6CYQv0UZeIjHvmEw87m9/29nk2Dv5Lmdp0zQ3zDQhiMWQf/GbA== dependencies: - "@typescript-eslint/scope-manager" "5.12.1" - "@typescript-eslint/types" "5.12.1" - "@typescript-eslint/typescript-estree" "5.12.1" + "@typescript-eslint/scope-manager" "5.16.0" + "@typescript-eslint/types" "5.16.0" + "@typescript-eslint/typescript-estree" "5.16.0" debug "^4.3.2" -"@typescript-eslint/scope-manager@5.12.1": - version "5.12.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.12.1.tgz#58734fd45d2d1dec49641aacc075fba5f0968817" - integrity sha512-J0Wrh5xS6XNkd4TkOosxdpObzlYfXjAFIm9QxYLCPOcHVv1FyyFCPom66uIh8uBr0sZCrtS+n19tzufhwab8ZQ== +"@typescript-eslint/scope-manager@5.16.0": + version "5.16.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.16.0.tgz#7e7909d64bd0c4d8aef629cdc764b9d3e1d3a69a" + integrity sha512-P+Yab2Hovg8NekLIR/mOElCDPyGgFZKhGoZA901Yax6WR6HVeGLbsqJkZ+Cvk5nts/dAlFKm8PfL43UZnWdpIQ== dependencies: - "@typescript-eslint/types" "5.12.1" - "@typescript-eslint/visitor-keys" "5.12.1" + "@typescript-eslint/types" "5.16.0" + "@typescript-eslint/visitor-keys" "5.16.0" -"@typescript-eslint/type-utils@5.12.1": - version "5.12.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.12.1.tgz#8d58c6a0bb176b5e9a91581cda1a7f91a114d3f0" - integrity sha512-Gh8feEhsNLeCz6aYqynh61Vsdy+tiNNkQtc+bN3IvQvRqHkXGUhYkUi+ePKzP0Mb42se7FDb+y2SypTbpbR/Sg== +"@typescript-eslint/type-utils@5.16.0": + version "5.16.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.16.0.tgz#b482bdde1d7d7c0c7080f7f2f67ea9580b9e0692" + integrity sha512-SKygICv54CCRl1Vq5ewwQUJV/8padIWvPgCxlWPGO/OgQLCijY9G7lDu6H+mqfQtbzDNlVjzVWQmeqbLMBLEwQ== dependencies: - "@typescript-eslint/utils" "5.12.1" + "@typescript-eslint/utils" "5.16.0" debug "^4.3.2" tsutils "^3.21.0" -"@typescript-eslint/types@5.12.1": - version "5.12.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.12.1.tgz#46a36a28ff4d946821b58fe5a73c81dc2e12aa89" - integrity sha512-hfcbq4qVOHV1YRdhkDldhV9NpmmAu2vp6wuFODL71Y0Ixak+FLeEU4rnPxgmZMnGreGEghlEucs9UZn5KOfHJA== +"@typescript-eslint/types@5.16.0": + version "5.16.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.16.0.tgz#5827b011982950ed350f075eaecb7f47d3c643ee" + integrity sha512-oUorOwLj/3/3p/HFwrp6m/J2VfbLC8gjW5X3awpQJ/bSG+YRGFS4dpsvtQ8T2VNveV+LflQHjlLvB6v0R87z4g== -"@typescript-eslint/typescript-estree@5.12.1": - version "5.12.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.12.1.tgz#6a9425b9c305bcbc38e2d1d9a24c08e15e02b722" - integrity sha512-ahOdkIY9Mgbza7L9sIi205Pe1inCkZWAHE1TV1bpxlU4RZNPtXaDZfiiFWcL9jdxvW1hDYZJXrFm+vlMkXRbBw== +"@typescript-eslint/typescript-estree@5.16.0": + version "5.16.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.16.0.tgz#32259459ec62f5feddca66adc695342f30101f61" + integrity sha512-SE4VfbLWUZl9MR+ngLSARptUv2E8brY0luCdgmUevU6arZRY/KxYoLI/3V/yxaURR8tLRN7bmZtJdgmzLHI6pQ== dependencies: - "@typescript-eslint/types" "5.12.1" - "@typescript-eslint/visitor-keys" "5.12.1" + "@typescript-eslint/types" "5.16.0" + "@typescript-eslint/visitor-keys" "5.16.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.12.1": - version "5.12.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.12.1.tgz#447c24a05d9c33f9c6c64cb48f251f2371eef920" - integrity sha512-Qq9FIuU0EVEsi8fS6pG+uurbhNTtoYr4fq8tKjBupsK5Bgbk2I32UGm0Sh+WOyjOPgo/5URbxxSNV6HYsxV4MQ== +"@typescript-eslint/utils@5.16.0": + version "5.16.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.16.0.tgz#42218b459d6d66418a4eb199a382bdc261650679" + integrity sha512-iYej2ER6AwmejLWMWzJIHy3nPJeGDuCqf8Jnb+jAQVoPpmWzwQOfa9hWVB8GIQE5gsCv/rfN4T+AYb/V06WseQ== dependencies: "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.12.1" - "@typescript-eslint/types" "5.12.1" - "@typescript-eslint/typescript-estree" "5.12.1" + "@typescript-eslint/scope-manager" "5.16.0" + "@typescript-eslint/types" "5.16.0" + "@typescript-eslint/typescript-estree" "5.16.0" eslint-scope "^5.1.1" eslint-utils "^3.0.0" -"@typescript-eslint/visitor-keys@5.12.1": - version "5.12.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.12.1.tgz#f722da106c8f9695ae5640574225e45af3e52ec3" - integrity sha512-l1KSLfupuwrXx6wc0AuOmC7Ko5g14ZOQ86wJJqRbdLbXLK02pK/DPiDDqCc7BqqiiA04/eAA6ayL0bgOrAkH7A== +"@typescript-eslint/visitor-keys@5.16.0": + version "5.16.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.16.0.tgz#f27dc3b943e6317264c7492e390c6844cd4efbbb" + integrity sha512-jqxO8msp5vZDhikTwq9ubyMHqZ67UIvawohr4qF3KhlpL7gzSjOd+8471H3nh5LyABkaI85laEKKU8SnGUK5/g== dependencies: - "@typescript-eslint/types" "5.12.1" + "@typescript-eslint/types" "5.16.0" eslint-visitor-keys "^3.0.0" "@ungap/promise-all-settled@1.1.2": @@ -1000,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" @@ -1205,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" @@ -1239,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.1100.0: + version "2.1100.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1100.0.tgz#20bbabc12fbc316067ba02af66bf371a455af9e3" + integrity sha512-StLSQCYFmFPxjoMntIb+8jUZ0vzmq3xkrwG5e/4qU1bSGWCmhhjvz6c+4j38AnIy8MFV1+tV8RArbhLUEV2dGw== dependencies: buffer "4.9.2" events "1.1.1" @@ -1463,10 +1495,10 @@ bufferutil@^4.0.1: dependencies: node-gyp-build "~3.7.0" -bull@4.6.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/bull/-/bull-4.6.2.tgz#fdde3b13245cff79571d85c4702884dfbf419b27" - integrity sha512-RA6wnL+rZ2GRHMhz+UxFEn7hlLBpxPEiW4A0UY3YwWnheRA55AT4MC0PknxqO1K55pGFKSh/GpHQaj8flJMm+w== +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" @@ -1523,7 +1555,7 @@ cache-content-type@^1.0.0: mime-types "^2.1.18" ylru "^1.2.0" -cacheable-lookup@6.0.4: +cacheable-lookup@6.0.4, cacheable-lookup@^6.0.4: version "6.0.4" resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-6.0.4.tgz#65c0e51721bb7f9f2cb513aed6da4a1b93ad7dc8" integrity sha512-mbcDEZCkv2CZF4G01kr8eBd/5agkt9oCqz75tJMSIsquvRZ2sL6Hi5zGVKi/0OSC9oO1GHfJ2AV0ZIOY9vye0A== @@ -1546,6 +1578,19 @@ cacheable-request@^7.0.1: normalize-url "^4.1.0" responselike "^2.0.0" +cacheable-request@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.2.tgz#ea0d0b889364a25854757301ca12b2da77f91d27" + integrity sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^4.0.0" + lowercase-keys "^2.0.0" + normalize-url "^6.0.1" + responselike "^2.0.0" + cafy@15.2.1: version "15.2.1" resolved "https://registry.yarnpkg.com/cafy/-/cafy-15.2.1.tgz#5a55eaeb721c604c7dca652f3d555c392e5f995a" @@ -1601,10 +1646,10 @@ chainsaw@~0.1.0: dependencies: traverse ">=0.3.0 <0.4" -chalk-template@0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/chalk-template/-/chalk-template-0.3.1.tgz#9511cd31ec3c4911448410d49645526c1c7a3a22" - integrity sha512-sbWkBbb9Tfo81aTtQrfP9eBSVCTL8biVvZ0tA1rH9xqVrKoV2T9Y6Bp94wB+DRXtSGl/UXsgV83Np5hLhNRXww== +chalk-template@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/chalk-template/-/chalk-template-0.4.0.tgz#692c034d0ed62436b9062c1707fadcd0f753204b" + integrity sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg== dependencies: chalk "^4.1.2" @@ -1616,10 +1661,10 @@ chalk@4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.0.0.tgz#bd96c6bb8e02b96e08c0c3ee2a9d90e050c7b832" - integrity sha512-/duVOqst+luxCQRKEo4bNxinsOQtMP80ZYm7mMqzuh5PociNL0PvmHFvREJ9ueYL2TxlHjBcmLCdmocx9Vg+IQ== +chalk@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.0.1.tgz#ca57d71e82bb534a296df63bbacc4a1c22b2a4b6" + integrity sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w== chalk@^2.4.2: version "2.4.2" @@ -1819,10 +1864,10 @@ color-support@^1.1.2: resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== -color@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/color/-/color-4.2.0.tgz#0c782459a3e98838ea01e4bc0fb43310ca35af78" - integrity sha512-hHTcrbvEnGjC7WBMk6ibQWFVDgEFTVmjrz2Q5HlU6ltwxv0JJN2Z8I7uRbWeQLF04dikxs8zgyZkazRJvSMtyQ== +color@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/color/-/color-4.2.1.tgz#498aee5fce7fc982606c8875cab080ac0547c884" + integrity sha512-MFJr0uY4RvTQUKvPq7dh9grVOTYSFeXja2mBXioCGjnjJoXrAp9jJ1NQTDR73c9nwBSAQiNKloKl5zq9WB9UPw== dependencies: color-convert "^2.0.1" color-string "^1.9.0" @@ -2058,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== @@ -2084,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" @@ -2091,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" @@ -2105,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" @@ -2167,6 +2219,11 @@ defer-to-connect@^2.0.0: resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.0.tgz#83d6b199db041593ac84d781b5222308ccf4c2c1" integrity sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg== +defer-to-connect@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" + integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== + define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" @@ -2219,6 +2276,11 @@ detect-libc@^2.0.0: resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.0.tgz#c528bc09bc6d1aa30149228240917c225448f204" integrity sha512-S55LzUl8HUav8l9E2PBTlC5PAJrHK7tkM+XXFGD+fbsbkTzhCpG6K05LxJcUOEWzMa4v6ptcMZ9s3fOdJDu0Zw== +detect-libc@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" + integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== + detect-node@2.1.0, detect-node@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" @@ -2371,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" @@ -2413,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" @@ -2646,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.9.0: - version "8.9.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.9.0.tgz#a2a8227a99599adc4342fd9b854cb8d8d6412fdb" - integrity sha512-PB09IGwv4F4b0/atrbcMFboF/giawbBLVC7fyDamk5Wtey4Jh2K+rYaBhCAbUyEI4QzB1ly09Uglc9iCtFaG2Q== +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.1.0" + "@eslint/eslintrc" "^1.2.1" "@humanwhocodes/config-array" "^0.9.2" ajv "^6.10.0" chalk "^4.0.0" @@ -2745,7 +2814,7 @@ event-target-shim@^5.0.0: resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== -eventemitter3@4.0.7, eventemitter3@^4.0.4, eventemitter3@^4.0.7: +eventemitter3@^4.0.4, eventemitter3@^4.0.7: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== @@ -2882,14 +2951,21 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" -file-type@16.5.3: - version "16.5.3" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-16.5.3.tgz#474b7e88c74724046abb505e9b8ed4db30c4fc06" - integrity sha512-uVsl7iFhHSOY4bEONLlTK47iAHtNsFHWP5YE4xJfZ4rnX7S1Q3wce09XgqSC7E/xh8Ncv/be1lNoyprlUH/x6A== +file-type@17.1.1: + version "17.1.1" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-17.1.1.tgz#24c59bc663df0c0c181b31dfacde25e06431afbe" + integrity sha512-heRUMZHby2Qj6wZAA3YHeMlRmZNQTcb6VxctkGmM+mcM6ROQKvHpr7SS6EgdfEhH+s25LDshBjvPx/Ecm+bOVQ== dependencies: - readable-web-to-node-stream "^3.0.0" - strtok3 "^6.2.4" - token-types "^4.1.1" + readable-web-to-node-stream "^3.0.2" + 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" @@ -2966,9 +3042,14 @@ 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" + resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.1.tgz#ac80660e4f87ee0d3d3c3638b7da8278ddb8ec96" + integrity sha512-EFRDrsMm/kyqbTQocNvRXMLjc7Es2Vk+IQFx/YW7hkUH1eBl4J1fqiP34l74Yt0pFLCNpc06fkbVk00008mzjg== form-data@^4.0.0: version "4.0.0" @@ -3148,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== @@ -3160,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== @@ -3235,21 +3316,23 @@ got@11.5.1: p-cancelable "^2.0.0" responselike "^2.0.0" -got@11.8.2: - version "11.8.2" - resolved "https://registry.yarnpkg.com/got/-/got-11.8.2.tgz#7abb3959ea28c31f3576f1576c1effce23f33599" - integrity sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ== +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.0.0" - "@szmarczak/http-timer" "^4.0.5" - "@types/cacheable-request" "^6.0.1" + "@sindresorhus/is" "^4.6.0" + "@szmarczak/http-timer" "^5.0.1" + "@types/cacheable-request" "^6.0.2" "@types/responselike" "^1.0.0" - cacheable-lookup "^5.0.3" - cacheable-request "^7.0.1" + cacheable-lookup "^6.0.4" + cacheable-request "^7.0.2" decompress-response "^6.0.0" - http2-wrapper "^1.0.0-beta.5.2" - lowercase-keys "^2.0.0" - p-cancelable "^2.0.0" + form-data-encoder "1.7.1" + get-stream "^6.0.1" + http2-wrapper "^2.1.10" + lowercase-keys "^3.0.0" + p-cancelable "^3.0.0" responselike "^2.0.0" graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.4: @@ -3438,7 +3521,7 @@ http-signature@1.3.6: jsprim "^2.0.2" sshpk "^1.14.1" -http2-wrapper@^1.0.0-beta.5.0, http2-wrapper@^1.0.0-beta.5.2: +http2-wrapper@^1.0.0-beta.5.0: version "1.0.3" resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg== @@ -3446,6 +3529,14 @@ http2-wrapper@^1.0.0-beta.5.0, http2-wrapper@^1.0.0-beta.5.2: quick-lru "^5.1.1" resolve-alpn "^1.0.0" +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== + dependencies: + quick-lru "^5.1.1" + resolve-alpn "^1.2.0" + http_ece@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/http_ece/-/http_ece-1.1.0.tgz#74780c6eb32d8ddfe9e36a83abcd81fe0cd4fb75" @@ -3509,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" @@ -3901,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" @@ -3932,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== @@ -4026,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" @@ -4179,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== @@ -4187,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" @@ -4204,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" @@ -4218,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== @@ -4231,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== @@ -4427,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== @@ -4445,6 +4558,11 @@ lowercase-keys@^2.0.0: resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== +lowercase-keys@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-3.0.0.tgz#c5e7d442e37ead247ae9db117a9d0a467c89d4f2" + integrity sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ== + lru-cache@^4.1.5: version "4.1.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" @@ -4547,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" @@ -4586,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" @@ -4683,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" @@ -4701,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" @@ -4793,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" @@ -4868,13 +4988,6 @@ node-fetch@*: fetch-blob "^3.1.4" formdata-polyfill "^4.0.10" -node-fetch@2.6.7, node-fetch@^2.6.1: - version "2.6.7" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" - integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== - dependencies: - whatwg-url "^5.0.0" - node-fetch@3.0.0-beta.9: version "3.0.0-beta.9" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.0.0-beta.9.tgz#0a7554cfb824380dd6812864389923c783c80d9b" @@ -4883,6 +4996,22 @@ node-fetch@3.0.0-beta.9: data-uri-to-buffer "^3.0.1" fetch-blob "^2.1.1" +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" + formdata-polyfill "^4.0.10" + +node-fetch@^2.6.1: + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" + node-gyp-build@^4.2.3: version "4.3.0" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3" @@ -4909,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" @@ -4951,6 +5080,11 @@ normalize-url@^4.1.0: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== +normalize-url@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" + integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== + npm-run-path@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.1.0.tgz#bc62f7f3f6952d9894bd08944ba011a6ee7b7e00" @@ -5122,6 +5256,11 @@ p-cancelable@^2.0.0: resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.0.0.tgz#4a3740f5bdaf5ed5d7c3e34882c6fb5d6b266a6e" integrity sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg== +p-cancelable@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-3.0.0.tgz#63826694b54d61ca1c20ebcb6d3ecf5e14cd8050" + integrity sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw== + p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" @@ -5295,10 +5434,10 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -peek-readable@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-4.0.1.tgz#9a045f291db254111c3412c1ce4fec27ddd4d202" - integrity sha512-7qmhptnR0WMSpxT5rMHG9bW/mYSR1uqaPFj2MHvT+y/aOUu6msJijpKt5SkTDKySwg65OWG2JwTMBlgcbwMHrQ== +peek-readable@^5.0.0-alpha.5: + version "5.0.0-alpha.5" + resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-5.0.0-alpha.5.tgz#ace5dfedf7bc33f17c9b5170b9d54f69a4fba79b" + integrity sha512-pJohF/tDwV3ntnT5+EkUo4E700q/j/OCDuPxtM+5/kFGjyOai/sK4/We4Cy1MB2OiTQliWU5DxPvYIKQAdPqAA== pg-connection-string@^2.5.0: version "2.5.0" @@ -5779,12 +5918,11 @@ readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-web-to-node-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.0.tgz#4ca5408e70471069119d691934141a52de413955" - integrity sha512-HNmLb3n0SteGAs8HQlErYPGeO+y7cvL/mVUKtXeUkl0iCZ/2GIgKGrCFHyS7UXFnO8uc9U+0y3pYIzAPsjFfvA== +readable-web-to-node-stream@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz#5d52bb5df7b54861fd48d015e93a2cb87b3ee0bb" + integrity sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw== dependencies: - "@types/readable-stream" "^2.3.9" readable-stream "^3.6.0" readdir-glob@^1.0.0: @@ -5816,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" @@ -5896,6 +6041,11 @@ resolve-alpn@^1.0.0: resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.0.0.tgz#745ad60b3d6aff4b4a48e01b8c0bdc70959e0e8c" integrity sha512-rTuiIEqFmGxne4IovivKSDzld2lWW9QCjqv80SYjPgf+gS35eaCAjaP54CCwGAwBtnCsvNLYtqxe1Nw+i6JEmA== +resolve-alpn@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" + integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== + resolve-dir@^1.0.0, resolve-dir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" @@ -5974,16 +6124,16 @@ s-age@1.1.2: resolved "https://registry.yarnpkg.com/s-age/-/s-age-1.1.2.tgz#c0cf15233ccc93f41de92ea42c36d957977d1ea2" integrity sha512-aSN2TlF39WLoZA/6cgYSJZhKt63kJ4EaadejPWjWY9/h4rksIqvfWY3gfd+3uAegSM1IXsA9aWeEhJtkxkFQtA== -safe-buffer@*, safe-buffer@5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== +safe-buffer@5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" @@ -6047,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" @@ -6059,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" @@ -6101,13 +6251,13 @@ sha.js@^2.4.11: inherits "^2.0.1" safe-buffer "^5.0.1" -sharp@0.30.1: - version "0.30.1" - resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.30.1.tgz#203efaf9acfc5c15c8a343800254621e56011c12" - integrity sha512-ycpz81q8AeVjz1pGvvirQBeJcYE2sXAjcLXR/69LWOe/oxavBLOrenZcTzvTXn83jqAGqY+OuwF+2kFXzbKtDA== +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.0" - detect-libc "^2.0.0" + color "^4.2.1" + detect-libc "^2.0.1" node-addon-api "^4.3.0" prebuild-install "^7.0.1" semver "^7.3.5" @@ -6402,13 +6552,13 @@ strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= -strtok3@^6.2.4: - version "6.2.4" - resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-6.2.4.tgz#302aea64c0fa25d12a0385069ba66253fdc38a81" - integrity sha512-GO8IcFF9GmFDvqduIspUBwCzCbqzegyVKIsSymcMgiZKeCfrN9SowtUoi8+b59WZMAjIzVZic/Ft97+pynR3Iw== +strtok3@^7.0.0-alpha.7: + version "7.0.0-alpha.8" + resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-7.0.0-alpha.8.tgz#23a7870974e0494b58b14af6dd1c2c67cf13314d" + integrity sha512-u+k19v+rTxBjGYxncRQjGvZYwYvEd0uP3D+uHKe/s4WB1eXS5ZwpZsTlBu5xSS4zEd89mTXECXg6WW3FSeV8cA== dependencies: "@tokenizer/token" "^0.3.0" - peek-readable "^4.0.1" + peek-readable "^5.0.0-alpha.5" style-loader@3.3.1: version "3.3.1" @@ -6464,10 +6614,10 @@ syslog-pro@1.0.0: dependencies: moment "^2.22.2" -systeminformation@5.11.4: - version "5.11.4" - resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.11.4.tgz#3ed99533c67d0b4bd357871687e014f1c2a37194" - integrity sha512-rh7bjpjP5whUaTknim5CiGdAiKZcgWhmbmxjzBRXDWqUc/k67bz2OP+03DdcX6/SN/CDSAi/NeUwM5o2gjHJoA== +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" @@ -6591,10 +6741,10 @@ token-stream@1.0.0: resolved "https://registry.yarnpkg.com/token-stream/-/token-stream-1.0.0.tgz#cc200eab2613f4166d27ff9afc7ca56d49df6eb4" integrity sha1-zCAOqyYT9BZtJ/+a/HylbUnfbrQ= -token-types@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/token-types/-/token-types-4.1.1.tgz#ef9e8c8e2e0ded9f1b3f8dbaa46a3228b113ba1a" - integrity sha512-hD+QyuUAyI2spzsI0B7gf/jJ2ggR4RjkAo37j3StuePhApJUwcWDjnHDOFdIWYSwNR28H14hpwm4EI+V1Ted1w== +token-types@^5.0.0-alpha.2: + version "5.0.0-alpha.2" + resolved "https://registry.yarnpkg.com/token-types/-/token-types-5.0.0-alpha.2.tgz#e43d63b2a8223a593d1c782a5149bec18f1abf97" + integrity sha512-EsG9UxAW4M6VATrEEjhPFTKEUi1OiJqTUMIZOGBN49fGxYjZB36k0p7to3HZSmWRoHm1QfZgrg3e02fpqAt5fQ== dependencies: "@tokenizer/token" "^0.3.0" ieee754 "^1.2.1" @@ -6630,20 +6780,20 @@ trace-redirect@1.0.6: resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk= -ts-loader@9.2.6: - version "9.2.6" - resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.2.6.tgz#9937c4dd0a1e3dbbb5e433f8102a6601c6615d74" - integrity sha512-QMTC4UFzHmu9wU2VHZEmWWE9cUajjfcdcws+Gh7FhiO+Dy0RnR1bNz0YCHqhI0yRowCE9arVnNxYHqELOy9Hjw== +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" micromatch "^4.0.0" semver "^7.3.4" -ts-node@10.5.0: - version "10.5.0" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.5.0.tgz#618bef5854c1fbbedf5e31465cbb224a1d524ef9" - integrity sha512-6kEJKwVxAJ35W4akuiysfKwKmjkbYxwQMTBaAxo9KKAx/Yd26mPUyhGz3ji+EsJoAgrLqVsYHNuuYwQe22lbtw== +ts-node@10.7.0: + version "10.7.0" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.7.0.tgz#35d503d0fab3e2baa672a0e94f4b40653c2463f5" + integrity sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A== dependencies: "@cspotcode/source-map-support" "0.7.0" "@tsconfig/node10" "^1.0.7" @@ -6671,7 +6821,17 @@ tsc-alias@1.4.1: mylas "^2.1.4" normalize-path "^3.0.0" -tsconfig-paths@3.12.0, tsconfig-paths@^3.12.0: +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.6" + strip-bom "^3.0.0" + +tsconfig-paths@^3.12.0: version "3.12.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz#19769aca6ee8f6a1a341e38c8fa45dd9fb18899b" integrity sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg== @@ -6686,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" @@ -6720,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" @@ -6774,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.44: - version "0.2.44" - resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.2.44.tgz#4cc07eb1eb7a0e7f3ec9e65ded9eb3c3aedbb3e1" - integrity sha512-yFyb9Ts73vGaS/O06TvLpzvT5U/ngO31GeciNc0eoH7P1QcG8kVZdOy9FHJqkTeDmIljMRgWjbYUoMw53ZY7Xw== +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.5.5: - version "4.5.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3" - integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA== +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" @@ -7215,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" @@ -7255,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" @@ -7278,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 a781f43c5a..ec18c369a3 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", @@ -26,15 +26,15 @@ "@types/punycode": "2.1.0", "@types/qrcode": "1.4.2", "@types/random-seed": "0.3.3", - "@types/seedrandom": "2.4.28", + "@types/seedrandom": "3.0.2", "@types/throttle-debounce": "2.1.0", "@types/tinycolor2": "1.4.3", "@types/uuid": "8.3.4", "@types/webpack": "5.28.0", "@types/webpack-stream": "3.2.12", "@types/websocket": "1.0.5", - "@types/ws": "8.2.3", - "@typescript-eslint/parser": "5.12.1", + "@types/ws": "8.5.3", + "@typescript-eslint/parser": "5.16.0", "@vue/compiler-sfc": "3.2.31", "abort-controller": "3.0.0", "autobind-decorator": "2.4.0", @@ -44,16 +44,16 @@ "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.6.0", - "cssnano": "5.0.17", + "css-loader": "6.7.1", + "cssnano": "5.1.5", "date-fns": "2.28.0", "deepcopy": "2.1.0", "escape-regexp": "0.0.1", - "eslint": "8.9.0", + "eslint": "8.11.0", "eslint-plugin-vue": "8.5.0", "eventemitter3": "4.0.7", "feed": "4.2.2", @@ -61,19 +61,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", "portscanner": "2.2.0", - "postcss": "8.4.6", + "postcss": "8.4.12", "postcss-loader": "6.2.1", "prismjs": "1.27.0", "private-ip": "2.3.3", @@ -86,7 +86,7 @@ "reflect-metadata": "0.1.13", "rndstr": "1.0.0", "s-age": "1.1.2", - "sass": "1.49.8", + "sass": "1.49.9", "sass-loader": "12.6.0", "seedrandom": "3.0.5", "strict-event-emitter-types": "2.0.0", @@ -94,33 +94,33 @@ "style-loader": "3.3.1", "syuilo-password-strength": "0.0.1", "textarea-caret": "3.1.0", - "three": "0.136.0", + "three": "0.139.0", "throttle-debounce": "3.0.1", "tinycolor2": "1.4.2", - "ts-loader": "9.2.6", + "ts-loader": "9.2.8", "tsc-alias": "1.5.0", - "tsconfig-paths": "3.12.0", - "twemoji-parser": "13.1.0", - "typescript": "4.5.5", + "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.12", + "vue-router": "4.0.14", "vue-style-loader": "4.1.3", "vue-svg-loader": "0.17.0-beta.2", "vuedraggable": "4.0.1", - "webpack": "5.69.1", + "webpack": "5.70.0", "webpack-cli": "4.9.2", "websocket": "1.0.34", "ws": "8.5.0" }, "devDependencies": { - "@typescript-eslint/eslint-plugin": "5.12.1", + "@typescript-eslint/eslint-plugin": "5.16.0", "cross-env": "7.0.3", - "cypress": "9.5.0", + "cypress": "9.5.2", "eslint-plugin-import": "2.25.4", "start-server-and-test": "1.14.0" } diff --git a/packages/client/src/account.ts b/packages/client/src/account.ts index 4aeceeccab..4772c0baa5 100644 --- a/packages/client/src/account.ts +++ b/packages/client/src/account.ts @@ -2,7 +2,7 @@ import { del, get, set } from '@/scripts/idb-proxy'; import { reactive } from 'vue'; import * as misskey from 'misskey-js'; import { apiUrl } from '@/config'; -import { waiting, api, popup, popupMenu, success } from '@/os'; +import { waiting, api, popup, popupMenu, success, alert } from '@/os'; import { unisonReload, reloadChannel } from '@/scripts/unison-reload'; import { showSuspendedDialog } from './scripts/show-suspended-dialog'; import { i18n } from './i18n'; @@ -89,7 +89,11 @@ function fetchAccount(token): Promise { signout(); }); } else { - signout(); + alert({ + type: 'error', + title: i18n.ts.failedToFetchAccountInformation, + text: JSON.stringify(res.error), + }); } } else { res.token = token; @@ -116,6 +120,7 @@ export async function login(token: Account['token'], redirect?: string) { if (_DEV_) console.log('logging as token ', token); const me = await fetchAccount(token); localStorage.setItem('account', JSON.stringify(me)); + document.cookie = `token=${token}; path=/; max-age=31536000`; // bull dashboardの認証とかで使う await addAccount(me.id, token); if (redirect) { diff --git a/packages/client/src/components/chart.vue b/packages/client/src/components/chart.vue index 3787c5f066..cc1aa9c20a 100644 --- a/packages/client/src/components/chart.vue +++ b/packages/client/src/components/chart.vue @@ -70,7 +70,8 @@ const colors = { red: '#FF4560', purple: '#e300db', orange: '#fe6919', - lime: '#c7f400', + lime: '#bde800', + cyan: '#00e0e0', }; const colorSets = [colors.blue, colors.green, colors.yellow, colors.red, colors.purple]; const getColor = (i) => { @@ -126,7 +127,7 @@ export default defineComponent({ name: string; type: 'line' | 'area'; color?: string; - borderDash?: number[]; + dashed?: boolean; hidden?: boolean; data: { x: number; @@ -216,7 +217,7 @@ export default defineComponent({ pointRadius: 0, borderWidth: props.bar ? 0 : 2, borderColor: x.color ? x.color : getColor(i), - borderDash: x.borderDash || [], + borderDash: x.dashed ? [5, 5] : [], borderJoinStyle: 'round', borderRadius: props.bar ? 3 : undefined, backgroundColor: props.bar ? (x.color ? x.color : getColor(i)) : alpha(x.color ? x.color : getColor(i), 0.1), @@ -225,7 +226,7 @@ export default defineComponent({ axis: 'y', colors: { 0: alpha(x.color ? x.color : getColor(i), 0), - [maxes[i]]: alpha(x.color ? x.color : getColor(i), 0.175), + [maxes[i]]: alpha(x.color ? x.color : getColor(i), 0.2), }, }, }, @@ -274,7 +275,7 @@ export default defineComponent({ y: { position: 'left', stacked: props.stacked, - suggestedMax: 100, + suggestedMax: 50, grid: { color: gridColor, borderColor: 'rgb(0, 0, 0, 0)', @@ -388,20 +389,33 @@ export default defineComponent({ type: 'area', data: format(raw.stalled), color: colors.red, + }, { + name: 'Pub Active', + type: 'line', + data: format(raw.pubActive), + color: colors.purple, + }, { + name: 'Sub Active', + type: 'line', + data: format(raw.subActive), + color: colors.orange, }, { name: 'Pub & Sub', - type: 'area', + type: 'line', data: format(raw.pubsub), - color: colors.lime, + dashed: true, + color: colors.cyan, }, { name: 'Pub', - type: 'area', + type: 'line', data: format(raw.pub), + dashed: true, color: colors.purple, }, { name: 'Sub', - type: 'area', + type: 'line', data: format(raw.sub), + dashed: true, color: colors.orange, }], }; @@ -582,7 +596,7 @@ export default defineComponent({ series: [{ name: 'All', type: 'line', - borderDash: [5, 5], + dashed: true, data: format( sum( raw.local.incSize, @@ -617,7 +631,7 @@ export default defineComponent({ series: [{ name: 'All', type: 'line', - borderDash: [5, 5], + dashed: true, data: format( sum( raw.local.incCount, @@ -784,6 +798,36 @@ export default defineComponent({ }; }; + const fetchPerUserFollowingChart = async (): Promise => { + const raw = await os.api('charts/user/following', { userId: props.args.user.id, limit: props.limit, span: props.span }); + return { + series: [{ + name: 'Local', + type: 'area', + data: format(raw.local.followings.total), + }, { + name: 'Remote', + type: 'area', + data: format(raw.remote.followings.total), + }], + }; + }; + + const fetchPerUserFollowersChart = async (): Promise => { + const raw = await os.api('charts/user/following', { userId: props.args.user.id, limit: props.limit, span: props.span }); + return { + series: [{ + name: 'Local', + type: 'area', + data: format(raw.local.followers.total), + }, { + name: 'Remote', + type: 'area', + data: format(raw.remote.followers.total), + }], + }; + }; + const fetchPerUserDriveChart = async (): Promise => { const raw = await os.api('charts/user/drive', { userId: props.args.user.id, limit: props.limit, span: props.span }); return { @@ -827,6 +871,8 @@ export default defineComponent({ case 'instance-drive-files-total': return fetchInstanceDriveFilesChart(true); case 'per-user-notes': return fetchPerUserNotesChart(); + case 'per-user-following': return fetchPerUserFollowingChart(); + case 'per-user-followers': return fetchPerUserFollowersChart(); case 'per-user-drive': return fetchPerUserDriveChart(); } }; diff --git a/packages/client/src/components/global/url.vue b/packages/client/src/components/global/url.vue index 56a8c3453a..55f6c5d5f9 100644 --- a/packages/client/src/components/global/url.vue +++ b/packages/client/src/components/global/url.vue @@ -24,6 +24,14 @@ import { url as local } from '@/config'; import * as os from '@/os'; import { useTooltip } from '@/scripts/use-tooltip'; +function safeURIDecode(str: string) { + try { + return decodeURIComponent(str); + } catch { + return str; + } +} + export default defineComponent({ props: { url: { @@ -54,9 +62,9 @@ export default defineComponent({ schema: url.protocol, hostname: decodePunycode(url.hostname), port: url.port, - pathname: decodeURIComponent(url.pathname), - query: decodeURIComponent(url.search), - hash: decodeURIComponent(url.hash), + pathname: safeURIDecode(url.pathname), + query: safeURIDecode(url.search), + hash: safeURIDecode(url.hash), self: self, attr: self ? 'to' : 'href', target: self ? null : '_blank', diff --git a/packages/client/src/components/launch-pad.vue b/packages/client/src/components/launch-pad.vue index ff8e995fd2..ffefc1b085 100644 --- a/packages/client/src/components/launch-pad.vue +++ b/packages/client/src/components/launch-pad.vue @@ -1,5 +1,5 @@ -