feat: 時限ロール (#10145)
* feat: 時限ロール * クライアントから期限を確認できるように * リファクタとか * fix test * fix test * fix test * clean up
This commit is contained in:
parent
7c3a390763
commit
1c5291f818
16 changed files with 296 additions and 391 deletions
|
@ -11,6 +11,8 @@ import { UserCacheService } from '@/core/UserCacheService.js';
|
|||
import type { RoleCondFormulaValue } from '@/models/entities/Role.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { StreamMessages } from '@/server/api/stream/types.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||
|
||||
export type RolePolicies = {
|
||||
|
@ -56,6 +58,9 @@ export class RoleService implements OnApplicationShutdown {
|
|||
private rolesCache: Cache<Role[]>;
|
||||
private roleAssignmentByUserIdCache: Cache<RoleAssignment[]>;
|
||||
|
||||
public static AlreadyAssignedError = class extends Error {};
|
||||
public static NotAssignedError = class extends Error {};
|
||||
|
||||
constructor(
|
||||
@Inject(DI.redisSubscriber)
|
||||
private redisSubscriber: Redis.Redis,
|
||||
|
@ -72,6 +77,8 @@ export class RoleService implements OnApplicationShutdown {
|
|||
private metaService: MetaService,
|
||||
private userCacheService: UserCacheService,
|
||||
private userEntityService: UserEntityService,
|
||||
private globalEventService: GlobalEventService,
|
||||
private idService: IdService,
|
||||
) {
|
||||
//this.onMessage = this.onMessage.bind(this);
|
||||
|
||||
|
@ -128,6 +135,7 @@ export class RoleService implements OnApplicationShutdown {
|
|||
cached.push({
|
||||
...body,
|
||||
createdAt: new Date(body.createdAt),
|
||||
expiresAt: body.expiresAt ? new Date(body.expiresAt) : null,
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
@ -193,7 +201,10 @@ export class RoleService implements OnApplicationShutdown {
|
|||
|
||||
@bindThis
|
||||
public async getUserRoles(userId: User['id']) {
|
||||
const assigns = await this.roleAssignmentByUserIdCache.fetch(userId, () => this.roleAssignmentsRepository.findBy({ userId }));
|
||||
const now = Date.now();
|
||||
let assigns = await this.roleAssignmentByUserIdCache.fetch(userId, () => this.roleAssignmentsRepository.findBy({ userId }));
|
||||
// 期限切れのロールを除外
|
||||
assigns = assigns.filter(a => a.expiresAt == null || (a.expiresAt.getTime() > now));
|
||||
const assignedRoleIds = assigns.map(x => x.roleId);
|
||||
const roles = await this.rolesCache.fetch(null, () => this.rolesRepository.findBy({}));
|
||||
const assignedRoles = roles.filter(r => assignedRoleIds.includes(r.id));
|
||||
|
@ -207,7 +218,10 @@ export class RoleService implements OnApplicationShutdown {
|
|||
*/
|
||||
@bindThis
|
||||
public async getUserBadgeRoles(userId: User['id']) {
|
||||
const assigns = await this.roleAssignmentByUserIdCache.fetch(userId, () => this.roleAssignmentsRepository.findBy({ userId }));
|
||||
const now = Date.now();
|
||||
let assigns = await this.roleAssignmentByUserIdCache.fetch(userId, () => this.roleAssignmentsRepository.findBy({ userId }));
|
||||
// 期限切れのロールを除外
|
||||
assigns = assigns.filter(a => a.expiresAt == null || (a.expiresAt.getTime() > now));
|
||||
const assignedRoleIds = assigns.map(x => x.roleId);
|
||||
const roles = await this.rolesCache.fetch(null, () => this.rolesRepository.findBy({}));
|
||||
const assignedBadgeRoles = roles.filter(r => r.asBadge && assignedRoleIds.includes(r.id));
|
||||
|
@ -316,6 +330,65 @@ export class RoleService implements OnApplicationShutdown {
|
|||
return users;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async assign(userId: User['id'], roleId: Role['id'], expiresAt: Date | null = null): Promise<void> {
|
||||
const now = new Date();
|
||||
|
||||
const existing = await this.roleAssignmentsRepository.findOneBy({
|
||||
roleId: roleId,
|
||||
userId: userId,
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
if (existing.expiresAt && (existing.expiresAt.getTime() < now.getTime())) {
|
||||
await this.roleAssignmentsRepository.delete({
|
||||
roleId: roleId,
|
||||
userId: userId,
|
||||
});
|
||||
} else {
|
||||
throw new RoleService.AlreadyAssignedError();
|
||||
}
|
||||
}
|
||||
|
||||
const created = await this.roleAssignmentsRepository.insert({
|
||||
id: this.idService.genId(),
|
||||
createdAt: now,
|
||||
expiresAt: expiresAt,
|
||||
roleId: roleId,
|
||||
userId: userId,
|
||||
}).then(x => this.roleAssignmentsRepository.findOneByOrFail(x.identifiers[0]));
|
||||
|
||||
this.rolesRepository.update(roleId, {
|
||||
lastUsedAt: new Date(),
|
||||
});
|
||||
|
||||
this.globalEventService.publishInternalEvent('userRoleAssigned', created);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async unassign(userId: User['id'], roleId: Role['id']): Promise<void> {
|
||||
const now = new Date();
|
||||
|
||||
const existing = await this.roleAssignmentsRepository.findOneBy({ roleId, userId });
|
||||
if (existing == null) {
|
||||
throw new RoleService.NotAssignedError();
|
||||
} else if (existing.expiresAt && (existing.expiresAt.getTime() < now.getTime())) {
|
||||
await this.roleAssignmentsRepository.delete({
|
||||
roleId: roleId,
|
||||
userId: userId,
|
||||
});
|
||||
throw new RoleService.NotAssignedError();
|
||||
}
|
||||
|
||||
await this.roleAssignmentsRepository.delete(existing.id);
|
||||
|
||||
this.rolesRepository.update(roleId, {
|
||||
lastUsedAt: now,
|
||||
});
|
||||
|
||||
this.globalEventService.publishInternalEvent('userRoleUnassigned', existing);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public onApplicationShutdown(signal?: string | undefined) {
|
||||
this.redisSubscriber.off('message', this.onMessage);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue