feat(authorization): add admin API for community authorization

Add new endpoint POST /api/v1/admin/authorizations/community
that allows administrators to directly authorize users as community
managers without requiring the user to self-apply first.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-10 17:03:34 -08:00
parent 003bef1c76
commit f8cfb5e597
8 changed files with 137 additions and 2 deletions

View File

@ -1,8 +1,8 @@
import { Controller, Post, Body, UseGuards, HttpCode, HttpStatus } from '@nestjs/common'
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger'
import { AuthorizationApplicationService } from '@/application/services'
import { GrantProvinceCompanyCommand, GrantCityCompanyCommand } from '@/application/commands'
import { GrantProvinceCompanyDto, GrantCityCompanyDto } from '@/api/dto/request'
import { GrantCommunityCommand, GrantProvinceCompanyCommand, GrantCityCompanyCommand } from '@/application/commands'
import { GrantCommunityDto, GrantProvinceCompanyDto, GrantCityCompanyDto } from '@/api/dto/request'
import { CurrentUser } from '@/shared/decorators'
import { JwtAuthGuard } from '@/shared/guards'
@ -13,6 +13,25 @@ import { JwtAuthGuard } from '@/shared/guards'
export class AdminAuthorizationController {
constructor(private readonly applicationService: AuthorizationApplicationService) {}
@Post('community')
@HttpCode(HttpStatus.CREATED)
@ApiOperation({ summary: '授权社区(管理员)' })
@ApiResponse({ status: 201, description: '授权成功' })
async grantCommunity(
@CurrentUser() user: { userId: string; accountSequence: number },
@Body() dto: GrantCommunityDto,
): Promise<{ message: string }> {
const command = new GrantCommunityCommand(
dto.userId,
dto.accountSequence,
dto.communityName,
user.userId,
user.accountSequence,
)
await this.applicationService.grantCommunity(command)
return { message: '社区授权成功' }
}
@Post('province-company')
@HttpCode(HttpStatus.CREATED)
@ApiOperation({ summary: '授权正式省公司(管理员)' })

View File

@ -0,0 +1,20 @@
import { IsString, IsNotEmpty, MaxLength, IsNumber } from 'class-validator'
import { ApiProperty } from '@nestjs/swagger'
export class GrantCommunityDto {
@ApiProperty({ description: '用户ID' })
@IsString()
@IsNotEmpty({ message: '用户ID不能为空' })
userId: string
@ApiProperty({ description: '账户序列号' })
@IsNumber()
@IsNotEmpty({ message: '账户序列号不能为空' })
accountSequence: number
@ApiProperty({ description: '社区名称', example: '深圳社区' })
@IsString()
@IsNotEmpty({ message: '社区名称不能为空' })
@MaxLength(100, { message: '社区名称最大100字符' })
communityName: string
}

View File

@ -1,6 +1,7 @@
export * from './apply-community-auth.dto'
export * from './apply-auth-province.dto'
export * from './apply-auth-city.dto'
export * from './grant-community.dto'
export * from './grant-province-company.dto'
export * from './grant-city-company.dto'
export * from './revoke-authorization.dto'

View File

@ -0,0 +1,9 @@
export class GrantCommunityCommand {
constructor(
public readonly userId: string,
public readonly accountSequence: number,
public readonly communityName: string,
public readonly adminId: string,
public readonly adminAccountSequence: number,
) {}
}

View File

@ -1,6 +1,7 @@
export * from './apply-community-auth.command'
export * from './apply-auth-province-company.command'
export * from './apply-auth-city-company.command'
export * from './grant-community.command'
export * from './grant-province-company.command'
export * from './grant-city-company.command'
export * from './revoke-authorization.command'

View File

@ -30,6 +30,7 @@ import {
ApplyAuthProvinceCompanyResult,
ApplyAuthCityCompanyCommand,
ApplyAuthCityCompanyResult,
GrantCommunityCommand,
GrantProvinceCompanyCommand,
GrantCityCompanyCommand,
RevokeAuthorizationCommand,
@ -218,6 +219,24 @@ export class AuthorizationApplicationService {
}
}
/**
*
*/
async grantCommunity(command: GrantCommunityCommand): Promise<void> {
const userId = UserId.create(command.userId, command.accountSequence)
const adminId = AdminUserId.create(command.adminId, command.adminAccountSequence)
const authorization = AuthorizationRole.createCommunity({
userId,
communityName: command.communityName,
adminId,
})
await this.authorizationRepository.save(authorization)
await this.eventPublisher.publishAll(authorization.domainEvents)
authorization.clearDomainEvents()
}
/**
*
*/

View File

@ -9,6 +9,7 @@ import {
import { RoleType, AuthorizationStatus, MonthlyTargetType } from '@/domain/enums'
import { DomainError } from '@/shared/exceptions'
import {
CommunityAuthorizedEvent,
CommunityAuthRequestedEvent,
AuthProvinceCompanyRequestedEvent,
AuthCityCompanyRequestedEvent,
@ -215,6 +216,48 @@ export class AuthorizationRole extends AggregateRoot {
return auth
}
// 工厂方法 - 管理员直接授权社区
static createCommunity(params: {
userId: UserId
communityName: string
adminId: AdminUserId
}): AuthorizationRole {
const auth = new AuthorizationRole({
authorizationId: AuthorizationId.generate(),
userId: params.userId,
roleType: RoleType.COMMUNITY,
regionCode: RegionCode.create(params.communityName),
regionName: params.communityName,
status: AuthorizationStatus.AUTHORIZED,
displayTitle: params.communityName,
authorizedAt: new Date(),
authorizedBy: params.adminId,
revokedAt: null,
revokedBy: null,
revokeReason: null,
assessmentConfig: AssessmentConfig.forCommunity(),
requireLocalPercentage: 0,
exemptFromPercentageCheck: true,
benefitActive: true,
benefitActivatedAt: new Date(),
benefitDeactivatedAt: null,
currentMonthIndex: 1,
createdAt: new Date(),
updatedAt: new Date(),
})
auth.addDomainEvent(
new CommunityAuthorizedEvent({
authorizationId: auth.authorizationId.value,
userId: params.userId.value,
communityName: params.communityName,
authorizedBy: params.adminId.value,
}),
)
return auth
}
// 工厂方法 - 创建授权省公司
static createAuthProvinceCompany(params: {
userId: UserId

View File

@ -1,6 +1,29 @@
import { DomainEvent } from './domain-event.base'
import { RoleType } from '@/domain/enums'
// 社区授权事件(管理员直接授权)
export class CommunityAuthorizedEvent extends DomainEvent {
readonly eventType = 'authorization.community.authorized'
readonly aggregateId: string
readonly payload: {
authorizationId: string
userId: string
communityName: string
authorizedBy: string
}
constructor(data: {
authorizationId: string
userId: string
communityName: string
authorizedBy: string
}) {
super()
this.aggregateId = data.authorizationId
this.payload = data
}
}
// 社区授权申请事件
export class CommunityAuthRequestedEvent extends DomainEvent {
readonly eventType = 'authorization.community.requested'