diff --git a/.config/example.yml b/.config/example.yml index 28160445b..7612c063e 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -160,14 +160,14 @@ id: 'aidx' # Job concurrency per worker #deliverJobConcurrency: 128 #inboxJobConcurrency: 16 -#relashionshipJobConcurrency: 16 -# What's relashionshipJob?: +#relationshipJobConcurrency: 16 +# What's relationshipJob?: # Follow, unfollow, block and unblock(ings) while following-imports, etc. or account migrations. # Job rate limiter #deliverJobPerSec: 128 #inboxJobPerSec: 32 -#relashionshipJobPerSec: 64 +#relationshipJobPerSec: 64 # Job attempts #deliverJobMaxAttempts: 12 diff --git a/CHANGELOG.md b/CHANGELOG.md index 8955e7e27..e78cde607 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,11 +45,19 @@ - Enhance: ノート作成画面のファイル添付メニューから直接ファイルを削除できるように - Enhance: MFMの属性でオートコンプリートが使用できるように #12735 - Enhance: 絵文字編集ダイアログをモーダルではなくウィンドウで表示するように +- Enhance: リモートのユーザーはメニューから直接リモートで表示できるように - Fix: ネイティブモードの絵文字がモノクロにならないように - Fix: v2023.12.0で追加された「モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能」が管理画面上で正しく表示されていない問題を修正 - Fix: AiScriptの`readline`関数が不正な値を返すことがある問題のv2023.12.0時点での修正がPlay以外に適用されていないのを修正 - Fix: v2023.12.1で追加された`$[clickable ...]`および`onClickEv`が正しく機能していないのを修正 +- Fix: Renoteのキーボードショートカットが機能していなかった問題を修正 +- Fix: 投稿フォームでアンケートの日時指定をした状態で再読み込みをすると期日が復元されない問題を修正 +- Fix: アンケートを設定したノートを「削除して編集」をするとアンケートの期日が引き継がれず、リセットされてしまう問題を修正 +- Fix: デッキのプロファイル作成時に名前を空にできる問題を修正 +- Fix: テーマ作成時に名称が空欄でも作成できてしまう問題を修正 +- Fix: プラグインで`Plugin:register_note_post_interruptor`を使用すると、ノートが投稿できなくなる問題を修正 - Enhance: ページ遷移時にPlayerを閉じるように +- Fix: iOSで大きな画像を変換してアップロードできない問題を修正 ### Server - Enhance: 連合先のレートリミットに引っかかった際にリトライするようになりました @@ -62,6 +70,7 @@ - Fix: `notes/create`で、`text`が空白文字のみで構成されていてかつリノート、ファイルまたは投票を含んでいるリクエストに対するレスポンスの`text`が`""`から`null`になるように変更 - Fix: ipv4とipv6の両方が利用可能な環境でallowedPrivateNetworksが設定されていた場合プライベートipの検証ができていなかった問題を修正 - Fix: properly handle cc followers +- Fix: ジョブに関する設定の名前を修正 relashionshipJobPerSec -> relationshipJobPerSec ### Service Worker - Enhance: オフライン表示のデザインを改善・多言語対応 diff --git a/cypress/e2e/router.cy.js b/cypress/e2e/router.cy.js new file mode 100644 index 000000000..81f497b5b --- /dev/null +++ b/cypress/e2e/router.cy.js @@ -0,0 +1,30 @@ +describe('Router transition', () => { + describe('Redirect', () => { + // サーバの初期化。ルートのテストに関しては各describeごとに1度だけ実行で十分だと思う(使いまわした方が早い) + before(() => { + cy.resetState(); + + // インスタンス初期セットアップ + cy.registerUser('admin', 'pass', true); + + // ユーザー作成 + cy.registerUser('alice', 'alice1234'); + + cy.login('alice', 'alice1234'); + + // アカウント初期設定ウィザード + // 表示に時間がかかるのでデフォルト秒数だとタイムアウトする + cy.get('[data-cy-user-setup] [data-cy-modal-window-close]', { timeout: 12000 }).click(); + cy.wait(500); + cy.get('[data-cy-modal-dialog-ok]').click(); + }); + + it('redirect to user profile', () => { + // テストのためだけに用意されたリダイレクト用ルートに飛ぶ + cy.visit('/redirect-test'); + + // プロフィールページのURLであることを確認する + cy.url().should('include', '/@alice') + }); + }); +}); diff --git a/locales/index.d.ts b/locales/index.d.ts index b494c844d..4117b8156 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -5010,7 +5010,7 @@ export interface Locale extends ILocale { */ "readConfirmText": ParameterizedString<"title">; /** - * 特に新規ユーザーのUXを損ねる可能性が高いため、ストック情報ではなくフロー情報の掲示にお知らせを使用することを推奨します。 + * 特に新規ユーザーのUXを損ねる可能性が高いため、常時掲示するための情報ではなく、即時性が求められる情報の掲示のためにお知らせを使用することを推奨します。 */ "shouldNotBeUsedToPresentPermanentInfo": string; /** diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index c62f0bfb9..48cc48c21 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1255,7 +1255,7 @@ _announcement: tooManyActiveAnnouncementDescription: "アクティブなお知らせが多いため、UXが低下する可能性があります。終了したお知らせはアーカイブすることを検討してください。" readConfirmTitle: "既読にしますか?" readConfirmText: "「{title}」の内容を読み、既読にします。" - shouldNotBeUsedToPresentPermanentInfo: "特に新規ユーザーのUXを損ねる可能性が高いため、ストック情報ではなくフロー情報の掲示にお知らせを使用することを推奨します。" + shouldNotBeUsedToPresentPermanentInfo: "特に新規ユーザーのUXを損ねる可能性が高いため、常時掲示するための情報ではなく、即時性が求められる情報の掲示のためにお知らせを使用することを推奨します。" dialogAnnouncementUxWarn: "ダイアログ形式のお知らせが同時に2つ以上ある場合、UXに悪影響を及ぼす可能性が非常に高いため、使用は慎重に行うことを推奨します。" silence: "非通知" silenceDescription: "オンにすると、このお知らせは通知されず、既読にする必要もなくなります。" diff --git a/packages/backend/package.json b/packages/backend/package.json index 7ffd2e6fe..89bf23d51 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -35,17 +35,17 @@ }, "optionalDependencies": { "@swc/core-android-arm64": "1.3.11", - "@swc/core-darwin-arm64": "1.3.105", - "@swc/core-darwin-x64": "1.3.105", + "@swc/core-darwin-arm64": "1.3.107", + "@swc/core-darwin-x64": "1.3.107", "@swc/core-freebsd-x64": "1.3.11", - "@swc/core-linux-arm-gnueabihf": "1.3.105", - "@swc/core-linux-arm64-gnu": "1.3.105", - "@swc/core-linux-arm64-musl": "1.3.105", - "@swc/core-linux-x64-gnu": "1.3.105", - "@swc/core-linux-x64-musl": "1.3.105", - "@swc/core-win32-arm64-msvc": "1.3.105", - "@swc/core-win32-ia32-msvc": "1.3.105", - "@swc/core-win32-x64-msvc": "1.3.105", + "@swc/core-linux-arm-gnueabihf": "1.3.107", + "@swc/core-linux-arm64-gnu": "1.3.107", + "@swc/core-linux-arm64-musl": "1.3.107", + "@swc/core-linux-x64-gnu": "1.3.107", + "@swc/core-linux-x64-musl": "1.3.107", + "@swc/core-win32-arm64-msvc": "1.3.107", + "@swc/core-win32-ia32-msvc": "1.3.107", + "@swc/core-win32-x64-msvc": "1.3.107", "@tensorflow/tfjs": "4.4.0", "@tensorflow/tfjs-node": "4.4.0", "bufferutil": "4.0.8", @@ -67,9 +67,9 @@ "dependencies": { "@aws-sdk/client-s3": "3.412.0", "@aws-sdk/lib-storage": "3.412.0", - "@bull-board/api": "5.13.0", - "@bull-board/fastify": "5.13.0", - "@bull-board/ui": "5.13.0", + "@bull-board/api": "5.14.0", + "@bull-board/fastify": "5.14.0", + "@bull-board/ui": "5.14.0", "@discordapp/twemoji": "15.0.2", "@fastify/accepts": "4.3.0", "@fastify/cookie": "9.3.1", @@ -85,11 +85,11 @@ "@nestjs/core": "10.3.1", "@nestjs/testing": "10.3.1", "@peertube/http-signature": "1.7.0", - "@simplewebauthn/server": "9.0.0", + "@simplewebauthn/server": "9.0.1", "@sinonjs/fake-timers": "11.2.2", "@smithy/node-http-handler": "2.3.1", "@swc/cli": "0.1.65", - "@swc/core": "1.3.105", + "@swc/core": "1.3.107", "@twemoji/parser": "15.0.0", "accepts": "1.3.8", "ajv": "8.12.0", @@ -109,13 +109,13 @@ "content-disposition": "0.5.4", "date-fns": "2.30.0", "deep-email-validator": "0.1.21", - "fastify": "4.25.2", + "fastify": "4.26.0", "fastify-raw-body": "4.3.0", "feed": "4.2.2", "file-type": "19.0.0", "fluent-ffmpeg": "2.1.2", "form-data": "4.0.0", - "got": "14.0.0", + "got": "14.1.0", "happy-dom": "10.0.3", "hpagent": "1.2.0", "http-link-header": "1.1.1", @@ -149,7 +149,7 @@ "pg": "8.11.3", "pino": "8.17.2", "pino-pretty": "10.3.1", - "pkce-challenge": "4.0.1", + "pkce-challenge": "4.1.0", "probe-image-size": "7.2.3", "promise-limit": "2.7.0", "pug": "3.0.2", @@ -175,7 +175,7 @@ "tmp": "0.2.1", "tsc-alias": "1.8.8", "tsconfig-paths": "4.2.0", - "typeorm": "0.3.19", + "typeorm": "0.3.20", "typescript": "5.3.3", "ulid": "2.3.0", "vary": "1.1.2", @@ -187,7 +187,7 @@ "@jest/globals": "29.7.0", "@misskey-dev/eslint-plugin": "1.0.0", "@nestjs/platform-express": "10.3.1", - "@simplewebauthn/types": "9.0.0", + "@simplewebauthn/types": "9.0.1", "@swc/jest": "0.2.31", "@types/accepts": "1.3.7", "@types/archiver": "6.0.2", @@ -204,12 +204,12 @@ "@types/jsrsasign": "10.5.12", "@types/mime-types": "2.1.4", "@types/ms": "0.7.34", - "@types/node": "20.11.6", + "@types/node": "20.11.10", "@types/nodemailer": "6.4.14", "@types/oauth": "0.9.4", "@types/oauth2orize": "1.11.3", "@types/oauth2orize-pkce": "0.1.2", - "@types/pg": "8.10.9", + "@types/pg": "8.11.0", "@types/pug": "2.0.10", "@types/punycode": "2.1.3", "@types/qrcode": "1.5.5", diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 9753b3c6b..add5d59d9 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -88,10 +88,10 @@ type Source = { bullmqWorkerOptions?: Partial; deliverJobConcurrency?: number; inboxJobConcurrency?: number; - relashionshipJobConcurrency?: number; + relationshipJobConcurrency?: number; deliverJobPerSec?: number; inboxJobPerSec?: number; - relashionshipJobPerSec?: number; + relationshipJobPerSec?: number; deliverJobMaxAttempts?: number; inboxJobMaxAttempts?: number; @@ -152,10 +152,10 @@ export type Config = { bullmqWorkerOptions: Partial; deliverJobConcurrency: number | undefined; inboxJobConcurrency: number | undefined; - relashionshipJobConcurrency: number | undefined; + relationshipJobConcurrency: number | undefined; deliverJobPerSec: number | undefined; inboxJobPerSec: number | undefined; - relashionshipJobPerSec: number | undefined; + relationshipJobPerSec: number | undefined; deliverJobMaxAttempts: number | undefined; inboxJobMaxAttempts: number | undefined; proxyRemoteFiles: boolean | undefined; @@ -278,10 +278,10 @@ export function loadConfig(): Config { bullmqWorkerOptions: config.bullmqWorkerOptions ?? {}, deliverJobConcurrency: config.deliverJobConcurrency, inboxJobConcurrency: config.inboxJobConcurrency, - relashionshipJobConcurrency: config.relashionshipJobConcurrency, + relationshipJobConcurrency: config.relationshipJobConcurrency, deliverJobPerSec: config.deliverJobPerSec, inboxJobPerSec: config.inboxJobPerSec, - relashionshipJobPerSec: config.relashionshipJobPerSec, + relationshipJobPerSec: config.relationshipJobPerSec, deliverJobMaxAttempts: config.deliverJobMaxAttempts, inboxJobMaxAttempts: config.inboxJobMaxAttempts, proxyRemoteFiles: config.proxyRemoteFiles, diff --git a/packages/backend/src/core/AccountMoveService.ts b/packages/backend/src/core/AccountMoveService.ts index d92bc23ea..ebb422db2 100644 --- a/packages/backend/src/core/AccountMoveService.ts +++ b/packages/backend/src/core/AccountMoveService.ts @@ -97,7 +97,7 @@ export class AccountMoveService { await this.apDeliverManagerService.deliverToFollowers(src, moveAct); // Publish meUpdated event - const iObj = await this.userEntityService.pack(src.id, src, { detail: true, includeSecrets: true }); + const iObj = await this.userEntityService.pack(src.id, src, { schema: 'MeDetailed', includeSecrets: true }); this.globalEventService.publishMainStream(src.id, 'meUpdated', iObj); // Unfollow after 24 hours diff --git a/packages/backend/src/core/AnnouncementService.ts b/packages/backend/src/core/AnnouncementService.ts index 45d6a5fcd..473576c60 100644 --- a/packages/backend/src/core/AnnouncementService.ts +++ b/packages/backend/src/core/AnnouncementService.ts @@ -169,7 +169,7 @@ export class AnnouncementService { id: In(announcements.map(a => a.userId).filter(id => id != null)), }); const packedUsers = await this.userEntityService.packMany(users, moderator, { - detail: false, + schema: 'UserLite' }); return announcements.map(announcement => ({ diff --git a/packages/backend/src/core/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts index 5b4c8cb44..45ee97384 100644 --- a/packages/backend/src/core/GlobalEventService.ts +++ b/packages/backend/src/core/GlobalEventService.ts @@ -54,15 +54,15 @@ export interface MainEventTypes { reply: Packed<'Note'>; renote: Packed<'Note'>; follow: Packed<'UserDetailedNotMe'>; - followed: Packed<'User'>; - unfollow: Packed<'User'>; - meUpdated: Packed<'User'>; + followed: Packed<'UserLite'>; + unfollow: Packed<'UserDetailedNotMe'>; + meUpdated: Packed<'MeDetailed'>; pageEvent: { pageId: MiPage['id']; event: string; var: any; userId: MiUser['id']; - user: Packed<'User'>; + user: Packed<'UserDetailed'>; }; urlUploadFinished: { marker?: string | null; @@ -92,7 +92,7 @@ export interface MainEventTypes { }; driveFileCreated: Packed<'DriveFile'>; readAntenna: MiAntenna; - receiveFollowRequest: Packed<'User'>; + receiveFollowRequest: Packed<'UserLite'>; announcementCreated: { announcement: Packed<'Announcement'>; }; @@ -140,8 +140,8 @@ type NoteStreamEventTypes = { }; export interface UserListEventTypes { - userAdded: Packed<'User'>; - userRemoved: Packed<'User'>; + userAdded: Packed<'UserLite'>; + userRemoved: Packed<'UserLite'>; } export interface AntennaEventTypes { diff --git a/packages/backend/src/core/UserBlockingService.ts b/packages/backend/src/core/UserBlockingService.ts index 39b19325c..c26784990 100644 --- a/packages/backend/src/core/UserBlockingService.ts +++ b/packages/backend/src/core/UserBlockingService.ts @@ -109,13 +109,13 @@ export class UserBlockingService implements OnModuleInit { if (this.userEntityService.isLocalUser(followee)) { this.userEntityService.pack(followee, followee, { - detail: true, + schema: 'MeDetailed', }).then(packed => this.globalEventService.publishMainStream(followee.id, 'meUpdated', packed)); } if (this.userEntityService.isLocalUser(follower) && !silent) { this.userEntityService.pack(followee, follower, { - detail: true, + schema: 'UserDetailedNotMe', }).then(async packed => { this.globalEventService.publishMainStream(follower.id, 'unfollow', packed); diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts index d600ffb9d..769d6a444 100644 --- a/packages/backend/src/core/UserFollowingService.ts +++ b/packages/backend/src/core/UserFollowingService.ts @@ -13,7 +13,6 @@ import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { IdService } from '@/core/IdService.js'; import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; -import type { Packed } from '@/misc/json-schema.js'; import InstanceChart from '@/core/chart/charts/instance.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { WebhookService } from '@/core/WebhookService.js'; @@ -293,9 +292,9 @@ export class UserFollowingService implements OnModuleInit { if (this.userEntityService.isLocalUser(follower) && !silent) { // Publish follow event this.userEntityService.pack(followee.id, follower, { - detail: true, + schema: 'UserDetailedNotMe', }).then(async packed => { - this.globalEventService.publishMainStream(follower.id, 'follow', packed as Packed<'UserDetailedNotMe'>); + this.globalEventService.publishMainStream(follower.id, 'follow', packed); const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('follow')); for (const webhook of webhooks) { @@ -360,7 +359,7 @@ export class UserFollowingService implements OnModuleInit { if (!silent && this.userEntityService.isLocalUser(follower)) { // Publish unfollow event this.userEntityService.pack(followee.id, follower, { - detail: true, + schema: 'UserDetailedNotMe', }).then(async packed => { this.globalEventService.publishMainStream(follower.id, 'unfollow', packed); @@ -500,7 +499,7 @@ export class UserFollowingService implements OnModuleInit { this.userEntityService.pack(follower.id, followee).then(packed => this.globalEventService.publishMainStream(followee.id, 'receiveFollowRequest', packed)); this.userEntityService.pack(followee.id, followee, { - detail: true, + schema: 'MeDetailed', }).then(packed => this.globalEventService.publishMainStream(followee.id, 'meUpdated', packed)); // 通知を作成 @@ -548,7 +547,7 @@ export class UserFollowingService implements OnModuleInit { }); this.userEntityService.pack(followee.id, followee, { - detail: true, + schema: 'MeDetailed', }).then(packed => this.globalEventService.publishMainStream(followee.id, 'meUpdated', packed)); } @@ -576,7 +575,7 @@ export class UserFollowingService implements OnModuleInit { } this.userEntityService.pack(followee.id, followee, { - detail: true, + schema: 'MeDetailed', }).then(packed => this.globalEventService.publishMainStream(followee.id, 'meUpdated', packed)); } @@ -696,7 +695,7 @@ export class UserFollowingService implements OnModuleInit { @bindThis private async publishUnfollow(followee: Both, follower: Local): Promise { const packedFollowee = await this.userEntityService.pack(followee.id, follower, { - detail: true, + schema: 'UserDetailedNotMe', }); this.globalEventService.publishMainStream(follower.id, 'unfollow', packedFollowee); diff --git a/packages/backend/src/core/chart/core.ts b/packages/backend/src/core/chart/core.ts index b7f1912fd..7c3289268 100644 --- a/packages/backend/src/core/chart/core.ts +++ b/packages/backend/src/core/chart/core.ts @@ -94,6 +94,29 @@ type ToJsonSchema = { }; export function getJsonSchema(schema: S): ToJsonSchema>> { + const unflatten = (str: string, parent: Record) => { + const keys = str.split('.'); + const key = keys.shift(); + const nextKey = keys[0]; + + if (key == null) return; + + if (parent.properties[key] == null) { + parent.properties[key] = nextKey ? { + type: 'object', + properties: {}, + required: [], + } : { + type: 'array', + items: { + type: 'number', + }, + }; + } + + if (nextKey) unflatten(keys.join('.'), parent.properties[key] as Record); + }; + const jsonSchema = { type: 'object', properties: {} as Record, @@ -101,10 +124,7 @@ export function getJsonSchema(schema: S): ToJsonSchema>>; diff --git a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts index fe5e8e090..3aa25d367 100644 --- a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts +++ b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts @@ -41,13 +41,13 @@ export class AbuseUserReportEntityService { targetUserId: report.targetUserId, assigneeId: report.assigneeId, reporter: this.userEntityService.pack(report.reporter ?? report.reporterId, me, { - detail: true, + schema: 'UserDetailed', }), targetUser: this.userEntityService.pack(report.targetUser ?? report.targetUserId, me, { - detail: true, + schema: 'UserDetailed', }), assignee: report.assigneeId ? this.userEntityService.pack(report.assignee ?? report.assigneeId, me, { - detail: true, + schema: 'UserDetailed', }) : null, forwarded: report.forwarded, category: report.category, diff --git a/packages/backend/src/core/entities/BlockingEntityService.ts b/packages/backend/src/core/entities/BlockingEntityService.ts index f5c49bb5b..4f8225700 100644 --- a/packages/backend/src/core/entities/BlockingEntityService.ts +++ b/packages/backend/src/core/entities/BlockingEntityService.ts @@ -37,7 +37,7 @@ export class BlockingEntityService { createdAt: this.idService.parse(blocking.id).date.toISOString(), blockeeId: blocking.blockeeId, blockee: this.userEntityService.pack(blocking.blockeeId, me, { - detail: true, + schema: 'UserDetailedNotMe', }), }); } diff --git a/packages/backend/src/core/entities/FlashEntityService.ts b/packages/backend/src/core/entities/FlashEntityService.ts index 5798201d6..f6b76d8a8 100644 --- a/packages/backend/src/core/entities/FlashEntityService.ts +++ b/packages/backend/src/core/entities/FlashEntityService.ts @@ -41,7 +41,7 @@ export class FlashEntityService { createdAt: this.idService.parse(flash.id).date.toISOString(), updatedAt: flash.updatedAt.toISOString(), userId: flash.userId, - user: this.userEntityService.pack(flash.user ?? flash.userId, me), // { detail: true } すると無限ループするので注意 + user: this.userEntityService.pack(flash.user ?? flash.userId, me), // { schema: 'UserDetailed' } すると無限ループするので注意 title: flash.title, summary: flash.summary, script: flash.script, diff --git a/packages/backend/src/core/entities/FollowingEntityService.ts b/packages/backend/src/core/entities/FollowingEntityService.ts index ae3f3b4f1..7bc584063 100644 --- a/packages/backend/src/core/entities/FollowingEntityService.ts +++ b/packages/backend/src/core/entities/FollowingEntityService.ts @@ -88,10 +88,10 @@ export class FollowingEntityService { followeeId: following.followeeId, followerId: following.followerId, followee: opts.populateFollowee ? this.userEntityService.pack(following.followee ?? following.followeeId, me, { - detail: true, + schema: 'UserDetailedNotMe', }) : undefined, follower: opts.populateFollower ? this.userEntityService.pack(following.follower ?? following.followerId, me, { - detail: true, + schema: 'UserDetailedNotMe', }) : undefined, }); } diff --git a/packages/backend/src/core/entities/ModerationLogEntityService.ts b/packages/backend/src/core/entities/ModerationLogEntityService.ts index 8ca80d99e..4d1451362 100644 --- a/packages/backend/src/core/entities/ModerationLogEntityService.ts +++ b/packages/backend/src/core/entities/ModerationLogEntityService.ts @@ -39,7 +39,7 @@ export class ModerationLogEntityService { info: log.info, userId: log.userId, user: this.userEntityService.pack(log.user ?? log.userId, me, { - detail: true, + schema: 'UserDetailed', }), }); } diff --git a/packages/backend/src/core/entities/MutingEntityService.ts b/packages/backend/src/core/entities/MutingEntityService.ts index 88660f424..190c3b5d4 100644 --- a/packages/backend/src/core/entities/MutingEntityService.ts +++ b/packages/backend/src/core/entities/MutingEntityService.ts @@ -38,7 +38,7 @@ export class MutingEntityService { expiresAt: muting.expiresAt ? muting.expiresAt.toISOString() : null, muteeId: muting.muteeId, mutee: this.userEntityService.pack(muting.muteeId, me, { - detail: true, + schema: 'UserDetailedNotMe', }), }); } diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index 2083f2d46..42a267b21 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -164,7 +164,7 @@ export class NoteEntityService implements OnModuleInit { return { multiple: poll.multiple, - expiresAt: poll.expiresAt, + expiresAt: poll.expiresAt?.toISOString() ?? null, choices, }; } @@ -329,9 +329,7 @@ export class NoteEntityService implements OnModuleInit { id: note.id, createdAt: this.idService.parse(note.id).date.toISOString(), userId: note.userId, - user: this.userEntityService.pack(note.user ?? note.userId, me, { - detail: false, - }), + user: this.userEntityService.pack(note.user ?? note.userId, me), text: text, cw: note.cw, visibility: note.visibility, diff --git a/packages/backend/src/core/entities/NotificationEntityService.ts b/packages/backend/src/core/entities/NotificationEntityService.ts index 26fda6e05..009c3331f 100644 --- a/packages/backend/src/core/entities/NotificationEntityService.ts +++ b/packages/backend/src/core/entities/NotificationEntityService.ts @@ -62,7 +62,7 @@ export class NotificationEntityService implements OnModuleInit { }, hint?: { packedNotes: Map>; - packedUsers: Map>; + packedUsers: Map>; }, ): Promise> { const notification = src; @@ -76,9 +76,7 @@ export class NotificationEntityService implements OnModuleInit { const userIfNeed = 'notifierId' in notification ? ( hint?.packedUsers != null ? hint.packedUsers.get(notification.notifierId) - : this.userEntityService.pack(notification.notifierId, { id: meId }, { - detail: false, - }) + : this.userEntityService.pack(notification.notifierId, { id: meId }) ) : undefined; const role = notification.type === 'roleAssigned' ? await this.roleEntityService.pack(notification.roleId, { id: meId }) : undefined; @@ -131,9 +129,7 @@ export class NotificationEntityService implements OnModuleInit { const users = userIds.length > 0 ? await this.usersRepository.find({ where: { id: In(userIds) }, }) : []; - const packedUsersArray = await this.userEntityService.packMany(users, { id: meId }, { - detail: false, - }); + const packedUsersArray = await this.userEntityService.packMany(users, { id: meId }); const packedUsers = new Map(packedUsersArray.map(p => [p.id, p])); // 既に解決されたフォローリクエストの通知を除外 @@ -160,7 +156,7 @@ export class NotificationEntityService implements OnModuleInit { }, hint?: { packedNotes: Map>; - packedUsers: Map>; + packedUsers: Map>; }, ): Promise> { const notification = src; @@ -174,18 +170,14 @@ export class NotificationEntityService implements OnModuleInit { const userIfNeed = 'notifierId' in notification ? ( hint?.packedUsers != null ? hint.packedUsers.get(notification.notifierId) - : this.userEntityService.pack(notification.notifierId, { id: meId }, { - detail: false, - }) + : this.userEntityService.pack(notification.notifierId, { id: meId }) ) : undefined; if (notification.type === 'reaction:grouped') { const reactions = await Promise.allSettled(notification.reactions.map(async reaction => { const user = hint?.packedUsers != null ? hint.packedUsers.get(reaction.userId)! - : await this.userEntityService.pack(reaction.userId, { id: meId }, { - detail: false, - }); + : await this.userEntityService.pack(reaction.userId, { id: meId }); return { user, reaction: reaction.reaction, @@ -206,9 +198,7 @@ export class NotificationEntityService implements OnModuleInit { return packedUser; } - return this.userEntityService.pack(userId, { id: meId }, { - detail: false, - }); + return this.userEntityService.pack(userId, { id: meId }); })); return await awaitAll({ id: notification.id, @@ -276,9 +266,7 @@ export class NotificationEntityService implements OnModuleInit { const users = userIds.length > 0 ? await this.usersRepository.find({ where: { id: In(userIds) }, }) : []; - const packedUsersArray = await this.userEntityService.packMany(users, { id: meId }, { - detail: false, - }); + const packedUsersArray = await this.userEntityService.packMany(users, { id: meId }); const packedUsers = new Map(packedUsersArray.map(p => [p.id, p])); // 既に解決されたフォローリクエストの通知を除外 diff --git a/packages/backend/src/core/entities/PageEntityService.ts b/packages/backend/src/core/entities/PageEntityService.ts index d387d068c..8b62dbde2 100644 --- a/packages/backend/src/core/entities/PageEntityService.ts +++ b/packages/backend/src/core/entities/PageEntityService.ts @@ -89,7 +89,7 @@ export class PageEntityService { createdAt: this.idService.parse(page.id).date.toISOString(), updatedAt: page.updatedAt.toISOString(), userId: page.userId, - user: this.userEntityService.pack(page.user ?? page.userId, me), // { detail: true } すると無限ループするので注意 + user: this.userEntityService.pack(page.user ?? page.userId, me), // { schema: 'UserDetailed' } すると無限ループするので注意 content: page.content, variables: page.variables, title: page.title, diff --git a/packages/backend/src/core/entities/RenoteMutingEntityService.ts b/packages/backend/src/core/entities/RenoteMutingEntityService.ts index b7440a68f..27bb02b34 100644 --- a/packages/backend/src/core/entities/RenoteMutingEntityService.ts +++ b/packages/backend/src/core/entities/RenoteMutingEntityService.ts @@ -37,7 +37,7 @@ export class RenoteMutingEntityService { createdAt: this.idService.parse(muting.id).date.toISOString(), muteeId: muting.muteeId, mutee: this.userEntityService.pack(muting.muteeId, me, { - detail: true, + schema: 'UserDetailedNotMe', }), }); } diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index 75029333d..bc75eebd6 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -31,14 +31,6 @@ import type { NoteEntityService } from './NoteEntityService.js'; import type { DriveFileEntityService } from './DriveFileEntityService.js'; import type { PageEntityService } from './PageEntityService.js'; -type IsUserDetailed = Detailed extends true ? Packed<'UserDetailed'> : Packed<'UserLite'>; -type IsMeAndIsUserDetailed = - Detailed extends true ? - ExpectsMe extends true ? Packed<'MeDetailed'> : - ExpectsMe extends false ? Packed<'UserDetailedNotMe'> : - Packed<'UserDetailed'> : - Packed<'UserLite'>; - const Ajv = _Ajv.default; const ajv = new Ajv(); @@ -304,34 +296,35 @@ export class UserEntityService implements OnModuleInit { return `${this.config.url}/users/${userId}`; } - public async pack( + public async pack( src: MiUser['id'] | MiUser, me: { id: MiUser['id'] } | null | undefined, options?: { - detail?: D, + schema?: S, includeSecrets?: boolean, userProfile?: MiUserProfile, }, - ): Promise> { + ): Promise> { const opts = Object.assign({ - detail: false, + schema: 'UserLite', includeSecrets: false, }, options); const user = typeof src === 'object' ? src : await this.usersRepository.findOneByOrFail({ id: src }); + const isDetailed = opts.schema !== 'UserLite'; const meId = me ? me.id : null; const isMe = meId === user.id; const iAmModerator = me ? await this.roleService.isModerator(me as MiUser) : false; if (user.isSuspended && !iAmModerator) throw new IdentifiableError('8ca4f428-b32e-4f83-ac43-406ed7cd0452', 'This user is suspended.'); - const relation = meId && !isMe && opts.detail ? await this.getRelation(meId, user.id) : null; - const pins = opts.detail ? await this.userNotePiningsRepository.createQueryBuilder('pin') + const relation = meId && !isMe && isDetailed ? await this.getRelation(meId, user.id) : null; + const pins = isDetailed ? await this.userNotePiningsRepository.createQueryBuilder('pin') .where('pin.userId = :userId', { userId: user.id }) .innerJoinAndSelect('pin.note', 'note') .orderBy('pin.id', 'DESC') .getMany() : []; - const profile = opts.detail ? (opts.userProfile ?? await this.userProfilesRepository.findOneByOrFail({ userId: user.id })) : null; + const profile = isDetailed ? (opts.userProfile ?? await this.userProfilesRepository.findOneByOrFail({ userId: user.id })) : null; const followingCount = profile == null ? null : (profile.followingVisibility === 'public') || isMe ? user.followingCount : @@ -343,12 +336,12 @@ export class UserEntityService implements OnModuleInit { (profile.followersVisibility === 'followers') && (relation && relation.isFollowing) ? user.followersCount : null; - const policies = opts.detail ? await this.roleService.getUserPolicies(user.id) : null; - const isModerator = (isMe || iAmModerator) && opts.detail ? this.roleService.isModerator(user) : null; - const isAdmin = (isMe || iAmModerator) && opts.detail ? this.roleService.isAdministrator(user) : null; - const unreadAnnouncements = isMe && opts.detail ? await this.announcementService.getUnreadAnnouncements(user) : null; + const policies = isDetailed ? await this.roleService.getUserPolicies(user.id) : null; + const isModerator = (isMe || iAmModerator) && isDetailed ? this.roleService.isModerator(user) : null; + const isAdmin = (isMe || iAmModerator) && isDetailed ? this.roleService.isAdministrator(user) : null; + const unreadAnnouncements = isMe && isDetailed ? await this.announcementService.getUnreadAnnouncements(user) : null; - const notificationsInfo = isMe && opts.detail ? await this.getNotificationsInfo(user.id) : null; + const notificationsInfo = isMe && isDetailed ? await this.getNotificationsInfo(user.id) : null; const packed = { id: user.id, @@ -384,7 +377,7 @@ export class UserEntityService implements OnModuleInit { displayOrder: r.displayOrder, }))) : undefined, - ...(opts.detail ? { + ...(isDetailed ? { url: profile!.url, uri: user.uri, movedTo: user.movedToUri ? this.apPersonService.resolvePerson(user.movedToUri).then(user => user.id).catch(() => null) : null, @@ -447,7 +440,7 @@ export class UserEntityService implements OnModuleInit { moderationNote: iAmModerator ? (profile!.moderationNote ?? '') : undefined, } : {}), - ...(opts.detail && (isMe || iAmModerator) ? { + ...(isDetailed && (isMe || iAmModerator) ? { avatarId: user.avatarId, bannerId: user.bannerId, isModerator: isModerator, @@ -518,21 +511,21 @@ export class UserEntityService implements OnModuleInit { notify: relation.following?.notify ?? 'none', withReplies: relation.following?.withReplies ?? false, } : {}), - } as Promiseable> as Promiseable>; + } as Promiseable>; return await awaitAll(packed); } - public async packMany( + public async packMany( users: (MiUser['id'] | MiUser)[], me: { id: MiUser['id'] } | null | undefined, options?: { - detail?: D, + schema?: S, includeSecrets?: boolean, }, - ): Promise[]> { + ): Promise[]> { return (await Promise.allSettled(users.map(u => this.pack(u, me, options)))) .filter(result => result.status === 'fulfilled') - .map(result => (result as PromiseFulfilledResult>).value); + .map(result => (result as PromiseFulfilledResult>).value); } } diff --git a/packages/backend/src/misc/clone.ts b/packages/backend/src/misc/clone.ts index 9d20deac3..52e6c825f 100644 --- a/packages/backend/src/misc/clone.ts +++ b/packages/backend/src/misc/clone.ts @@ -6,7 +6,7 @@ // structredCloneが遅いため // SEE: http://var.blog.jp/archives/86038606.html -type Cloneable = string | number | boolean | null | { [key: string]: Cloneable } | Cloneable[]; +type Cloneable = string | number | boolean | null | undefined | { [key: string]: Cloneable } | Cloneable[]; export function deepClone(x: T): T { if (typeof x === 'object') { @@ -14,7 +14,7 @@ export function deepClone(x: T): T { if (Array.isArray(x)) return x.map(deepClone) as T; const obj = {} as Record; for (const [k, v] of Object.entries(x)) { - obj[k] = deepClone(v); + obj[k] = v === undefined ? undefined : deepClone(v); } return obj as T; } else { diff --git a/packages/backend/src/misc/json-schema.ts b/packages/backend/src/misc/json-schema.ts index 059e1c0e0..e4bd72393 100644 --- a/packages/backend/src/misc/json-schema.ts +++ b/packages/backend/src/misc/json-schema.ts @@ -32,14 +32,14 @@ import { packedNoteFavoriteSchema } from '@/models/json-schema/note-favorite.js' import { packedNoteReactionSchema } from '@/models/json-schema/note-reaction.js'; import { packedNoteSchema } from '@/models/json-schema/note.js'; import { packedNotificationSchema } from '@/models/json-schema/notification.js'; -import { packedPageLikeSchema, packedPageSchema } from '@/models/json-schema/page.js'; +import { packedPageLikeSchema, packedPageBlockSchema, packedPageSchema } from '@/models/json-schema/page.js'; import { packedQueueCountSchema } from '@/models/json-schema/queue.js'; import { packedEmojiDetailedSchema, packedEmojiSimpleSchema } from '@/models/json-schema/emoji.js'; import { packedRenoteMutingSchema } from '@/models/json-schema/renote-muting.js'; import { packedUserListMembershipSchema, packedUserListSchema } from '@/models/json-schema/user-list.js'; import { packedAnnouncementSchema } from '@/models/json-schema/announcement.js'; import { packedSigninSchema } from '@/models/json-schema/signin.js'; -import { packedRoleLiteSchema, packedRoleSchema } from '@/models/json-schema/role.js'; +import { packedRoleLiteSchema, packedRoleSchema, packedRolePoliciesSchema } from '@/models/json-schema/role.js'; import { packedAdSchema } from '@/models/json-schema/ad.js'; import { packedReversiGameLiteSchema, packedReversiGameDetailedSchema } from '@/models/json-schema/reversi-game.js'; @@ -71,6 +71,7 @@ export const refs = { Hashtag: packedHashtagSchema, InviteCode: packedInviteCodeSchema, Page: packedPageSchema, + PageBlock: packedPageBlockSchema, PageLike: packedPageLikeSchema, Channel: packedChannelSchema, QueueCount: packedQueueCountSchema, @@ -87,6 +88,7 @@ export const refs = { Signin: packedSigninSchema, RoleLite: packedRoleLiteSchema, Role: packedRoleSchema, + RolePolicies: packedRolePoliciesSchema, ReversiGameLite: packedReversiGameLiteSchema, ReversiGameDetailed: packedReversiGameDetailedSchema, AbuseUserReport: packedAbuseUserReportSchema, @@ -95,6 +97,9 @@ export const refs = { export type Packed = SchemaType; +export type KeyOf = PropertiesToUnion; +type PropertiesToUnion

