diff --git a/.env b/.env index 76fee09..0ba7bcc 100644 --- a/.env +++ b/.env @@ -4,5 +4,5 @@ DB_HOST=localhost DB_PORT=5432 DB_USERNAME=recipe-backend DB_PASSWORD=yeshu0bue5aigaphie0eemoFey3farei -DB_DATABASE=recipe-backend +DB_DATABASE=recipe-backend-dev NODE_ENV=dev diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..af96791 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.vscode +build diff --git a/package.json b/package.json index 1aee609..26f241a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "recipe-backend", "version": "0.0.1", "description": "Awesome project developed with TypeORM.", - "type": "commonjs", + "type": "module", "devDependencies": { "@types/node": "^22.13.10", "ts-node": "^10.9.2", diff --git a/src/controllers/AuthController.ts b/src/controllers/AuthController.ts index ca2159b..064fac6 100644 --- a/src/controllers/AuthController.ts +++ b/src/controllers/AuthController.ts @@ -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 { - 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(); diff --git a/src/controllers/UserController.ts b/src/controllers/UserController.ts index a00444d..66301d8 100644 --- a/src/controllers/UserController.ts +++ b/src/controllers/UserController.ts @@ -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 { // 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); diff --git a/src/data-source.ts b/src/data-source.ts index d197067..bcbae59 100644 --- a/src/data-source.ts +++ b/src/data-source.ts @@ -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: [], }); \ No newline at end of file diff --git a/src/dtos/AbstractDto.ts b/src/dtos/AbstractDto.ts index a45560d..be585a9 100644 --- a/src/dtos/AbstractDto.ts +++ b/src/dtos/AbstractDto.ts @@ -1,5 +1,5 @@ export abstract class AbstractDto { - id: string; + id?: string; createdAt?: Date; updatedAt?: Date; } \ No newline at end of file diff --git a/src/dtos/CreateUserRequestDto.ts b/src/dtos/CreateUserRequestDto.ts index c0f653d..68d4012 100644 --- a/src/dtos/CreateUserRequestDto.ts +++ b/src/dtos/CreateUserRequestDto.ts @@ -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; } \ No newline at end of file diff --git a/src/dtos/LoginRequestDto.ts b/src/dtos/LoginRequestDto.ts index d1b1ae6..a3da6ee 100644 --- a/src/dtos/LoginRequestDto.ts +++ b/src/dtos/LoginRequestDto.ts @@ -2,6 +2,6 @@ * Defines a login request */ export class LoginRequestDto { - userName: string; - password: string; + userName?: string; + password?: string; } \ No newline at end of file diff --git a/src/dtos/LoginResponseDto.ts b/src/dtos/LoginResponseDto.ts index 9c57701..695927b 100644 --- a/src/dtos/LoginResponseDto.ts +++ b/src/dtos/LoginResponseDto.ts @@ -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; } \ No newline at end of file diff --git a/src/dtos/UserDto.ts b/src/dtos/UserDto.ts index 002fe54..56fc6c2 100644 --- a/src/dtos/UserDto.ts +++ b/src/dtos/UserDto.ts @@ -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; } \ No newline at end of file diff --git a/src/endpoints/AuthPoint.ts b/src/endpoints/AuthPoint.ts index 91abda3..ac1ca93 100644 --- a/src/endpoints/AuthPoint.ts +++ b/src/endpoints/AuthPoint.ts @@ -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) { diff --git a/src/endpoints/UserPoint.ts b/src/endpoints/UserPoint.ts index 7ee533d..698ea58 100644 --- a/src/endpoints/UserPoint.ts +++ b/src/endpoints/UserPoint.ts @@ -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(); diff --git a/src/entities/AbstractEntity.ts b/src/entities/AbstractEntity.ts index 1b32897..be561b9 100644 --- a/src/entities/AbstractEntity.ts +++ b/src/entities/AbstractEntity.ts @@ -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; } diff --git a/src/entities/UserEntity.ts b/src/entities/UserEntity.ts index a57d7ac..0587bca 100644 --- a/src/entities/UserEntity.ts +++ b/src/entities/UserEntity.ts @@ -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; } - diff --git a/src/index.ts b/src/index.ts index b26b7f8..b02171e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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(); \ No newline at end of file +startServer(); diff --git a/src/mappers/AbstractDtoEntityMapper.ts b/src/mappers/AbstractDtoEntityMapper.ts index 5b76d3b..2a9f8a5 100644 --- a/src/mappers/AbstractDtoEntityMapper.ts +++ b/src/mappers/AbstractDtoEntityMapper.ts @@ -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, diff --git a/src/mappers/UserDtoEntityMapper.ts b/src/mappers/UserDtoEntityMapper.ts index 7dfc502..e6791e8 100644 --- a/src/mappers/UserDtoEntityMapper.ts +++ b/src/mappers/UserDtoEntityMapper.ts @@ -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 { toDto(entity: UserEntity): UserDto { @@ -22,10 +23,11 @@ export class UserDtoEntityMapper extends AbstractDtoEntityMapper { 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(); - }; -}; \ No newline at end of file + }; +};*/ \ No newline at end of file diff --git a/src/repositories/AbstractRepository.ts b/src/repositories/AbstractRepository.ts index d415c6e..4a9ece8 100644 --- a/src/repositories/AbstractRepository.ts +++ b/src/repositories/AbstractRepository.ts @@ -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 { +export abstract class AbstractRepository { protected repo: Repository; constructor(entity: { new (): T }) { diff --git a/src/repositories/UserRepository.ts b/src/repositories/UserRepository.ts index 392ece5..f18c906 100644 --- a/src/repositories/UserRepository.ts +++ b/src/repositories/UserRepository.ts @@ -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 { constructor() { diff --git a/src/utils/encryptionUtils.ts b/src/utils/encryptionUtils.ts index aa00fef..71cde4a 100644 --- a/src/utils/encryptionUtils.ts +++ b/src/utils/encryptionUtils.ts @@ -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" }); } } \ No newline at end of file diff --git a/src/utils/stringUtils.ts b/src/utils/stringUtils.ts deleted file mode 100644 index 9569793..0000000 --- a/src/utils/stringUtils.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function isNeitherNullNorEmpty(input : string) : boolean { - return input && input.length !==0; -} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 8ad9f4b..b90ec23 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,11 +4,12 @@ "es2021" ], "target": "es2021", - "module": "commonjs", - "moduleResolution": "node", + "module": "node16", + "moduleResolution": "node16", "outDir": "./build", "emitDecoratorMetadata": true, "experimentalDecorators": true, - "sourceMap": true + "sourceMap": true, + "strict": true } } \ No newline at end of file