feat: implement authentication and product management services with validation schemas

This commit is contained in:
2026-01-13 00:04:13 +03:00
parent da428f83bc
commit ae6521977d
6 changed files with 171 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
import { Request, Response, NextFunction } from "express";
import { AuthService } from "../services/auth.service";
export class AuthController {
static async register(req: Request, res: Response, next: NextFunction) {
try {
const { email, password } = req.body;
const user = await AuthService.register(email, password);
res.status(201).json(user);
} catch (err) {
next(err);
}
}
static async login(req: Request, res: Response, next: NextFunction) {
try {
const { email, password } = req.body;
const result = await AuthService.login(email, password);
res.status(200).json(result);
} catch (err) {
next(err);
}
}
}
+32
View File
@@ -0,0 +1,32 @@
import { Request, Response, NextFunction } from "express";
import { ProductService } from "../services/product.service";
export class ProductController {
static async create(req: Request, res: Response, next: NextFunction) {
try {
const product = await ProductService.create(req.body);
res.status(201).json(product);
} catch (err) {
next(err);
}
}
static async list(_req: Request, res: Response, next: NextFunction) {
try {
const products = await ProductService.list();
res.status(200).json(products);
} catch (err) {
next(err);
}
}
static async update(req: Request, res: Response, next: NextFunction) {
try {
const { id } = req.params;
const product = await ProductService.update(id, req.body);
res.status(200).json(product);
} catch (err) {
next(err);
}
}
}
+47
View File
@@ -0,0 +1,47 @@
import bcrypt from "bcrypt";
import { UserModel } from "../models/user.model";
import { signToken } from "../utils/jwt";
const SALT_ROUNDS = 10;
export class AuthService {
static async register(email: string, password: string) {
const existing = await UserModel.findOne({ email });
if (existing) {
throw { statusCode: 409, message: "Email already registered" };
}
const passwordHash = await bcrypt.hash(password, SALT_ROUNDS);
const user = await UserModel.create({
email,
passwordHash,
role: "customer",
});
return {
id: user._id,
email: user.email,
role: user.role,
};
}
static async login(email: string, password: string) {
const user = await UserModel.findOne({ email });
if (!user) {
throw { statusCode: 401, message: "Invalid credentials" };
}
const valid = await bcrypt.compare(password, user.passwordHash);
if (!valid) {
throw { statusCode: 401, message: "Invalid credentials" };
}
const token = signToken({
sub: user._id,
role: user.role,
});
return { token };
}
}
+29
View File
@@ -0,0 +1,29 @@
import { ProductModel } from "../models/product.model";
export class ProductService {
static async create(data: { name: string; price: number; stock: number }) {
const product = await ProductModel.create(data);
return product;
}
static async list() {
return ProductModel.find().lean();
}
static async update(
id: string,
data: Partial<{ name: string; price: number; stock: number }>,
) {
const product = await ProductModel.findByIdAndUpdate(
id,
{ $set: data },
{ new: true },
);
if (!product) {
throw { statusCode: 404, message: "Product not found" };
}
return product;
}
}
+15
View File
@@ -0,0 +1,15 @@
import { z } from "zod";
export const registerSchema = z.object({
body: z.object({
email: z.string().email(),
password: z.string().min(8),
}),
});
export const loginSchema = z.object({
body: z.object({
email: z.string().email(),
password: z.string().min(1),
}),
});
+20
View File
@@ -0,0 +1,20 @@
import { z } from "zod";
export const createOrderSchema = z.object({
body: z.object({
items: z
.array(
z.object({
productId: z.string().uuid(),
quantity: z.number().int().positive(),
}),
)
.min(1),
}),
});
export const orderIdParamSchema = z.object({
params: z.object({
id: z.string().uuid(),
}),
});