running now

This commit is contained in:
Anika Raemer 2025-09-22 20:17:46 +02:00
parent 85cd083750
commit c17bb05f0a
25 changed files with 156 additions and 114 deletions

View file

@ -1,9 +1,9 @@
import { UserRepository } from "../repositories/UserRepository";
import { encrypt } from "../utils/encryptionUtils";
import { ValidationError, UnauthorizedError } from "../errors/httpErrors";
import { UserDtoEntityMapper } from "../mappers/UserDtoEntityMapper";
import { LoginResponseDto } from "../dtos/LoginResponseDto";
import { LoginRequestDto } from "../dtos/LoginRequestDto";
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";
export class AuthController {
constructor(
@ -12,25 +12,27 @@ export class AuthController {
) {}
async login(loginRequest : LoginRequestDto): Promise<LoginResponseDto> {
const userName :string = loginRequest.userName;
const password :string = loginRequest.password;
const userName :string|undefined = loginRequest.userName;
const password :string|undefined = loginRequest.password;
if (!userName || !password) {
throw new ValidationError("Username and password are required");
}
// Find user by userName
const user = await this.userRepository.findByUserName(userName);
// check user before trying to access password!
if(!user){
throw new UnauthorizedError("Invalid username or password");
}
// Compare password
const passwordMatches = encrypt.comparepassword(user.password, password);
if (!passwordMatches || !user ) {
if (!passwordMatches) {
throw new UnauthorizedError("Invalid username or password");
}
// Create JWT
const token = encrypt.generateToken({
id: user.id,
userName: user.userName,
role: user.role,
});
const responseDto = new LoginResponseDto();

View file

@ -1,10 +1,9 @@
import { ValidationError, ConflictError } from "../errors/httpErrors";
import { CreateUserRequestDto } from "../dtos/CreateUserRequestDto";
import { UserDto } from "../dtos/UserDto";
import { encrypt } from "../utils/encryptionUtils";
import { UserRepository } from "../repositories/UserRepository";
import { UserDtoEntityMapper } from "../mappers/UserDtoEntityMapper";
import { isNeitherNullNorEmpty } from "../utils/stringUtils";
import { ValidationError, ConflictError } from "../errors/httpErrors.js";
import { CreateUserRequestDto } from "../dtos/CreateUserRequestDto.js";
import { UserDto } from "../dtos/UserDto.js";
import { encrypt } from "../utils/encryptionUtils.js";
import { UserRepository } from "../repositories/UserRepository.js";
import { UserDtoEntityMapper } from "../mappers/UserDtoEntityMapper.js";
export class UserController {
constructor(
@ -14,23 +13,30 @@ export class UserController {
async createUser(dto: CreateUserRequestDto): Promise<UserDto> {
// check mandatory fields
if (!isNeitherNullNorEmpty(dto.userData.email)) {
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");
}
if (!isNeitherNullNorEmpty(dto.userData.userName)) {
throw new ValidationError("Username is required");
}
if(!isNeitherNullNorEmpty(dto.password){
const password = dto.password;
if(!password || (password && password.length == 0)){
throw new ValidationError("Password is required");
}
// user name must be uniqu
const existingUser = await this.userRepository.findByUserName(dto.userData.email);
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);
if (existingUser) {
throw new ConflictError("User with this user name already exists");
}
const userEntity = this.mapper.toEntity(dto.userData);
userEntity.password = await encrypt.encryptpass(dto.password);
userEntity.password = await encrypt.encryptpass(password);
const savedUser = await this.userRepository.create(userEntity);

View file

@ -2,7 +2,13 @@ import "reflect-metadata";
import { DataSource } from "typeorm";
import * as dotenv from "dotenv";
import { UserEntity } from "./entities/UserEntity";
import { UserEntity } from "./entities/UserEntity.js";
import { fileURLToPath } from "url";
import { dirname, join } from "path";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
dotenv.config();
@ -20,7 +26,7 @@ export const AppDataSource = new DataSource({
synchronize: NODE_ENV === "dev" ? false : false,
//logging logs sql command on the terminal
logging: NODE_ENV === "dev" ? false : false,
entities: [UserEntity],
migrations: [__dirname + "/migration/*.ts"],
entities: [join(__dirname, "/entities/*.{ts,js}")],
migrations: [join(__dirname, "/migrations/*.{ts,js}")],
subscribers: [],
});

View file

@ -1,5 +1,5 @@
export abstract class AbstractDto {
id: string;
id?: string;
createdAt?: Date;
updatedAt?: Date;
}

View file

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

View file

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

View file

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

View file

@ -1,9 +1,9 @@
import { AbstractDto } from "./AbstractDto";
import { AbstractDto } from "./AbstractDto.js";
export class UserDto extends AbstractDto {
firstName?: string;
lastName?: string;
userName: string;
email: string;
role: string;
userName!: string;
email!: string;
role?: string;
}

View file

@ -1,13 +1,13 @@
import { Router } from "express";
import { AuthController } from "../controllers/AuthController";
import { UserRepository } from "../repositories/UserRepository";
import { UserDtoEntityMapper } from "../mappers/UserDtoEntityMapper";
import { AuthController } from "../controllers/AuthController.js";
import { UserRepository } from "../repositories/UserRepository.js";
import { UserDtoEntityMapper } from "../mappers/UserDtoEntityMapper.js";
import {
ValidationError,
UnauthorizedError,
InternalServerError,
} from "../errors/httpErrors";
import { LoginRequestDto } from "../dtos/LoginRequestDto";
} from "../errors/httpErrors.js";
import { LoginRequestDto } from "../dtos/LoginRequestDto.js";
const router = Router();
@ -18,7 +18,7 @@ const authController = new AuthController(userRepository, mapper);
router.post("/login", async (req, res) => {
try {
const requestDto: LoginRequestDto = req.body;
const responseDto = await authController.login(requestDto));
const responseDto = await authController.login(requestDto);
res.json(responseDto);
} catch (err: any) {
if (err instanceof ValidationError || err instanceof UnauthorizedError) {

View file

@ -1,14 +1,14 @@
import { Router } from "express";
import { UserController } from "../controllers/UserController";
import { CreateUserRequestDto } from "../dtos/CreateUserRequestDto";
import { UserRepository } from "../repositories/UserRepository";
import { UserDtoEntityMapper } from "../mappers/UserDtoEntityMapper";
import { UserController } from "../controllers/UserController.js";
import { CreateUserRequestDto } from "../dtos/CreateUserRequestDto.js";
import { UserRepository } from "../repositories/UserRepository.js";
import { UserDtoEntityMapper } from "../mappers/UserDtoEntityMapper.js";
import {
ValidationError,
ConflictError,
NotFoundError,
InternalServerError,
} from "../errors/httpErrors";
} from "../errors/httpErrors.js";
const router = Router();

View file

@ -6,11 +6,11 @@ import {
export abstract class AbstractEntity {
@PrimaryGeneratedColumn("uuid")
id: string;
id?: string;
@CreateDateColumn()
createdAt: Date;
createdAt?: Date;
@UpdateDateColumn()
updatedAt: Date;
updatedAt?: Date;
}

View file

@ -1,24 +1,23 @@
import { Entity, Column } from "typeorm";
import { AbstractEntity } from "./AbstractEntity";
import { AbstractEntity } from "./AbstractEntity.js";
@Entity({ name: "user" })
export class UserEntity extends AbstractEntity {
@Column({ nullable: false })
userName: string;
userName!: string;
@Column({ nullable: false })
email: string;
email!: string;
@Column({ nullable: false })
password: string;
password!: string;
@Column({ nullable: true })
firstName: string;
firstName?: string;
@Column({ nullable: true })
lastName: string;
lastName?: string;
@Column({ default: "user" })
role: string;
role!: string;
}

View file

@ -1,19 +1,18 @@
import { AppDataSource } from "./data-source"
import * as express from "express";
import * as dotenv from "dotenv";
import { Request, Response } from "express";
import authRoutes from "./endpoints/AuthPoint"
import userRoutes from "./endpoints/UserPoint";
//import { recipePoint } from "./endpoints/RecipePoint";
import {errorHandler} from "./middleware/errorHandler"
import "reflect-metadata";
import express, { NextFunction, Request, Response } from "express";
import dotenv from "dotenv";
import { AppDataSource } from "./data-source.js";
import authRoutes from "./endpoints/AuthPoint.js";
import userRoutes from "./endpoints/UserPoint.js";
// import recipeRoutes from "./endpoints/RecipePoint.js";
import { errorHandler } from "./middleware/errorHandler.js";
dotenv.config();
const app = express();
app.use(express.json());
app.use(errorHandler);
async function startServer() {
try {
// 1⃣ Initialize database
@ -27,21 +26,26 @@ async function startServer() {
// 2⃣ Setup routes
app.use("/auth", authRoutes);
app.use("/user", userRoutes);
// app.use("/recipe", recipeRoutes);
app.get("*", (req: Request, res: Response) => {
res.status(505).json({ message: "Bad Request" });
console.log("auth and user routes added")
app.get(/(.*)/, (req: Request, res: Response, next: NextFunction) => {
res.status(400).json({ message: "Bad Request" });
});
console.log("Routes set up")
// 3⃣ Start listening
const PORT = parseInt(process.env.PORT || "4000", 10);
const PORT = Number(process.env.PORT) || 4000;
const HOST = process.env.HOST || "localhost";
app.listen(PORT, HOST, () => {
console.log(`Server running on http://${HOST}:${PORT}`);
console.log(`Server running on http://${HOST}:${PORT}`);
});
} catch (err) {
console.error("Error during Data Source initialization:", err);
console.error("❌ Error during Data Source initialization:", err);
process.exit(1);
}
}
// Call the async start function
startServer();
startServer();

View file

@ -1,5 +1,5 @@
import { AbstractDto } from "../dtos/AbstractDto";
import { AbstractEntity } from "../entities/AbstractEntity";
import { AbstractDto } from "../dtos/AbstractDto.js";
import { AbstractEntity } from "../entities/AbstractEntity.js";
export abstract class AbstractDtoEntityMapper<
E extends AbstractEntity,

View file

@ -1,6 +1,7 @@
import { AbstractDtoEntityMapper } from "./AbstractDtoEntityMapper";
import { UserEntity } from "../entities/UserEntity";
import { UserDto } from "../dtos/UserDto";
import { AbstractDtoEntityMapper } from "./AbstractDtoEntityMapper.js";
import { UserEntity } from "../entities/UserEntity.js";
import { UserDto } from "../dtos/UserDto.js";
import { ValidationError } from "../errors/httpErrors.js";
export class UserDtoEntityMapper extends AbstractDtoEntityMapper<UserEntity, UserDto> {
toDto(entity: UserEntity): UserDto {
@ -22,10 +23,11 @@ export class UserDtoEntityMapper extends AbstractDtoEntityMapper<UserEntity, Use
entity.userName = dto.userName;
entity.email = dto.email;
// ⚠️ Dont forget password handling — usually set elsewhere, not exposed in DTO
entity.firstName = dto.firstName;
entity.lastName = dto.lastName;
entity.role = dto.role;
if(dto.role){
entity.role = dto.role;
}
return entity;
}

View file

@ -1,8 +1,17 @@
import { NextFunction, Request, Response } from "express";
import * as jwt from "jsonwebtoken";
import * as dotenv from "dotenv";
dotenv.config();
declare global {
namespace Express {
interface Request {
currentUser?: string | jwt.JwtPayload;
}
}
}
export const authentication = (
req: Request,
res: Response,
@ -12,14 +21,22 @@ export const authentication = (
if (!header) {
return res.status(401).json({ message: "Unauthorized" });
}
const token = header.split(" ")[1];
if (!token) {
return res.status(401).json({ message: "Unauthorized" });
}
const decode = jwt.verify(token, process.env.JWT_SECRET);
if (!decode) {
const JWT_SECRET = process.env.JWT_SECRET;
if (!JWT_SECRET) {
throw new Error("JWT_SECRET not defined");
}
try {
const decoded = jwt.verify(token, JWT_SECRET);
req.currentUser = decoded;
next();
} catch (err) {
return res.status(401).json({ message: "Unauthorized" });
}
req[" currentUser"] = decode;
next();
};
};

View file

@ -1,10 +1,16 @@
import { NextFunction, Request, Response } from "express";
import { AppDataSource } from "../data-source";
import { UserEntity } from "../entities/UserEntity";
/* import { NextFunction, Request, Response } from "express";
import { AppDataSource } from "../data-source.js";
import { UserEntity } from "../entities/UserEntity.js";
// @todo we'll need some other means to determin the user corresponding to the token here as it seems...
export const authorization = (roles: string[]) => {
return async (req: Request, res: Response, next: NextFunction) => {
const userRepo = AppDataSource.getRepository(UserEntity);
const currentUser = req.currentUser;
if(!currentUser){
return res.status(403).json({ message: "Forbidden - currentUser is missing" });
}
const userId = currentUser.id
const user = await userRepo.findOne({
where: { id: req[" currentUser"].id },
});
@ -13,5 +19,5 @@ export const authorization = (roles: string[]) => {
return res.status(403).json({ message: "Forbidden" });
}
next();
};
};
};
};*/

View file

@ -1,7 +1,8 @@
import { Repository, DeepPartial } from "typeorm";
import { AppDataSource } from "../data-source";
import { Repository, DeepPartial, ObjectLiteral } from "typeorm";
import { AppDataSource } from "../data-source.js";
import { AbstractEntity } from "../entities/AbstractEntity.js";
export abstract class AbstractRepository<T> {
export abstract class AbstractRepository<T extends AbstractEntity> {
protected repo: Repository<T>;
constructor(entity: { new (): T }) {

View file

@ -1,5 +1,5 @@
import { AbstractRepository } from "./AbstractRepository";
import { UserEntity } from "../entities/UserEntity";
import { AbstractRepository } from "./AbstractRepository.js";
import { UserEntity } from "../entities/UserEntity.js";
export class UserRepository extends AbstractRepository<UserEntity> {
constructor() {

View file

@ -1,7 +1,6 @@
import * as jwt from "jsonwebtoken";
import * as bcrypt from "bcrypt";
import * as dotenv from "dotenv";
import { payload } from "../dtos/userDto";
dotenv.config();
const { JWT_SECRET = "" } = process.env;
@ -9,11 +8,11 @@ export class encrypt {
static async encryptpass(password: string) {
return bcrypt.hashSync(password, 12);
}
static comparepassword(hashPassword: string, password: string) {
return bcrypt.compareSync(password, hashPassword);
}
static comparepassword(password: string, hashPassword: string) {
return bcrypt.compareSync(password, hashPassword);
}
static generateToken(payload: payload) {
static generateToken(payload: object) {
return jwt.sign(payload, JWT_SECRET, { expiresIn: "1d" });
}
}

View file

@ -1,3 +0,0 @@
export function isNeitherNullNorEmpty(input : string) : boolean {
return input && input.length !==0;
}