= p['properties'] extends NonNullable ? keyof p['properties'] : never; + type TypeStringef = 'null' | 'boolean' | 'integer' | 'number' | 'string' | 'array' | 'object' | 'any'; type StringDefToType = T extends 'null' ? null : diff --git a/packages/backend/src/models/Announcement.ts b/packages/backend/src/models/Announcement.ts index 140a9fe18..53e7b4823 100644 --- a/packages/backend/src/models/Announcement.ts +++ b/packages/backend/src/models/Announcement.ts @@ -45,7 +45,7 @@ export class MiAnnouncement { length: 256, nullable: false, default: 'info', }) - public icon: string; + public icon: 'info' | 'warning' | 'error' | 'success'; // normal ... お知らせページ掲載 // banner ... お知らせページ掲載 + バナー表示 @@ -54,7 +54,7 @@ export class MiAnnouncement { length: 256, nullable: false, default: 'normal', }) - public display: string; + public display: 'normal' | 'banner' | 'dialog'; @Column('boolean', { default: false, diff --git a/packages/backend/src/models/json-schema/announcement.ts b/packages/backend/src/models/json-schema/announcement.ts index 4ef738cba..e1e6746ae 100644 --- a/packages/backend/src/models/json-schema/announcement.ts +++ b/packages/backend/src/models/json-schema/announcement.ts @@ -37,10 +37,12 @@ export const packedAnnouncementSchema = { icon: { type: 'string', optional: false, nullable: false, + enum: ['info', 'warning', 'error', 'success'], }, display: { type: 'string', optional: false, nullable: false, + enum: ['dialog', 'normal', 'banner'], }, needConfirmationToRead: { type: 'boolean', diff --git a/packages/backend/src/models/json-schema/blocking.ts b/packages/backend/src/models/json-schema/blocking.ts index 0b58f1f8d..1b3227d45 100644 --- a/packages/backend/src/models/json-schema/blocking.ts +++ b/packages/backend/src/models/json-schema/blocking.ts @@ -25,7 +25,7 @@ export const packedBlockingSchema = { blockee: { type: 'object', optional: false, nullable: false, - ref: 'UserDetailed', + ref: 'UserDetailedNotMe', }, }, } as const; diff --git a/packages/backend/src/models/json-schema/following.ts b/packages/backend/src/models/json-schema/following.ts index a948a24b6..8a46d3a78 100644 --- a/packages/backend/src/models/json-schema/following.ts +++ b/packages/backend/src/models/json-schema/following.ts @@ -30,12 +30,12 @@ export const packedFollowingSchema = { followee: { type: 'object', optional: true, nullable: false, - ref: 'UserDetailed', + ref: 'UserDetailedNotMe', }, follower: { type: 'object', optional: true, nullable: false, - ref: 'UserDetailed', + ref: 'UserDetailedNotMe', }, }, } as const; diff --git a/packages/backend/src/models/json-schema/muting.ts b/packages/backend/src/models/json-schema/muting.ts index dde9dc028..c3d0bb603 100644 --- a/packages/backend/src/models/json-schema/muting.ts +++ b/packages/backend/src/models/json-schema/muting.ts @@ -30,7 +30,7 @@ export const packedMutingSchema = { mutee: { type: 'object', optional: false, nullable: false, - ref: 'UserDetailed', + ref: 'UserDetailedNotMe', }, }, } as const; diff --git a/packages/backend/src/models/json-schema/note.ts b/packages/backend/src/models/json-schema/note.ts index 2b7722129..929f697e8 100644 --- a/packages/backend/src/models/json-schema/note.ts +++ b/packages/backend/src/models/json-schema/note.ts @@ -69,6 +69,7 @@ export const packedNoteSchema = { visibility: { type: 'string', optional: false, nullable: false, + enum: ['public', 'home', 'followers', 'specified'], }, mentions: { type: 'array', @@ -117,6 +118,48 @@ export const packedNoteSchema = { poll: { type: 'object', optional: true, nullable: true, + properties: { + expiresAt: { + type: 'string', + optional: true, nullable: true, + format: 'date-time', + }, + multiple: { + type: 'boolean', + optional: false, nullable: false, + }, + choices: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + properties: { + isVoted: { + type: 'boolean', + optional: false, nullable: false, + }, + text: { + type: 'string', + optional: false, nullable: false, + }, + votes: { + type: 'number', + optional: false, nullable: false, + }, + }, + }, + }, + }, + }, + emojis: { + type: 'object', + optional: true, nullable: false, + additionalProperties: { + anyOf: [{ + type: 'string', + }], + }, }, channelId: { type: 'string', @@ -162,9 +205,23 @@ export const packedNoteSchema = { type: 'string', optional: false, nullable: true, }, + reactionEmojis: { + type: 'object', + optional: false, nullable: false, + additionalProperties: { + anyOf: [{ + type: 'string', + }], + }, + }, reactions: { type: 'object', optional: false, nullable: false, + additionalProperties: { + anyOf: [{ + type: 'number', + }], + }, }, renoteCount: { type: 'number', @@ -196,7 +253,7 @@ export const packedNoteSchema = { }, myReaction: { - type: 'object', + type: 'string', optional: true, nullable: true, }, }, diff --git a/packages/backend/src/models/json-schema/notification.ts b/packages/backend/src/models/json-schema/notification.ts index c6d6e8431..6286950de 100644 --- a/packages/backend/src/models/json-schema/notification.ts +++ b/packages/backend/src/models/json-schema/notification.ts @@ -5,7 +5,7 @@ import { notificationTypes } from '@/types.js'; -export const packedNotificationSchema = { +const baseSchema = { type: 'object', properties: { id: { @@ -23,68 +23,368 @@ export const packedNotificationSchema = { optional: false, nullable: false, enum: [...notificationTypes, 'reaction:grouped', 'renote:grouped'], }, - user: { - type: 'object', - ref: 'UserLite', - optional: true, nullable: true, - }, - userId: { - type: 'string', - optional: true, nullable: true, - format: 'id', - }, - note: { - type: 'object', - ref: 'Note', - optional: true, nullable: true, - }, - reaction: { - type: 'string', - optional: true, nullable: true, - }, - achievement: { - type: 'string', - optional: true, nullable: false, - }, - body: { - type: 'string', - optional: true, nullable: true, - }, - header: { - type: 'string', - optional: true, nullable: true, - }, - icon: { - type: 'string', - optional: true, nullable: true, - }, - reactions: { - type: 'array', - optional: true, nullable: true, - items: { - type: 'object', - properties: { - user: { - type: 'object', - ref: 'UserLite', - optional: false, nullable: false, - }, - reaction: { - type: 'string', - optional: false, nullable: false, - }, - }, - required: ['user', 'reaction'], + }, +} as const; + +export const packedNotificationSchema = { + type: 'object', + oneOf: [{ + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['note'], }, - }, - users: { - type: 'array', - optional: true, nullable: true, - items: { + user: { type: 'object', ref: 'UserLite', optional: false, nullable: false, }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + note: { + type: 'object', + ref: 'Note', + optional: false, nullable: false, + }, }, - }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['mention'], + }, + user: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + note: { + type: 'object', + ref: 'Note', + optional: false, nullable: false, + }, + }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['reply'], + }, + user: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + note: { + type: 'object', + ref: 'Note', + optional: false, nullable: false, + }, + }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['renote'], + }, + user: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + note: { + type: 'object', + ref: 'Note', + optional: false, nullable: false, + }, + }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['quote'], + }, + user: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + note: { + type: 'object', + ref: 'Note', + optional: false, nullable: false, + }, + }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['reaction'], + }, + user: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + note: { + type: 'object', + ref: 'Note', + optional: false, nullable: false, + }, + reaction: { + type: 'string', + optional: false, nullable: false, + }, + }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['pollEnded'], + }, + user: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + note: { + type: 'object', + ref: 'Note', + optional: false, nullable: false, + }, + }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['follow'], + }, + user: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['receiveFollowRequest'], + }, + user: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['followRequestAccepted'], + }, + user: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['roleAssigned'], + }, + role: { + type: 'object', + ref: 'Role', + optional: false, nullable: false, + }, + }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['achievementEarned'], + }, + achievement: { + type: 'string', + optional: false, nullable: false, + }, + }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['app'], + }, + body: { + type: 'string', + optional: false, nullable: false, + }, + header: { + type: 'string', + optional: false, nullable: false, + }, + icon: { + type: 'string', + optional: false, nullable: false, + }, + }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['reaction:grouped'], + }, + note: { + type: 'object', + ref: 'Note', + optional: false, nullable: false, + }, + reactions: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + properties: { + user: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + reaction: { + type: 'string', + optional: false, nullable: false, + }, + }, + required: ['user', 'reaction'], + }, + }, + }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['renote:grouped'], + }, + note: { + type: 'object', + ref: 'Note', + optional: false, nullable: false, + }, + users: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + }, + }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['test'], + }, + }, + }], } as const; diff --git a/packages/backend/src/models/json-schema/page.ts b/packages/backend/src/models/json-schema/page.ts index b022f9651..a7216f6f8 100644 --- a/packages/backend/src/models/json-schema/page.ts +++ b/packages/backend/src/models/json-schema/page.ts @@ -3,6 +3,107 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +const blockBaseSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + }, + type: { + type: 'string', + optional: false, nullable: false, + }, + }, +} as const; + +const textBlockSchema = { + type: 'object', + properties: { + ...blockBaseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['text'], + }, + text: { + type: 'string', + optional: false, nullable: false, + }, + }, +} as const; + +const sectionBlockSchema = { + type: 'object', + properties: { + ...blockBaseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['section'], + }, + title: { + type: 'string', + optional: false, nullable: false, + }, + children: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + ref: 'PageBlock', + }, + }, + }, +} as const; + +const imageBlockSchema = { + type: 'object', + properties: { + ...blockBaseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['image'], + }, + fileId: { + type: 'string', + optional: false, nullable: true, + }, + }, +} as const; + +const noteBlockSchema = { + type: 'object', + properties: { + ...blockBaseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['note'], + }, + detailed: { + type: 'boolean', + optional: false, nullable: false, + }, + note: { + type: 'string', + optional: false, nullable: true, + }, + }, +} as const; + +export const packedPageBlockSchema = { + type: 'object', + oneOf: [ + textBlockSchema, + sectionBlockSchema, + imageBlockSchema, + noteBlockSchema, + ], +} as const; + export const packedPageSchema = { type: 'object', properties: { @@ -38,6 +139,7 @@ export const packedPageSchema = { items: { type: 'object', optional: false, nullable: false, + ref: 'PageBlock', }, }, variables: { diff --git a/packages/backend/src/models/json-schema/renote-muting.ts b/packages/backend/src/models/json-schema/renote-muting.ts index feed1ceb0..769b33f51 100644 --- a/packages/backend/src/models/json-schema/renote-muting.ts +++ b/packages/backend/src/models/json-schema/renote-muting.ts @@ -25,7 +25,7 @@ export const packedRenoteMutingSchema = { mutee: { type: 'object', optional: false, nullable: false, - ref: 'UserDetailed', + ref: 'UserDetailedNotMe', }, }, } as const; diff --git a/packages/backend/src/models/json-schema/role.ts b/packages/backend/src/models/json-schema/role.ts index b6be0eedf..1a8ae072f 100644 --- a/packages/backend/src/models/json-schema/role.ts +++ b/packages/backend/src/models/json-schema/role.ts @@ -1,26 +1,127 @@ -const rolePolicyValue = { +export const packedRolePoliciesSchema = { type: 'object', + optional: false, nullable: false, properties: { - value: { - oneOf: [ - { - type: 'integer', - optional: false, nullable: false, - }, - { - type: 'boolean', - optional: false, nullable: false, - }, - ], + gtlAvailable: { + type: 'boolean', + optional: false, nullable: false, }, - priority: { + ltlAvailable: { + type: 'boolean', + optional: false, nullable: false, + }, + canPublicNote: { + type: 'boolean', + optional: false, nullable: false, + }, + canCreateContent: { + type: 'boolean', + optional: false, nullable: false, + }, + canUpdateContent: { + type: 'boolean', + optional: false, nullable: false, + }, + canDeleteContent: { + type: 'boolean', + optional: false, nullable: false, + }, + canUpdateAvatar: { + type: 'boolean', + optional: false, nullable: false, + }, + canUpdateBanner: { + type: 'boolean', + optional: false, nullable: false, + }, + canInvite: { + type: 'boolean', + optional: false, nullable: false, + }, + inviteLimit: { type: 'integer', optional: false, nullable: false, }, - useDefault: { + inviteLimitCycle: { + type: 'integer', + optional: false, nullable: false, + }, + inviteExpirationTime: { + type: 'integer', + optional: false, nullable: false, + }, + canManageCustomEmojis: { type: 'boolean', optional: false, nullable: false, }, + canManageAvatarDecorations: { + type: 'boolean', + optional: false, nullable: false, + }, + canSearchNotes: { + type: 'boolean', + optional: false, nullable: false, + }, + canUseTranslator: { + type: 'boolean', + optional: false, nullable: false, + }, + canUseDriveFileInSoundSettings: { + type: 'boolean', + optional: false, nullable: false, + }, + canHideAds: { + type: 'boolean', + optional: false, nullable: false, + }, + driveCapacityMb: { + type: 'integer', + optional: false, nullable: false, + }, + alwaysMarkNsfw: { + type: 'boolean', + optional: false, nullable: false, + }, + pinLimit: { + type: 'integer', + optional: false, nullable: false, + }, + antennaLimit: { + type: 'integer', + optional: false, nullable: false, + }, + wordMuteLimit: { + type: 'integer', + optional: false, nullable: false, + }, + webhookLimit: { + type: 'integer', + optional: false, nullable: false, + }, + clipLimit: { + type: 'integer', + optional: false, nullable: false, + }, + noteEachClipsLimit: { + type: 'integer', + optional: false, nullable: false, + }, + userListLimit: { + type: 'integer', + optional: false, nullable: false, + }, + userEachUserListsLimit: { + type: 'integer', + optional: false, nullable: false, + }, + rateLimitFactor: { + type: 'integer', + optional: false, nullable: false, + }, + avatarDecorationLimit: { + type: 'integer', + optional: false, nullable: false, + }, }, } as const; @@ -121,32 +222,28 @@ export const packedRoleSchema = { policies: { type: 'object', optional: false, nullable: false, - properties: { - pinLimit: rolePolicyValue, - canInvite: rolePolicyValue, - clipLimit: rolePolicyValue, - canHideAds: rolePolicyValue, - inviteLimit: rolePolicyValue, - antennaLimit: rolePolicyValue, - gtlAvailable: rolePolicyValue, - ltlAvailable: rolePolicyValue, - webhookLimit: rolePolicyValue, - canPublicNote: rolePolicyValue, - userListLimit: rolePolicyValue, - wordMuteLimit: rolePolicyValue, - alwaysMarkNsfw: rolePolicyValue, - canSearchNotes: rolePolicyValue, - driveCapacityMb: rolePolicyValue, - rateLimitFactor: rolePolicyValue, - inviteLimitCycle: rolePolicyValue, - noteEachClipsLimit: rolePolicyValue, - inviteExpirationTime: rolePolicyValue, - canManageCustomEmojis: rolePolicyValue, - userEachUserListsLimit: rolePolicyValue, - canManageAvatarDecorations: rolePolicyValue, - canUseDriveFileInSoundSettings: rolePolicyValue, - canUseTranslator: rolePolicyValue, - avatarDecorationLimit: rolePolicyValue, + additionalProperties: { + anyOf: [{ + type: 'object', + properties: { + value: { + oneOf: [ + { + type: 'integer', + }, + { + type: 'boolean', + }, + ], + }, + priority: { + type: 'integer', + }, + useDefault: { + type: 'boolean', + }, + }, + }], }, }, usersCount: { diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts index dc94b0339..67babdbb7 100644 --- a/packages/backend/src/models/json-schema/user.ts +++ b/packages/backend/src/models/json-schema/user.ts @@ -582,108 +582,7 @@ export const packedMeDetailedOnlySchema = { policies: { type: 'object', nullable: false, optional: false, - properties: { - gtlAvailable: { - type: 'boolean', - nullable: false, optional: false, - }, - ltlAvailable: { - type: 'boolean', - nullable: false, optional: false, - }, - canPublicNote: { - type: 'boolean', - nullable: false, optional: false, - }, - canInvite: { - type: 'boolean', - nullable: false, optional: false, - }, - inviteLimit: { - type: 'number', - nullable: false, optional: false, - }, - inviteLimitCycle: { - type: 'number', - nullable: false, optional: false, - }, - inviteExpirationTime: { - type: 'number', - nullable: false, optional: false, - }, - canManageCustomEmojis: { - type: 'boolean', - nullable: false, optional: false, - }, - canManageAvatarDecorations: { - type: 'boolean', - nullable: false, optional: false, - }, - canSearchNotes: { - type: 'boolean', - nullable: false, optional: false, - }, - canUseTranslator: { - type: 'boolean', - nullable: false, optional: false, - }, - canUseDriveFileInSoundSettings: { - type: 'boolean', - nullable: false, optional: false, - }, - canHideAds: { - type: 'boolean', - nullable: false, optional: false, - }, - driveCapacityMb: { - type: 'number', - nullable: false, optional: false, - }, - alwaysMarkNsfw: { - type: 'boolean', - nullable: false, optional: false, - }, - pinLimit: { - type: 'number', - nullable: false, optional: false, - }, - antennaLimit: { - type: 'number', - nullable: false, optional: false, - }, - wordMuteLimit: { - type: 'number', - nullable: false, optional: false, - }, - webhookLimit: { - type: 'number', - nullable: false, optional: false, - }, - clipLimit: { - type: 'number', - nullable: false, optional: false, - }, - noteEachClipsLimit: { - type: 'number', - nullable: false, optional: false, - }, - userListLimit: { - type: 'number', - nullable: false, optional: false, - }, - userEachUserListsLimit: { - type: 'number', - nullable: false, optional: false, - }, - rateLimitFactor: { - type: 'number', - nullable: false, optional: false, - }, - avatarDecorationLimit: { - type: 'number', - nullable: false, optional: false, - }, - }, + ref: 'RolePolicies', }, //#region secrets email: { @@ -778,13 +677,5 @@ export const packedUserSchema = { type: 'object', ref: 'UserDetailed', }, - { - type: 'object', - ref: 'UserDetailedNotMe', - }, - { - type: 'object', - ref: 'MeDetailed', - }, ], } as const; diff --git a/packages/backend/src/queue/QueueProcessorService.ts b/packages/backend/src/queue/QueueProcessorService.ts index b89827d43..54deb8a98 100644 --- a/packages/backend/src/queue/QueueProcessorService.ts +++ b/packages/backend/src/queue/QueueProcessorService.ts @@ -286,9 +286,9 @@ export class QueueProcessorService implements OnApplicationShutdown { }, { ...baseWorkerOptions(this.config.redisForRelationshipQueue, this.config.bullmqWorkerOptions, QUEUE.RELATIONSHIP), autorun: false, - concurrency: this.config.relashionshipJobConcurrency ?? 16, + concurrency: this.config.relationshipJobConcurrency ?? 16, limiter: { - max: this.config.relashionshipJobPerSec ?? 64, + max: this.config.relationshipJobPerSec ?? 64, duration: 1000, }, }); diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts index cae49203c..c060aa97e 100644 --- a/packages/backend/src/server/ServerService.ts +++ b/packages/backend/src/server/ServerService.ts @@ -211,7 +211,7 @@ export class ServerService implements OnApplicationShutdown { }); this.globalEventService.publishMainStream(profile.userId, 'meUpdated', await this.userEntityService.pack(profile.userId, { id: profile.userId }, { - detail: true, + schema: 'MeDetailed', includeSecrets: true, })); diff --git a/packages/backend/src/server/api/ApiServerService.ts b/packages/backend/src/server/api/ApiServerService.ts index 9d22a4f87..730474699 100644 --- a/packages/backend/src/server/api/ApiServerService.ts +++ b/packages/backend/src/server/api/ApiServerService.ts @@ -157,7 +157,7 @@ export class ApiServerService { return { ok: true, token: token.token, - user: await this.userEntityService.pack(token.userId, { id: token.userId }, { detail: true }), + user: await this.userEntityService.pack(token.userId, { id: token.userId }, { schema: 'UserDetailed' }), }; } else { return { diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts index 6b4d9d9f7..95016cdba 100644 --- a/packages/backend/src/server/api/SignupApiService.ts +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -213,7 +213,7 @@ export class SignupApiService { }); const res = await this.userEntityService.pack(account, account, { - detail: true, + schema: 'MeDetailed', includeSecrets: true, }); diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index 432c824ee..7418fb24c 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -4,8 +4,7 @@ */ import { permissions } from 'misskey-js'; -import type { Schema } from '@/misc/json-schema.js'; -import { RolePolicies } from '@/core/RoleService.js'; +import type { KeyOf, Schema } from '@/misc/json-schema.js'; import * as ep___admin_meta from './endpoints/admin/meta.js'; import * as ep___admin_abuseUserReports from './endpoints/admin/abuse-user-reports.js'; @@ -788,7 +787,7 @@ interface IEndpointMetaBase { */ readonly requireAdmin?: boolean; - readonly requireRolePolicy?: keyof RolePolicies; + readonly requireRolePolicy?: KeyOf<'RolePolicies'>; /** * 引っ越し済みのユーザーによるリクエストを禁止するか diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts index aafd24843..695934969 100644 --- a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts +++ b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts @@ -62,17 +62,17 @@ export const meta = { reporter: { type: 'object', nullable: false, optional: false, - ref: 'User', + ref: 'UserDetailed', }, targetUser: { type: 'object', nullable: false, optional: false, - ref: 'User', + ref: 'UserDetailed', }, assignee: { type: 'object', nullable: true, optional: true, - ref: 'User', + ref: 'UserDetailed', }, category: { type: 'string', 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 f54d567ff..b18a7e0e4 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts @@ -11,6 +11,7 @@ import { SignupService } from '@/core/SignupService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { localUsernameSchema, passwordSchema } from '@/models/User.js'; import { DI } from '@/di-symbols.js'; +import { Packed } from '@/misc/json-schema.js'; export const meta = { tags: ['admin'], @@ -18,7 +19,7 @@ export const meta = { res: { type: 'object', optional: false, nullable: false, - ref: 'User', + ref: 'MeDetailed', properties: { token: { type: 'string', @@ -60,11 +61,11 @@ export default class extends Endpoint { // eslint- }); const res = await this.userEntityService.pack(account, account, { - detail: true, + schema: 'MeDetailed', includeSecrets: true, - }); + }) as Packed<'MeDetailed'> & { token: string }; - (res as any).token = secret; + res.token = secret; return res; }); diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/find-by-email.ts b/packages/backend/src/server/api/endpoints/admin/accounts/find-by-email.ts index 93673453d..a7fab9ab6 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/find-by-email.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/find-by-email.ts @@ -27,7 +27,7 @@ export const meta = { res: { type: 'object', optional: false, nullable: false, - ref: 'User', + ref: 'UserDetailed', }, } as const; @@ -58,7 +58,7 @@ export default class extends Endpoint { // eslint- } const res = await this.userEntityService.pack(profile.user!, null, { - detail: true, + schema: 'UserDetailed', }); return res; diff --git a/packages/backend/src/server/api/endpoints/admin/roles/users.ts b/packages/backend/src/server/api/endpoints/admin/roles/users.ts index 66f4d9d26..1e0568599 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/users.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/users.ts @@ -40,7 +40,7 @@ export const meta = { }, required: ['id', 'createdAt', 'user'], }, - } + }, } as const; export const paramDef = { @@ -92,7 +92,7 @@ export default class extends Endpoint { // eslint- return await Promise.all(assigns.map(async assign => ({ id: assign.id, createdAt: this.idService.parse(assign.id).date.toISOString(), - user: await this.userEntityService.pack(assign.user!, me, { detail: true }), + user: await this.userEntityService.pack(assign.user!, me, { schema: 'UserDetailed' }), expiresAt: assign.expiresAt?.toISOString() ?? null, }))); }); diff --git a/packages/backend/src/server/api/endpoints/admin/show-users.ts b/packages/backend/src/server/api/endpoints/admin/show-users.ts index 1d31e5e80..80611266d 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-users.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-users.ts @@ -114,7 +114,7 @@ export default class extends Endpoint { // eslint- const users = await query.getMany(); - return await this.userEntityService.packMany(users, me, { detail: true }); + return await this.userEntityService.packMany(users, me, { schema: 'UserDetailed' }); }); } } diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index 7e5c7a917..4c1236eaa 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -148,7 +148,7 @@ export default class extends Endpoint { // eslint- if (user != null) { return { type: 'User', - object: await this.userEntityService.pack(user, me, { detail: true }), + object: await this.userEntityService.pack(user, me, { schema: 'UserDetailedNotMe' }), }; } else if (note != null) { try { 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 c82fa0ff1..9861b866f 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/userkey.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/userkey.ts @@ -27,7 +27,7 @@ export const meta = { user: { type: 'object', optional: false, nullable: false, - ref: 'UserDetailedNotMe', + ref: 'UserDetailed', }, }, }, @@ -112,7 +112,7 @@ export default class extends Endpoint { // eslint- return { accessToken: accessToken.token, user: await this.userEntityService.pack(session.userId, me, { - detail: true, + schema: 'UserDetailed', }), }; }); diff --git a/packages/backend/src/server/api/endpoints/blocking/create.ts b/packages/backend/src/server/api/endpoints/blocking/create.ts index 4f05ea1b3..1faa82a66 100644 --- a/packages/backend/src/server/api/endpoints/blocking/create.ts +++ b/packages/backend/src/server/api/endpoints/blocking/create.ts @@ -103,7 +103,7 @@ export default class extends Endpoint { // eslint- await this.userBlockingService.block(blocker, blockee); return await this.userEntityService.pack(blockee.id, blocker, { - detail: true, + schema: 'UserDetailedNotMe', }); }); } diff --git a/packages/backend/src/server/api/endpoints/blocking/delete.ts b/packages/backend/src/server/api/endpoints/blocking/delete.ts index ea2b87639..cd41829b1 100644 --- a/packages/backend/src/server/api/endpoints/blocking/delete.ts +++ b/packages/backend/src/server/api/endpoints/blocking/delete.ts @@ -104,7 +104,7 @@ export default class extends Endpoint { // eslint- await this.userBlockingService.unblock(blocker, blockee); return await this.userEntityService.pack(blockee.id, blocker, { - detail: true, + schema: 'UserDetailedNotMe', }); }); } diff --git a/packages/backend/src/server/api/endpoints/bubble-game/ranking.ts b/packages/backend/src/server/api/endpoints/bubble-game/ranking.ts index 9c057760c..731bcc5b6 100644 --- a/packages/backend/src/server/api/endpoints/bubble-game/ranking.ts +++ b/packages/backend/src/server/api/endpoints/bubble-game/ranking.ts @@ -61,7 +61,7 @@ export default class extends Endpoint { // eslint- relations: ['user'], }); - const users = await this.userEntityService.packMany(records.map(r => r.user!), null, { detail: false }); + const users = await this.userEntityService.packMany(records.map(r => r.user!), null); return records.map(r => ({ id: r.id, diff --git a/packages/backend/src/server/api/endpoints/federation/users.ts b/packages/backend/src/server/api/endpoints/federation/users.ts index d97171865..df8b66ab4 100644 --- a/packages/backend/src/server/api/endpoints/federation/users.ts +++ b/packages/backend/src/server/api/endpoints/federation/users.ts @@ -54,7 +54,7 @@ export default class extends Endpoint { // eslint- .limit(ps.limit) .getMany(); - return await this.userEntityService.packMany(users, me, { detail: true }); + return await this.userEntityService.packMany(users, me, { schema: 'UserDetailedNotMe' }); }); } } diff --git a/packages/backend/src/server/api/endpoints/hashtags/users.ts b/packages/backend/src/server/api/endpoints/hashtags/users.ts index 8302d2380..5071dd22b 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/users.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/users.ts @@ -76,7 +76,7 @@ export default class extends Endpoint { // eslint- const users = await query.limit(ps.limit).getMany(); - return await this.userEntityService.packMany(users, me, { detail: true }); + return await this.userEntityService.packMany(users, me, { schema: 'UserDetailed' }); }); } } diff --git a/packages/backend/src/server/api/endpoints/i.ts b/packages/backend/src/server/api/endpoints/i.ts index c24e04918..c61379458 100644 --- a/packages/backend/src/server/api/endpoints/i.ts +++ b/packages/backend/src/server/api/endpoints/i.ts @@ -71,8 +71,8 @@ export default class extends Endpoint { // eslint- userProfile.loggedInDates = [...userProfile.loggedInDates, today]; } - return await this.userEntityService.pack(userProfile.user!, userProfile.user!, { - detail: true, + return await this.userEntityService.pack(userProfile.user!, userProfile.user!, { + schema: 'MeDetailed', includeSecrets: isSecure, userProfile, }); 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 e32f97743..17f862349 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/done.ts @@ -64,7 +64,7 @@ export default class extends Endpoint { // eslint- // Publish meUpdated event this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, { - detail: true, + schema: 'MeDetailed', includeSecrets: true, })); 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 5f1d80fb4..98c63c6f1 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 @@ -111,7 +111,7 @@ export default class extends Endpoint { // Publish meUpdated event this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, { - detail: true, + schema: 'MeDetailed', includeSecrets: true, })); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts b/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts index 2ed701014..b68f23bf8 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts @@ -74,7 +74,7 @@ export default class extends Endpoint { // eslint- // Publish meUpdated event this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, { - detail: true, + schema: 'MeDetailed', includeSecrets: true, })); }); 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 c185ad2f7..865e53727 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 @@ -99,7 +99,7 @@ export default class extends Endpoint { // eslint- // Publish meUpdated event this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, { - detail: true, + schema: 'MeDetailed', includeSecrets: true, })); 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 fe0cbbc3c..8da24cc1a 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts @@ -78,7 +78,7 @@ export default class extends Endpoint { // eslint- // Publish meUpdated event this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, { - detail: true, + schema: 'MeDetailed', includeSecrets: true, })); }); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/update-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/update-key.ts index 1a140c1d0..8d894e8f8 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/update-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/update-key.ts @@ -69,7 +69,7 @@ export default class extends Endpoint { // eslint- // Publish meUpdated event this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, { - detail: true, + schema: 'MeDetailed', includeSecrets: true, })); diff --git a/packages/backend/src/server/api/endpoints/i/pin.ts b/packages/backend/src/server/api/endpoints/i/pin.ts index 566c57398..2a39f01bb 100644 --- a/packages/backend/src/server/api/endpoints/i/pin.ts +++ b/packages/backend/src/server/api/endpoints/i/pin.ts @@ -68,8 +68,8 @@ export default class extends Endpoint { // eslint- throw err; }); - return await this.userEntityService.pack(me.id, me, { - detail: true, + return await this.userEntityService.pack(me.id, me, { + schema: 'MeDetailed', }); }); } diff --git a/packages/backend/src/server/api/endpoints/i/unpin.ts b/packages/backend/src/server/api/endpoints/i/unpin.ts index a80dce084..6167c09e8 100644 --- a/packages/backend/src/server/api/endpoints/i/unpin.ts +++ b/packages/backend/src/server/api/endpoints/i/unpin.ts @@ -52,8 +52,8 @@ export default class extends Endpoint { // eslint- throw err; }); - return await this.userEntityService.pack(me.id, me, { - detail: true, + return await this.userEntityService.pack(me.id, me, { + schema: 'MeDetailed', }); }); } 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 3339c7c77..9b3262131 100644 --- a/packages/backend/src/server/api/endpoints/i/update-email.ts +++ b/packages/backend/src/server/api/endpoints/i/update-email.ts @@ -50,7 +50,7 @@ export const meta = { res: { type: 'object', - ref: 'UserDetailed', + ref: 'MeDetailed', }, } as const; @@ -109,7 +109,7 @@ export default class extends Endpoint { // eslint- }); const iObj = await this.userEntityService.pack(me.id, me, { - detail: true, + schema: 'MeDetailed', includeSecrets: true, }); diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index d8ecd7842..72d5e0aef 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -433,8 +433,8 @@ export default class extends Endpoint { // eslint- verifiedLinks: [], }); - const iObj = await this.userEntityService.pack(user.id, user, { - detail: true, + const iObj = await this.userEntityService.pack(user.id, user, { + schema: 'MeDetailed', includeSecrets: isSecure, }); diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index 2d838cc34..e0c74a239 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -304,6 +304,11 @@ export const meta = { type: 'string', optional: false, nullable: true, }, + policies: { + type: 'object', + optional: false, nullable: false, + ref: 'RolePolicies', + }, }, }, } as const; diff --git a/packages/backend/src/server/api/endpoints/page-push.ts b/packages/backend/src/server/api/endpoints/page-push.ts index 0a6851658..49fcaf061 100644 --- a/packages/backend/src/server/api/endpoints/page-push.ts +++ b/packages/backend/src/server/api/endpoints/page-push.ts @@ -55,7 +55,7 @@ export default class extends Endpoint { // eslint- var: ps.var, userId: me.id, user: await this.userEntityService.pack(me.id, { id: page.userId }, { - detail: true, + schema: 'UserDetailed', }), }); }); diff --git a/packages/backend/src/server/api/endpoints/pinned-users.ts b/packages/backend/src/server/api/endpoints/pinned-users.ts index 390042c81..415633e8b 100644 --- a/packages/backend/src/server/api/endpoints/pinned-users.ts +++ b/packages/backend/src/server/api/endpoints/pinned-users.ts @@ -52,7 +52,7 @@ export default class extends Endpoint { // eslint- host: acct.host ?? IsNull(), }))); - return await this.userEntityService.packMany(users.filter(x => x !== null) as MiUser[], me, { detail: true }); + return await this.userEntityService.packMany(users.filter(x => x !== null) as MiUser[], me, { schema: 'UserDetailed' }); }); } } diff --git a/packages/backend/src/server/api/endpoints/retention.ts b/packages/backend/src/server/api/endpoints/retention.ts index dac6d6540..263169313 100644 --- a/packages/backend/src/server/api/endpoints/retention.ts +++ b/packages/backend/src/server/api/endpoints/retention.ts @@ -14,6 +14,32 @@ export const meta = { requireCredential: false, res: { + type: 'array', + items: { + type: 'object', + properties: { + createdAt: { + type: 'string', + format: 'date-time', + }, + users: { + type: 'number', + }, + data: { + type: 'object', + additionalProperties: { + anyOf: [{ + type: 'number', + }], + }, + }, + }, + required: [ + 'createdAt', + 'users', + 'data', + ], + }, }, allowGet: true, diff --git a/packages/backend/src/server/api/endpoints/roles/users.ts b/packages/backend/src/server/api/endpoints/roles/users.ts index d304d075b..2e43af5c5 100644 --- a/packages/backend/src/server/api/endpoints/roles/users.ts +++ b/packages/backend/src/server/api/endpoints/roles/users.ts @@ -33,11 +33,11 @@ export const meta = { properties: { id: { type: 'string', - format: 'misskey:id' + format: 'misskey:id', }, user: { type: 'object', - ref: 'User' + ref: 'UserDetailed', }, }, required: ['id', 'user'], @@ -94,7 +94,7 @@ export default class extends Endpoint { // eslint- return await Promise.all(assigns.map(async assign => ({ id: assign.id, - user: await this.userEntityService.pack(assign.user!, me, { detail: true }), + user: await this.userEntityService.pack(assign.user!, me, { schema: 'UserDetailed' }), }))); }); } diff --git a/packages/backend/src/server/api/endpoints/users.ts b/packages/backend/src/server/api/endpoints/users.ts index 8dc584131..b886dd4a6 100644 --- a/packages/backend/src/server/api/endpoints/users.ts +++ b/packages/backend/src/server/api/endpoints/users.ts @@ -89,7 +89,7 @@ export default class extends Endpoint { // eslint- const users = await query.getMany(); - return await this.userEntityService.packMany(users, me, { detail: true }); + return await this.userEntityService.packMany(users, me, { schema: 'UserDetailed' }); }); } } diff --git a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts index dd323e84a..5ad9d5746 100644 --- a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts +++ b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts @@ -132,7 +132,7 @@ export default class extends Endpoint { // eslint- // Make replies object (includes weights) const repliesObj = await Promise.all(topRepliedUsers.map(async (user) => ({ - user: await this.userEntityService.pack(user, me, { detail: true }), + user: await this.userEntityService.pack(user, me, { schema: 'UserDetailed' }), weight: repliedUsers[user] / peak, }))); diff --git a/packages/backend/src/server/api/endpoints/users/lists/get-memberships.ts b/packages/backend/src/server/api/endpoints/users/lists/get-memberships.ts index 8e2e9257a..586886f80 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/get-memberships.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/get-memberships.ts @@ -46,7 +46,7 @@ export const meta = { }, user: { type: 'object', - ref: 'User', + ref: 'UserLite', }, withReplies: { type: 'boolean', diff --git a/packages/backend/src/server/api/endpoints/users/recommendation.ts b/packages/backend/src/server/api/endpoints/users/recommendation.ts index 1b30e99b1..c73495e3c 100644 --- a/packages/backend/src/server/api/endpoints/users/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/users/recommendation.ts @@ -76,7 +76,7 @@ export default class extends Endpoint { // eslint- const users = await query.limit(ps.limit).offset(ps.offset).getMany(); - return await this.userEntityService.packMany(users, me, { detail: true }); + return await this.userEntityService.packMany(users, me, { schema: 'UserDetailed' }); }); } } diff --git a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts index 4bf25d9fb..7d36f6f93 100644 --- a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts +++ b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts @@ -131,7 +131,7 @@ export default class extends Endpoint { // eslint- .getMany(); } - return await this.userEntityService.packMany(users, me, { detail: !!ps.detail }); + return await this.userEntityService.packMany(users, me, { schema: ps.detail ? 'UserDetailed' : 'UserLite' }); }); } } diff --git a/packages/backend/src/server/api/endpoints/users/search.ts b/packages/backend/src/server/api/endpoints/users/search.ts index 32b5c1237..bc8613659 100644 --- a/packages/backend/src/server/api/endpoints/users/search.ts +++ b/packages/backend/src/server/api/endpoints/users/search.ts @@ -141,7 +141,7 @@ export default class extends Endpoint { // eslint- } } - return await this.userEntityService.packMany(users, me, { detail: ps.detail }); + return await this.userEntityService.packMany(users, me, { schema: ps.detail ? 'UserDetailed' : 'UserLite' }); }); } } diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts index 3c94b3891..e4f0f0cda 100644 --- a/packages/backend/src/server/api/endpoints/users/show.ts +++ b/packages/backend/src/server/api/endpoints/users/show.ts @@ -117,7 +117,7 @@ export default class extends Endpoint { // eslint- } return await this.userEntityService.packMany(_users, me, { - detail: true, + schema: 'UserDetailed', }); } else { // Lookup user @@ -147,7 +147,7 @@ export default class extends Endpoint { // eslint- } return await this.userEntityService.pack(user, me, { - detail: true, + schema: 'UserDetailed', }); } }); diff --git a/packages/backend/src/server/api/stream/channels/admin.ts b/packages/backend/src/server/api/stream/channels/admin.ts index b8f369ce8..a37ecd175 100644 --- a/packages/backend/src/server/api/stream/channels/admin.ts +++ b/packages/backend/src/server/api/stream/channels/admin.ts @@ -9,9 +9,9 @@ import Channel, { type MiChannelService } from '../channel.js'; class AdminChannel extends Channel { public readonly chName = 'admin'; - public static shouldShare = true; - public static requireCredential = true as const; - public static kind = 'read:admin:stream'; + public static readonly shouldShare = true; + public static readonly requireCredential = true as const; + public static readonly kind = 'read:admin:stream'; @bindThis public async init(params: any) { diff --git a/packages/backend/src/server/api/stream/channels/antenna.ts b/packages/backend/src/server/api/stream/channels/antenna.ts index a20e74bfb..ec76acd8b 100644 --- a/packages/backend/src/server/api/stream/channels/antenna.ts +++ b/packages/backend/src/server/api/stream/channels/antenna.ts @@ -12,9 +12,9 @@ import Channel, { type MiChannelService } from '../channel.js'; class AntennaChannel extends Channel { public readonly chName = 'antenna'; - public static shouldShare = false; - public static requireCredential = true as const; - public static kind = 'read:account'; + public static readonly shouldShare = false; + public static readonly requireCredential = true as const; + public static readonly kind = 'read:account'; private antennaId: string; constructor( diff --git a/packages/backend/src/server/api/stream/channels/channel.ts b/packages/backend/src/server/api/stream/channels/channel.ts index 9d6feb0d1..16e9cd80e 100644 --- a/packages/backend/src/server/api/stream/channels/channel.ts +++ b/packages/backend/src/server/api/stream/channels/channel.ts @@ -12,8 +12,8 @@ import Channel, { type MiChannelService } from '../channel.js'; class ChannelChannel extends Channel { public readonly chName = 'channel'; - public static shouldShare = false; - public static requireCredential = false as const; + public static readonly shouldShare = false; + public static readonly requireCredential = false as const; private channelId: string; constructor( diff --git a/packages/backend/src/server/api/stream/channels/drive.ts b/packages/backend/src/server/api/stream/channels/drive.ts index 4bf34a72c..a1ca097a7 100644 --- a/packages/backend/src/server/api/stream/channels/drive.ts +++ b/packages/backend/src/server/api/stream/channels/drive.ts @@ -9,9 +9,9 @@ import Channel, { type MiChannelService } from '../channel.js'; class DriveChannel extends Channel { public readonly chName = 'drive'; - public static shouldShare = true; - public static requireCredential = true as const; - public static kind = 'read:account'; + public static readonly shouldShare = true; + public static readonly requireCredential = true as const; + public static readonly kind = 'read:account'; @bindThis public async init(params: any) { diff --git a/packages/backend/src/server/api/stream/channels/global-timeline.ts b/packages/backend/src/server/api/stream/channels/global-timeline.ts index fb43be412..8de4abcf1 100644 --- a/packages/backend/src/server/api/stream/channels/global-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts @@ -16,8 +16,8 @@ import Channel, { type MiChannelService } from '../channel.js'; class GlobalTimelineChannel extends Channel { public readonly chName = 'globalTimeline'; - public static shouldShare = false; - public static requireCredential = false as const; + public static readonly shouldShare = false; + public static readonly requireCredential = false as const; private withRenotes: boolean; private withFiles: boolean; diff --git a/packages/backend/src/server/api/stream/channels/hashtag.ts b/packages/backend/src/server/api/stream/channels/hashtag.ts index 8fdb9c780..d1a083f3a 100644 --- a/packages/backend/src/server/api/stream/channels/hashtag.ts +++ b/packages/backend/src/server/api/stream/channels/hashtag.ts @@ -13,8 +13,8 @@ import Channel, { type MiChannelService } from '../channel.js'; class HashtagChannel extends Channel { public readonly chName = 'hashtag'; - public static shouldShare = false; - public static requireCredential = false as const; + public static readonly shouldShare = false; + public static readonly requireCredential = false as const; private q: string[][]; constructor( diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts index e489a3066..116821420 100644 --- a/packages/backend/src/server/api/stream/channels/home-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts @@ -14,9 +14,9 @@ import Channel, { type MiChannelService } from '../channel.js'; class HomeTimelineChannel extends Channel { public readonly chName = 'homeTimeline'; - public static shouldShare = false; - public static requireCredential = true as const; - public static kind = 'read:account'; + public static readonly shouldShare = false; + public static readonly requireCredential = true as const; + public static readonly kind = 'read:account'; private withRenotes: boolean; private withFiles: boolean; diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts index 722929723..c6a03ff3c 100644 --- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts @@ -16,9 +16,9 @@ import Channel, { type MiChannelService } from '../channel.js'; class HybridTimelineChannel extends Channel { public readonly chName = 'hybridTimeline'; - public static shouldShare = false; - public static requireCredential = true as const; - public static kind = 'read:account'; + public static readonly shouldShare = false; + public static readonly requireCredential = true as const; + public static readonly kind = 'read:account'; private withRenotes: boolean; private withReplies: boolean; private withFiles: boolean; diff --git a/packages/backend/src/server/api/stream/channels/local-timeline.ts b/packages/backend/src/server/api/stream/channels/local-timeline.ts index 6400ff5fc..8abb3834a 100644 --- a/packages/backend/src/server/api/stream/channels/local-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts @@ -15,8 +15,8 @@ import Channel, { type MiChannelService } from '../channel.js'; class LocalTimelineChannel extends Channel { public readonly chName = 'localTimeline'; - public static shouldShare = false; - public static requireCredential = false as const; + public static readonly shouldShare = false; + public static readonly requireCredential = false as const; private withRenotes: boolean; private withReplies: boolean; private withFiles: boolean; diff --git a/packages/backend/src/server/api/stream/channels/main.ts b/packages/backend/src/server/api/stream/channels/main.ts index ab605e3ec..393f3db14 100644 --- a/packages/backend/src/server/api/stream/channels/main.ts +++ b/packages/backend/src/server/api/stream/channels/main.ts @@ -11,9 +11,9 @@ import Channel, { type MiChannelService } from '../channel.js'; class MainChannel extends Channel { public readonly chName = 'main'; - public static shouldShare = true; - public static requireCredential = true as const; - public static kind = 'read:account'; + public static readonly shouldShare = true; + public static readonly requireCredential = true as const; + public static readonly kind = 'read:account'; constructor( private noteEntityService: NoteEntityService, diff --git a/packages/backend/src/server/api/stream/channels/queue-stats.ts b/packages/backend/src/server/api/stream/channels/queue-stats.ts index 5ceb2c3bb..d6e4bfc2d 100644 --- a/packages/backend/src/server/api/stream/channels/queue-stats.ts +++ b/packages/backend/src/server/api/stream/channels/queue-stats.ts @@ -12,8 +12,8 @@ const ev = new Xev(); class QueueStatsChannel extends Channel { public readonly chName = 'queueStats'; - public static shouldShare = true; - public static requireCredential = false as const; + public static readonly shouldShare = true; + public static readonly requireCredential = false as const; constructor(id: string, connection: Channel['connection']) { super(id, connection); diff --git a/packages/backend/src/server/api/stream/channels/reversi-game.ts b/packages/backend/src/server/api/stream/channels/reversi-game.ts index fb24a29b7..3a86197c8 100644 --- a/packages/backend/src/server/api/stream/channels/reversi-game.ts +++ b/packages/backend/src/server/api/stream/channels/reversi-game.ts @@ -13,8 +13,8 @@ import Channel, { type MiChannelService } from '../channel.js'; class ReversiGameChannel extends Channel { public readonly chName = 'reversiGame'; - public static shouldShare = false; - public static requireCredential = false as const; + public static readonly shouldShare = false; + public static readonly requireCredential = false as const; private gameId: MiReversiGame['id'] | null = null; constructor( diff --git a/packages/backend/src/server/api/stream/channels/reversi.ts b/packages/backend/src/server/api/stream/channels/reversi.ts index 4ea9784ba..103c6f12e 100644 --- a/packages/backend/src/server/api/stream/channels/reversi.ts +++ b/packages/backend/src/server/api/stream/channels/reversi.ts @@ -9,9 +9,9 @@ import Channel, { type MiChannelService } from '../channel.js'; class ReversiChannel extends Channel { public readonly chName = 'reversi'; - public static shouldShare = true; - public static requireCredential = true as const; - public static kind = 'read:account'; + public static readonly shouldShare = true; + public static readonly requireCredential = true as const; + public static readonly kind = 'read:account'; @bindThis public async init(params: any) { diff --git a/packages/backend/src/server/api/stream/channels/role-timeline.ts b/packages/backend/src/server/api/stream/channels/role-timeline.ts index be762c5a3..143c8c059 100644 --- a/packages/backend/src/server/api/stream/channels/role-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/role-timeline.ts @@ -14,8 +14,8 @@ import Channel, { type MiChannelService } from '../channel.js'; class RoleTimelineChannel extends Channel { public readonly chName = 'roleTimeline'; - public static shouldShare = false; - public static requireCredential = false as const; + public static readonly shouldShare = false; + public static readonly requireCredential = false as const; private roleId: string; constructor( diff --git a/packages/backend/src/server/api/stream/channels/server-stats.ts b/packages/backend/src/server/api/stream/channels/server-stats.ts index 615b6946c..e0422ef01 100644 --- a/packages/backend/src/server/api/stream/channels/server-stats.ts +++ b/packages/backend/src/server/api/stream/channels/server-stats.ts @@ -12,8 +12,8 @@ const ev = new Xev(); class ServerStatsChannel extends Channel { public readonly chName = 'serverStats'; - public static shouldShare = true; - public static requireCredential = false as const; + public static readonly shouldShare = true; + public static readonly requireCredential = false as const; constructor(id: string, connection: Channel['connection']) { super(id, connection); 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 d59ef5500..d41e1abac 100644 --- a/packages/backend/src/server/api/stream/channels/user-list.ts +++ b/packages/backend/src/server/api/stream/channels/user-list.ts @@ -15,8 +15,8 @@ import Channel, { type MiChannelService } from '../channel.js'; class UserListChannel extends Channel { public readonly chName = 'userList'; - public static shouldShare = false; - public static requireCredential = false as const; + public static readonly shouldShare = false; + public static readonly requireCredential = false as const; private listId: string; private membershipsMap: Record | undefined> = {}; private listUsersClock: NodeJS.Timeout; diff --git a/packages/frontend/@types/global.d.ts b/packages/frontend/@types/global.d.ts index 7d9335cc5..936e74dec 100644 --- a/packages/frontend/@types/global.d.ts +++ b/packages/frontend/@types/global.d.ts @@ -16,3 +16,8 @@ declare const _DATA_TRANSFER_DECK_COLUMN_: string; // for dev-mode declare const _LANGS_FULL_: string[][]; + +// TagCanvas +interface Window { + TagCanvas: any; +} diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 64ee45a07..a2280501b 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -20,7 +20,7 @@ "@discordapp/twemoji": "15.0.2", "@github/webauthn-json": "2.1.1", "@mcaptcha/vanilla-glue": "0.1.0-alpha-3", - "@misskey-dev/browser-image-resizer": "2.2.1-misskey.10", + "@misskey-dev/browser-image-resizer": "2024.1.0", "@rollup/plugin-json": "6.1.0", "@rollup/plugin-replace": "5.0.5", "@rollup/plugin-typescript": "11.1.6", @@ -40,7 +40,7 @@ "chartjs-chart-matrix": "2.0.1", "chartjs-plugin-gradient": "0.6.1", "chartjs-plugin-zoom": "2.0.1", - "chromatic": "10.5.0", + "chromatic": "10.6.1", "compare-versions": "6.1.0", "cropperjs": "2.0.0-beta.4", "date-fns": "2.30.0", @@ -77,8 +77,8 @@ "vuedraggable": "next" }, "devDependencies": { - "@misskey-dev/eslint-plugin": "^1.0.0", - "@misskey-dev/summaly": "^5.0.3", + "@misskey-dev/eslint-plugin": "1.0.0", + "@misskey-dev/summaly": "5.0.3", "@storybook/addon-actions": "7.6.10", "@storybook/addon-essentials": "7.6.10", "@storybook/addon-interactions": "7.6.10", @@ -102,12 +102,12 @@ "@types/estree": "1.0.5", "@types/matter-js": "0.19.6", "@types/micromatch": "4.0.6", - "@types/node": "20.11.6", + "@types/node": "20.11.10", "@types/punycode": "2.1.3", "@types/sanitize-html": "2.9.5", "@types/throttle-debounce": "5.0.2", "@types/tinycolor2": "1.4.6", - "@types/uuid": "9.0.7", + "@types/uuid": "9.0.8", "@types/ws": "8.5.10", "@typescript-eslint/eslint-plugin": "6.18.1", "@typescript-eslint/parser": "6.18.1", @@ -118,12 +118,12 @@ "cypress": "13.6.3", "eslint": "8.56.0", "eslint-plugin-import": "2.29.1", - "eslint-plugin-vue": "9.20.1", + "eslint-plugin-vue": "9.21.0", "fast-glob": "3.3.2", "happy-dom": "10.0.3", "intersection-observer": "0.12.2", "micromatch": "4.0.5", - "msw": "2.1.4", + "msw": "2.1.5", "msw-storybook-addon": "1.10.0", "nodemon": "3.0.3", "prettier": "3.2.4", diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts index c67911c9c..fd5a03a70 100644 --- a/packages/frontend/src/boot/common.ts +++ b/packages/frontend/src/boot/common.ts @@ -22,7 +22,7 @@ import { getAccountFromId } from '@/scripts/get-account-from-id.js'; import { deckStore } from '@/ui/deck/deck-store.js'; import { miLocalStorage } from '@/local-storage.js'; import { fetchCustomEmojis } from '@/custom-emojis.js'; -import { setupRouter } from '@/global/router/definition.js'; +import { setupRouter } from '@/router/definition.js'; export async function common(createVue: () => App) { console.info(`Misskey v${version}`); diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts index 3a7bab89a..790f6df82 100644 --- a/packages/frontend/src/boot/main-boot.ts +++ b/packages/frontend/src/boot/main-boot.ts @@ -19,7 +19,7 @@ import { claimAchievement, claimedAchievements } from '@/scripts/achievements.js import { initializeSw } from '@/scripts/initialize-sw.js'; import { deckStore } from '@/ui/deck/deck-store.js'; import { emojiPicker } from '@/scripts/emoji-picker.js'; -import { mainRouter } from '@/global/router/main.js'; +import { mainRouter } from '@/router/main.js'; export async function mainBoot() { const { isClientUpdated } = await common(() => createApp( diff --git a/packages/frontend/src/components/MkAbuseReportWindow.vue b/packages/frontend/src/components/MkAbuseReportWindow.vue index 3afbee19c..60c9c075e 100644 --- a/packages/frontend/src/components/MkAbuseReportWindow.vue +++ b/packages/frontend/src/components/MkAbuseReportWindow.vue @@ -76,7 +76,7 @@ import { i18n } from '@/i18n.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; const props = defineProps<{ - user: Misskey.entities.User; + user: Misskey.entities.UserDetailed; initialComment?: string; }>(); diff --git a/packages/frontend/src/components/MkAchievements.vue b/packages/frontend/src/components/MkAchievements.vue index ac8bc39bb..3c6292d34 100644 --- a/packages/frontend/src/components/MkAchievements.vue +++ b/packages/frontend/src/components/MkAchievements.vue @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only - {{ c.text }} - + {{ c.text }} + {{ c.text }}

