diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml index dae42eca1455..273c160a1e32 100644 --- a/locales/ar-SA.yml +++ b/locales/ar-SA.yml @@ -590,6 +590,8 @@ useGlobalSettingDesc: "اذا فعّل ستطبق إعدادات إشعارات other: "منوعات" regenerateLoginToken: "أعد توليد الرمز" regenerateLoginTokenDescription: "ينشئ رمز استيثاق جديد في العادة هذا ليس ضروريًا ؛ عند إنشاء رمز جديد ستُخرج جميع الأجهزة." +tokenRegenerated: "تم إعادة توليد الرمز" +tokenRegenerationFailed: "فشل إعادة توليد الرمز" setMultipleBySeparatingWithSpace: "يمكنك ادخال أكثر من مدخل واحد وذلك بفصلها بمسافات." fileIdOrUrl: "معرف الملف أو رابط" behavior: "السلوك" diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml index 2631d6526b41..0c2c2551d167 100644 --- a/locales/bn-BD.yml +++ b/locales/bn-BD.yml @@ -611,6 +611,8 @@ useGlobalSettingDesc: "চালু করলে, আপনার অ্যা other: "অন্যান্য" regenerateLoginToken: "লগইন টোকেন আবার বানান" regenerateLoginTokenDescription: "লগ ইন করার জন্য ব্যবহৃত অভ্যন্তরীণ টোকেন পুনরায় তৈরি করে। সাধারণত আপনার এটি করার দরকার নেই। এটি করলে, আপনি সমস্ত ডিভাইসে লগ আউট হয়ে যাবেন৷" +tokenRegenerated: "টোকেন পুনরায় তৈরি করা হয়েছে" +tokenRegenerationFailed: "টোকেন পুনরায় তৈরি করা যায়নি" setMultipleBySeparatingWithSpace: "আপনি একটি স্পেস দিয়ে আলাদা করে একাধিক এন্ট্রি দিতে পারেন।" fileIdOrUrl: "ফাইল ID অথবা URL" behavior: "আচরণ" diff --git a/locales/de-DE.yml b/locales/de-DE.yml index 7df7cee7a916..c9b0ae945a5c 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -642,6 +642,8 @@ useGlobalSetting: "Globale Einstellung verwenden" 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" +tokenRegenerated: "Token wurde regeneriert" +tokenRegenerationFailed: "Token konnte nicht regeneriert werden" 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" diff --git a/locales/en-US.unknown.yml b/locales/en-US.unknown.yml index 1881a0c90e1f..814b3fa15164 100644 --- a/locales/en-US.unknown.yml +++ b/locales/en-US.unknown.yml @@ -630,6 +630,8 @@ useGlobalSettingDesc: "If turned on, your account's notification settings will b other: "Other" regenerateLoginToken: "Regenerate login token" regenerateLoginTokenDescription: "Regenerates the token used internally during login. Normally this action is not necessary. If regenerated, all devices will be logged out." +tokenRegenerated: "Token regenerated" +tokenRegenerationFailed: "Failed to regenerate token" setMultipleBySeparatingWithSpace: "Separate multiple entries with spaces." fileIdOrUrl: "File ID or URL" behavior: "Behavior" diff --git a/locales/en-US.yml b/locales/en-US.yml index b205e6781f66..a2996496d0d4 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -642,6 +642,8 @@ 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" +tokenRegenerated: "Token regenerated" +tokenRegenerationFailed: "Failed to regenerate token" 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" diff --git a/locales/es-ES.yml b/locales/es-ES.yml index 14b9e62106f8..7432a6a85258 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -642,6 +642,8 @@ useGlobalSetting: "Usar ajustes globales" useGlobalSettingDesc: "Al activarse, se usará la configuración de notificaciones de la cuenta, al desactivarse se pueden hacer configuraciones particulares." other: "Otro" regenerateLoginToken: "Regenerar token de login" +tokenRegenerated: "Token regenerado" +tokenRegenerationFailed: "Error al regenerar el token" regenerateLoginTokenDescription: "Regenerar el token usado internamente durante el login. No siempre es necesario hacerlo. Al hacerlo de nuevo, se deslogueará en todos los dispositivos." setMultipleBySeparatingWithSpace: "Puedes añadir mas de uno, separado por espacios." fileIdOrUrl: "Id del archivo o URL" diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index 2ef1ef5a92eb..96312b7057c9 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -624,6 +624,8 @@ useGlobalSettingDesc: "S'il est activé, les paramètres de notification de votr other: "Autre" regenerateLoginToken: "Régénérer le jeton de connexion" regenerateLoginTokenDescription: "Générer un nouveau jeton d'authentification. Cette opération ne devrait pas être nécessaire ; lors de la génération d'un nouveau jeton, tous les appareils seront déconnectés. " +tokenRegenerated: "Token regenerado" +tokenRegenerationFailed: "Error al regenerar el token" setMultipleBySeparatingWithSpace: "Vous pouvez en définir plusieurs, en les séparant par des espaces." fileIdOrUrl: "ID du fichier ou URL" behavior: "Comportement" diff --git a/locales/id-ID.yml b/locales/id-ID.yml index 044c965792bf..e386cabc65a4 100644 --- a/locales/id-ID.yml +++ b/locales/id-ID.yml @@ -628,6 +628,8 @@ useGlobalSettingDesc: "Jika dinyalakan, setelan pemberitahuan akun kamu akan dig other: "Lainnya" regenerateLoginToken: "Perbarui token login" regenerateLoginTokenDescription: "Perbarui token yang digunakan secara internal saat login. Normalnya aksi ini tidak diperlukan. Jika diperbarui, semua perangkat akan dilogout." +tokenRegenerated: "Token telah diperbarui" +tokenRegenerationFailed: "Gagal memperbarui token" setMultipleBySeparatingWithSpace: "Kamu dapat menyetel banyak dengan memisahkannya menggunakan spasi." fileIdOrUrl: "File-ID atau URL" behavior: "Perilaku" diff --git a/locales/it-IT.yml b/locales/it-IT.yml index 0fc1b55add99..e2e925e6a44f 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -643,6 +643,8 @@ useGlobalSettingDesc: "Quando attiva, verranno utilizzate le impostazioni notifi other: "Avanzate" regenerateLoginToken: "Genera di nuovo un token di connessione" regenerateLoginTokenDescription: "Genera un nuovo token di autenticazione. Solitamente questa operazione non è necessaria: quando si genera un nuovo token, tutti i dispositivi vanno disconnessi." +tokenRegenerated: "Il token è stato rigenerato" +tokenRegenerationFailed: "Impossibile rigenerare il token" setMultipleBySeparatingWithSpace: "È possibile creare multiple voci separate da spazi." fileIdOrUrl: "ID o URL del file" behavior: "Comportamento" diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 78941f59c005..c2335f70ad29 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -644,6 +644,8 @@ useGlobalSettingDesc: "オンにすると、アカウントの通知設定が使 other: "その他" regenerateLoginToken: "ログイントークンを再生成" regenerateLoginTokenDescription: "ログインに使用される内部トークンを再生成します。通常この操作を行う必要はありません。再生成すると、全てのデバイスでログアウトされます。" +tokenRegenerated: "トークンが再生成されました" +tokenRegenerationFailed: "トークンの再生成に失敗しました" setMultipleBySeparatingWithSpace: "スペースで区切って複数設定できます。" fileIdOrUrl: "ファイルIDまたはURL" behavior: "動作" @@ -1954,4 +1956,4 @@ _disabledTimeline: _drivecleaner: orderBySizeDesc: "サイズが大きい順" - orderByCreatedAtAsc: "追加日が古い順" \ No newline at end of file + orderByCreatedAtAsc: "追加日が古い順" diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml index c6858614807b..9685789708d0 100644 --- a/locales/ja-KS.yml +++ b/locales/ja-KS.yml @@ -642,6 +642,8 @@ useGlobalSetting: "グローバル設定を使ってや" useGlobalSettingDesc: "オンにすると、アカウントの通知設定が使われるで。オフにすると、別々に設定できるようになるで。" other: "その他" regenerateLoginToken: "ログイントークンを再生成" +tokenRegenerated: "トークンが再生成されたで" +tokenRegenerationFailed: "トークンの再生成に失敗したで" regenerateLoginTokenDescription: "ログインに使われる内部トークンをもっかい作るで。いつもならこれをやる必要はないで。もっかい作ると、全部のデバイスでログアウトされるで気ぃつけてなー。" setMultipleBySeparatingWithSpace: "スペースで区切って何個でも設定できるで。" fileIdOrUrl: "ファイルIDかURL" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 04f801a6e805..caf464c03e81 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -641,6 +641,8 @@ useGlobalSettingDesc: "활성화하면 계정의 알림 설정이 적용됩니 other: "기타" regenerateLoginToken: "로그인 토큰을 재생성" regenerateLoginTokenDescription: "로그인할 때 사용되는 내부 토큰을 재생성합니다. 일반적으로 이 작업을 실행할 필요는 없습니다. 이 기능을 사용하면 이 계정으로 로그인한 모든 기기에서 로그아웃됩니다." +tokenRegenerated: "토큰을 재생성했습니다" +tokenRegenerationFailed: "토큰을 재생성할 수 없습니다" setMultipleBySeparatingWithSpace: "공백으로 구분하여 여러 개 설정할 수 있습니다." fileIdOrUrl: "파일 ID 또는 URL" behavior: "동작" diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml index abd06f0f80cb..c41c7ff11ea6 100644 --- a/locales/pl-PL.yml +++ b/locales/pl-PL.yml @@ -610,6 +610,8 @@ useGlobalSettingDesc: "Jeżeli włączone, zostaną wykorzystane ustawienia powi other: "Inne" regenerateLoginToken: "Generuj token logowania ponownie" regenerateLoginTokenDescription: "Regeneruje token używany wewnętrznie podczas logowania. Zazwyczaj nie jest to konieczne. Po regeneracji wszystkie urządzenia zostaną wylogowane." +tokenRegenerated: "Token został wygenerowany ponownie" +tokenRegenerationFailed: "Nie udało się wygenerować tokena" setMultipleBySeparatingWithSpace: "Możesz ustawić wiele, oddzielając je spacjami." fileIdOrUrl: "ID pliku albo URL" behavior: "Zachowanie" diff --git a/locales/ro-RO.yml b/locales/ro-RO.yml index a5080f3d6017..d61e445c548e 100644 --- a/locales/ro-RO.yml +++ b/locales/ro-RO.yml @@ -610,6 +610,8 @@ useGlobalSettingDesc: "Dacă opțiunea e pornită, notificările contului tău v other: "Altele" regenerateLoginToken: "Regenerează token de login" regenerateLoginTokenDescription: "Regenerează token-ul folosit intern în timpul logări. În mod normal asta nu este necesar. Odată regenerat, toate dispozitivele vor fi delogate." +tokenRegenerated: "Token regenerat" +tokenRegenerationFailed: "Regenerarea token-ului a eșuat" setMultipleBySeparatingWithSpace: "Separă mai multe intrări cu spații." fileIdOrUrl: "Introdu ID sau URL" behavior: "Comportament" diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml index 84acc076932d..be49e9a50e8e 100644 --- a/locales/ru-RU.yml +++ b/locales/ru-RU.yml @@ -634,6 +634,8 @@ useGlobalSettingDesc: "Если включено, будут использов other: "Другие" regenerateLoginToken: "Создать новый токен для входа" regenerateLoginTokenDescription: "Создаёт новый токен, используемый внутри программы во время входа. Обычно в этом нет необходимости. При создании все устройства будут отключены." +tokenRegenerated: "Токен создан" +tokenRegenerationFailed: "Не удалось создать токен" setMultipleBySeparatingWithSpace: "Можно написать несколько через пробел" fileIdOrUrl: "Идентификатор файла или ссылка" behavior: "Поведение" diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml index 2e6a8d37dd42..e9aff3ac72b3 100644 --- a/locales/sk-SK.yml +++ b/locales/sk-SK.yml @@ -635,6 +635,8 @@ useGlobalSettingDesc: "Ak je zapnuté, použijú sa oznámenia vášho účtu. A other: "Ostatní" regenerateLoginToken: "Pregenerovať prihlasovací token" regenerateLoginTokenDescription: "Pregeneruje token interne používaný počas prihlásenia. Normálne toto netreba robiť. Ak sa pregeneruje, všetky zariadenia sa odhlásia." +tokenRegenerated: "Token bol pregenerovaný" +tokenRegenerationFailed: "Token sa nepodarilo pregenerovať" setMultipleBySeparatingWithSpace: "Viaceré položky oddeľte medzerami." fileIdOrUrl: "ID alebo URL súboru" behavior: "Správanie" diff --git a/locales/th-TH.yml b/locales/th-TH.yml index 1302c318ad96..eacc7fe6013c 100644 --- a/locales/th-TH.yml +++ b/locales/th-TH.yml @@ -636,6 +636,8 @@ useGlobalSettingDesc: "หากเปิดไว้ ระบบจะใช other: "อื่น ๆ" regenerateLoginToken: "สร้างโทเค็นการเข้าสู่ระบบอีกครั้ง" regenerateLoginTokenDescription: "สร้างโทเค็นใหม่ที่ใช้ภายในระหว่างการเข้าสู่ระบบ โดยตามหลักปกติแล้วการดำเนินการนี้ไม่จำเป็น หากสร้างใหม่ อุปกรณ์ทั้งหมดจะถูกออกจากระบบนะ" +tokenRegenerated: "โทเค็นถูกสร้างใหม่แล้ว" +tokenRegenerationFailed: "ไม่สามารถสร้างโทเค็นใหม่ได้" setMultipleBySeparatingWithSpace: "คั่นหลายรายการด้วยช่องว่าง" fileIdOrUrl: "ไฟล์ ID หรือ URL" behavior: "พฤติกรรม" diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml index 06eb7ceee99c..c3ac2db4a006 100644 --- a/locales/uk-UA.yml +++ b/locales/uk-UA.yml @@ -625,6 +625,8 @@ useGlobalSettingDesc: "Якщо увімкнено, то будуть викор other: "Інше" regenerateLoginToken: "Оновити Login Token" regenerateLoginTokenDescription: "Регенерувати внутрішній ключ використовуваний під час входу. Зазвичай цього не потрібно робити. При регенерації всі пристрої вийдуть з системи." +tokenRegenerated: "Токен було оновлено" +tokenRegenerationFailed: "Не вдалося оновити токен" setMultipleBySeparatingWithSpace: "Можна вказати кілька значень, відділивши їх пробілом." fileIdOrUrl: "Ідентифікатор файлу або посилання" behavior: "Поведінка" diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml index 079eeb477e25..ab379834cafa 100644 --- a/locales/vi-VN.yml +++ b/locales/vi-VN.yml @@ -634,6 +634,8 @@ useGlobalSettingDesc: "Nếu được bật, cài đặt thông báo của bạn other: "Khác" regenerateLoginToken: "Tạo lại mã đăng nhập" regenerateLoginTokenDescription: "Tạo lại mã nội bộ có thể dùng để đăng nhập. Thông thường hành động này là không cần thiết. Nếu được tạo lại, tất cả các thiết bị sẽ bị đăng xuất." +tokenRegenerated: "Mã đã được tạo lại" +tokenRegenerationFailed: "Không thể tạo lại mã" setMultipleBySeparatingWithSpace: "Tách nhiều mục nhập bằng dấu cách." fileIdOrUrl: "ID tập tin hoặc URL" behavior: "Thao tác" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index cf3d779f1a69..b649681457b0 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -643,6 +643,8 @@ useGlobalSettingDesc: "启用时,将使用账户通知设置。关闭时,则 other: "其他" regenerateLoginToken: "重新生成登录令牌" regenerateLoginTokenDescription: "重新生成用于登录的内部令牌。通常您不需要这样做。重新生成后,您将在所有设备上登出。" +tokenRegenerated: "令牌已重新生成" +tokenRegenerationFailed: "令牌重新生成失败" setMultipleBySeparatingWithSpace: "您可以使用空格分隔多个项目。" fileIdOrUrl: "文件ID或者URL" behavior: "行为" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index abc029eee2c3..5b37356cda09 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -643,6 +643,8 @@ useGlobalSettingDesc: "啟用時,將使用帳戶通知設定。停用時,則 other: "其他" regenerateLoginToken: "重新產生登入權杖" regenerateLoginTokenDescription: "重新產生用於登入的內部權杖。一般情況下是不需要這樣做的。一旦重產,所有裝置將會被登出。" +tokenRegenerated: "權杖已重新生成" +tokenRegenerationFailed: "權杖重新生成失敗" setMultipleBySeparatingWithSpace: "您可以使用空格分隔多個項目。" fileIdOrUrl: "檔案ID或URL" behavior: "行為" diff --git a/packages/backend/assets/error.png b/packages/backend/assets/error.png index d5f7658625b0..bca5c8434bfc 100644 Binary files a/packages/backend/assets/error.png and b/packages/backend/assets/error.png differ diff --git a/packages/backend/assets/error10000.png b/packages/backend/assets/error10000.png new file mode 100644 index 000000000000..3cd0c09d2180 Binary files /dev/null and b/packages/backend/assets/error10000.png differ diff --git a/packages/backend/assets/error11000.png b/packages/backend/assets/error11000.png new file mode 100644 index 000000000000..6a30d6013d94 Binary files /dev/null and b/packages/backend/assets/error11000.png differ diff --git a/packages/backend/assets/error12000.png b/packages/backend/assets/error12000.png new file mode 100644 index 000000000000..db16b338454c Binary files /dev/null and b/packages/backend/assets/error12000.png differ diff --git a/packages/backend/assets/error13000.png b/packages/backend/assets/error13000.png new file mode 100644 index 000000000000..bca5c8434bfc Binary files /dev/null and b/packages/backend/assets/error13000.png differ diff --git a/packages/backend/package.json b/packages/backend/package.json index df22dda34fbe..591224703688 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -5,6 +5,7 @@ "type": "module", "scripts": { "dev": "npx nodemon --watch src --watch ../../built/_vite_ -e 'js,ts,json' --exec 'npm run build && npm run start'", + "dev:frontend-only": "npx nodemon --watch src --watch ../../built/_vite_ -e 'js,ts,json' --exec 'npm run build && npm run start'", "livereload": "npx livereload ./ -e 'start-time'", "build:start": "npm run build && npm run start", "start": "node --experimental-json-modules --experimental-specifier-resolution=node ./built/boot/index.js", diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index 498ceced7a47..a1c1d800d38e 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -56,7 +56,7 @@ export class QueueService { activity: activity, signature, }; - + return this.inboxQueue.add(data, { attempts: this.config.inboxJobMaxAttempts ?? 8, timeout: 5 * 60 * 1000, // 5min @@ -212,7 +212,8 @@ export class QueueService { soft: opts.soft, }, { removeOnComplete: true, - removeOnFail: true, + removeOnFail: false, + attempts: 10, }); } @@ -246,7 +247,7 @@ export class QueueService { createdAt: Date.now(), eventId: uuid(), }; - + return this.webhookDeliverQueue.add(data, { attempts: 4, timeout: 1 * 60 * 1000, // 1min @@ -264,7 +265,7 @@ export class QueueService { //deliverLogger.succ(`Cleaned ${jobs.length} ${status} jobs`); }); this.deliverQueue.clean(0, 'delayed'); - + this.inboxQueue.once('cleaned', (jobs, status) => { //inboxLogger.succ(`Cleaned ${jobs.length} ${status} jobs`); }); diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index 9d0752c462c6..4cdfe6240c02 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -363,6 +363,10 @@ export class UserEntityService implements OnModuleInit { userProfile?: UserProfile, }, ): Promise> { + if (!src) { + throw new Error('User not found'); + } + const opts = Object.assign({ detail: false, includeSecrets: false, diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts index b5e01e16a093..389ac1665637 100644 --- a/packages/backend/src/postgres.ts +++ b/packages/backend/src/postgres.ts @@ -211,7 +211,6 @@ export function createPostgresDataSource(config: Config) { password: config.db.pass, database: config.db.db, extra: { - statement_timeout: 1000 * 10, ...config.db.extra, }, synchronize: process.env.NODE_ENV === 'test', diff --git a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts index e36a78de6aa2..37dc5e1dd63c 100644 --- a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts @@ -8,10 +8,10 @@ import { DriveService } from '@/core/DriveService.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import type { Note } from '@/models/entities/Note.js'; import { EmailService } from '@/core/EmailService.js'; +import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; import type { DbUserDeleteJobData } from '../types.js'; -import { bindThis } from '@/decorators.js'; @Injectable() export class DeleteAccountProcessorService { @@ -46,6 +46,7 @@ export class DeleteAccountProcessorService { const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { + this.logger.warn('User not found'); return; } diff --git a/packages/backend/src/server/api/endpoints/admin/delete-account.ts b/packages/backend/src/server/api/endpoints/admin/delete-account.ts index d0485fddd885..62bb42edfa9d 100644 --- a/packages/backend/src/server/api/endpoints/admin/delete-account.ts +++ b/packages/backend/src/server/api/endpoints/admin/delete-account.ts @@ -33,10 +33,6 @@ export default class extends Endpoint { ) { super(meta, paramDef, async (ps) => { const user = await this.usersRepository.findOneByOrFail({ id: ps.userId }); - if (user.isDeleted) { - return; - } - await this.deleteAccountService.deleteAccount(user); }); } diff --git a/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts b/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts index a8964af44946..b2d059116b01 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts @@ -6,7 +6,7 @@ export const meta = { tags: ['admin'], requireCredential: true, - requireModerator: true, + requireAdmin: true, } as const; export const paramDef = { 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 4f7e02fe922f..f79049dda37e 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts @@ -9,7 +9,7 @@ export const meta = { tags: ['admin'], requireCredential: true, - requireModerator: true, + requireAdmin: true, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/admin/drive/files.ts b/packages/backend/src/server/api/endpoints/admin/drive/files.ts index 8a4498d5fa23..85d107efabc7 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/files.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/files.ts @@ -9,7 +9,7 @@ export const meta = { tags: ['admin'], requireCredential: true, - requireModerator: true, + requireAdmin: true, res: { type: 'array', 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 1d27ac213716..dc9130442532 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 @@ -9,7 +9,7 @@ export const meta = { tags: ['admin'], requireCredential: true, - requireModerator: true, + requireAdmin: true, errors: { noSuchFile: { 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 b9d8dce47a4f..6af9d1a896d8 100644 --- a/packages/backend/src/server/api/endpoints/clips/add-note.ts +++ b/packages/backend/src/server/api/endpoints/clips/add-note.ts @@ -17,7 +17,7 @@ export const meta = { limit: { duration: ms('1hour'), - max: 20, + max: 500, }, errors: { 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 77a03d9811fa..8ab71d3823b1 100644 --- a/packages/backend/src/server/api/endpoints/i/delete-account.ts +++ b/packages/backend/src/server/api/endpoints/i/delete-account.ts @@ -33,10 +33,6 @@ export default class extends Endpoint { ) { super(meta, paramDef, async (ps, me) => { const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); - const userDetailed = await this.usersRepository.findOneByOrFail({ id: me.id }); - if (userDetailed.isDeleted) { - return; - } // 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 b1eaab390890..9f98abafe217 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -91,7 +91,7 @@ export const paramDef = { fields: { type: 'array', minItems: 0, - maxItems: 16, + maxItems: 50, items: { type: 'object', properties: { diff --git a/packages/backend/src/server/api/endpoints/messaging/history.ts b/packages/backend/src/server/api/endpoints/messaging/history.ts index 98b8f43cbb29..6b7fd6ed676b 100644 --- a/packages/backend/src/server/api/endpoints/messaging/history.ts +++ b/packages/backend/src/server/api/endpoints/messaging/history.ts @@ -1,8 +1,12 @@ import { Inject, Injectable } from '@nestjs/common'; -import { Brackets, IsNull } from 'typeorm'; +import { Brackets } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { MessagingMessage } from '@/models/entities/MessagingMessage.js'; -import type { MutingsRepository, UserGroupJoiningsRepository, MessagingMessagesRepository, BlockingsRepository } from '@/models/index.js'; +import type { + MutingsRepository, + UserGroupJoiningsRepository, + MessagingMessagesRepository, + BlockingsRepository, +} from '@/models/index.js'; import { MessagingMessageEntityService } from '@/core/entities/MessagingMessageEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -26,11 +30,7 @@ export const meta = { export const paramDef = { type: 'object', - properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - group: { type: 'boolean', default: false }, - isAll: { type: 'boolean', default: false }, - }, + properties: {}, required: [], } as const; @@ -52,146 +52,94 @@ export default class extends Endpoint { const mute = await this.mutingsRepository.findBy({ muterId: me.id, }); + + // 自分がミュートした人 const muteeIds = mute.map(m => m.muteeId); + // 自分がブロックした人 const block = await this.blockingsRepository.findBy({ blockerId: me.id, }); const blockeeIds = block.map(m => m.blockeeId); - if (ps.isAll) { - // 自分が所属しているグループ一覧 - const groupIds = await this.userGroupJoiningsRepository.findBy({ - userId: me.id, - }).then(xs => xs.map(x => x.userGroupId)); - - // グループから最新のメッセージを一つずつ取得 (ミュートを除外) - const groupMessages = groupIds.length ? await this - .messagingMessagesRepository - .createQueryBuilder('root') - .where(qb => { - qb.where('root.id IN (' + this.messagingMessagesRepository - .createQueryBuilder('message') - .where('message.groupId IN (:...groupIds)', { groupIds: groupIds }) - .groupBy('message.groupId') - .select('max(message.id) as id').getQuery() + ')'); - if (muteeIds.length > 0) { - qb.andWhere('root.userId NOT IN (:...mute)', { mute: muteeIds }); - } - if (blockeeIds.length > 0) { - qb.andWhere('root.userId NOT IN (:...block)', { block: blockeeIds }); - } - }) - .setParameters({ groupIds: groupIds, mute: muteeIds, block: blockeeIds }).getMany() : []; - - // ユーザー一覧から最新のメッセージを一つずつ取得 - const userMessages = await this - .messagingMessagesRepository - .createQueryBuilder('root') - .where(qb => { - qb.where('root.id IN (' + this.messagingMessagesRepository - .createQueryBuilder('message') - .where(new Brackets(qb => { - qb.where(new Brackets(qb => { - // 他人が送信したメッセージ - qb.where('message.recipientId = :meId', { meId: me.id }); - - if (muteeIds.length > 0) { - qb.andWhere('message.userId NOT IN (:...mute)', { mute: muteeIds }); - } - - if (blockeeIds.length > 0) { - qb.andWhere('message.userId NOT IN (:...block)', { block: blockeeIds }); - } - })).orWhere(new Brackets(qb => { - // 自分が送信したメッセージ - qb.where('message.userId = :meId', { meId: me.id }); - - if (muteeIds.length > 0) { - qb.andWhere('message.recipientId NOT IN (:...mute)', { mute: muteeIds }); - } - - if (blockeeIds.length > 0) { - qb.andWhere('message.recipientId NOT IN (:...block)', { block: blockeeIds }); - } - })); - })) - .andWhere( - // 期間を一ヶ月前までに絞る - 'message.createdAt > :monthAgo', - { monthAgo: new Date(Date.now() - 1000 * 60 * 60 * 24 * 30) }, - ) - .andWhere('message.groupId IS NULL') - .groupBy('message.userId, message.recipientId') - .select('max(message.id) as id') - .getQuery() + ')'); - }).setParameters({ meId: me.id, mute: muteeIds, block: blockeeIds, monthAgo: new Date(Date.now() - 1000 * 60 * 60 * 24 * 30) }).getMany(); - - const messages = Array.from(new Map([ - ...groupMessages, - ...Array.from(new Map(userMessages - .sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()) - .map(m => [[m.userId, m.recipientId].sort().join(), m])).values()), - ].map(m => [m.id, m])).values()); - - return Promise.all( - messages - .map(h => this.messagingMessageEntityService.pack(h.id, me)), - ); - } - - const groups = ps.group ? await this.userGroupJoiningsRepository.findBy({ + // 自分が所属しているグループ一覧 + const groupIds = await this.userGroupJoiningsRepository.findBy({ userId: me.id, - }).then(xs => xs.map(x => x.userGroupId)) : []; - - if (ps.group && groups.length === 0) { - return []; - } - - const history: MessagingMessage[] = []; - - for (let i = 0; i < ps.limit; i++) { - const found = ps.group - ? history.map(m => m.groupId!) - : history.map(m => (m.userId === me.id) ? m.recipientId! : m.userId!); - - const query = this.messagingMessagesRepository.createQueryBuilder('message') - .orderBy('message.createdAt', 'DESC'); - - if (ps.group) { - query.where('message.groupId IN (:...groups)', { groups: groups }); - - if (found.length > 0) { - query.andWhere('message.groupId NOT IN (:...found)', { found: found }); - } - } else { - query.where(new Brackets(qb => { - qb - .where('message.userId = :userId', { userId: me.id }) - .orWhere('message.recipientId = :userId', { userId: me.id }); - })); - query.andWhere('message.groupId IS NULL'); - - if (found.length > 0) { - query.andWhere('message.userId NOT IN (:...found)', { found: found }); - query.andWhere('message.recipientId NOT IN (:...found)', { found: found }); - } - - if (mute.length > 0) { - query.andWhere('message.userId NOT IN (:...mute)', { mute: mute.map(m => m.muteeId) }); - query.andWhere('message.recipientId NOT IN (:...mute)', { mute: mute.map(m => m.muteeId) }); - } - } - const message = await query.getOne(); - - if (message) { - history.push(message); - } else { - break; - } - } - - return await Promise.all(history.map(h => this.messagingMessageEntityService.pack(h.id, me))); + }).then(xs => xs.map(x => x.userGroupId)); + + // グループから最新のメッセージを一つずつ取得 + // グループの場合ブロックとミュートを除外できない + const groupMessages = groupIds.length ? await this + .messagingMessagesRepository + .createQueryBuilder('root') + .where(qb => { + qb.where('root.id IN (' + this.messagingMessagesRepository + .createQueryBuilder('message') + .where('message.groupId IN (:...groupIds)', { groupIds: groupIds }) + .groupBy('message.groupId') + .select('max(message.id) as id').getQuery() + ')'); + }) + .setParameters({ groupIds: groupIds }).getMany() : []; + + // ユーザー一覧から最新のメッセージを一つずつ取得 + const userMessages = await this + .messagingMessagesRepository + .createQueryBuilder('root') + .where(qb => { + qb.where('root.id IN (' + this.messagingMessagesRepository + .createQueryBuilder('message') + .where(new Brackets(qb => { + qb.where(new Brackets(qb => { + // 他人が送信したメッセージ + qb.where('message.recipientId = :meId', { meId: me.id }); + + if (muteeIds.length > 0) { + qb.andWhere('message.userId NOT IN (:...mute)', { mute: muteeIds }); + } + + if (blockeeIds.length > 0) { + qb.andWhere('message.userId NOT IN (:...block)', { block: blockeeIds }); + } + })).orWhere(new Brackets(qb => { + // 自分が送信したメッセージ + qb.where('message.userId = :meId', { meId: me.id }); + + if (muteeIds.length > 0) { + qb.andWhere('message.recipientId NOT IN (:...mute)', { mute: muteeIds }); + } + + if (blockeeIds.length > 0) { + qb.andWhere('message.recipientId NOT IN (:...block)', { block: blockeeIds }); + } + })); + })) + .andWhere( + // 期間を一ヶ月前までに絞る + 'message.createdAt > :monthAgo', + { monthAgo: new Date(Date.now() - 1000 * 60 * 60 * 24 * 30) }, + ) + .andWhere('message.groupId IS NULL') + .groupBy('message.userId, message.recipientId') + .select('max(message.id) as id') + .getQuery() + ')'); + }).setParameters({ + meId: me.id, + mute: muteeIds, + block: blockeeIds, + monthAgo: new Date(Date.now() - 1000 * 60 * 60 * 24 * 30), + }).getMany(); + + const messages = Array.from(new Map([ + ...groupMessages, + ...Array.from(new Map(userMessages + .sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()) + .map(m => [[m.userId, m.recipientId].sort().join(), m])).values()), + ].map(m => [m.id, m])).values()); + + return Promise.all( + messages + .map(h => this.messagingMessageEntityService.pack(h.id, me)), + ); }); } } diff --git a/packages/backend/src/server/api/endpoints/notes/featured.ts b/packages/backend/src/server/api/endpoints/notes/featured.ts index 6bf17b222a0e..49b3bcbf5b00 100644 --- a/packages/backend/src/server/api/endpoints/notes/featured.ts +++ b/packages/backend/src/server/api/endpoints/notes/featured.ts @@ -8,7 +8,7 @@ import { DI } from '@/di-symbols.js'; export const meta = { tags: ['notes'], - requireCredential: false, + requireCredential: true, allowGet: true, cacheSec: 3600, 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 9118d33936ec..c1fb113185c2 100644 --- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts @@ -12,6 +12,8 @@ import { ApiError } from '../../error.js'; export const meta = { tags: ['notes'], + requireCredential: true, + res: { type: 'array', optional: false, nullable: false, 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 8c1c07a9f459..5e58e2042768 100644 --- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts @@ -14,6 +14,8 @@ import { ApiError } from '../../error.js'; export const meta = { tags: ['notes'], + requireCredential: true, + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts index 31042ebc1f28..a4d604ef745e 100644 --- a/packages/backend/src/server/api/endpoints/notes/search.ts +++ b/packages/backend/src/server/api/endpoints/notes/search.ts @@ -13,7 +13,7 @@ import { ApiError } from '../../error.js'; export const meta = { tags: ['notes'], - requireCredential: false, + requireCredential: true, res: { type: 'array', @@ -83,11 +83,26 @@ export default class extends Endpoint { query.andWhere('note.channelId = :channelId', { channelId: ps.channelId }); } - // const notes = await query.take(ps.limit).getMany(); + query + .andWhere('note.text ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` }) + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); - // return await this.noteEntityService.packMany(notes, me); - // 負荷対策のため 2024-03-28 - return []; + this.queryService.generateVisibilityQuery(query, me); + if (me) this.queryService.generateMutedUserQuery(query, me); + if (me) this.queryService.generateBlockedUserQuery(query, me); + + const notes = await query.take(ps.limit).getMany(); + return await this.noteEntityService.packMany(notes, me); }); } } 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 8daee3a6f514..f9b2f36072b5 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/joined.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/joined.ts @@ -44,16 +44,23 @@ export default class extends Endpoint { private userGroupEntityService: UserGroupEntityService, ) { super(meta, paramDef, async (ps, me) => { - const ownedGroups = await this.userGroupsRepository.findBy({ - userId: me.id, - }); - - const joinings = await this.userGroupJoiningsRepository.findBy({ - userId: me.id, - ...(ownedGroups.length > 0 ? { - userGroupId: Not(In(ownedGroups.map(x => x.id))), - } : {}), - }); + const ownedGroups = await this.userGroupsRepository.find({ + where: { + userId: me.id, + }, + order: { + createdAt: 'DESC', + }, + }); + + const joinings = await this.userGroupJoiningsRepository.find({ where: { + userId: me.id, + ...(ownedGroups.length > 0 ? { + userGroupId: Not(In(ownedGroups.map(x => x.id))), + } : {}), + }, order: { + createdAt: 'DESC', + } }); return await Promise.all(joinings.map(x => this.userGroupEntityService.pack(x.userGroupId))); }); 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 0bc6e8b3fc71..3b2682eb548f 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/owned.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/owned.ts @@ -40,9 +40,14 @@ export default class extends Endpoint { private userGroupEntityService: UserGroupEntityService, ) { super(meta, paramDef, async (ps, me) => { - const userGroups = await this.userGroupsRepository.findBy({ - userId: me.id, - }); + const userGroups = await this.userGroupsRepository.find({ + where: { + userId: me.id, + }, + order: { + createdAt: 'DESC', + }, + }); return await Promise.all(userGroups.map(x => this.userGroupEntityService.pack(x))); }); 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 1c1fdc23f16f..e4487497ae37 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/push.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts @@ -18,7 +18,7 @@ export const meta = { limit: { duration: ms('1hour'), - max: 30, + max: 1000, }, errors: { diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts index ba432c273bfb..e40fe158c4b4 100644 --- a/packages/backend/src/server/api/endpoints/users/show.ts +++ b/packages/backend/src/server/api/endpoints/users/show.ts @@ -90,14 +90,14 @@ export default class extends Endpoint { super(meta, paramDef, async (ps, me, _1, _2, _3, ip) => { let user; - const isModerator = await this.roleService.isModerator(me); - if (ps.userIds) { if (ps.userIds.length === 0) { return []; } - const users = await this.usersRepository.findBy(isModerator ? { + const isAdministrator = await this.roleService.isAdministrator(me); + + const users = await this.usersRepository.findBy(isAdministrator ? { id: In(ps.userIds), } : { id: In(ps.userIds), @@ -107,13 +107,19 @@ export default class extends Endpoint { // リクエストされた通りに並べ替え const _users: User[] = []; for (const id of ps.userIds) { - _users.push(users.find(x => x.id === id)!); + const user = users.find(x => x.id === id); + if (!user) { + continue; + } + _users.push(user); } return await Promise.all(_users.map(u => this.userEntityService.pack(u, me, { detail: true, }))); } else { + const isModerator = await this.roleService.isModerator(me); + // Lookup user if (typeof ps.host === 'string' && typeof ps.username === 'string') { user = await this.remoteUserResolveService.resolveUser(ps.username, ps.host).catch(err => { diff --git a/packages/backend/test/unit/chart.ts b/packages/backend/test/unit/chart.ts index d0f5d3e1df3e..8534ee4acd10 100644 --- a/packages/backend/test/unit/chart.ts +++ b/packages/backend/test/unit/chart.ts @@ -42,7 +42,6 @@ describe('Chart', () => { password: config.db.pass, database: config.db.db, extra: { - statement_timeout: 1000 * 10, ...config.db.extra, }, synchronize: true, diff --git a/packages/frontend/assets/error.png b/packages/frontend/assets/error.png index d5f7658625b0..bca5c8434bfc 100644 Binary files a/packages/frontend/assets/error.png and b/packages/frontend/assets/error.png differ diff --git a/packages/frontend/assets/error10000.png b/packages/frontend/assets/error10000.png new file mode 100644 index 000000000000..3cd0c09d2180 Binary files /dev/null and b/packages/frontend/assets/error10000.png differ diff --git a/packages/frontend/assets/error11000.png b/packages/frontend/assets/error11000.png new file mode 100644 index 000000000000..6a30d6013d94 Binary files /dev/null and b/packages/frontend/assets/error11000.png differ diff --git a/packages/frontend/assets/error12000.png b/packages/frontend/assets/error12000.png new file mode 100644 index 000000000000..db16b338454c Binary files /dev/null and b/packages/frontend/assets/error12000.png differ diff --git a/packages/frontend/assets/error13000.png b/packages/frontend/assets/error13000.png new file mode 100644 index 000000000000..bca5c8434bfc Binary files /dev/null and b/packages/frontend/assets/error13000.png differ diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 363a6507af08..cee955436124 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -4,7 +4,7 @@ v-show="!isDeleted" ref="el" v-hotkey="keymap" - :class="[$style.root, { [$style.showActionsOnlyHover]: defaultStore.state.showNoteActionsOnlyHover }]" + :class="[$style.root, { [$style.showActionsOnlyHover]: defaultStore.state.showNoteActionsOnlyHover }]" :tabindex="!isDeleted ? '-1' : undefined" > @@ -38,7 +38,9 @@ - +
@@ -55,7 +57,7 @@
- +

@@ -71,9 +73,9 @@

({{ i18n.ts.private }}) - + + + {{ appearNote.channel.name }} + {{ appearNote.channel.name }} +
- - - -
+ + + +
@@ -36,6 +41,7 @@ import { i18n } from '@/i18n'; const props = defineProps<{ pagination: Paging; noGap?: boolean; + hideNote?: boolean; }>(); const pagingComponent = shallowRef>(); diff --git a/packages/frontend/src/components/MkRolePreview.vue b/packages/frontend/src/components/MkRolePreview.vue index 2f5866f3401e..dc4f545f1ca9 100644 --- a/packages/frontend/src/components/MkRolePreview.vue +++ b/packages/frontend/src/components/MkRolePreview.vue @@ -1,65 +1,72 @@ diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue index 87f7c61a9287..0add5bf767cc 100644 --- a/packages/frontend/src/components/MkTimeline.vue +++ b/packages/frontend/src/components/MkTimeline.vue @@ -1,5 +1,11 @@ diff --git a/packages/frontend/src/pages/about.vue b/packages/frontend/src/pages/about.vue index 0f1a5f194d71..cf0bc8279956 100644 --- a/packages/frontend/src/pages/about.vue +++ b/packages/frontend/src/pages/about.vue @@ -131,11 +131,6 @@ const headerTabs = $computed(() => [ key: 'overview', title: i18n.ts.overview, }, - { - key: 'emojis', - title: i18n.ts.customEmojis, - icon: 'ti ti-icons', - }, { key: 'federation', title: i18n.ts.federation, diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue index 9930d59699f7..7f0c99bfef0e 100644 --- a/packages/frontend/src/pages/channel.vue +++ b/packages/frontend/src/pages/channel.vue @@ -12,19 +12,19 @@
+ > + +
+ > + +
@@ -54,9 +54,9 @@
- {{ i18n.ts.postToTheChannel }} + + {{ i18n.ts.postToTheChannel }} +
diff --git a/packages/frontend/src/pages/messaging/index.vue b/packages/frontend/src/pages/messaging/index.vue index 6a0f772cda78..f4817e4c0f9b 100644 --- a/packages/frontend/src/pages/messaging/index.vue +++ b/packages/frontend/src/pages/messaging/index.vue @@ -220,7 +220,7 @@ function attach() { // @ts-ignore connection.on('read', onRead); - os.api('messaging/history', { isAll: true, limit: 100 }) + os.api('messaging/history', {}) .then((_messages) => { _messages.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); messages = _messages; diff --git a/packages/frontend/src/pages/messaging/messaging-room.vue b/packages/frontend/src/pages/messaging/messaging-room.vue index a9a69a65d12f..ac1fb14be6e8 100644 --- a/packages/frontend/src/pages/messaging/messaging-room.vue +++ b/packages/frontend/src/pages/messaging/messaging-room.vue @@ -81,6 +81,7 @@ import { computed, watch, onMounted, nextTick, onBeforeUnmount, onActivated, onD import * as Misskey from 'misskey-js'; import { acct as Acct } from 'misskey-js'; import debounce from 'lodash/debounce'; +import { User } from 'misskey-js/built/dts/autogen/models'; import XMessage from './messaging-room.message.vue'; import XForm from './messaging-room.form.vue'; import XPageHeader from './messaging-room.header.vue'; @@ -148,14 +149,24 @@ watch( if (!group) { return 0; } - const userIds: string[] = group.userIds; - const users = await Promise.all( - userIds.map((userId) => { - return os.api('users/show', { - userId: userId, - }); - }), - ); + const userIds: string[] = Array.from(new Set(group.userIds)); + const users: User[] = Array.from( + new Map( + ( + await os.api('users/show', { + userIds, + }) + ).map((u) => [u.id, u]), + ).values(), + ).sort((a, b) => { + // @ts-ignore + if (a.id === group?.ownerId || b.id === group?.ownerId) { + // @ts-ignore + return a.id === group?.ownerId ? -1 : 1; + } + // @ts-ignore + return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(); + }) as User[]; // @ts-ignore groupUsers = users; onlineUserCount = users.filter((_user) => _user.onlineStatus === 'online').length; diff --git a/packages/frontend/src/pages/my-groups/index.vue b/packages/frontend/src/pages/my-groups/index.vue index 8524ac26e923..a0302a0d0c69 100644 --- a/packages/frontend/src/pages/my-groups/index.vue +++ b/packages/frontend/src/pages/my-groups/index.vue @@ -16,10 +16,6 @@ {{ group.name }}
-
-
- -
@@ -33,10 +29,6 @@ {{ i18n.ts.leaveGroup }} -
-
- -
@@ -55,10 +47,6 @@ -
-
- -
@@ -73,7 +61,6 @@ import MkButton from '@/components/MkButton.vue'; import * as os from '@/os'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; -import MkAvatars from '@/components/MkAvatars.vue'; const props = withDefaults( defineProps<{ @@ -164,15 +151,15 @@ async function leave(group) { let ownedPagination = $ref({ endpoint: 'users/groups/owned' as const, - limit: 10, + noPaging: true, }); let joinedPagination = $ref({ endpoint: 'users/groups/joined' as const, - limit: 10, + noPaging: true, }); let invitationPagination = $ref({ endpoint: 'i/user-group-invites' as const, - limit: 10, + noPaging: true, }); definePageMetadata( @@ -197,7 +184,6 @@ definePageMetadata( padding: 1em; border: 1px solid var(--border); border-radius: 0.5em; - margin-bottom: 1em; ._title { display: flex; @@ -209,7 +195,7 @@ definePageMetadata( font-weight: bold; padding: 0.25em; gap: 0.25em; - border-radius: 0.5em 0.5em 0 0; + border-radius: 0.5em; ._title__name { margin: 0.5em; diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue index a5f6c11f89d5..3fb1e3c8b345 100644 --- a/packages/frontend/src/pages/settings/profile.vue +++ b/packages/frontend/src/pages/settings/profile.vue @@ -1,78 +1,91 @@ diff --git a/packages/frontend/src/pages/settings/security.vue b/packages/frontend/src/pages/settings/security.vue index 0cc2df09c5e6..5d76e394101a 100644 --- a/packages/frontend/src/pages/settings/security.vue +++ b/packages/frontend/src/pages/settings/security.vue @@ -1,37 +1,39 @@ diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue index d982a76d03eb..d5510d598569 100644 --- a/packages/frontend/src/pages/timeline.vue +++ b/packages/frontend/src/pages/timeline.vue @@ -1,24 +1,36 @@ diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue index 7efaaebf5dda..a18a9b31b7dd 100644 --- a/packages/frontend/src/pages/user/home.vue +++ b/packages/frontend/src/pages/user/home.vue @@ -1,114 +1,135 @@ diff --git a/packages/frontend/src/pages/welcome.entrance.c.vue b/packages/frontend/src/pages/welcome.entrance.c.vue index d2d07bb1f0f2..2dcc89d6c378 100644 --- a/packages/frontend/src/pages/welcome.entrance.c.vue +++ b/packages/frontend/src/pages/welcome.entrance.c.vue @@ -1,306 +1,329 @@ diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts index d39d8f8a513a..bca33a2a92ba 100644 --- a/packages/frontend/src/router.ts +++ b/packages/frontend/src/router.ts @@ -63,10 +63,11 @@ export const routes = [ component: page(() => import('./pages/settings/profile.vue')), }, { - path: '/roles', - name: 'roles', - component: page(() => import('./pages/settings/roles.vue')), - }, { + path: '/roles', + name: 'roles', + component: page(() => import('./pages/settings/roles.vue')), + }, + { path: '/privacy', name: 'privacy', component: page(() => import('./pages/settings/privacy.vue')), @@ -82,10 +83,11 @@ export const routes = [ component: page(() => import('./pages/settings/drive.vue')), }, { - path: '/drive/cleaner', - name: 'drive', - component: page(() => import('./pages/settings/drive-cleaner.vue')), - }, { + path: '/drive/cleaner', + name: 'drive', + component: page(() => import('./pages/settings/drive-cleaner.vue')), + }, + { path: '/notifications', name: 'notifications', component: page(() => import('./pages/settings/notifications.vue')), @@ -253,9 +255,10 @@ export const routes = [ component: page(() => import('./pages/about-misskey.vue')), }, { - path: '/ads', - component: page(() => import('./pages/ads.vue')), -}, { + path: '/ads', + component: page(() => import('./pages/ads.vue')), + }, + { path: '/theme-editor', component: page(() => import('./pages/theme-editor.vue')), loginRequired: true, @@ -263,15 +266,18 @@ export const routes = [ { path: '/roles/:role', component: page(() => import('./pages/role.vue')), + loginRequired: true, }, { path: '/user-tags/:tag', component: page(() => import('./pages/user-tag.vue')), + loginRequired: true, }, { path: '/explore', component: page(() => import('./pages/explore.vue')), hash: 'initialTab', + loginRequired: true, }, { path: '/search', @@ -279,9 +285,10 @@ export const routes = [ query: { q: 'query', channel: 'channel', - type: 'type', - origin: 'origin', + type: 'type', + origin: 'origin', }, + loginRequired: true, }, { path: '/authorize-follow', @@ -387,14 +394,17 @@ export const routes = [ { path: '/channels/:channelId', component: page(() => import('./pages/channel.vue')), + loginRequired: true, }, { path: '/channels', component: page(() => import('./pages/channels.vue')), + loginRequired: true, }, { path: '/custom-emojis-manager', component: page(() => import('./pages/custom-emojis-manager.vue')), + loginRequired: true, }, { path: '/registry/keys/system/:path(*)?', @@ -496,10 +506,11 @@ export const routes = [ component: page(() => import('./pages/admin/settings.vue')), }, { - path: '/moderation', - name: 'moderation', - component: page(() => import('./pages/admin/moderation.vue')), - }, { + path: '/moderation', + name: 'moderation', + component: page(() => import('./pages/admin/moderation.vue')), + }, + { path: '/email-settings', name: 'email-settings', component: page(() => import('./pages/admin/email-settings.vue')), @@ -646,6 +657,7 @@ export const routes = [ { path: '/timeline', component: page(() => import('./pages/timeline.vue')), + loginRequired: true, }, { name: 'index', diff --git a/packages/frontend/src/scripts/get-user-menu.ts b/packages/frontend/src/scripts/get-user-menu.ts index 00dc64d1168a..bb2cb0a6c4a9 100644 --- a/packages/frontend/src/scripts/get-user-menu.ts +++ b/packages/frontend/src/scripts/get-user-menu.ts @@ -117,13 +117,13 @@ export function getUserMenu(user: misskey.entities.UserDetailed, router: Router } } - async function toggleRenoteMute() { - os.apiWithDialog(user.isRenoteMuted ? 'renote-mute/delete' : 'renote-mute/create', { - userId: user.id, - }).then(() => { - user.isRenoteMuted = !user.isRenoteMuted; - }); - } + async function toggleRenoteMute() { + os.apiWithDialog(user.isRenoteMuted ? 'renote-mute/delete' : 'renote-mute/create', { + userId: user.id, + }).then(() => { + user.isRenoteMuted = !user.isRenoteMuted; + }); + } async function toggleBlock() { if (!(await getConfirmed(user.isBlocking ? i18n.ts.unblockConfirm : i18n.ts.blockConfirm))) return; @@ -192,7 +192,7 @@ export function getUserMenu(user: misskey.entities.UserDetailed, router: Router icon: 'ti ti-mail', text: i18n.ts.sendMessage, action: () => { - os.post({ specified: user, initialText: `@${user.username} ` }); + os.post({ specified: user, initialText: `@${user.username} ` }); }, }, meId !== user.id @@ -251,32 +251,49 @@ export function getUserMenu(user: misskey.entities.UserDetailed, router: Router .filter((r) => r.target === 'manual') .map((r) => ({ text: r.name, - action: async () => { - const { canceled, result: period } = await os.select({ - title: i18n.ts.period, - items: [{ - value: 'indefinitely', text: i18n.ts.indefinitely, - }, { - value: 'oneHour', text: i18n.ts.oneHour, - }, { - value: 'oneDay', text: i18n.ts.oneDay, - }, { - value: 'oneWeek', text: i18n.ts.oneWeek, - }, { - value: 'oneMonth', text: i18n.ts.oneMonth, - }], - default: 'indefinitely', - }); - if (canceled) return; + action: async () => { + const { canceled, result: period } = await os.select({ + title: i18n.ts.period, + items: [ + { + value: 'indefinitely', + text: i18n.ts.indefinitely, + }, + { + value: 'oneHour', + text: i18n.ts.oneHour, + }, + { + value: 'oneDay', + text: i18n.ts.oneDay, + }, + { + value: 'oneWeek', + text: i18n.ts.oneWeek, + }, + { + value: 'oneMonth', + text: i18n.ts.oneMonth, + }, + ], + default: 'indefinitely', + }); + if (canceled) return; - const expiresAt = period === 'indefinitely' ? null - : period === 'oneHour' ? Date.now() + (1000 * 60 * 60) - : period === 'oneDay' ? Date.now() + (1000 * 60 * 60 * 24) - : period === 'oneWeek' ? Date.now() + (1000 * 60 * 60 * 24 * 7) - : period === 'oneMonth' ? Date.now() + (1000 * 60 * 60 * 24 * 30) - : null; + const expiresAt = + period === 'indefinitely' + ? null + : period === 'oneHour' + ? Date.now() + 1000 * 60 * 60 + : period === 'oneDay' + ? Date.now() + 1000 * 60 * 60 * 24 + : period === 'oneWeek' + ? Date.now() + 1000 * 60 * 60 * 24 * 7 + : period === 'oneMonth' + ? Date.now() + 1000 * 60 * 60 * 24 * 30 + : null; - os.apiWithDialog('admin/roles/assign', { roleId: r.id, userId: user.id, expiresAt }); + os.apiWithDialog('admin/roles/assign', { roleId: r.id, userId: user.id, expiresAt }); }, })); }, @@ -292,10 +309,11 @@ export function getUserMenu(user: misskey.entities.UserDetailed, router: Router action: toggleMute, }, { - icon: user.isRenoteMuted ? 'ti ti-repeat' : 'ti ti-repeat-off', - text: user.isRenoteMuted ? i18n.ts.renoteUnmute : i18n.ts.renoteMute, - action: toggleRenoteMute, - }, { + icon: user.isRenoteMuted ? 'ti ti-repeat' : 'ti ti-repeat-off', + text: user.isRenoteMuted ? i18n.ts.renoteUnmute : i18n.ts.renoteMute, + action: toggleRenoteMute, + }, + { icon: 'ti ti-ban', text: user.isBlocking ? i18n.ts.unblock : i18n.ts.block, action: toggleBlock, @@ -324,6 +342,20 @@ export function getUserMenu(user: misskey.entities.UserDetailed, router: Router if (iAmModerator) { menu = menu.concat([ null, + { + icon: 'ti ti-ban', + text: user.isSuspended ? i18n.ts.unsuspend : i18n.ts.suspend, + action: async () => { + const { canceled } = await os.confirm({ + type: 'warning', + text: user.isSuspended ? i18n.ts.unsuspendConfirm : i18n.ts.suspendConfirm, + }); + + if (canceled) return; + + os.apiWithDialog(user.isSuspended ? 'admin/unsuspend-user' : 'admin/suspend-user', { userId: user.id }); + }, + }, { icon: 'ti ti-user-exclamation', text: i18n.ts.moderation, diff --git a/packages/frontend/src/ui/deck/antenna-column.vue b/packages/frontend/src/ui/deck/antenna-column.vue index 76a8b6e760b9..5fd6d82024bf 100644 --- a/packages/frontend/src/ui/deck/antenna-column.vue +++ b/packages/frontend/src/ui/deck/antenna-column.vue @@ -1,11 +1,23 @@ diff --git a/packages/frontend/src/ui/deck/direct-column.vue b/packages/frontend/src/ui/deck/direct-column.vue index 15b76c4d92bc..566d60e02969 100644 --- a/packages/frontend/src/ui/deck/direct-column.vue +++ b/packages/frontend/src/ui/deck/direct-column.vue @@ -1,31 +1,31 @@ diff --git a/packages/frontend/src/ui/deck/list-column.vue b/packages/frontend/src/ui/deck/list-column.vue index 352c1d246a4a..dece40e34768 100644 --- a/packages/frontend/src/ui/deck/list-column.vue +++ b/packages/frontend/src/ui/deck/list-column.vue @@ -1,15 +1,27 @@ diff --git a/packages/frontend/src/ui/deck/mentions-column.vue b/packages/frontend/src/ui/deck/mentions-column.vue index 852d7a8f7e2f..96c6c311ad87 100644 --- a/packages/frontend/src/ui/deck/mentions-column.vue +++ b/packages/frontend/src/ui/deck/mentions-column.vue @@ -1,28 +1,28 @@ diff --git a/packages/frontend/src/ui/deck/tl-column.vue b/packages/frontend/src/ui/deck/tl-column.vue index c23943d4db8a..90cd224fbd3e 100644 --- a/packages/frontend/src/ui/deck/tl-column.vue +++ b/packages/frontend/src/ui/deck/tl-column.vue @@ -1,22 +1,40 @@ diff --git a/packages/frontend/src/widgets/WidgetTimeline.vue b/packages/frontend/src/widgets/WidgetTimeline.vue index 0f6f25b0a989..01485d14adbe 100644 --- a/packages/frontend/src/widgets/WidgetTimeline.vue +++ b/packages/frontend/src/widgets/WidgetTimeline.vue @@ -1,31 +1,61 @@