running now
This commit is contained in:
parent
85cd083750
commit
c17bb05f0a
25 changed files with 156 additions and 114 deletions
2
.env
2
.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
|
||||
|
|
|
|||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
.vscode
|
||||
build
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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: [],
|
||||
});
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
export abstract class AbstractDto {
|
||||
id: string;
|
||||
id?: string;
|
||||
createdAt?: Date;
|
||||
updatedAt?: Date;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -2,6 +2,6 @@
|
|||
* Defines a login request
|
||||
*/
|
||||
export class LoginRequestDto {
|
||||
userName: string;
|
||||
password: string;
|
||||
userName?: string;
|
||||
password?: string;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
32
src/index.ts
32
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,19 +26,24 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
// ⚠️ Don’t forget password handling — usually set elsewhere, not exposed in DTO
|
||||
entity.firstName = dto.firstName;
|
||||
entity.lastName = dto.lastName;
|
||||
if(dto.role){
|
||||
entity.role = dto.role;
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
};
|
||||
|
|
@ -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 },
|
||||
});
|
||||
|
|
@ -14,4 +20,4 @@ export const authorization = (roles: string[]) => {
|
|||
}
|
||||
next();
|
||||
};
|
||||
};
|
||||
};*/
|
||||
|
|
@ -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 }) {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
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" });
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
export function isNeitherNullNorEmpty(input : string) : boolean {
|
||||
return input && input.length !==0;
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue