diff --git a/backend/src/collections/roledb/roledb.module.ts b/backend/src/collections/roledb/roledb.module.ts index 7410ae4..518e97e 100644 --- a/backend/src/collections/roledb/roledb.module.ts +++ b/backend/src/collections/roledb/roledb.module.ts @@ -36,7 +36,7 @@ export class RolesModule implements OnModuleInit { private async nukeRoles() { this.logger.error('Nuking system roles'); - const result = this.rolesService.nukeSystemRoles(true); + const result = await this.rolesService.nukeSystemRoles(true); if (HasFailed(result)) { this.logger.error(`Failed to nuke roles because: ${result.getReason()}`); } diff --git a/backend/src/collections/roledb/roledb.service.ts b/backend/src/collections/roledb/roledb.service.ts index 8618c5a..dfd56c8 100644 --- a/backend/src/collections/roledb/roledb.service.ts +++ b/backend/src/collections/roledb/roledb.service.ts @@ -132,7 +132,7 @@ export class RolesService { where: { name }, }); - if (!found) return Fail('User not found'); + if (!found) return Fail('Role not found'); return found as ERoleBackend; } catch (e: any) { return Fail(e?.message); diff --git a/backend/src/collections/userdb/userdb.service.ts b/backend/src/collections/userdb/userdb.service.ts index ae55341..e12f64b 100644 --- a/backend/src/collections/userdb/userdb.service.ts +++ b/backend/src/collections/userdb/userdb.service.ts @@ -88,43 +88,46 @@ export class UsersService { public async addRoles( user: string | EUserBackend, roles: Roles, - ): AsyncFailable { + ): AsyncFailable { const userToModify = await this.resolve(user); if (HasFailed(userToModify)) return userToModify; - // This is stupid - userToModify.roles = [...new Set([...userToModify.roles, ...roles])]; + const newRoles = [...new Set([...userToModify.roles, ...roles])]; - try { - await this.usersRepository.save(userToModify); - } catch (e: any) { - return Fail(e?.message); - } - - return true; + return this.setRoles(userToModify, newRoles); } public async removeRoles( user: string | EUserBackend, roles: Roles, - ): AsyncFailable { + ): AsyncFailable { const userToModify = await this.resolve(user); if (HasFailed(userToModify)) return userToModify; - // Make sure we don't remove unremovable roles - roles = roles.filter((role) => !PermanentRolesList.includes(role)); + const newRoles = userToModify.roles.filter((role) => !roles.includes(role)); - userToModify.roles = userToModify.roles.filter( - (role) => !roles.includes(role), + return this.setRoles(userToModify, newRoles); + } + + public async setRoles( + user: string | EUserBackend, + roles: Roles, + ): AsyncFailable { + const userToModify = await this.resolve(user); + if (HasFailed(userToModify)) return userToModify; + + const rolesToKeep = userToModify.roles.filter((role) => + PermanentRolesList.includes(role), ); + const newRoles = [...new Set([...rolesToKeep, ...roles])]; + + userToModify.roles = newRoles; try { - await this.usersRepository.save(userToModify); + return await this.usersRepository.save(userToModify); } catch (e: any) { return Fail(e?.message); } - - return true; } public async findOne( diff --git a/backend/src/layers/httpexception/httpexception.filter.ts b/backend/src/layers/httpexception/httpexception.filter.ts index bf7b075..63ea06a 100644 --- a/backend/src/layers/httpexception/httpexception.filter.ts +++ b/backend/src/layers/httpexception/httpexception.filter.ts @@ -2,7 +2,7 @@ import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from '@nestjs/common'; import { FastifyReply, FastifyRequest } from 'fastify'; -import { ApiErrorResponse } from 'picsur-shared/dist/dto/api.dto'; +import { ApiErrorResponse } from 'picsur-shared/dist/dto/api'; @Catch(HttpException) export class MainExceptionFilter implements ExceptionFilter { diff --git a/backend/src/layers/success/success.interceptor.ts b/backend/src/layers/success/success.interceptor.ts index c1e2a36..0f11fb0 100644 --- a/backend/src/layers/success/success.interceptor.ts +++ b/backend/src/layers/success/success.interceptor.ts @@ -2,7 +2,7 @@ import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'; -import { ApiResponse } from 'picsur-shared/dist/dto/api.dto'; +import { ApiResponse } from 'picsur-shared/dist/dto/api'; import { map, Observable } from 'rxjs'; @Injectable() diff --git a/backend/src/managers/auth/auth.service.ts b/backend/src/managers/auth/auth.service.ts index fb1656c..28189c0 100644 --- a/backend/src/managers/auth/auth.service.ts +++ b/backend/src/managers/auth/auth.service.ts @@ -2,7 +2,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { instanceToPlain, plainToClass } from 'class-transformer'; import { validate } from 'class-validator'; -import { JwtDataDto } from 'picsur-shared/dist/dto/auth.dto'; +import { JwtDataDto } from 'picsur-shared/dist/dto/jwt.dto'; import { EUserBackend } from '../../models/entities/user.entity'; @Injectable() diff --git a/backend/src/managers/auth/guards/jwt.strategy.ts b/backend/src/managers/auth/guards/jwt.strategy.ts index 5d477c3..01de761 100644 --- a/backend/src/managers/auth/guards/jwt.strategy.ts +++ b/backend/src/managers/auth/guards/jwt.strategy.ts @@ -8,8 +8,9 @@ import { PassportStrategy } from '@nestjs/passport'; import { plainToClass } from 'class-transformer'; import { validate } from 'class-validator'; import { ExtractJwt, Strategy } from 'passport-jwt'; -import { JwtDataDto } from 'picsur-shared/dist/dto/auth.dto'; +import { JwtDataDto } from 'picsur-shared/dist/dto/jwt.dto'; import { EUserBackend } from '../../../models/entities/user.entity'; + @Injectable() export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') { private readonly logger = new Logger('JwtStrategy'); diff --git a/backend/src/managers/auth/guards/main.guard.ts b/backend/src/managers/auth/guards/main.guard.ts index 6b07107..bc7e268 100644 --- a/backend/src/managers/auth/guards/main.guard.ts +++ b/backend/src/managers/auth/guards/main.guard.ts @@ -42,13 +42,13 @@ export class MainAuthGuard extends AuthGuard(['jwt', 'guest']) { const permissions = this.extractPermissions(context); if (HasFailed(permissions)) { - this.logger.warn(permissions.getReason()); + this.logger.warn("222"+permissions.getReason()); throw new InternalServerErrorException(); } const userPermissions = await this.usersService.getPermissions(user); if (HasFailed(userPermissions)) { - this.logger.warn(userPermissions.getReason()); + this.logger.warn("111"+userPermissions.getReason()); throw new InternalServerErrorException(); } diff --git a/backend/src/routes/api/api.module.ts b/backend/src/routes/api/api.module.ts index 1ac5ec1..fc1f5e0 100644 --- a/backend/src/routes/api/api.module.ts +++ b/backend/src/routes/api/api.module.ts @@ -1,5 +1,5 @@ import { Module } from '@nestjs/common'; -import { AuthModule } from './auth/auth.module'; +import { UserApiModule } from './auth/user.module'; import { ExperimentModule } from './experiment/experiment.module'; import { InfoModule } from './info/info.module'; import { PrefModule } from './pref/pref.module'; @@ -7,7 +7,7 @@ import { RolesApiModule } from './roles/roles.module'; @Module({ imports: [ - AuthModule, + UserApiModule, PrefModule, ExperimentModule, InfoModule, diff --git a/backend/src/routes/api/auth/auth.controller.ts b/backend/src/routes/api/auth/user.controller.ts similarity index 57% rename from backend/src/routes/api/auth/auth.controller.ts rename to backend/src/routes/api/auth/user.controller.ts index b441a83..cb33e37 100644 --- a/backend/src/routes/api/auth/auth.controller.ts +++ b/backend/src/routes/api/auth/user.controller.ts @@ -7,13 +7,19 @@ import { Post, Request } from '@nestjs/common'; -import { AuthUserInfoRequest } from 'picsur-shared/dist/dto/api/auth.dto'; import { - AuthDeleteRequest, - AuthLoginResponse, - AuthMeResponse, - AuthRegisterRequest -} from 'picsur-shared/dist/dto/auth.dto'; + UserDeleteRequest, + UserDeleteResponse, + UserInfoRequest, + UserInfoResponse, + UserListResponse, + UserLoginResponse, + UserMeResponse, + UserRegisterRequest, + UserRegisterResponse, + UserUpdateRolesRequest, + UserUpdateRolesResponse +} from 'picsur-shared/dist/dto/api/user.dto'; import { HasFailed } from 'picsur-shared/dist/types'; import { UsersService } from '../../../collections/userdb/userdb.service'; import { @@ -23,8 +29,8 @@ import { import { AuthManagerService } from '../../../managers/auth/auth.service'; import AuthFasityRequest from '../../../models/dto/authrequest.dto'; -@Controller('api/auth') -export class AuthController { +@Controller('api/user') +export class UserController { private readonly logger = new Logger('AuthController'); constructor( @@ -34,20 +40,17 @@ export class AuthController { @Post('login') @UseLocalAuth('user-login') - async login(@Request() req: AuthFasityRequest) { - const response: AuthLoginResponse = { + async login(@Request() req: AuthFasityRequest): Promise { + return { jwt_token: await this.authService.createToken(req.user), }; - - return response; } @Post('register') @RequiredPermissions('user-register') async register( - @Request() req: AuthFasityRequest, - @Body() register: AuthRegisterRequest, - ) { + @Body() register: UserRegisterRequest, + ): Promise { const user = await this.usersService.create( register.username, register.password, @@ -58,7 +61,11 @@ export class AuthController { } if (register.isAdmin) { - await this.usersService.addRoles(user, ['admin']); + const result = await this.usersService.addRoles(user, ['admin']); + if (HasFailed(result)) { + this.logger.warn(result.getReason()); + throw new InternalServerErrorException('Could not add admin role'); + } } return user; @@ -67,9 +74,8 @@ export class AuthController { @Post('delete') @RequiredPermissions('user-manage') async delete( - @Request() req: AuthFasityRequest, - @Body() deleteData: AuthDeleteRequest, - ) { + @Body() deleteData: UserDeleteRequest, + ): Promise { const user = await this.usersService.delete(deleteData.username); if (HasFailed(user)) { this.logger.warn(user.getReason()); @@ -79,9 +85,27 @@ export class AuthController { return user; } + @Post('roles') + @RequiredPermissions('user-manage') + async setPermissions( + @Body() body: UserUpdateRolesRequest, + ): Promise { + const updatedUser = await this.usersService.setRoles( + body.username, + body.roles, + ); + + if (HasFailed(updatedUser)) { + this.logger.warn(updatedUser.getReason()); + throw new InternalServerErrorException('Could not update user'); + } + + return updatedUser; + } + @Post('info') @RequiredPermissions('user-manage') - async getUser(@Body() body: AuthUserInfoRequest) { + async getUser(@Body() body: UserInfoRequest): Promise { const user = await this.usersService.findOne(body.username); if (HasFailed(user)) { this.logger.warn(user.getReason()); @@ -93,30 +117,32 @@ export class AuthController { @Get('list') @RequiredPermissions('user-manage') - async listUsers(@Request() req: AuthFasityRequest) { - const users = this.usersService.findAll(); + async listUsers(): Promise { + const users = await this.usersService.findAll(); if (HasFailed(users)) { this.logger.warn(users.getReason()); throw new InternalServerErrorException('Could not list users'); } - return users; + return { + users, + total: users.length, + }; } @Get('me') @RequiredPermissions('user-view') - async me(@Request() req: AuthFasityRequest) { + async me(@Request() req: AuthFasityRequest): Promise { const permissions = await this.usersService.getPermissions(req.user); if (HasFailed(permissions)) { this.logger.warn(permissions.getReason()); throw new InternalServerErrorException('Could not get permissions'); } - const meResponse: AuthMeResponse = new AuthMeResponse(); - meResponse.user = req.user; - meResponse.permissions = permissions; - meResponse.newJwtToken = await this.authService.createToken(req.user); - - return meResponse; + return { + user: req.user, + permissions, + newJwtToken: await this.authService.createToken(req.user), + }; } } diff --git a/backend/src/routes/api/auth/auth.module.ts b/backend/src/routes/api/auth/user.module.ts similarity index 58% rename from backend/src/routes/api/auth/auth.module.ts rename to backend/src/routes/api/auth/user.module.ts index 757f50f..6e41c69 100644 --- a/backend/src/routes/api/auth/auth.module.ts +++ b/backend/src/routes/api/auth/user.module.ts @@ -1,9 +1,9 @@ import { Module } from '@nestjs/common'; import { AuthManagerModule } from '../../../managers/auth/auth.module'; -import { AuthController } from './auth.controller'; +import { UserController } from './user.controller'; @Module({ imports: [AuthManagerModule], - controllers: [AuthController], + controllers: [UserController], }) -export class AuthModule {} +export class UserApiModule {} diff --git a/backend/src/routes/api/info/info.controller.ts b/backend/src/routes/api/info/info.controller.ts index 40f4ab2..0ac26f7 100644 --- a/backend/src/routes/api/info/info.controller.ts +++ b/backend/src/routes/api/info/info.controller.ts @@ -9,11 +9,10 @@ export class InfoController { @Get() @NoAuth() - getInfo() { - const response: InfoResponse = { + async getInfo(): Promise { + return { demo: this.hostConfig.isDemo(), production: this.hostConfig.isProduction(), }; - return response; } } diff --git a/backend/src/routes/api/pref/pref.controller.ts b/backend/src/routes/api/pref/pref.controller.ts index 0834ca9..ae39977 100644 --- a/backend/src/routes/api/pref/pref.controller.ts +++ b/backend/src/routes/api/pref/pref.controller.ts @@ -8,9 +8,10 @@ import { Post } from '@nestjs/common'; import { - SysPreferences, + SysPreferenceResponse, UpdateSysPreferenceRequest -} from 'picsur-shared/dist/dto/syspreferences.dto'; +} from 'picsur-shared/dist/dto/api/pref.dto'; +import { SysPreferences } from 'picsur-shared/dist/dto/syspreferences.dto'; import { HasFailed } from 'picsur-shared/dist/types'; import { SysPreferenceService } from '../../../collections/syspreferencesdb/syspreferencedb.service'; import { RequiredPermissions } from '../../../decorators/permissions.decorator'; @@ -23,7 +24,7 @@ export class PrefController { constructor(private prefService: SysPreferenceService) {} @Get('sys/:key') - async getSysPref(@Param('key') key: string) { + async getSysPref(@Param('key') key: string): Promise { const returned = await this.prefService.getPreference( key as SysPreferences, ); @@ -39,7 +40,7 @@ export class PrefController { async setSysPref( @Param('key') key: string, @Body() body: UpdateSysPreferenceRequest, - ) { + ): Promise { const value = body.value; const returned = await this.prefService.setPreference( key as SysPreferences, diff --git a/backend/src/routes/api/roles/roles.controller.ts b/backend/src/routes/api/roles/roles.controller.ts index d0318fd..6b8c0db 100644 --- a/backend/src/routes/api/roles/roles.controller.ts +++ b/backend/src/routes/api/roles/roles.controller.ts @@ -8,9 +8,14 @@ import { } from '@nestjs/common'; import { RoleCreateRequest, + RoleCreateResponse, RoleDeleteRequest, + RoleDeleteResponse, RoleInfoRequest, - RoleUpdateRequest + RoleInfoResponse, + RoleListResponse, + RoleUpdateRequest, + RoleUpdateResponse } from 'picsur-shared/dist/dto/api/roles.dto'; import { HasFailed } from 'picsur-shared/dist/types'; import { RolesService } from '../../../collections/roledb/roledb.service'; @@ -24,19 +29,22 @@ export class RolesController { constructor(private rolesService: RolesService) {} @Get('/list') - getRoles() { - const roles = this.rolesService.findAll(); + async getRoles(): Promise { + const roles = await this.rolesService.findAll(); if (HasFailed(roles)) { this.logger.warn(roles.getReason()); throw new InternalServerErrorException('Could not list roles'); } - return roles; + return { + roles, + total: roles.length, + }; } @Post('/info') - getRole(@Body() body: RoleInfoRequest) { - const role = this.rolesService.findOne(body.name); + async getRole(@Body() body: RoleInfoRequest): Promise { + const role = await this.rolesService.findOne(body.name); if (HasFailed(role)) { this.logger.warn(role.getReason()); throw new InternalServerErrorException('Could not find role'); @@ -45,9 +53,11 @@ export class RolesController { return role; } - @Post('/permissions/set') - updateRole(@Body() body: RoleUpdateRequest) { - const updatedRole = this.rolesService.setPermissions( + @Post('/permissions') + async updateRole( + @Body() body: RoleUpdateRequest, + ): Promise { + const updatedRole = await this.rolesService.setPermissions( body.name, body.permissions, ); @@ -59,39 +69,11 @@ export class RolesController { return updatedRole; } - @Post('/permissions/add') - addPermissions(@Body() body: RoleUpdateRequest) { - const updatedRole = this.rolesService.addPermissions( - body.name, - body.permissions, - ); - if (HasFailed(updatedRole)) { - this.logger.warn(updatedRole.getReason()); - throw new InternalServerErrorException('Could not add role permissions'); - } - - return updatedRole; - } - - @Post('/permissions/remove') - removePermissions(@Body() body: RoleUpdateRequest) { - const updatedRole = this.rolesService.removePermissions( - body.name, - body.permissions, - ); - if (HasFailed(updatedRole)) { - this.logger.warn(updatedRole.getReason()); - throw new InternalServerErrorException( - 'Could not remove role permissions', - ); - } - - return updatedRole; - } - @Post('/create') - createRole(@Body() role: RoleCreateRequest) { - const newRole = this.rolesService.create(role.name, role.permissions); + async createRole( + @Body() role: RoleCreateRequest, + ): Promise { + const newRole = await this.rolesService.create(role.name, role.permissions); if (HasFailed(newRole)) { this.logger.warn(newRole.getReason()); throw new InternalServerErrorException('Could not create role'); @@ -101,8 +83,10 @@ export class RolesController { } @Post('/delete') - deleteRole(@Body() role: RoleDeleteRequest) { - const deletedRole = this.rolesService.delete(role.name); + async deleteRole( + @Body() role: RoleDeleteRequest, + ): Promise { + const deletedRole = await this.rolesService.delete(role.name); if (HasFailed(deletedRole)) { this.logger.warn(deletedRole.getReason()); throw new InternalServerErrorException('Could not delete role'); diff --git a/backend/src/routes/image/imageroute.controller.ts b/backend/src/routes/image/imageroute.controller.ts index 608f16f..68f8db3 100644 --- a/backend/src/routes/image/imageroute.controller.ts +++ b/backend/src/routes/image/imageroute.controller.ts @@ -12,6 +12,7 @@ import { } from '@nestjs/common'; import { isHash } from 'class-validator'; import { FastifyReply, FastifyRequest } from 'fastify'; +import { ImageMetaResponse } from 'picsur-shared/dist/dto/api/image.dto'; import { HasFailed } from 'picsur-shared/dist/types'; import { MultiPart } from '../../decorators/multipart.decorator'; import { RequiredPermissions } from '../../decorators/permissions.decorator'; @@ -29,7 +30,7 @@ export class ImageController { async getImage( @Res({ passthrough: true }) res: FastifyReply, @Param('hash') hash: string, - ) { + ): Promise { if (!isHash(hash, 'sha256')) throw new BadRequestException('Invalid hash'); const image = await this.imagesService.retrieveComplete(hash); @@ -43,7 +44,7 @@ export class ImageController { } @Get('meta/:hash') - async getImageMeta(@Param('hash') hash: string) { + async getImageMeta(@Param('hash') hash: string): Promise { if (!isHash(hash, 'sha256')) throw new BadRequestException('Invalid hash'); const image = await this.imagesService.retrieveInfo(hash); @@ -60,7 +61,7 @@ export class ImageController { async uploadImage( @Req() req: FastifyRequest, @MultiPart(ImageUploadDto) multipart: ImageUploadDto, - ) { + ): Promise { const fileBuffer = await multipart.image.toBuffer(); const image = await this.imagesService.upload(fileBuffer); if (HasFailed(image)) { diff --git a/frontend/src/app/api/api.service.ts b/frontend/src/app/api/api.service.ts index 7b7f7d6..08768aa 100644 --- a/frontend/src/app/api/api.service.ts +++ b/frontend/src/app/api/api.service.ts @@ -4,12 +4,11 @@ import { validate } from 'class-validator'; import { ApiResponse, ApiSuccessResponse -} from 'picsur-shared/dist/dto/api.dto'; +} from 'picsur-shared/dist/dto/api'; import { AsyncFailable, Fail, HasFailed } from 'picsur-shared/dist/types'; import { MultiPartRequest } from '../models/multi-part-request'; import { KeyService } from './key.service'; - @Injectable({ providedIn: 'root', }) diff --git a/frontend/src/app/api/image.service.ts b/frontend/src/app/api/image.service.ts index 275077d..396305e 100644 --- a/frontend/src/app/api/image.service.ts +++ b/frontend/src/app/api/image.service.ts @@ -1,4 +1,5 @@ import { Injectable } from '@angular/core'; +import { ImageMetaResponse } from 'picsur-shared/dist/dto/api/image.dto'; import { EImage } from 'picsur-shared/dist/entities/image.entity'; import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types'; import { ImageUploadRequest } from '../models/image-upload-request'; @@ -12,7 +13,7 @@ export class ImageService { public async UploadImage(image: File): AsyncFailable { const result = await this.api.postForm( - EImage, + ImageMetaResponse, '/i', new ImageUploadRequest(image) ); @@ -23,7 +24,7 @@ export class ImageService { } public async GetImageMeta(image: string): AsyncFailable { - return await this.api.get(EImage, `/i/meta/${image}`); + return await this.api.get(ImageMetaResponse, `/i/meta/${image}`); } public GetImageURL(image: string): string { diff --git a/frontend/src/app/api/user.service.ts b/frontend/src/app/api/user.service.ts index 3e0b8ed..0184e70 100644 --- a/frontend/src/app/api/user.service.ts +++ b/frontend/src/app/api/user.service.ts @@ -3,16 +3,13 @@ import { plainToClass } from 'class-transformer'; import { validate } from 'class-validator'; import jwt_decode from 'jwt-decode'; import { - AuthLoginRequest, - AuthLoginResponse, - AuthMeResponse, - JwtDataDto -} from 'picsur-shared/dist/dto/auth.dto'; + UserLoginRequest, + UserLoginResponse, + UserMeResponse +} from 'picsur-shared/dist/dto/api/user.dto'; +import { JwtDataDto } from 'picsur-shared/dist/dto/jwt.dto'; import { EUser } from 'picsur-shared/dist/entities/user.entity'; -import { - AsyncFailable, - Fail, HasFailed -} from 'picsur-shared/dist/types'; +import { AsyncFailable, Fail, HasFailed } from 'picsur-shared/dist/types'; import { BehaviorSubject } from 'rxjs'; import { ApiService } from './api.service'; import { KeyService } from './key.service'; @@ -38,19 +35,19 @@ export class UserService { private userSubject = new BehaviorSubject(null); constructor(private api: ApiService, private key: KeyService) { - this.init().catch(console.error); + this.init().catch(this.logger.error); } public async login(username: string, password: string): AsyncFailable { - const request: AuthLoginRequest = { + const request: UserLoginRequest = { username, password, }; const response = await this.api.post( - AuthLoginRequest, - AuthLoginResponse, - '/api/auth/login', + UserLoginRequest, + UserLoginResponse, + '/api/user/login', request ); @@ -117,7 +114,7 @@ export class UserService { } private async fetchUser(): AsyncFailable { - const got = await this.api.get(AuthMeResponse, '/api/auth/me'); + const got = await this.api.get(UserMeResponse, '/api/user/me'); if (HasFailed(got)) return got; this.key.set(got.newJwtToken); diff --git a/frontend/src/app/components/header/header.component.ts b/frontend/src/app/components/header/header.component.ts index 30eed56..3ea80d2 100644 --- a/frontend/src/app/components/header/header.component.ts +++ b/frontend/src/app/components/header/header.component.ts @@ -13,6 +13,8 @@ import { UtilService } from 'src/app/util/util.service'; styleUrls: ['./header.component.scss'], }) export class HeaderComponent implements OnInit { + private readonly logger = console; + private currentUser: EUser | null = null; public get user() { @@ -36,7 +38,7 @@ export class HeaderComponent implements OnInit { @AutoUnsubscribe() subscribeUser() { return this.userService.liveUser.subscribe((user) => { - console.log('user', user); + this.logger.log('user', user); this.currentUser = user; }); } diff --git a/shared/src/dto/api/image.dto.ts b/shared/src/dto/api/image.dto.ts new file mode 100644 index 0000000..7956074 --- /dev/null +++ b/shared/src/dto/api/image.dto.ts @@ -0,0 +1,3 @@ +import { EImage } from '../../entities/image.entity'; + +export class ImageMetaResponse extends EImage {} diff --git a/shared/src/dto/api/pref.dto.ts b/shared/src/dto/api/pref.dto.ts new file mode 100644 index 0000000..7f6d9a0 --- /dev/null +++ b/shared/src/dto/api/pref.dto.ts @@ -0,0 +1,9 @@ +import { IsNotEmpty } from 'class-validator'; +import { ESysPreference } from '../../entities/syspreference.entity'; + +export class UpdateSysPreferenceRequest { + @IsNotEmpty() + value: string; +} + +export class SysPreferenceResponse extends ESysPreference {} diff --git a/shared/src/dto/api/roles.dto.ts b/shared/src/dto/api/roles.dto.ts index 426f026..81d7cb7 100644 --- a/shared/src/dto/api/roles.dto.ts +++ b/shared/src/dto/api/roles.dto.ts @@ -1,13 +1,32 @@ -import { IsArray, IsEnum, IsNotEmpty, IsString } from 'class-validator'; +import { Type } from 'class-transformer'; +import { IsArray, IsDefined, IsEnum, IsInt, IsNotEmpty, IsPositive, IsString, ValidateNested } from 'class-validator'; import { ERole } from '../../entities/role.entity'; import { Permissions, PermissionsList } from '../permissions'; +// RoleInfo export class RoleInfoRequest { @IsNotEmpty() @IsString() name: string; } +export class RoleInfoResponse extends ERole {} + +// RoleList +export class RoleListResponse { + @IsArray() + @IsDefined() + @ValidateNested() + @Type(() => ERole) + roles: ERole[]; + + @IsInt() + @IsPositive() + @IsDefined() + total: number; +} + +// RoleUpdate export class RoleUpdateRequest { @IsNotEmpty() @IsString() @@ -17,10 +36,15 @@ export class RoleUpdateRequest { @IsEnum(PermissionsList, { each: true }) permissions: Permissions; } +export class RoleUpdateResponse extends ERole {} +// RoleCreate export class RoleCreateRequest extends ERole {} +export class RoleCreateResponse extends ERole {} +// RoleDelete export class RoleDeleteRequest { @IsNotEmpty() name: string; } +export class RoleDeleteResponse extends ERole {} diff --git a/shared/src/dto/api/auth.dto.ts b/shared/src/dto/api/user.dto.ts similarity index 50% rename from shared/src/dto/api/auth.dto.ts rename to shared/src/dto/api/user.dto.ts index e9d1fb6..dfe7c64 100644 --- a/shared/src/dto/api/auth.dto.ts +++ b/shared/src/dto/api/user.dto.ts @@ -3,17 +3,22 @@ import { IsArray, IsBoolean, IsDefined, - IsEnum, IsNotEmpty, + IsEnum, + IsInt, + IsNotEmpty, IsOptional, + IsPositive, IsString, ValidateNested } from 'class-validator'; import { EUser } from '../../entities/user.entity'; import { Permissions, PermissionsList } from '../permissions'; +import { Roles } from '../roles.dto'; // Api -export class AuthLoginRequest { +// UserLogin +export class UserLoginRequest { @IsNotEmpty() @IsString() username: string; @@ -23,13 +28,14 @@ export class AuthLoginRequest { password: string; } -export class AuthLoginResponse { +export class UserLoginResponse { @IsString() @IsDefined() jwt_token: string; } -export class AuthRegisterRequest { +// UserRegister +export class UserRegisterRequest { @IsString() @IsNotEmpty() username: string; @@ -43,15 +49,42 @@ export class AuthRegisterRequest { isAdmin?: boolean; } -export class AuthDeleteRequest { +export class UserRegisterResponse extends EUser {} + +// UserDelete +export class UserDeleteRequest { @IsString() @IsNotEmpty() username: string; } -export class AuthDeleteResponse extends EUser {} +export class UserDeleteResponse extends EUser {} -export class AuthMeResponse { +// UserInfo +export class UserInfoRequest { + @IsString() + @IsNotEmpty() + username: string; +} + +export class UserInfoResponse extends EUser {} + +// UserList +export class UserListResponse { + @IsArray() + @IsDefined() + @ValidateNested() + @Type(() => EUser) + users: EUser[]; + + @IsInt() + @IsPositive() + @IsDefined() + total: number; +} + +// UserMe +export class UserMeResponse { @IsDefined() @ValidateNested() @Type(() => EUser) @@ -67,8 +100,16 @@ export class AuthMeResponse { newJwtToken: string; } -export class AuthUserInfoRequest { +// UserUpdateRoles +export class UserUpdateRolesRequest { @IsString() @IsNotEmpty() username: string; + + @IsArray() + @IsDefined() + @IsString({ each: true }) + roles: Roles; } + +export class UserUpdateRolesResponse extends EUser {} diff --git a/shared/src/dto/syspreferences.dto.ts b/shared/src/dto/syspreferences.dto.ts index 8ec5c11..b3e55bd 100644 --- a/shared/src/dto/syspreferences.dto.ts +++ b/shared/src/dto/syspreferences.dto.ts @@ -1,4 +1,3 @@ -import { IsNotEmpty } from 'class-validator'; import tuple from '../types/tuple'; const SysPreferencesTuple = tuple( @@ -9,8 +8,3 @@ const SysPreferencesTuple = tuple( export const SysPreferences: string[] = SysPreferencesTuple; export type SysPreferences = typeof SysPreferencesTuple[number]; - -export class UpdateSysPreferenceRequest { - @IsNotEmpty() - value: string; -} diff --git a/shared/src/types/failable.ts b/shared/src/types/failable.ts index ab2e09e..1dcb8da 100644 --- a/shared/src/types/failable.ts +++ b/shared/src/types/failable.ts @@ -14,6 +14,8 @@ export type Failable = T | Failure; export type AsyncFailable = Promise>; +// TODO: prevent promise from being allowed in these 2 functions + export function HasFailed(failable: Failable): failable is Failure { return failable instanceof Failure; }