diff --git a/src/controllers/UserController.ts b/src/controllers/UserController.ts index eb3a458..cdc91af 100644 --- a/src/controllers/UserController.ts +++ b/src/controllers/UserController.ts @@ -1,10 +1,10 @@ import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { getConnectionManager, Repository } from 'typeorm'; -import { EntityFromBody } from 'typeorm-routing-controllers-extensions'; import { UserIdsNotMatchingError, UserNotFoundError } from '../errors/UserErrors'; import { UserGroupNotFoundError } from '../errors/UserGroupErrors'; import { CreateUser } from '../models/actions/CreateUser'; +import { UpdateUser } from '../models/actions/UpdateUser'; import { User } from '../models/entities/User'; import { ResponseEmpty } from '../models/responses/ResponseEmpty'; import { ResponseUser } from '../models/responses/ResponseUser'; @@ -71,18 +71,21 @@ export class UserController { @ResponseSchema(UserNotFoundError, { statusCode: 404 }) @ResponseSchema(UserIdsNotMatchingError, { statusCode: 406 }) @OpenAPI({ description: "Update a user object (id can't be changed)." }) - async put(@Param('id') id: number, @EntityFromBody() user: User) { - let oldUser = await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups'] }); + async put(@Param('id') id: number, @Body({ validate: true }) updateUser: UpdateUser) { + let oldUser = await this.userRepository.findOne({ id: id }); + delete oldUser.permissions; + delete oldUser.groups; + delete oldUser.actions; if (!oldUser) { throw new UserNotFoundError(); } - if (oldUser.id != user.id) { + if (oldUser.id != updateUser.id) { throw new UserIdsNotMatchingError(); } + await this.userRepository.save(await updateUser.toUser(oldUser)); - await this.userRepository.update(oldUser, user); return new ResponseUser(await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups'] })); } @@ -91,7 +94,7 @@ export class UserController { @ResponseSchema(User) @ResponseSchema(ResponseEmpty, { statusCode: 204 }) @OnUndefined(204) - @OpenAPI({ description: 'Delete a specified runner (if it exists).' }) + @OpenAPI({ description: 'Delete a user runner (if it exists).' }) async remove(@Param("id") id: number, @QueryParam("force") force: boolean) { let user = await this.userRepository.findOne({ id: id }); if (!user) { return null; } diff --git a/src/models/actions/CreateUser.ts b/src/models/actions/CreateUser.ts index 1f80ee3..041bd8e 100644 --- a/src/models/actions/CreateUser.ts +++ b/src/models/actions/CreateUser.ts @@ -67,7 +67,7 @@ export class CreateUser { * Optional. */ @IsOptional() - group?: number[] | number + groups?: number[] | number //TODO: ProfilePics @@ -95,13 +95,16 @@ export class CreateUser { return newUser; } + /** + * Get's all groups for this user by their id's; + */ public async getGroups() { - if (!this.group) { return null; } + if (!this.groups) { return null; } let groups = new Array(); - if (!Array.isArray(this.group)) { - this.group = [this.group] + if (!Array.isArray(this.groups)) { + this.groups = [this.groups] } - for (let group of this.group) { + for (let group of this.groups) { let found = await getConnectionManager().get().getRepository(UserGroup).findOne({ id: group }); if (!found) { throw new UserGroupNotFoundError(); } groups.push(found); diff --git a/src/models/actions/UpdateUser.ts b/src/models/actions/UpdateUser.ts new file mode 100644 index 0000000..abd489a --- /dev/null +++ b/src/models/actions/UpdateUser.ts @@ -0,0 +1,123 @@ +import * as argon2 from "argon2"; +import { IsBoolean, IsEmail, IsInt, IsOptional, IsPhoneNumber, IsString } from 'class-validator'; +import { getConnectionManager } from 'typeorm'; +import { config } from '../../config'; +import { UsernameOrEmailNeededError } from '../../errors/AuthError'; +import { RunnerGroupNotFoundError } from '../../errors/RunnerGroupErrors'; +import { User } from '../entities/User'; +import { UserGroup } from '../entities/UserGroup'; + +export class UpdateUser { + + /** + * The updated users's id. + */ + @IsInt() + id: number; + + /** + * The updated user's first name. + */ + @IsString() + firstname: string; + + /** + * The updated user's middle name. + * Optinal. + */ + @IsString() + @IsOptional() + middlename?: string; + + /** + * The updated user's last name. + */ + @IsString() + lastname: string; + + /** + * The updated user's username. + * You have to provide at least one of: {email, username}. + */ + @IsOptional() + @IsString() + username?: string; + + /** + * The updated user's email address. + * You have to provide at least one of: {email, username}. + */ + @IsEmail() + @IsString() + @IsOptional() + email?: string; + + /** + * The updated user's phone number. + * Optional + */ + @IsPhoneNumber(config.phone_validation_countrycode) + @IsOptional() + phone?: string; + + /** + * The new updated's password. Only provide if you want it updated. + * This will of course not be saved in plaintext :) + */ + @IsString() + @IsOptional() + password?: string; + + /** + * Should the user be enabled? + */ + @IsBoolean() + enabled: boolean = true; + + /** + * The new user's groups' id(s). + * You can provide either one groupId or an array of groupIDs. + * Optional. + */ + @IsOptional() + groups?: UserGroup[] + + /** + * Creates a User entity from this. + */ + public async toUser(user: User): Promise { + user.email = this.email; + user.username = this.username; + if (user.email === undefined && user.username === undefined) { + throw new UsernameOrEmailNeededError(); + } + if (this.password) { + user.password = await argon2.hash(this.password + user.uuid); + user.refreshTokenCount = user.refreshTokenCount + 1; + } + + user.enabled = this.enabled; + user.firstname = this.firstname + user.middlename = this.middlename + user.lastname = this.lastname + user.phone = this.phone; + user.groups = await this.getGroups(); + //TODO: ProfilePics + + return user; + } + + public async getGroups() { + if (!this.groups) { return null; } + let groups = new Array(); + if (!Array.isArray(this.groups)) { + this.groups = [this.groups] + } + for (let group of this.groups) { + let found = await getConnectionManager().get().getRepository(UserGroup).findOne({ id: group.id }); + if (!found) { throw new RunnerGroupNotFoundError(); } + groups.push(found); + } + return groups; + } +} \ No newline at end of file diff --git a/src/seeds/SeedUsers.ts b/src/seeds/SeedUsers.ts index dae3b17..38027dd 100644 --- a/src/seeds/SeedUsers.ts +++ b/src/seeds/SeedUsers.ts @@ -29,7 +29,7 @@ export default class SeedUsers implements Seeder { initialUser.lastname = "demo"; initialUser.username = "demo"; initialUser.password = "demo"; - initialUser.group = group; + initialUser.groups = group; return await connection.getRepository(User).save(await initialUser.toUser()); }