diff --git a/CHANGELOG.md b/CHANGELOG.md index 87e127b2cb..c6d674b4f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ - Enhance: 画像エフェクトのパラメータ名の多言語対応 - Enhance: 依存ソフトウェアの更新 - Enhance: ノートを非表示にする相対期間を1ヶ月単位で自由に指定できるように +- Enhance: メールアドレス確認画面のUIを改善 - Fix: 投稿フォームでファイルのアップロードが中止または失敗した際のハンドリングを修正 - Fix: 一部の設定検索結果が存在しないパスになる問題を修正 (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/1171) @@ -62,6 +63,8 @@ - Enhance: ノートの削除処理の効率化 - Enhance: 全体的なパフォーマンスの向上 - Enhance: 依存ソフトウェアの更新 +- Enhance: `clips/list` APIがページネーションに対応しました +- Fix: `notes/mentions` で場合によっては並び順が正しく返されない問題を修正 - Fix: SystemWebhook設定でsecretを空に出来ない問題を修正 diff --git a/locales/index.d.ts b/locales/index.d.ts index eefd8a5ecb..c31a3f4e83 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -5001,7 +5001,7 @@ export interface Locale extends ILocale { /** * メールアドレスの確認中に問題が発生しました。リンクの有効期限が切れている可能性があります。 */ - "signupPendingError": string; + "emailVerificationFailedError": string; /** * 「内容を隠す」がオンの場合は注釈の記述が必要です。 */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index d04445282a..522f53ce4d 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1245,7 +1245,7 @@ releaseToRefresh: "離してリロード" refreshing: "リロード中" pullDownToRefresh: "引っ張ってリロード" useGroupedNotifications: "通知をグルーピング" -signupPendingError: "メールアドレスの確認中に問題が発生しました。リンクの有効期限が切れている可能性があります。" +emailVerificationFailedError: "メールアドレスの確認中に問題が発生しました。リンクの有効期限が切れている可能性があります。" cwNotationRequired: "「内容を隠す」がオンの場合は注釈の記述が必要です。" doReaction: "リアクションする" code: "コード" diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts index 23c085ee27..7325c53df0 100644 --- a/packages/backend/src/server/ServerService.ts +++ b/packages/backend/src/server/ServerService.ts @@ -238,30 +238,6 @@ export class ServerService implements OnApplicationShutdown { } }); - fastify.get<{ Params: { code: string } }>('/verify-email/:code', async (request, reply) => { - const profile = await this.userProfilesRepository.findOneBy({ - emailVerifyCode: request.params.code, - }); - - if (profile != null) { - await this.userProfilesRepository.update({ userId: profile.userId }, { - emailVerified: true, - emailVerifyCode: null, - }); - - this.globalEventService.publishMainStream(profile.userId, 'meUpdated', await this.userEntityService.pack(profile.userId, { id: profile.userId }, { - schema: 'MeDetailed', - includeSecrets: true, - })); - - reply.code(200).send('Verification succeeded! メールアドレスの認証に成功しました。'); - return; - } else { - reply.code(404).send('Verification failed. Please try again. メールアドレスの認証に失敗しました。もう一度お試しください'); - return; - } - }); - fastify.register(this.clientServerService.createServer); this.streamingApiServerService.attach(fastify.server); diff --git a/packages/backend/src/server/api/endpoint-list.ts b/packages/backend/src/server/api/endpoint-list.ts index c0c43dd5c9..f4aa07875d 100644 --- a/packages/backend/src/server/api/endpoint-list.ts +++ b/packages/backend/src/server/api/endpoint-list.ts @@ -412,6 +412,7 @@ export * as 'users/search' from './endpoints/users/search.js'; export * as 'users/search-by-username-and-host' from './endpoints/users/search-by-username-and-host.js'; export * as 'users/show' from './endpoints/users/show.js'; export * as 'users/update-memo' from './endpoints/users/update-memo.js'; +export * as 'verify-email' from './endpoints/verify-email.js'; export * as 'chat/messages/create-to-user' from './endpoints/chat/messages/create-to-user.js'; export * as 'chat/messages/create-to-room' from './endpoints/chat/messages/create-to-room.js'; export * as 'chat/messages/delete' from './endpoints/chat/messages/delete.js'; diff --git a/packages/backend/src/server/api/endpoints/clips/list.ts b/packages/backend/src/server/api/endpoints/clips/list.ts index 2e4a3ff820..af20ea9f8d 100644 --- a/packages/backend/src/server/api/endpoints/clips/list.ts +++ b/packages/backend/src/server/api/endpoints/clips/list.ts @@ -5,6 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; import type { ClipsRepository } from '@/models/_.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -29,7 +30,13 @@ export const meta = { export const paramDef = { type: 'object', - properties: {}, + properties: { + limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: 'string', format: 'misskey:id' }, + untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, + }, required: [], } as const; @@ -39,12 +46,14 @@ export default class extends Endpoint { // eslint- @Inject(DI.clipsRepository) private clipsRepository: ClipsRepository, + private queryService: QueryService, private clipEntityService: ClipEntityService, ) { super(meta, paramDef, async (ps, me) => { - const clips = await this.clipsRepository.findBy({ - userId: me.id, - }); + const query = this.queryService.makePaginationQuery(this.clipsRepository.createQueryBuilder('clip'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere('clip.userId = :userId', { userId: me.id }); + + const clips = await query.limit(ps.limit).getMany(); return await this.clipEntityService.packMany(clips, me); }); diff --git a/packages/backend/src/server/api/endpoints/notes/mentions.ts b/packages/backend/src/server/api/endpoints/notes/mentions.ts index 05ffdc1f97..e775bdb7fd 100644 --- a/packages/backend/src/server/api/endpoints/notes/mentions.ts +++ b/packages/backend/src/server/api/endpoints/notes/mentions.ts @@ -66,7 +66,7 @@ export default class extends Endpoint { // eslint- .orWhere(':meIdAsList <@ note.visibleUserIds'); })) // Avoid scanning primary key index - .orderBy('CONCAT(note.id)', 'DESC') + .orderBy('CONCAT(note.id)', (ps.sinceDate || ps.sinceId) ? 'ASC' : 'DESC') .innerJoinAndSelect('note.user', 'user') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') diff --git a/packages/backend/src/server/api/endpoints/verify-email.ts b/packages/backend/src/server/api/endpoints/verify-email.ts new file mode 100644 index 0000000000..e069ed59f2 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/verify-email.ts @@ -0,0 +1,66 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { UserProfilesRepository } from '@/models/_.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DI } from '@/di-symbols.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { ApiError } from '../error.js'; + +export const meta = { + requireCredential: false, + + tags: ['account'], + + errors: { + noSuchCode: { + message: 'No such code.', + code: 'NO_SUCH_CODE', + id: '97c1f576-e4b8-4b8a-a6dc-9cb65e7f6f85', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + code: { type: 'string' }, + }, + required: ['code'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + private userEntityService: UserEntityService, + private globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async (ps) => { + const profile = await this.userProfilesRepository.findOneBy({ + emailVerifyCode: ps.code, + }); + + if (profile == null) { + throw new ApiError(meta.errors.noSuchCode); + } + + await this.userProfilesRepository.update({ userId: profile.userId }, { + emailVerified: true, + emailVerifyCode: null, + }); + + this.globalEventService.publishMainStream(profile.userId, 'meUpdated', await this.userEntityService.pack(profile.userId, { id: profile.userId }, { + schema: 'MeDetailed', + includeSecrets: true, + })); + }); + } +} + diff --git a/packages/frontend/src/_boot_.ts b/packages/frontend/src/_boot_.ts index 354fb95544..111a4abbfd 100644 --- a/packages/frontend/src/_boot_.ts +++ b/packages/frontend/src/_boot_.ts @@ -16,7 +16,7 @@ import '@/style.scss'; import { mainBoot } from '@/boot/main-boot.js'; import { subBoot } from '@/boot/sub-boot.js'; -const subBootPaths = ['/share', '/auth', '/miauth', '/oauth', '/signup-complete', '/install-extensions']; +const subBootPaths = ['/share', '/auth', '/miauth', '/oauth', '/signup-complete', '/verify-email', '/install-extensions']; if (subBootPaths.some(i => window.location.pathname === i || window.location.pathname.startsWith(i + '/'))) { subBoot(); diff --git a/packages/frontend/src/components/MkButton.vue b/packages/frontend/src/components/MkButton.vue index a77ebd6ac5..b729128a21 100644 --- a/packages/frontend/src/components/MkButton.vue +++ b/packages/frontend/src/components/MkButton.vue @@ -36,6 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only + + diff --git a/packages/frontend/src/router.definition.ts b/packages/frontend/src/router.definition.ts index 57d9a860d6..e25e0fe161 100644 --- a/packages/frontend/src/router.definition.ts +++ b/packages/frontend/src/router.definition.ts @@ -202,6 +202,9 @@ export const ROUTE_DEF = [{ }, { path: '/signup-complete/:code', component: page(() => import('@/pages/signup-complete.vue')), +}, { + path: '/verify-email/:code', + component: page(() => import('@/pages/verify-email.vue')), }, { path: '/announcements', component: page(() => import('@/pages/announcements.vue')), diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index ae12547f35..170c20f163 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -1196,6 +1196,9 @@ type ClipsDeleteRequest = operations['clips___delete']['requestBody']['content'] // @public (undocumented) type ClipsFavoriteRequest = operations['clips___favorite']['requestBody']['content']['application/json']; +// @public (undocumented) +type ClipsListRequest = operations['clips___list']['requestBody']['content']['application/json']; + // @public (undocumented) type ClipsListResponse = operations['clips___list']['responses']['200']['content']['application/json']; @@ -1741,6 +1744,7 @@ declare namespace entities { ClipsCreateResponse, ClipsDeleteRequest, ClipsFavoriteRequest, + ClipsListRequest, ClipsListResponse, ClipsMyFavoritesResponse, ClipsNotesRequest, @@ -2129,6 +2133,7 @@ declare namespace entities { UsersUpdateMemoRequest, V2AdminEmojiListRequest, V2AdminEmojiListResponse, + VerifyEmailRequest, Error_2 as Error, UserLite, UserDetailedNotMeOnly, @@ -3807,6 +3812,9 @@ type V2AdminEmojiListRequest = operations['v2___admin___emoji___list']['requestB // @public (undocumented) type V2AdminEmojiListResponse = operations['v2___admin___emoji___list']['responses']['200']['content']['application/json']; +// @public (undocumented) +type VerifyEmailRequest = operations['verify-email']['requestBody']['content']['application/json']; + // Warnings were encountered during analysis: // // src/entities.ts:55:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts index 5407b7a653..c4428efcc2 100644 --- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts +++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts @@ -4762,5 +4762,16 @@ declare module '../api.js' { params: P, credential?: string | null, ): Promise>; + + /** + * No description provided. + * + * **Credential required**: *No* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; } } diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts index d7cb2a46eb..4b83a9dd9b 100644 --- a/packages/misskey-js/src/autogen/endpoint.ts +++ b/packages/misskey-js/src/autogen/endpoint.ts @@ -264,6 +264,7 @@ import type { ClipsCreateResponse, ClipsDeleteRequest, ClipsFavoriteRequest, + ClipsListRequest, ClipsListResponse, ClipsMyFavoritesResponse, ClipsNotesRequest, @@ -652,6 +653,7 @@ import type { UsersUpdateMemoRequest, V2AdminEmojiListRequest, V2AdminEmojiListResponse, + VerifyEmailRequest, } from './entities.js'; export type Endpoints = { @@ -829,7 +831,7 @@ export type Endpoints = { 'clips/create': { req: ClipsCreateRequest; res: ClipsCreateResponse }; 'clips/delete': { req: ClipsDeleteRequest; res: EmptyResponse }; 'clips/favorite': { req: ClipsFavoriteRequest; res: EmptyResponse }; - 'clips/list': { req: EmptyRequest; res: ClipsListResponse }; + 'clips/list': { req: ClipsListRequest; res: ClipsListResponse }; 'clips/my-favorites': { req: EmptyRequest; res: ClipsMyFavoritesResponse }; 'clips/notes': { req: ClipsNotesRequest; res: ClipsNotesResponse }; 'clips/remove-note': { req: ClipsRemoveNoteRequest; res: EmptyResponse }; @@ -1083,6 +1085,7 @@ export type Endpoints = { 'users/show': { req: UsersShowRequest; res: UsersShowResponse }; 'users/update-memo': { req: UsersUpdateMemoRequest; res: EmptyResponse }; 'v2/admin/emoji/list': { req: V2AdminEmojiListRequest; res: V2AdminEmojiListResponse }; + 'verify-email': { req: VerifyEmailRequest; res: EmptyResponse }; }; /** diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts index a14febb6e6..4ebe9a5155 100644 --- a/packages/misskey-js/src/autogen/entities.ts +++ b/packages/misskey-js/src/autogen/entities.ts @@ -267,6 +267,7 @@ export type ClipsCreateRequest = operations['clips___create']['requestBody']['co export type ClipsCreateResponse = operations['clips___create']['responses']['200']['content']['application/json']; export type ClipsDeleteRequest = operations['clips___delete']['requestBody']['content']['application/json']; export type ClipsFavoriteRequest = operations['clips___favorite']['requestBody']['content']['application/json']; +export type ClipsListRequest = operations['clips___list']['requestBody']['content']['application/json']; export type ClipsListResponse = operations['clips___list']['responses']['200']['content']['application/json']; export type ClipsMyFavoritesResponse = operations['clips___my-favorites']['responses']['200']['content']['application/json']; export type ClipsNotesRequest = operations['clips___notes']['requestBody']['content']['application/json']; @@ -655,3 +656,4 @@ export type UsersShowResponse = operations['users___show']['responses']['200'][' export type UsersUpdateMemoRequest = operations['users___update-memo']['requestBody']['content']['application/json']; export type V2AdminEmojiListRequest = operations['v2___admin___emoji___list']['requestBody']['content']['application/json']; export type V2AdminEmojiListResponse = operations['v2___admin___emoji___list']['responses']['200']['content']['application/json']; +export type VerifyEmailRequest = operations['verify-email']['requestBody']['content']['application/json']; diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 05ac143762..3525d082d5 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -3906,6 +3906,15 @@ export type paths = { */ post: operations['v2___admin___emoji___list']; }; + '/verify-email': { + /** + * verify-email + * @description No description provided. + * + * **Credential required**: *No* + */ + post: operations['verify-email']; + }; }; export type webhooks = Record; export type components = { @@ -18254,6 +18263,20 @@ export interface operations { }; }; clips___list: { + requestBody: { + content: { + 'application/json': { + /** @default 10 */ + limit?: number; + /** Format: misskey:id */ + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; + sinceDate?: number; + untilDate?: number; + }; + }; + }; responses: { /** @description OK (with results) */ 200: { @@ -36387,5 +36410,67 @@ export interface operations { }; }; }; + 'verify-email': { + requestBody: { + content: { + 'application/json': { + code: string; + }; + }; + }; + responses: { + /** @description OK (without any results) */ + 204: { + headers: { + [name: string]: unknown; + }; + }; + /** @description Client error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; }