Rename some API object and add update user to UserPoint. However, it seems to be broken somehow

This commit is contained in:
araemer 2025-11-13 21:32:27 +01:00
parent 81f1c3668b
commit a9bd803112
12 changed files with 143 additions and 41 deletions

View file

@ -5,7 +5,7 @@ meta {
} }
post { post {
url: http://localhost:4000/user url: http://localhost:4000/user/create
body: json body: json
auth: bearer auth: bearer
} }

View file

@ -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
}

View file

@ -0,0 +1,7 @@
/**
* DTO for changing user password
*/
export class ChangeUserPasswordRequest {
userId?: string;
password?: string;
}

View file

@ -3,7 +3,7 @@ import { UserDto } from "./UserDto.js";
/** /**
* DTO used for user creation * DTO used for user creation
*/ */
export class CreateUserRequestDto { export class CreateUserRequest {
userData?: UserDto; userData?: UserDto;
password?: string; password?: string;
} }

View file

@ -0,0 +1,5 @@
import { UserDto } from "./UserDto.js";
export class CreateUserResponse{
userData?: UserDto;
}

View file

@ -1,7 +1,7 @@
/** /**
* Defines a login request * Defines a login request
*/ */
export class LoginRequestDto { export class LoginRequest {
userName?: string; userName?: string;
password?: string; password?: string;
} }

View file

