From 3980b2ca55cdf6a694ef8cad911e161672c0444f Mon Sep 17 00:00:00 2001 From: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Thu, 5 Mar 2026 19:24:30 +0900 Subject: fix: review fixes (#17208) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: OAuthのContent-Typeを正しく判定するように * fix(frontend): fix outdated comments * fix: storagePersistenceのtop-level awaitを解消 * fix * fix(frontend): add comment Co-Authored-By: anatawa12 * fix * fix: rename `users/get-following-users-by-birthday` * fix: fix types * Update MkForm.vue * refactor utility/storage.ts --------- Co-authored-by: anatawa12 Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com> --- CHANGELOG.md | 2 +- packages/backend/src/server/api/endpoint-list.ts | 2 +- .../src/server/api/endpoints/admin/update-meta.ts | 2 +- .../src/server/api/endpoints/users/following.ts | 4 +- .../users/get-following-birthday-users.ts | 167 --------------------- .../users/get-following-users-by-birthday.ts | 167 +++++++++++++++++++++ .../src/server/oauth/OAuth2ProviderService.ts | 4 +- packages/frontend/src/components/MkDraggable.vue | 1 + packages/frontend/src/components/MkForm.vue | 2 +- .../src/pages/admin/RolesEditorFormula.vue | 2 +- .../src/pages/page-editor/page-editor.blocks.vue | 2 +- packages/frontend/src/pages/settings/index.vue | 4 +- packages/frontend/src/pages/settings/other.vue | 4 +- packages/frontend/src/utility/storage.ts | 12 +- .../src/widgets/WidgetBirthdayFollowings.user.vue | 2 +- .../src/widgets/WidgetBirthdayFollowings.vue | 2 +- packages/misskey-js/etc/misskey-js.api.md | 8 +- packages/misskey-js/src/autogen/apiClientJSDoc.ts | 4 +- packages/misskey-js/src/autogen/endpoint.ts | 6 +- packages/misskey-js/src/autogen/entities.ts | 4 +- packages/misskey-js/src/autogen/types.ts | 12 +- 21 files changed, 214 insertions(+), 199 deletions(-) delete mode 100644 packages/backend/src/server/api/endpoints/users/get-following-birthday-users.ts create mode 100644 packages/backend/src/server/api/endpoints/users/get-following-users-by-birthday.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 24271d7fa0..520b202f59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ ## 2026.2.0 ### Note -- `users/following` の `birthday` プロパティは非推奨になりました。代わりに `users/get-following-birthday-users` をご利用ください。 +- `users/following` の `birthday` プロパティは非推奨になりました。代わりに `users/get-following-users-by-birthday` をご利用ください。 ### General - Enhance: 「もうすぐ誕生日のユーザー」ウィジェットで、誕生日が至近のユーザーも表示できるように diff --git a/packages/backend/src/server/api/endpoint-list.ts b/packages/backend/src/server/api/endpoint-list.ts index 9311c80eaa..6679005c3c 100644 --- a/packages/backend/src/server/api/endpoint-list.ts +++ b/packages/backend/src/server/api/endpoint-list.ts @@ -391,7 +391,7 @@ export * as 'users/featured-notes' from './endpoints/users/featured-notes.js'; export * as 'users/flashs' from './endpoints/users/flashs.js'; export * as 'users/followers' from './endpoints/users/followers.js'; export * as 'users/following' from './endpoints/users/following.js'; -export * as 'users/get-following-birthday-users' from './endpoints/users/get-following-birthday-users.js'; +export * as 'users/get-following-users-by-birthday' from './endpoints/users/get-following-users-by-birthday.js'; export * as 'users/gallery/posts' from './endpoints/users/gallery/posts.js'; export * as 'users/get-frequently-replied-users' from './endpoints/users/get-frequently-replied-users.js'; export * as 'users/lists/create' from './endpoints/users/lists/create.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index 7a8dfc4555..372fe3a25f 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -341,7 +341,7 @@ export default class extends Endpoint { // eslint- if (ps.clientOptions !== undefined) { set.clientOptions = { - ...serverSettings.clientOptions, + ...this.serverSettings.clientOptions, ...ps.clientOptions, }; } diff --git a/packages/backend/src/server/api/endpoints/users/following.ts b/packages/backend/src/server/api/endpoints/users/following.ts index 3b3352858f..4defcc9dcf 100644 --- a/packages/backend/src/server/api/endpoints/users/following.ts +++ b/packages/backend/src/server/api/endpoints/users/following.ts @@ -86,7 +86,7 @@ export const paramDef = { sinceDate: { type: 'integer' }, untilDate: { type: 'integer' }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - birthday: { ...birthdaySchema, nullable: true, description: '@deprecated use get-following-birthday-users instead.' }, + birthday: { ...birthdaySchema, nullable: true, description: '@deprecated use get-following-users-by-birthday instead.' }, }, }, ], @@ -146,7 +146,7 @@ export default class extends Endpoint { // eslint- .andWhere('following.followerId = :userId', { userId: user.id }) .innerJoinAndSelect('following.followee', 'followee'); - // @deprecated use get-following-birthday-users instead. + // @deprecated use get-following-users-by-birthday instead. if (ps.birthday) { query.innerJoin(this.userProfilesRepository.metadata.targetName, 'followeeProfile', 'followeeProfile.userId = following.followeeId'); diff --git a/packages/backend/src/server/api/endpoints/users/get-following-birthday-users.ts b/packages/backend/src/server/api/endpoints/users/get-following-birthday-users.ts deleted file mode 100644 index 124114244e..0000000000 --- a/packages/backend/src/server/api/endpoints/users/get-following-birthday-users.ts +++ /dev/null @@ -1,167 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { Inject, Injectable } from '@nestjs/common'; -import { Brackets } from 'typeorm'; -import { DI } from '@/di-symbols.js'; -import type { - FollowingsRepository, - UserProfilesRepository, -} from '@/models/_.js'; -import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import type { Packed } from '@/misc/json-schema.js'; - -export const meta = { - tags: ['users'], - - requireCredential: true, - kind: 'read:account', - - description: 'Find users who have a birthday on the specified range.', - - res: { - type: 'array', - optional: false, nullable: false, - items: { - type: 'object', - optional: false, nullable: false, - properties: { - id: { - type: 'string', - optional: false, nullable: false, - format: 'misskey:id', - }, - birthday: { - type: 'string', - optional: false, nullable: false, - }, - user: { - type: 'object', - optional: false, nullable: false, - ref: 'UserLite', - }, - }, - }, - }, -} as const; - -export const paramDef = { - type: 'object', - properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - offset: { type: 'integer', default: 0 }, - birthday: { - oneOf: [{ - type: 'object', - properties: { - month: { type: 'integer', minimum: 1, maximum: 12 }, - day: { type: 'integer', minimum: 1, maximum: 31 }, - }, - required: ['month', 'day'], - }, { - type: 'object', - properties: { - begin: { - type: 'object', - properties: { - month: { type: 'integer', minimum: 1, maximum: 12 }, - day: { type: 'integer', minimum: 1, maximum: 31 }, - }, - required: ['month', 'day'], - }, - end: { - type: 'object', - properties: { - month: { type: 'integer', minimum: 1, maximum: 12 }, - day: { type: 'integer', minimum: 1, maximum: 31 }, - }, - required: ['month', 'day'], - }, - }, - required: ['begin', 'end'], - }], - }, - }, - required: ['birthday'], -} as const; - -@Injectable() -export default class extends Endpoint { // eslint-disable-line import/no-default-export - constructor( - @Inject(DI.userProfilesRepository) - private userProfilesRepository: UserProfilesRepository, - @Inject(DI.followingsRepository) - private followingsRepository: FollowingsRepository, - - private userEntityService: UserEntityService, - ) { - super(meta, paramDef, async (ps, me) => { - const query = this.followingsRepository - .createQueryBuilder('following') - .andWhere('following.followerId = :userId', { userId: me.id }) - .innerJoin(this.userProfilesRepository.metadata.targetName, 'followeeProfile', 'followeeProfile.userId = following.followeeId'); - - if (Object.hasOwn(ps.birthday, 'begin') && Object.hasOwn(ps.birthday, 'end')) { - const range = ps.birthday as { begin: { month: number; day: number }; end: { month: number; day: number }; }; - - // 誕生日は mmdd の形式の最大4桁の数字(例: 8月30日 → 830)でインデックスが効くようになっているので、その形式に変換 - const begin = range.begin.month * 100 + range.begin.day; - const end = range.end.month * 100 + range.end.day; - - if (begin <= end) { - query.andWhere('get_birthday_date(followeeProfile.birthday) BETWEEN :begin AND :end', { begin, end }); - } else { - // 12/31 から 1/1 の範囲を取得するために OR で対応 - query.andWhere(new Brackets(qb => { - qb.where('get_birthday_date(followeeProfile.birthday) BETWEEN :begin AND 1231', { begin }); - qb.orWhere('get_birthday_date(followeeProfile.birthday) BETWEEN 101 AND :end', { end }); - })); - } - } else { - const { month, day } = ps.birthday as { month: number; day: number }; - // なぜか get_birthday_date() = :birthday だとインデックスが効かないので、BETWEEN で対応 - query.andWhere('get_birthday_date(followeeProfile.birthday) BETWEEN :birthday AND :birthday', { birthday: month * 100 + day }); - } - - query.select('following.followeeId', 'user_id'); - query.addSelect('get_birthday_date(followeeProfile.birthday)', 'birthday_date'); - query.orderBy('birthday_date', 'ASC'); - - const birthdayUsers = await query - .offset(ps.offset).limit(ps.limit) - .getRawMany<{ birthday_date: number; user_id: string }>(); - - const users = new Map>(( - await this.userEntityService.packMany( - birthdayUsers.map(u => u.user_id), - me, - { schema: 'UserLite' }, - ) - ).map(u => [u.id, u])); - - return birthdayUsers - .map(item => { - const birthday = new Date(); - birthday.setHours(0, 0, 0, 0); - // item.birthday_date は mmdd の形式の最大4桁の数字(例: 8月30日 → 830)で出力されるので、日付に戻してDateオブジェクトに設定 - birthday.setMonth(Math.floor(item.birthday_date / 100) - 1, item.birthday_date % 100); - - if (birthday.getTime() < new Date().setHours(0, 0, 0, 0)) { - birthday.setFullYear(new Date().getFullYear() + 1); - } - - const birthdayStr = `${birthday.getFullYear()}-${(birthday.getMonth() + 1).toString().padStart(2, '0')}-${(birthday.getDate()).toString().padStart(2, '0')}`; - return { - id: item.user_id, - birthday: birthdayStr, - user: users.get(item.user_id), - }; - }) - .filter(item => item.user != null) - .map(item => item as { id: string; birthday: string; user: Packed<'UserLite'> }); - }); - } -} diff --git a/packages/backend/src/server/api/endpoints/users/get-following-users-by-birthday.ts b/packages/backend/src/server/api/endpoints/users/get-following-users-by-birthday.ts new file mode 100644 index 0000000000..947c19d81e --- /dev/null +++ b/packages/backend/src/server/api/endpoints/users/get-following-users-by-birthday.ts @@ -0,0 +1,167 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import { Brackets } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import type { + FollowingsRepository, + UserProfilesRepository, +} from '@/models/_.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import type { Packed } from '@/misc/json-schema.js'; + +export const meta = { + tags: ['users'], + + requireCredential: true, + kind: 'read:account', + + description: 'Retrieve users who have a birthday on the specified range.', + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'misskey:id', + }, + birthday: { + type: 'string', + optional: false, nullable: false, + }, + user: { + type: 'object', + optional: false, nullable: false, + ref: 'UserLite', + }, + }, + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + offset: { type: 'integer', default: 0 }, + birthday: { + oneOf: [{ + type: 'object', + properties: { + month: { type: 'integer', minimum: 1, maximum: 12 }, + day: { type: 'integer', minimum: 1, maximum: 31 }, + }, + required: ['month', 'day'], + }, { + type: 'object', + properties: { + begin: { + type: 'object', + properties: { + month: { type: 'integer', minimum: 1, maximum: 12 }, + day: { type: 'integer', minimum: 1, maximum: 31 }, + }, + required: ['month', 'day'], + }, + end: { + type: 'object', + properties: { + month: { type: 'integer', minimum: 1, maximum: 12 }, + day: { type: 'integer', minimum: 1, maximum: 31 }, + }, + required: ['month', 'day'], + }, + }, + required: ['begin', 'end'], + }], + }, + }, + required: ['birthday'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + + private userEntityService: UserEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.followingsRepository + .createQueryBuilder('following') + .andWhere('following.followerId = :userId', { userId: me.id }) + .innerJoin(this.userProfilesRepository.metadata.targetName, 'followeeProfile', 'followeeProfile.userId = following.followeeId'); + + if (Object.hasOwn(ps.birthday, 'begin') && Object.hasOwn(ps.birthday, 'end')) { + const range = ps.birthday as { begin: { month: number; day: number }; end: { month: number; day: number }; }; + + // 誕生日は mmdd の形式の最大4桁の数字(例: 8月30日 → 830)でインデックスが効くようになっているので、その形式に変換 + const begin = range.begin.month * 100 + range.begin.day; + const end = range.end.month * 100 + range.end.day; + + if (begin <= end) { + query.andWhere('get_birthday_date(followeeProfile.birthday) BETWEEN :begin AND :end', { begin, end }); + } else { + // 12/31 から 1/1 の範囲を取得するために OR で対応 + query.andWhere(new Brackets(qb => { + qb.where('get_birthday_date(followeeProfile.birthday) BETWEEN :begin AND 1231', { begin }); + qb.orWhere('get_birthday_date(followeeProfile.birthday) BETWEEN 101 AND :end', { end }); + })); + } + } else { + const { month, day } = ps.birthday as { month: number; day: number }; + // なぜか get_birthday_date() = :birthday だとインデックスが効かないので、BETWEEN で対応 + query.andWhere('get_birthday_date(followeeProfile.birthday) BETWEEN :birthday AND :birthday', { birthday: month * 100 + day }); + } + + query.select('following.followeeId', 'user_id'); + query.addSelect('get_birthday_date(followeeProfile.birthday)', 'birthday_date'); + query.orderBy('birthday_date', 'ASC'); + + const birthdayUsers = await query + .offset(ps.offset).limit(ps.limit) + .getRawMany<{ birthday_date: number; user_id: string }>(); + + const users = new Map>(( + await this.userEntityService.packMany( + birthdayUsers.map(u => u.user_id), + me, + { schema: 'UserLite' }, + ) + ).map(u => [u.id, u])); + + return birthdayUsers + .map(item => { + const birthday = new Date(); + birthday.setHours(0, 0, 0, 0); + // item.birthday_date は mmdd の形式の最大4桁の数字(例: 8月30日 → 830)で出力されるので、日付に戻してDateオブジェクトに設定 + birthday.setMonth(Math.floor(item.birthday_date / 100) - 1, item.birthday_date % 100); + + if (birthday.getTime() < new Date().setHours(0, 0, 0, 0)) { + birthday.setFullYear(new Date().getFullYear() + 1); + } + + const birthdayStr = `${birthday.getFullYear()}-${(birthday.getMonth() + 1).toString().padStart(2, '0')}-${(birthday.getDate()).toString().padStart(2, '0')}`; + return { + id: item.user_id, + birthday: birthdayStr, + user: users.get(item.user_id), + }; + }) + .filter(item => item.user != null) + .map(item => item as { id: string; birthday: string; user: Packed<'UserLite'> }); + }); + } +} diff --git a/packages/backend/src/server/oauth/OAuth2ProviderService.ts b/packages/backend/src/server/oauth/OAuth2ProviderService.ts index 47f4bf947d..840c34b806 100644 --- a/packages/backend/src/server/oauth/OAuth2ProviderService.ts +++ b/packages/backend/src/server/oauth/OAuth2ProviderService.ts @@ -142,7 +142,9 @@ async function discoverClientInformation(logger: Logger, httpRequestService: Htt redirectUris.push(...httpLinkHeader.parse(linkHeader).get('rel', 'redirect_uri').map(r => r.uri)); } - if (res.headers.get('content-type')?.includes('application/json')) { + const contentType = res.headers.get('content-type'); + const mediaType = contentType ? contentType.split(';')[0].trim() : null; + if (mediaType === 'application/json') { // Client discovery via JSON document (11 July 2024 spec) // https://indieauth.spec.indieweb.org/#client-metadata // "Clients SHOULD have a JSON [RFC7159] document at their client_id URL containing diff --git a/packages/frontend/src/components/MkDraggable.vue b/packages/frontend/src/components/MkDraggable.vue index 07fa1045b9..6e2e038f87 100644 --- a/packages/frontend/src/components/MkDraggable.vue +++ b/packages/frontend/src/components/MkDraggable.vue @@ -109,6 +109,7 @@ function onDragstart(ev: DragEvent, item: T) { // Chromeのバグで、Dragstartハンドラ内ですぐにDOMを変更する(=リアクティブなプロパティを変更する)とDragが終了してしまう // SEE: https://stackoverflow.com/questions/19639969/html5-dragend-event-firing-immediately + // SEE: https://issues.chromium.org/issues/41150279 window.setTimeout(() => { dragging.value = true; }, 10); diff --git a/packages/frontend/src/components/MkForm.vue b/packages/frontend/src/components/MkForm.vue index 1ece0ad4c3..f2360e8cdd 100644 --- a/packages/frontend/src/components/MkForm.vue +++ b/packages/frontend/src/components/MkForm.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only -->