import { Controller, Get, NotFoundException, Param, Query, } from '@nestjs/common'; import { ApiOkResponse, ApiOperation, ApiProperty, ApiPropertyOptional, ApiTags, } from '@nestjs/swagger'; import { IdentityService } from '@task-stream/api/identity/application'; import { User } from '@task-stream/api/identity/domain'; import { ApiReadErrors } from '@task-stream/api/shared/presenters'; import { Type } from 'class-transformer'; import { IsInt, IsOptional, IsString, Max, MaxLength, Min, } from 'class-validator'; import { AccessToken, ApiPireonAuth, CurrentUser, } from '../auth/auth.decorators'; export class UserDto { @ApiProperty({ description: 'Pireon user id' }) id!: string; @ApiProperty() name!: string; @ApiProperty() email!: string; @ApiProperty() isActive!: boolean; } export class MeDto extends UserDto { @ApiProperty({ type: [String], description: 'Global roles in Pireon' }) roles!: string[]; @ApiProperty({ type: [String], description: 'Roles inside the task-stream OAuth client', }) appRoles!: string[]; } export class UserSearchQueryDto { @ApiPropertyOptional({ type: String, description: 'Name or email', default: '', }) @IsOptional() @IsString() @MaxLength(100) search = ''; @ApiPropertyOptional({ type: Number, default: 20, minimum: 1, maximum: 100 }) @IsOptional() @Type(() => Number) @IsInt() @Min(1) @Max(100) limit = 20; } /** REST adapter of identity: who I am and Pireon's user directory. */ @ApiTags('Users') @ApiPireonAuth() @Controller('users') export class UsersController { constructor(private readonly identity: IdentityService) {} @Get('me') @ApiOperation({ summary: 'The user behind the access token' }) @ApiOkResponse({ type: MeDto }) @ApiReadErrors() me(@CurrentUser() user: User): MeDto { return user.toSnapshot(); } @Get() @ApiOperation({ summary: 'Search Pireon users (to assign tickets or add members)', description: 'Needs the `users:read` scope in the access token.', }) @ApiOkResponse({ type: [UserDto] }) @ApiReadErrors() search(@Query() query: UserSearchQueryDto, @AccessToken() token: string) { return this.identity.searchUsers(query.search, token, query.limit); } @Get(':id') @ApiOperation({ summary: 'A Pireon user' }) @ApiOkResponse({ type: UserDto }) @ApiReadErrors() async findOne(@Param('id') id: string, @AccessToken() token: string) { const user = await this.identity.findUser(id, token); if (!user) throw new NotFoundException('User not found'); return user; } }