{{ button.text }} @@ -20,19 +20,19 @@ SPDX-License-Identifier: AGPL-3.0-only - + - + - + - + @@ -42,8 +42,8 @@ SPDX-License-Identifier: AGPL-3.0-only
@@ -52,7 +52,7 @@ SPDX-License-Identifier: AGPL-3.0-only -
+
@@ -68,7 +68,7 @@ import MkInput from '@/components/MkInput.vue'; import MkSwitch from '@/components/MkSwitch.vue'; import MkTextarea from '@/components/MkTextarea.vue'; import MkSelect from '@/components/MkSelect.vue'; -import { AsUiComponent } from '@/scripts/aiscript/ui.js'; +import { AsUiComponent, AsUiRoot, AsUiPostFormButton } from '@/scripts/aiscript/ui.js'; import MkFolder from '@/components/MkFolder.vue'; import MkPostForm from '@/components/MkPostForm.vue'; @@ -85,20 +85,32 @@ const props = withDefaults(defineProps<{ const c = props.component; function g(id) { - return props.components.find(x => x.value.id === id).value; + const v = props.components.find(x => x.value.id === id)?.value; + if (v) return v; + + return { + id: 'dummy', + type: 'root', + children: [], + } as AsUiRoot; } -const valueForSwitch = ref(c.default ?? false); +const valueForSwitch = ref('default' in c && typeof c.default === 'boolean' ? c.default : false); function onSwitchUpdate(v) { valueForSwitch.value = v; - if (c.onChange) c.onChange(v); + if ('onChange' in c && c.onChange) { + c.onChange(v as never); + } } function openPostForm() { + const form = (c as AsUiPostFormButton).form; + if (!form) return; + os.post({ - initialText: c.form.text, - initialCw: c.form.cw, + initialText: form.text, + initialCw: form.cw, instant: true, }); } diff --git a/packages/frontend/src/components/MkButton.vue b/packages/frontend/src/components/MkButton.vue index 8d4631968..70de6a851 100644 --- a/packages/frontend/src/components/MkButton.vue +++ b/packages/frontend/src/components/MkButton.vue @@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
diff --git a/packages/frontend/src/components/MkCaptcha.vue b/packages/frontend/src/components/MkCaptcha.vue index f60c721ea..7aa08cf51 100644 --- a/packages/frontend/src/components/MkCaptcha.vue +++ b/packages/frontend/src/components/MkCaptcha.vue @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only @@ -135,7 +135,7 @@ const selectedValue = ref(props.select?.default ?? null); const okButtonDisabledReason = computed(() => { if (props.input) { if (props.input.minLength) { - if ((inputValue.value || inputValue.value === '') && (inputValue.value as string).length < props.input.minLength) { + if (inputValue.value == null || (inputValue.value as string).length < props.input.minLength) { return 'charactersBelow'; } } diff --git a/packages/frontend/src/components/MkDrive.file.vue b/packages/frontend/src/components/MkDrive.file.vue index 8a74319f2..4ff1ae30a 100644 --- a/packages/frontend/src/components/MkDrive.file.vue +++ b/packages/frontend/src/components/MkDrive.file.vue @@ -47,7 +47,7 @@ import { i18n } from '@/i18n.js'; import { $i } from '@/account.js'; import { getDriveFileMenu } from '@/scripts/get-drive-file-menu.js'; import { deviceKind } from '@/scripts/device-kind.js'; -import { useRouter } from '@/global/router/supplier.js'; +import { useRouter } from '@/router/supplier.js'; const router = useRouter(); diff --git a/packages/frontend/src/components/MkDrive.folder.vue b/packages/frontend/src/components/MkDrive.folder.vue index 0d02aa5cb..b3569bd00 100644 --- a/packages/frontend/src/components/MkDrive.folder.vue +++ b/packages/frontend/src/components/MkDrive.folder.vue @@ -205,7 +205,7 @@ function onDragend() { } function go() { - emit('move', props.folder.id); + emit('move', props.folder); } function rename() { diff --git a/packages/frontend/src/components/MkDrive.vue b/packages/frontend/src/components/MkDrive.vue index 56b5425c5..083d1b58f 100644 --- a/packages/frontend/src/components/MkDrive.vue +++ b/packages/frontend/src/components/MkDrive.vue @@ -98,6 +98,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { nextTick, onActivated, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue'; import * as Misskey from 'misskey-js'; import MkButton from './MkButton.vue'; +import type { MenuItem } from '@/types/menu.js'; import XNavFolder from '@/components/MkDrive.navFolder.vue'; import XFolder from '@/components/MkDrive.folder.vue'; import XFile from '@/components/MkDrive.file.vue'; @@ -427,7 +428,7 @@ function chooseFolder(folderToChoose: Misskey.entities.DriveFolder) { } } -function move(target?: Misskey.entities.DriveFolder) { +function move(target?: Misskey.entities.DriveFolder | Misskey.entities.DriveFolder['id' | 'parentId']) { if (!target) { goRoot(); return; @@ -613,7 +614,7 @@ function fetchMoreFiles() { } function getMenu() { - return [{ + const menu: MenuItem[] = [{ type: 'switch', text: i18n.ts.keepOriginalUploading, ref: keepOriginal, @@ -634,7 +635,7 @@ function getMenu() { }, folder.value ? { text: i18n.ts.renameFolder, icon: 'ti ti-forms', - action: () => { renameFolder(folder.value); }, + action: () => { if (folder.value) renameFolder(folder.value); }, } : undefined, folder.value ? { text: i18n.ts.deleteFolder, icon: 'ti ti-trash', @@ -644,6 +645,8 @@ function getMenu() { icon: 'ti ti-folder-plus', action: () => { createFolder(); }, }]; + + return menu; } function showMenu(ev: MouseEvent) { diff --git a/packages/frontend/src/components/MkEmojiPicker.section.vue b/packages/frontend/src/components/MkEmojiPicker.section.vue index 6510701a9..e49d36f02 100644 --- a/packages/frontend/src/components/MkEmojiPicker.section.vue +++ b/packages/frontend/src/components/MkEmojiPicker.section.vue @@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
- (:{{ customEmojiTree.length }} :{{ emojis.length }}) + (:{{ customEmojiTree?.length }} :{{ emojis.length }})
import { ref, computed, Ref } from 'vue'; import { CustomEmojiFolderTree, getEmojiName } from '@/scripts/emojilist.js'; -import { i18n } from '../i18n.js'; +import { i18n } from '@/i18n.js'; import { customEmojis } from '@/custom-emojis.js'; import MkEmojiPickerSection from '@/components/MkEmojiPicker.section.vue'; @@ -87,7 +87,7 @@ function computeButtonTitle(ev: MouseEvent): void { elm.title = getEmojiName(emoji) ?? emoji; } -function nestedChosen(emoji: any, ev?: MouseEvent) { +function nestedChosen(emoji: any, ev: MouseEvent) { emit('chosen', emoji, ev); } diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue index e4ae8712c..dd6be332e 100644 --- a/packages/frontend/src/components/MkEmojiPicker.vue +++ b/packages/frontend/src/components/MkEmojiPicker.vue @@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+