feat: refactor repositories and services to use type imports and improve structure

This commit is contained in:
2026-01-13 00:28:49 +03:00
parent 64e304214b
commit 2efdc8910f
10 changed files with 134 additions and 59 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
import { ZodSchema } from "zod";
import type { ZodSchema } from "zod";
import type { Request, Response, NextFunction } from "express";
export const validate =
+27
View File
@@ -0,0 +1,27 @@
import type { ClientSession } from "mongoose";
import { OrderModel, type OrderDocument } from "../models/order.model.js";
export class OrderRepository {
static create(
data: Omit<OrderDocument, "_id" | "createdAt" | "updatedAt">,
session: ClientSession,
): Promise<OrderDocument> {
return OrderModel.create([data], { session }).then((res) => res[0]);
}
static findById(id: string, session?: ClientSession) {
return OrderModel.findById(id).session(session ?? null);
}
static findAll() {
return OrderModel.find().lean();
}
static findByUser(userId: string) {
return OrderModel.find({ userId }).lean();
}
static save(order: OrderDocument, session?: ClientSession) {
return order.save({ session });
}
}
+49
View File
@@ -0,0 +1,49 @@
import type { ClientSession } from "mongoose";
import { ProductModel } from "../models/product.model.js";
import type { ProductDocument } from "../models/product.model.js";
export class ProductRepository {
static findAll(): Promise<ProductDocument[]> {
return ProductModel.find().lean();
}
static findByIds(
ids: string[],
session?: ClientSession,
): Promise<ProductDocument[]> {
return ProductModel.find({ _id: { $in: ids } }).session(session ?? null);
}
static findById(id: string): Promise<ProductDocument | null> {
return ProductModel.findById(id);
}
static create(
data: Pick<ProductDocument, "name" | "price" | "stock">,
): Promise<ProductDocument> {
return ProductModel.create(data);
}
static updateById(
id: string,
update: Partial<Pick<ProductDocument, "name" | "price" | "stock">>,
): Promise<ProductDocument | null> {
return ProductModel.findByIdAndUpdate(id, update, { new: true });
}
static decrementStock(id: string, quantity: number, session: ClientSession) {
return ProductModel.updateOne(
{ _id: id, stock: { $gte: quantity } },
{ $inc: { stock: -quantity } },
{ session },
);
}
static incrementStock(id: string, quantity: number, session: ClientSession) {
return ProductModel.updateOne(
{ _id: id },
{ $inc: { stock: quantity } },
{ session },
);
}
}
+20
View File
@@ -0,0 +1,20 @@
import type { ClientSession } from "mongoose";
import { UserModel } from "../models/user.model.js";
import type { UserDocument } from "../models/user.model.js";
export class UserRepository {
static findByEmail(email: string): Promise<UserDocument | null> {
return UserModel.findOne({ email });
}
static findById(id: string): Promise<UserDocument | null> {
return UserModel.findById(id);
}
static create(
data: Pick<UserDocument, "email" | "passwordHash" | "role">,
session?: ClientSession,
): Promise<UserDocument> {
return UserModel.create([data], { session }).then((res) => res[0]);
}
}
+5 -5
View File
@@ -1,10 +1,10 @@
import { Router } from "express";
import { AuthController } from "../controllers/auth.controller";
import { validate } from "../middleware/validate.middleware";
import { authRateLimiter } from "../middleware/rateLimit.middleware";
import { registerSchema, loginSchema } from "../validation/auth.schema";
import { AuthController } from "../controllers/auth.controller.js";
import { validate } from "../middleware/validate.middleware.js";
import { authRateLimiter } from "../middleware/rateLimit.middleware.js";
import { registerSchema, loginSchema } from "../validation/auth.schema.js";
const router = Router();
const router: Router = Router();
router.post(
"/register",
+6 -6
View File
@@ -1,14 +1,14 @@
import { Router } from "express";
import { OrderController } from "../controllers/order.controller";
import { authMiddleware } from "../middleware/auth.middleware";
import { requireRole } from "../middleware/role.middleware";
import { validate } from "../middleware/validate.middleware";
import { OrderController } from "../controllers/order.controller.js";
import { authMiddleware } from "../middleware/auth.middleware.js";
import { requireRole } from "../middleware/role.middleware.js";
import { validate } from "../middleware/validate.middleware.js";
import {
createOrderSchema,
orderIdParamSchema,
} from "../validation/order.schema";
} from "../validation/order.schema.js";
const router = Router();
const router: Router = Router();
router.post(
"/",
+6 -6
View File
@@ -1,14 +1,14 @@
import { Router } from "express";
import { ProductController } from "../controllers/product.controller";
import { authMiddleware } from "../middleware/auth.middleware";
import { requireRole } from "../middleware/role.middleware";
import { validate } from "../middleware/validate.middleware";
import { ProductController } from "../controllers/product.controller.js";
import { authMiddleware } from "../middleware/auth.middleware.js";
import { requireRole } from "../middleware/role.middleware.js";
import { validate } from "../middleware/validate.middleware.js";
import {
createProductSchema,
updateProductSchema,
} from "../validation/product.schema";
} from "../validation/product.schema.js";
const router = Router();
const router: Router = Router();
router.post(
"/",
+12 -26
View File
@@ -1,47 +1,33 @@
import bcrypt from "bcrypt";
import { UserModel } from "../models/user.model";
import { signToken } from "../utils/jwt";
const SALT_ROUNDS = 10;
import { UserRepository } from "../repositories/user.repo.js";
import { hashPassword, verifyPassword } from "../utils/password.js";
import { signToken } from "../utils/jwt.js";
export class AuthService {
static async register(email: string, password: string) {
const existing = await UserModel.findOne({ email });
const existing = await UserRepository.findByEmail(email);
if (existing) {
throw { statusCode: 409, message: "Email already registered" };
}
const passwordHash = await bcrypt.hash(password, SALT_ROUNDS);
const passwordHash = await hashPassword(password);
const user = await UserModel.create({
const user = await UserRepository.create({
email,
passwordHash,
role: "customer",
});
return {
id: user._id,
email: user.email,
role: user.role,
};
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) {
const user = await UserRepository.findByEmail(email);
if (!user || !(await verifyPassword(password, user.passwordHash))) {
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 };
return {
token: signToken({ sub: user._id, role: user.role }),
};
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
import mongoose from "mongoose";
import { ProductRepository } from "../repositories/product.repo";
import { OrderRepository } from "../repositories/order.repo";
import { ProductRepository } from "../repositories/product.repo.js";
import { OrderRepository } from "../repositories/order.repo.js";
export class OrderService {
static async createOrder(
+6 -13
View File
@@ -1,29 +1,22 @@
import { ProductModel } from "../models/product.model";
import { ProductRepository } from "../repositories/product.repo.js";
export class ProductService {
static async create(data: { name: string; price: number; stock: number }) {
const product = await ProductModel.create(data);
return product;
static create(data: { name: string; price: number; stock: number }) {
return ProductRepository.create(data);
}
static async list() {
return ProductModel.find().lean();
static list() {
return ProductRepository.findAll();
}
static async update(
id: string,
data: Partial<{ name: string; price: number; stock: number }>,
) {
const product = await ProductModel.findByIdAndUpdate(
id,
{ $set: data },
{ new: true },
);
const product = await ProductRepository.updateById(id, data);
if (!product) {
throw { statusCode: 404, message: "Product not found" };
}
return product;
}
}