From a9bd803112d3989c094705dd2c79e611fec9cb4b Mon Sep 17 00:00:00 2001 From: araemer Date: Thu, 13 Nov 2025 21:32:27 +0100 Subject: [PATCH] Rename some API object and add update user to UserPoint. However, it seems to be broken somehow --- bruno/recipe-backend/createUser.bru | 2 +- bruno/recipe-backend/updateUser.bru | 30 +++++++++ src/dtos/ChangeUserPasswordRequest.ts | 7 +++ ...UserRequestDto.ts => CreateUserRequest.ts} | 2 +- src/dtos/CreateUserResponse.ts | 5 ++ .../{LoginRequestDto.ts => LoginRequest.ts} | 2 +- .../{LoginResponseDto.ts => LoginResponse.ts} | 2 +- src/endpoints/AuthPoint.ts | 8 +-- src/endpoints/RecipePoint.ts | 6 +- src/endpoints/UserPoint.ts | 49 ++++++++++++--- src/handlers/AuthHandler.ts | 10 +-- src/handlers/UserHandler.ts | 61 ++++++++++++++----- 12 files changed, 143 insertions(+), 41 deletions(-) create mode 100644 bruno/recipe-backend/updateUser.bru create mode 100644 src/dtos/ChangeUserPasswordRequest.ts rename src/dtos/{CreateUserRequestDto.ts => CreateUserRequest.ts} (77%) create mode 100644 src/dtos/CreateUserResponse.ts rename src/dtos/{LoginRequestDto.ts => LoginRequest.ts} (71%) rename src/dtos/{LoginResponseDto.ts => LoginResponse.ts} (82%) diff --git a/bruno/recipe-backend/createUser.bru b/bruno/recipe-backend/createUser.bru index a23626d..7762c77 100644 --- a/bruno/recipe-backend/createUser.bru +++ b/bruno/recipe-backend/createUser.bru @@ -5,7 +5,7 @@ meta { } post { - url: http://localhost:4000/user + url: http://localhost:4000/user/create body: json auth: bearer } diff --git a/bruno/recipe-backend/updateUser.bru b/bruno/recipe-backend/updateUser.bru new file mode 100644 index 0000000..681734c --- /dev/null +++ b/bruno/recipe-backend/updateUser.bru @@ -0,0 +1,30 @@ +meta { + name: updateUser + type: http + seq: 8 +} + +post { + url: https://localhost:4000/user/update + body: json + auth: bearer +} + +auth:bearer { + token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImE0NDdlNDM0LTQyMWYtNDJiYS04MGRlLTM0ZDE1YzJmNWE2YyIsImlhdCI6MTc2MzA2NTI0MCwiZXhwIjoxNzYzMTUxNjQwfQ.e7v1JnlNHm7zwSzumlZIy2Dxfojqsxk55aYC9UA7BkE +} + +body:json { + { + "id": "a447e434-421f-42ba-80de-34d15c2f5a6c", + "userName": "admin", + "email": "anika@raemer.net", + "firstName": "Anika", + "lastName": "Rämer", + "role": "admin" + } +} + +settings { + encodeUrl: true +} diff --git a/src/dtos/ChangeUserPasswordRequest.ts b/src/dtos/ChangeUserPasswordRequest.ts new file mode 100644 index 0000000..e7f822e --- /dev/null +++ b/src/dtos/ChangeUserPasswordRequest.ts @@ -0,0 +1,7 @@ +/** + * DTO for changing user password + */ +export class ChangeUserPasswordRequest { + userId?: string; + password?: string; +} \ No newline at end of file diff --git a/src/dtos/CreateUserRequestDto.ts b/src/dtos/CreateUserRequest.ts similarity index 77% rename from src/dtos/CreateUserRequestDto.ts rename to src/dtos/CreateUserRequest.ts index 68d4012..0f91842 100644 --- a/src/dtos/CreateUserRequestDto.ts +++ b/src/dtos/CreateUserRequest.ts @@ -3,7 +3,7 @@ import { UserDto } from "./UserDto.js"; /** * DTO used for user creation */ -export class CreateUserRequestDto { +export class CreateUserRequest { userData?: UserDto; password?: string; } \ No newline at end of file diff --git a/src/dtos/CreateUserResponse.ts b/src/dtos/CreateUserResponse.ts new file mode 100644 index 0000000..09408c1 --- /dev/null +++ b/src/dtos/CreateUserResponse.ts @@ -0,0 +1,5 @@ +import { UserDto } from "./UserDto.js"; + +export class CreateUserResponse{ + userData?: UserDto; +} \ No newline at end of file diff --git a/src/dtos/LoginRequestDto.ts b/src/dtos/LoginRequest.ts similarity index 71% rename from src/dtos/LoginRequestDto.ts rename to src/dtos/LoginRequest.ts index a3da6ee..70d1e09 100644 --- a/src/dtos/LoginRequestDto.ts +++ b/src/dtos/LoginRequest.ts @@ -1,7 +1,7 @@ /** * Defines a login request */ -export class LoginRequestDto { +export class LoginRequest { userName?: string; password?: string; } \ No newline at end of file diff --git a/src/dtos/LoginResponseDto.ts b/src/dtos/LoginResponse.ts similarity index 82% rename from src/dtos/LoginResponseDto.ts rename to src/dtos/LoginResponse.ts index fdb5a39..bdcb2d9 100644 --- a/src/dtos/LoginResponseDto.ts +++ b/src/dtos/LoginResponse.ts @@ -3,7 +3,7 @@ import { UserDto } from "./UserDto.js"; /** * Response to a successful login */ -export class LoginResponseDto { +export class LoginResponse { userData?: UserDto; token?: string; expiryDate? : Date; diff --git a/src/endpoints/AuthPoint.ts b/src/endpoints/AuthPoint.ts index f2b91fb..8ee84d0 100644 --- a/src/endpoints/AuthPoint.ts +++ b/src/endpoints/AuthPoint.ts @@ -7,7 +7,7 @@ import { UnauthorizedError, InternalServerError, } from "../errors/httpErrors.js"; -import { LoginRequestDto } from "../dtos/LoginRequestDto.js"; +import { LoginRequest } from "../dtos/LoginRequest.js"; export const authBasicRoute = "/auth" @@ -18,13 +18,13 @@ const authController = new AuthHandler(userRepository, mapper); /** * Login using username and password - * Consumes LoginRequestDto - * Responds with LoginResponseDto + * Consumes LoginRequest + * Responds with LoginResponse */ router.post("/login", async (req, res) => { console.log("login point called") try { - const requestDto: LoginRequestDto = req.body; + const requestDto: LoginRequest = req.body; const responseDto = await authController.login(requestDto); res.json(responseDto); } catch (err: any) { diff --git a/src/endpoints/RecipePoint.ts b/src/endpoints/RecipePoint.ts index ebaa8fb..50e2bcd 100644 --- a/src/endpoints/RecipePoint.ts +++ b/src/endpoints/RecipePoint.ts @@ -19,7 +19,7 @@ const recipeIngredientMapper = new RecipeIngredientDtoEntityMapper(); const recipeIngredientGroupMapper = new RecipeIngredientGroupDtoEntityMapper(recipeIngredientMapper); const recipeInstructionStepMapper = new RecipeInstructionStepDtoEntityMapper(); const recipeMapper = new RecipeDtoEntityMapper(recipeInstructionStepMapper, recipeIngredientGroupMapper); -const recipeController = new RecipeHandler(recipeRepository, recipeMapper); +const recipeHandler = new RecipeHandler(recipeRepository, recipeMapper); /** @@ -34,7 +34,7 @@ router.post( "/create-or-update", asyncHandler(async(req, res) => { const requestDto: RecipeDto = req.body; - const responseDto = await recipeController.createOrUpdateRecipe(requestDto); + const responseDto = await recipeHandler.createOrUpdateRecipe(requestDto); res.status(201).json(responseDto); }) ) @@ -47,7 +47,7 @@ router.get( "/:id", asyncHandler(async(req, res) => { const id = req.params.id; - const responseDto = await recipeController.getRecipeById(id); + const responseDto = await recipeHandler.getRecipeById(id); res.status(201).json(responseDto); }) ); diff --git a/src/endpoints/UserPoint.ts b/src/endpoints/UserPoint.ts index 58d8266..4273eb0 100644 --- a/src/endpoints/UserPoint.ts +++ b/src/endpoints/UserPoint.ts @@ -1,9 +1,11 @@ import { Router } from "express"; import { UserHandler } from "../handlers/UserHandler.js"; -import { CreateUserRequestDto } from "../dtos/CreateUserRequestDto.js"; +import { CreateUserRequest } from "../dtos/CreateUserRequest.js"; import { UserRepository } from "../repositories/UserRepository.js"; import { UserDtoEntityMapper } from "../mappers/UserDtoEntityMapper.js"; import { asyncHandler } from "../utils/asyncHandler.js"; +import {CreateUserResponse} from "../dtos/CreateUserResponse.js"; +import {UserDto} from "../dtos/UserDto.js"; /** * Handles all user related routes @@ -11,24 +13,51 @@ import { asyncHandler } from "../utils/asyncHandler.js"; const router = Router(); // Inject repo + mapper here -const userRepository = new UserRepository(); -const userMapper = new UserDtoEntityMapper(); -const userController = new UserHandler(userRepository, userMapper); +const handler + = new UserHandler(new UserRepository(), new UserDtoEntityMapper()); /** * Create a new user - * Consumes CreateUserRequestDto + * Consumes CreateUserRequest * Responds with UserDto */ router.post( - "/", + "/create", asyncHandler(async (req, res) => { - const requestDto: CreateUserRequestDto = req.body; - const responseDto = await userController.createUser(requestDto); - res.status(201).json(responseDto); + const request: CreateUserRequest = req.body; + const user : UserDto = await handler.createUser(request); + const response : CreateUserResponse = { userData: user }; + res.status(201).json(response); }) ); +/** + * Update existing user + * Consumes UserDto + * Responds with UserDto + * Does not allow for password change. Use change-password instead + */ +router.post( + "/update", + asyncHandler(async (req, res) => { + const dto : UserDto = req.body; + const response = await handler.updateUserData(dto); + res.status(201).json(response); + }) +) + +/** + * Update password of existing user + * Consumes ChangeUserPasswordRequest + * Responds with code 201 indicating success + */ +router.post( + "/change-password", + asyncHandler(async (req, res) => { + throw Error("not implemented!"); + }) +) + /** * Get user data for current user * Responds with UserDto @@ -39,7 +68,7 @@ router.get("/me", const id = req.currentUser?.id; if(id){ // it breaks here because id is no longer a uuid - const responseDto = await userController.getUserById(id); + const responseDto = await handler.getUserById(id); res.status(201).json(responseDto); } }) diff --git a/src/handlers/AuthHandler.ts b/src/handlers/AuthHandler.ts index 32065fb..3018d09 100644 --- a/src/handlers/AuthHandler.ts +++ b/src/handlers/AuthHandler.ts @@ -2,8 +2,8 @@ import { UserRepository } from "../repositories/UserRepository.js"; import { encrypt } from "../utils/encryptionUtils.js"; import { ValidationError, UnauthorizedError } from "../errors/httpErrors.js"; import { UserDtoEntityMapper } from "../mappers/UserDtoEntityMapper.js"; -import { LoginResponseDto } from "../dtos/LoginResponseDto.js"; -import { LoginRequestDto } from "../dtos/LoginRequestDto.js"; +import { LoginResponse } from "../dtos/LoginResponse.js"; +import { LoginRequest } from "../dtos/LoginRequest.js"; /** * Controller responsible for authentication, e.g., login or issueing a token with extended @@ -17,10 +17,10 @@ export class AuthHandler { /** * Login: Check user and password and generate token - * @param loginRequest LoginRequestDto containing userName and password for login + * @param loginRequest LoginRequest containing userName and password for login * @returns LoginResponse containing token and user data for the user who just logged in */ - async login(loginRequest : LoginRequestDto): Promise { + async login(loginRequest : LoginRequest): Promise { const userName :string|undefined = loginRequest.userName; const password :string|undefined = loginRequest.password; console.log("user", userName, " is trying to log in") @@ -50,7 +50,7 @@ export class AuthHandler { id: userId!, // ! to indicate that we've definitely checked for userId being defined }); - const responseDto = new LoginResponseDto(); + const responseDto = new LoginResponse(); responseDto.userData = this.mapper.toDto(user); responseDto.token = tokenInfo.token; responseDto.expiryDate = tokenInfo.expiryDate; diff --git a/src/handlers/UserHandler.ts b/src/handlers/UserHandler.ts index bccb8e3..cce1695 100644 --- a/src/handlers/UserHandler.ts +++ b/src/handlers/UserHandler.ts @@ -1,5 +1,5 @@ import { ValidationError, ConflictError, NotFoundError } from "../errors/httpErrors.js"; -import { CreateUserRequestDto } from "../dtos/CreateUserRequestDto.js"; +import { CreateUserRequest } from "../dtos/CreateUserRequest.js"; import { UserDto } from "../dtos/UserDto.js"; import { encrypt } from "../utils/encryptionUtils.js"; import { UserRepository } from "../repositories/UserRepository.js"; @@ -17,26 +17,21 @@ export class UserHandler { /** * Create a new user - * @param dto CreateUserRequestDto containing data for the user to add + * @param dto CreateUserRequest containing data for the user to add * @returns UserDto Data of the user as stored in the database */ - async createUser(dto: CreateUserRequestDto): Promise { + async createUser(dto: CreateUserRequest): Promise { // check mandatory fields - if(!dto.userData){ - throw new ValidationError("User data is required") - } - const email = dto.userData.email; - if (!email || (email && email.length == 0)) { - throw new ValidationError("Email is required"); - } - const password = dto.password; + if(!dto.userData){ + throw new ValidationError("User data is required") + } + this.validateUserData(dto.userData); + const userName = dto.userData.userName; + const password = dto.password; if(!password || (password && password.length == 0)){ throw new ValidationError("Password is required"); } - const userName = dto.userData.userName; - if (!userName|| (userName && userName.length == 0)){ - throw new ValidationError("Username is required"); - } + // user name must be unique const existingUser = await this.userRepository.findByUserName(userName); @@ -52,6 +47,42 @@ export class UserHandler { return this.mapper.toDto(savedUser); } + private validateUserData(dto: UserDto) : void { + const email = dto.email; + if (!email || (email && email.length == 0)) { + throw new ValidationError("Email is required"); + } + const userName = dto.userName; + if (!userName || (userName && userName.length == 0)) { + throw new ValidationError("Username is required"); + } + } + + /** + * Update user data + * @param dto UserDto containing updates userData + */ + async updateUserData(dto: UserDto) : Promise { + // Ensure that user data is complete + this.validateUserData(dto); + const userId = dto.id + if(userId === undefined || userId.length === 0){ + // user does not exist yet -> Wrong method is used + throw new ValidationError("Cannot save user without valid userId. Use user/create to create a new user"); + } + // First: Load current version of user from database + const entity = await this.userRepository.findById(userId); + if (!entity) { + throw new ValidationError("No user with ID " + userId + " found in database!") + } + // merge changes into entity + this.mapper.mergeDtoIntoEntity(dto, entity); + // persist changes + const savedEntity = await this.userRepository.update(entity); + return this.mapper.toDto(savedEntity); + } + + /** * Load data of a specific user * @param userId Id of user to load