mirror of
https://github.com/andwati/order-service.git
synced 2026-10-23 12:32:15 +00:00
feat: refactor repositories and services to use type imports and improve structure
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { ZodSchema } from "zod";
|
||||
import type { ZodSchema } from "zod";
|
||||
import type { Request, Response, NextFunction } from "express";
|
||||
|
||||
export const validate =
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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(
|
||||
"/",
|
||||
|
||||
@@ -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(
|
||||
"/",
|
||||
|
||||
@@ -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 }),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user