@ -3,7 +3,7 @@ import { UserDto } from "./UserDto.js";
/** /**
* Response to a successful login * Response to a successful login
*/ */
export class LoginResponseDto { export class LoginResponse {
userData?: UserDto; userData?: UserDto;
token?: string; token?: string;
expiryDate? : Date; expiryDate? : Date;

View file

@ -7,7 +7,7 @@ import {
UnauthorizedError, UnauthorizedError,
InternalServerError, InternalServerError,
} from "../errors/httpErrors.js"; } from "../errors/httpErrors.js";
import { LoginRequestDto } from "../dtos/LoginRequestDto.js"; import { LoginRequest } from "../dtos/LoginRequest.js";
export const authBasicRoute = "/auth" export const authBasicRoute = "/auth"
@ -18,13 +18,13 @@ const authController = new AuthHandler(userRepository, mapper);
/** /**
* Login using username and password * Login using username and password
* Consumes LoginRequestDto * Consumes LoginRequest
* Responds with LoginResponseDto * Responds with LoginResponse
*/ */
router.post("/login", async (req, res) => { router.post("/login", async (req, res) => {
console.log("login point called") console.log("login point called")
try { try {
const requestDto: LoginRequestDto = req.body; const requestDto: LoginRequest = req.body;
const responseDto = await authController.login(requestDto); const responseDto = await authController.login(requestDto);
res.json(responseDto); res.json(responseDto);
} catch (err: any) { } catch (err: any) {

View file

@ -19,7 +19,7 @@ const recipeIngredientMapper = new RecipeIngredientDtoEntityMapper();
const recipeIngredientGroupMapper = new RecipeIngredientGroupDtoEntityMapper(recipeIngredientMapper); const recipeIngredientGroupMapper = new RecipeIngredientGroupDtoEntityMapper(recipeIngredientMapper);
const recipeInstructionStepMapper = new RecipeInstructionStepDtoEntityMapper(); const recipeInstructionStepMapper = new RecipeInstructionStepDtoEntityMapper();
const recipeMapper = new RecipeDtoEntityMapper(recipeInstructionStepMapper, recipeIngredientGroupMapper); 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", "/create-or-update",
asyncHandler(async(req, res) => { asyncHandler(async(req, res) => {
const requestDto: RecipeDto = req.body; const requestDto: RecipeDto = req.body;
const responseDto = await recipeController.createOrUpdateRecipe(requestDto); const responseDto = await recipeHandler.createOrUpdateRecipe(requestDto);
res.status(201).json(responseDto); res.status(201).json(responseDto);
}) })
) )
@ -47,7 +47,7 @@ router.get(
"/:id", "/:id",
asyncHandler(async(req, res) => { asyncHandler(async(req, res) => {
const id = req.params.id; const id = req.params.id;
const responseDto = await recipeController.getRecipeById(id); const responseDto = await recipeHandler.getRecipeById(id);
res.status(201).json(responseDto); res.status(201).json(responseDto);
}) })
); );

View file

@ -1,9 +1,11 @@
import { Router } from "express"; import { Router } from "express";
import { UserHandler } from "../handlers/UserHandler.js"; 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 { UserRepository } from "../repositories/UserRepository.js";
import { UserDtoEntityMapper } from "../mappers/UserDtoEntityMapper.js"; import { UserDtoEntityMapper } from "../mappers/UserDtoEntityMapper.js";
import { asyncHandler } from "../utils/asyncHandler.js"; import { asyncHandler } from "../utils/asyncHandler.js";
import {CreateUserResponse} from "../dtos/CreateUserResponse.js";
import {UserDto} from "../dtos/UserDto.js";
/** /**
* Handles all user related routes * Handles all user related routes
@ -11,24 +13,51 @@ import { asyncHandler } from "../utils/asyncHandler.js";
const router = Router(); const router = Router();
// Inject repo + mapper here // Inject repo + mapper here
const userRepository = new UserRepository(); const handler
const userMapper = new UserDtoEntityMapper(); = new UserHandler(new UserRepository(), new UserDtoEntityMapper());
const userController = new UserHandler(userRepository, userMapper);
/** /**
* Create a new user * Create a new user
* Consumes CreateUserRequestDto * Consumes CreateUserRequest
* Responds with UserDto * Responds with UserDto
*/ */
router.post( router.post(
"/", "/create",
asyncHandler(async (req, res) => { asyncHandler(async (req, res) => {
const requestDto: CreateUserRequestDto = req.body; const request: CreateUserRequest = req.body;
const responseDto = await userController.createUser(requestDto); const user : UserDto = await handler.createUser(request);
res.status(201).json(responseDto); 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 * Get user data for current user
* Responds with UserDto * Responds with UserDto
@ -39,7 +68,7 @@ router.get("/me",
const id = req.currentUser?.id; const id = req.currentUser?.id;
if(id){ if(id){
// it breaks here because id is no longer a uuid // 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); res.status(201).json(responseDto);
} }
}) })

View file

@ -2,8 +2,8 @@ import { UserRepository } from "../repositories/UserRepository.js";
import { encrypt } from "../utils/encryptionUtils.js"; import { encrypt } from "../utils/encryptionUtils.js";
import { ValidationError, UnauthorizedError } from "../errors/httpErrors.js"; import { ValidationError, UnauthorizedError } from "../errors/httpErrors.js";
import { UserDtoEntityMapper } from "../mappers/UserDtoEntityMapper.js"; import { UserDtoEntityMapper } from "../mappers/UserDtoEntityMapper.js";
import { LoginResponseDto } from "../dtos/LoginResponseDto.js"; import { LoginResponse } from "../dtos/LoginResponse.js";
import { LoginRequestDto } from "../dtos/LoginRequestDto.js"; import { LoginRequest } from "../dtos/LoginRequest.js";
/** /**
* Controller responsible for authentication, e.g., login or issueing a token with extended * 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 * 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 * @returns LoginResponse containing token and user data for the user who just logged in
*/ */
async login(loginRequest : LoginRequestDto): Promise<LoginResponseDto> { async login(loginRequest : LoginRequest): Promise<LoginResponse> {
const userName :string|undefined = loginRequest.userName; const userName :string|undefined = loginRequest.userName;
const password :string|undefined = loginRequest.password; const password :string|undefined = loginRequest.password;
console.log("user", userName, " is trying to log in") 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 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.userData = this.mapper.toDto(user);
responseDto.token = tokenInfo.token; responseDto.token = tokenInfo.token;
responseDto.expiryDate = tokenInfo.expiryDate; responseDto.expiryDate = tokenInfo.expiryDate;

View file

@ -1,5 +1,5 @@
import { ValidationError, ConflictError, NotFoundError } from "../errors/httpErrors.js"; 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 { UserDto } from "../dtos/UserDto.js";
import { encrypt } from "../utils/encryptionUtils.js"; import { encrypt } from "../utils/encryptionUtils.js";
import { UserRepository } from "../repositories/UserRepository.js"; import { UserRepository } from "../repositories/UserRepository.js";
@ -17,26 +17,21 @@ export class UserHandler {
/** /**
* Create a new user * 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 * @returns UserDto Data of the user as stored in the database
*/ */
async createUser(dto: CreateUserRequestDto): Promise<UserDto> { async createUser(dto: CreateUserRequest): Promise<UserDto> {
// check mandatory fields // check mandatory fields
if(!dto.userData){ if(!dto.userData){
throw new ValidationError("User data is required") throw new ValidationError("User data is required")
} }
const email = dto.userData.email; this.validateUserData(dto.userData);
if (!email || (email && email.length == 0)) { const userName = dto.userData.userName;
throw new ValidationError("Email is required"); const password = dto.password;
}
const password = dto.password;
if(!password || (password && password.length == 0)){ if(!password || (password && password.length == 0)){
throw new ValidationError("Password is required"); 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 // user name must be unique
const existingUser = await this.userRepository.findByUserName(userName); const existingUser = await this.userRepository.findByUserName(userName);
@ -52,6 +47,42 @@ export class UserHandler {
return this.mapper.toDto(savedUser); 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<UserDto> {
// 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 * Load data of a specific user
* @param userId Id of user to load * @param userId Id of user to load