diff options
| author | yukineko <27853966+hideki0403@users.noreply.github.com> | 2023-07-15 09:57:58 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-07-15 09:57:58 +0900 |
| commit | 02957a1b5daaaf821ce21c11cc47cf169c4fc535 (patch) | |
| tree | ecaa8fee0d547bb3733551ccebf85281357b9feb /packages/backend/src/server/api/endpoints/admin/invite | |
| parent | fix(build): d.ts生成時にexport defaultを生成するように (#11280) (diff) | |
| download | misskey-02957a1b5daaaf821ce21c11cc47cf169c4fc535.tar.gz misskey-02957a1b5daaaf821ce21c11cc47cf169c4fc535.tar.bz2 misskey-02957a1b5daaaf821ce21c11cc47cf169c4fc535.zip | |
enhance: 招待機能の改善 (#11195)
* refactor(backend): 招待機能を改修
* feat(backend): 招待コードのcreate/delete/listエンドポイントを追加
* add(misskey-js): エンドポイントと型を追加
* change(backend): metaでinvite関連の情報も返すように
* add(misskey-js): エンドポイントと型を追加
* add(backend): `/endpoints/invite/limit`を追加
* fix: createdByがnullableではなかったのを修正
* fix: relationが取得できていなかった問題を修正
* fix: パラメータを間違えていたのを修正
* feat(client): 招待ページを実装
* change(client): インスタンスメニューの「招待」押した場合に招待ページに飛ぶように変更
* feat: 招待コードをコピーできるように
* change(backend): metaに招待コード発行に関する情報を持たせるのをやめる
* feat: ロールごとに招待コードの発行上限数などを設定できるように
* change(client): 招待コードをコピーしたときにダイアログを出すように
* add: 招待に関する管理者用のエンドポイントを追加
* change(backend): モデレーターであれば作成者以外でも招待コードを削除できるように
* change(backend): admin/invite/listはオフセットでページネーションするように
* feat(client): 招待コードの管理ページを追加
* feat(client): 招待コードのリストをソートできるように
* change: `admin/invite/create`のレスポンスを修正
* fix(client): 有効期限を指定できていなかった問題を修正
* refactor: 必要のない箇所を削除
* perf(backend): use limit() instead of take()
* change(client): 作成ボタンを見た目を変更
* refactor: 招待コードの生成部分を共通化し、コード内に"01OI"のいずれかの文字を含まないように
* fix(client): paginationの仕様が変わっていたので修正
* change(backend): expiresAtパラメータのnullを許容
* change(client): 有効期限を設けないときは日付の入力欄を非表示に
* fix: 自身のポリシーよりもインスタンス側のポリシーが優先表示される問題を修正
* fix: n時間のときに「n時間間」となってしまうのを修正
* fix(backend): ポリシーが途中で変更されたときに作成可能数がマイナス表記になってしまうのを修正
* change(client): 招待コードのユーザー名が不明な理由を表示するように
* update: CHANGELOG.md
* lint
* refactor
* refactor
* tweak ui
* :art:
* :art:
* add(backend): indexを追加
* change(backend): indexの追加に伴う変更
* change(client): インスタンスメニューの「招待」の場所を変更
* add(frontend): MkInviteCode用のstorybookを追加
* Update misskey-js.api.md
* fix(misskey-js): InviteのcreatedByの型が間違っていたのを修正
---------
Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
Co-authored-by: tamaina <tamaina@hotmail.co.jp>
Diffstat (limited to 'packages/backend/src/server/api/endpoints/admin/invite')
| -rw-r--r-- | packages/backend/src/server/api/endpoints/admin/invite/create.ts | 80 | ||||
| -rw-r--r-- | packages/backend/src/server/api/endpoints/admin/invite/list.ts | 70 |
2 files changed, 150 insertions, 0 deletions
diff --git a/packages/backend/src/server/api/endpoints/admin/invite/create.ts b/packages/backend/src/server/api/endpoints/admin/invite/create.ts new file mode 100644 index 0000000000..664b4d819f --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/invite/create.ts @@ -0,0 +1,80 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { RegistrationTicketsRepository } from '@/models/index.js'; +import { InviteCodeEntityService } from '@/core/entities/InviteCodeEntityService.js'; +import { IdService } from '@/core/IdService.js'; +import { DI } from '@/di-symbols.js'; +import { generateInviteCode } from '@/misc/generate-invite-code.js'; +import { ApiError } from '../../../error.js'; + +export const meta = { + tags: ['admin'], + + requireCredential: true, + requireModerator: true, + + errors: { + invalidDateTime: { + message: 'Invalid date-time format', + code: 'INVALID_DATE_TIME', + id: 'f1380b15-3760-4c6c-a1db-5c3aaf1cbd49', + }, + }, + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + properties: { + code: { + type: 'string', + optional: false, nullable: false, + example: 'GR6S02ERUA5VR', + }, + }, + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + count: { type: 'integer', minimum: 1, maximum: 100, default: 1 }, + expiresAt: { type: 'string', nullable: true }, + }, + required: [], +} as const; + +// eslint-disable-next-line import/no-default-export +@Injectable() +export default class extends Endpoint<typeof meta, typeof paramDef> { + constructor( + @Inject(DI.registrationTicketsRepository) + private registrationTicketsRepository: RegistrationTicketsRepository, + + private inviteCodeEntityService: InviteCodeEntityService, + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + if (ps.expiresAt && isNaN(Date.parse(ps.expiresAt))) { + throw new ApiError(meta.errors.invalidDateTime); + } + + const ticketsPromises = []; + + for (let i = 0; i < ps.count; i++) { + ticketsPromises.push(this.registrationTicketsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : null, + code: generateInviteCode(), + }).then(x => this.registrationTicketsRepository.findOneByOrFail(x.identifiers[0]))); + } + + const tickets = await Promise.all(ticketsPromises); + return await this.inviteCodeEntityService.packMany(tickets, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/invite/list.ts b/packages/backend/src/server/api/endpoints/admin/invite/list.ts new file mode 100644 index 0000000000..5d7a7f632c --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/invite/list.ts @@ -0,0 +1,70 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { RegistrationTicketsRepository } from '@/models/index.js'; +import { InviteCodeEntityService } from '@/core/entities/InviteCodeEntityService.js'; +import { DI } from '@/di-symbols.js'; + +export const meta = { + tags: ['admin'], + + requireCredential: true, + requireModerator: true, + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 }, + offset: { type: 'integer', default: 0 }, + type: { type: 'string', enum: ['unused', 'used', 'expired', 'all'], default: 'all' }, + sort: { type: 'string', enum: ['+createdAt', '-createdAt', '+usedAt', '-usedAt'] }, + }, + required: [], +} as const; + +// eslint-disable-next-line import/no-default-export +@Injectable() +export default class extends Endpoint<typeof meta, typeof paramDef> { + constructor( + @Inject(DI.registrationTicketsRepository) + private registrationTicketsRepository: RegistrationTicketsRepository, + + private inviteCodeEntityService: InviteCodeEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.registrationTicketsRepository.createQueryBuilder('ticket') + .leftJoinAndSelect('ticket.createdBy', 'createdBy') + .leftJoinAndSelect('ticket.usedBy', 'usedBy'); + + switch (ps.type) { + case 'unused': query.andWhere('ticket.usedBy IS NULL'); break; + case 'used': query.andWhere('ticket.usedBy IS NOT NULL'); break; + case 'expired': query.andWhere('ticket.expiresAt < :now', { now: new Date() }); break; + } + + switch (ps.sort) { + case '+createdAt': query.orderBy('ticket.createdAt', 'DESC'); break; + case '-createdAt': query.orderBy('ticket.createdAt', 'ASC'); break; + case '+usedAt': query.orderBy('ticket.usedAt', 'DESC', 'NULLS LAST'); break; + case '-usedAt': query.orderBy('ticket.usedAt', 'ASC', 'NULLS FIRST'); break; + default: query.orderBy('ticket.id', 'DESC'); break; + } + + query.limit(ps.limit); + query.skip(ps.offset); + + const tickets = await query.getMany(); + + return await this.inviteCodeEntityService.packMany(tickets, me); + }); + } +} |