forked from swapnil.bendal/TypeScript-Backend-Template
[feat] - implement player management interfaces, entities, and routes
This commit is contained in:
17
.env.example
17
.env.example
@@ -1,17 +0,0 @@
|
||||
NODE_ENV=
|
||||
PORT=
|
||||
|
||||
# JWT
|
||||
JWT_SECRET=your_jwt_secret
|
||||
JWT_ACCESS_EXPIRATION_MINUTES=230
|
||||
JWT_REFRESH_EXPIRATION_DAYS=30
|
||||
JWT_RESET_PASSWORD_EXPIRATION_MINUTES=10
|
||||
JWT_VERIFY_EMAIL_EXPIRATION_MINUTES=10
|
||||
|
||||
|
||||
# DataBase
|
||||
DB_USERNAME=username
|
||||
DB_PASSWORD=password
|
||||
DB_DATABASE_NAME=database_name
|
||||
DB_HOSTNAME=host
|
||||
DB_PORT=port
|
||||
6794
package-lock.json
generated
Normal file
6794
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -27,13 +27,14 @@ class App {
|
||||
this.app.use(express.json());
|
||||
this.app.use(express.urlencoded({ extended: true }));
|
||||
this.app.use("/public", express.static(path.join(__dirname, "../public")));
|
||||
}
|
||||
}
|
||||
|
||||
private initializeRoutes(): void {
|
||||
this.app.use('/api', routes);
|
||||
// Define our routes
|
||||
this.app.use((req: Request, res: Response, next: NextFunction) => {
|
||||
next(new ApiError(404, "Not found"));
|
||||
console.log("reached in routes");
|
||||
next(new ApiError(404, " Not Found"));
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
28
src/controllers/playerController.ts
Normal file
28
src/controllers/playerController.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Request, Response } from 'express';
|
||||
import ApiResponse from '../utils/helper/ApiResponse';
|
||||
import { AsyncHandler } from '../utils/handler/async.handler';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { INTERFACE_TYPE } from '../utils';
|
||||
import { IPlayerInteractor } from "../interfaces/IPlayerInteractor";
|
||||
import { Player } from '../entities/principletype';
|
||||
@injectable()
|
||||
export class PlayerController {
|
||||
private interactor: IPlayerInteractor;
|
||||
|
||||
constructor(
|
||||
@inject(INTERFACE_TYPE.PlayerInteractor) interactor: IPlayerInteractor
|
||||
) {
|
||||
this.interactor = interactor;
|
||||
}
|
||||
|
||||
@AsyncHandler()
|
||||
async onCreatePlayer(req: Request, res: Response) {
|
||||
const data :Player = req.body;
|
||||
console.log(data);
|
||||
const newPlayer= await this.interactor.createPlayer(data);
|
||||
res.status(201).json(new ApiResponse(201, newPlayer, 'Successfully created'));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { IProductInteractor } from '../interfaces/IProductInteractor';
|
||||
import ApiResponse from '../utils/helper/ApiResponse';
|
||||
import { AsyncHandler } from '../utils/handler/async.handler';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { INTERFACE_TYPE } from '../utils';
|
||||
@injectable()
|
||||
export class ProductController {
|
||||
private interactor: IProductInteractor;
|
||||
|
||||
constructor(
|
||||
@inject(INTERFACE_TYPE.ProductInteractor) interactor: IProductInteractor
|
||||
) {
|
||||
this.interactor = interactor;
|
||||
}
|
||||
|
||||
@AsyncHandler()
|
||||
async onCreateProduct(req: Request, res: Response) {
|
||||
const data = await this.interactor.createProduct(req.body);
|
||||
res.status(201).json(new ApiResponse(201, data, 'Successfully created'));
|
||||
}
|
||||
|
||||
@AsyncHandler()
|
||||
async onGetProducts(req: Request, res: Response) {
|
||||
const offset = Number.isInteger(Number(req.query.offset)) ? parseInt(`${req.query.offset}`, 10) : 0;
|
||||
const limit = Number.isInteger(Number(req.query.limit)) ? parseInt(`(${req.query.limit}`, 10) : 10;
|
||||
|
||||
const data = await this.interactor.getProducts(limit, offset);
|
||||
|
||||
if (data.length === 0) {
|
||||
res.status(200).json(new ApiResponse(200, null, 'No products found'));
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).json(new ApiResponse(200, data, 'Products retrieved successfully'));
|
||||
}
|
||||
|
||||
@AsyncHandler()
|
||||
async onGetByIDProducts(req: Request, res: Response) {
|
||||
const data = await this.interactor.getByIdProduct(Number(req.params.id));
|
||||
if (!data) {
|
||||
res.status(200).json(new ApiResponse(200, null, 'No products found'));
|
||||
return;
|
||||
}
|
||||
res.status(200).json(new ApiResponse(200, data, 'Products retrieved successfully'));
|
||||
}
|
||||
|
||||
@AsyncHandler()
|
||||
async onUpdateStock(req: Request, res: Response) {
|
||||
const data = await this.interactor.updateStock(parseInt(`${req.params.id}`, 10), parseInt(`${req.body.stock}`, 10));
|
||||
res.status(200).json(new ApiResponse(200, data, 'Successfully updated'));
|
||||
}
|
||||
}
|
||||
27
src/entities/IAM/iam_principal.ts
Normal file
27
src/entities/IAM/iam_principal.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
||||
|
||||
@Entity()
|
||||
export class IAMPrincipal {
|
||||
@PrimaryGeneratedColumn()
|
||||
id!:number;
|
||||
|
||||
@Column()
|
||||
emailaddress!: string;
|
||||
|
||||
@Column()
|
||||
password!: string;
|
||||
|
||||
@Column()
|
||||
firstname!: string;
|
||||
|
||||
@Column()
|
||||
lastname!: string;
|
||||
|
||||
@Column()
|
||||
mobilenumber!: string;
|
||||
|
||||
@Column()
|
||||
team_xid!: number;
|
||||
|
||||
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"
|
||||
|
||||
@Entity()
|
||||
export class User {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
id!: number
|
||||
|
||||
@Column()
|
||||
firstName!: string
|
||||
|
||||
@Column()
|
||||
lastName!: string
|
||||
|
||||
@Column()
|
||||
age!: number
|
||||
|
||||
}
|
||||
12
src/entities/main/club.ts
Normal file
12
src/entities/main/club.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
||||
|
||||
@Entity()
|
||||
export class Club {
|
||||
@PrimaryGeneratedColumn()
|
||||
id!: number;
|
||||
|
||||
@Column()
|
||||
name!: string;
|
||||
|
||||
|
||||
}
|
||||
15
src/entities/main/team.ts
Normal file
15
src/entities/main/team.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToMany, JoinTable } from 'typeorm';
|
||||
import { Club } from "./club";
|
||||
|
||||
@Entity()
|
||||
export class Team {
|
||||
@PrimaryGeneratedColumn()
|
||||
id!: number;
|
||||
|
||||
@Column()
|
||||
name!: string;
|
||||
|
||||
@ManyToMany(() => Club)
|
||||
@JoinTable()
|
||||
clubs!: Club[];
|
||||
}
|
||||
@@ -1,20 +1,16 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||
|
||||
@Entity()
|
||||
export class Product {
|
||||
export class Player {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
id!: number;
|
||||
|
||||
@Column()
|
||||
name!: string
|
||||
type!: string;
|
||||
|
||||
@Column()
|
||||
description!: string
|
||||
name!: string;
|
||||
|
||||
@Column()
|
||||
price!: number
|
||||
|
||||
@Column()
|
||||
stock!: number
|
||||
}
|
||||
35
src/interactors/playerinteracter.ts
Normal file
35
src/interactors/playerinteracter.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { IMailer } from "../interfaces/IMailer";
|
||||
import { IMessageBroker } from "../interfaces/IMessageBroker";
|
||||
import { inject, injectable } from "inversify";
|
||||
import { INTERFACE_TYPE } from "../utils";
|
||||
import { IPlayerRepository } from "../interfaces/IPlayerRepository";
|
||||
import { IPlayerInteractor } from "../interfaces/IPlayerInteractor";
|
||||
import { Player } from "../entities/principletype";
|
||||
|
||||
|
||||
@injectable()
|
||||
export class PlayerInteractor implements IPlayerInteractor {
|
||||
|
||||
private repository: IPlayerRepository
|
||||
private mailer: IMailer
|
||||
private broker: IMessageBroker
|
||||
|
||||
constructor(
|
||||
@inject(INTERFACE_TYPE.PlayerRepository) repository: IPlayerRepository ,
|
||||
@inject(INTERFACE_TYPE.Mailer) mailer: IMailer,
|
||||
@inject(INTERFACE_TYPE.MessageBroker) broker: IMessageBroker
|
||||
) {
|
||||
this.repository = repository
|
||||
this.mailer = mailer
|
||||
this.broker = broker
|
||||
}
|
||||
|
||||
|
||||
async createPlayer(data : Player): Promise<Player> {
|
||||
const players = await this.repository.create(data);
|
||||
return players[0]; // Assuming the first player is the one you need
|
||||
// Do something notify promotion message
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import { IMailer } from "../interfaces/IMailer";
|
||||
import { Product } from "../entities/Product";
|
||||
import { IProductInteractor } from "../interfaces/IProductInteractor";
|
||||
import { IProductRepository } from "../interfaces/IProductRepository";
|
||||
import { IMessageBroker } from "../interfaces/IMessageBroker";
|
||||
import { inject, injectable } from "inversify";
|
||||
import { INTERFACE_TYPE } from "../utils";
|
||||
|
||||
@injectable()
|
||||
export class ProductInteractor implements IProductInteractor {
|
||||
|
||||
private repository: IProductRepository
|
||||
private mailer: IMailer
|
||||
private broker: IMessageBroker
|
||||
|
||||
constructor(
|
||||
@inject(INTERFACE_TYPE.ProductRepository) repository: IProductRepository,
|
||||
@inject(INTERFACE_TYPE.Mailer) mailer: IMailer,
|
||||
@inject(INTERFACE_TYPE.MessageBroker) broker: IMessageBroker
|
||||
) {
|
||||
this.repository = repository
|
||||
this.mailer = mailer
|
||||
this.broker = broker
|
||||
}
|
||||
|
||||
async createProduct(input: never): Promise<Product> {
|
||||
const data = await this.repository.create(input)
|
||||
// Do something notify promotion message
|
||||
await this.broker.NotifyToPromotionService(data)
|
||||
return data
|
||||
}
|
||||
async updateStock(id: number, stock: number): Promise<Product> {
|
||||
const data = await this.repository.update(id, { stock: stock })
|
||||
// Do some update Admin update a stock
|
||||
await this.mailer.SendMail("someone@gmail.com", "Update Stock", data)
|
||||
return data
|
||||
}
|
||||
async getProducts(limit: number, offset: number): Promise<Product[]> {
|
||||
return await this.repository.find(limit, offset)
|
||||
}
|
||||
async getByIdProduct(id: number): Promise<Product> {
|
||||
return await this.repository.findById(id)
|
||||
}
|
||||
|
||||
}
|
||||
6
src/interfaces/IPlayerInteractor.ts
Normal file
6
src/interfaces/IPlayerInteractor.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { Player } from "../entities/principletype";
|
||||
|
||||
export interface IPlayerInteractor {
|
||||
createPlayer(input: Partial<Player>): Promise<Player>;
|
||||
|
||||
}
|
||||
7
src/interfaces/IPlayerRepository.ts
Normal file
7
src/interfaces/IPlayerRepository.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Player } from "../entities/principletype";
|
||||
|
||||
export interface IPlayerRepository {
|
||||
create(data: Player): Promise<Player[]>;
|
||||
|
||||
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { Product } from "../entities/Product";
|
||||
|
||||
export interface IProductInteractor {
|
||||
createProduct(input: Partial<Product>): Promise<Product>;
|
||||
updateStock(id: number, stock: number): Promise<Product>;
|
||||
getProducts(limit: number, offset: number): Promise<Product[]>;
|
||||
getByIdProduct(id: number): Promise<Product>;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { Product } from "../entities/Product";
|
||||
|
||||
export interface IProductRepository {
|
||||
create(data: Product): Promise<Product>;
|
||||
update(id: number, data: Partial<Product>): Promise<Product>;
|
||||
find(limit: number, offset: number): Promise<Product[]>;
|
||||
findById(id: number): Promise<Product>;
|
||||
}
|
||||
@@ -11,10 +11,12 @@ class error {
|
||||
next: NextFunction
|
||||
): void {
|
||||
// Define a broader type for error
|
||||
console.log(err.message);
|
||||
let error: ApiError | Error & { statusCode?: number; errors?: Error[] } = err;
|
||||
|
||||
|
||||
// Handle Sequelize validation and unique constraint errors
|
||||
if (error.errors && Array.isArray(error.errors)) {
|
||||
if ( Array.isArray(error.errors) && error.errors.length > 0) {
|
||||
const messages = error.errors.map((e: Error) => e.message);
|
||||
error = new ApiError(
|
||||
400,
|
||||
|
||||
@@ -9,7 +9,7 @@ import { pick } from '../utils/handler/pick.handler';
|
||||
* @returns Middleware function to validate request properties.
|
||||
*/
|
||||
const validate =
|
||||
(schema: Partial<Record<keyof Request, ObjectSchema<never>>>) =>
|
||||
(schema: Partial<Record<keyof Request, ObjectSchema<object>>>) =>
|
||||
(req: Request, res: Response, next: NextFunction): void => {
|
||||
// Define valid request keys explicitly
|
||||
const validRequestKeys = ['params', 'query', 'body', 'file', 'files'] as (keyof Request)[];
|
||||
@@ -32,7 +32,7 @@ const validate =
|
||||
validatedValues.forEach((value, index) => {
|
||||
const key = Object.keys(validSchema)[index];
|
||||
// Safely assign the validated value to the request object
|
||||
req[key as keyof Request] = value; // Use `Request` here instead of `Request.ResBody`
|
||||
(req as unknown as Record<string, unknown>)[key] = value; // Use `Record<string, unknown>` to avoid type errors
|
||||
});
|
||||
next();
|
||||
})
|
||||
|
||||
25
src/repositories/playerRepository.ts
Normal file
25
src/repositories/playerRepository.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Repository } from "typeorm";
|
||||
import { AppDataSource } from "../config/data-source";
|
||||
import { Player } from "../entities/principletype";
|
||||
import {IPlayerRepository} from "../interfaces/IPlayerRepository"
|
||||
import { injectable } from "inversify";
|
||||
|
||||
@injectable()
|
||||
export class PlayerRepository implements IPlayerRepository {
|
||||
private readonly playerRepository: Repository<Player>;
|
||||
|
||||
constructor() {
|
||||
this.playerRepository = AppDataSource.getRepository(Player);
|
||||
}
|
||||
|
||||
// Create a new product
|
||||
async create(data: Player): Promise<Player[]> {
|
||||
const player = this.playerRepository.create(data);
|
||||
console.log(player);
|
||||
const savedPlayer = await this.playerRepository.save(player);
|
||||
return [savedPlayer];
|
||||
}
|
||||
|
||||
// Update a product
|
||||
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
import { Repository } from "typeorm";
|
||||
import { AppDataSource } from "../config/data-source";
|
||||
import { Product } from "../entities/Product";
|
||||
import { IProductRepository } from "../interfaces/IProductRepository";
|
||||
import ApiError from "../utils/helper/ApiError";
|
||||
import { injectable } from "inversify";
|
||||
|
||||
@injectable()
|
||||
export class ProductRepository implements IProductRepository {
|
||||
private readonly productRepository: Repository<Product>;
|
||||
|
||||
constructor() {
|
||||
this.productRepository = AppDataSource.getRepository(Product);
|
||||
}
|
||||
|
||||
// Create a new product
|
||||
async create(data: Product): Promise<Product> {
|
||||
const product = this.productRepository.create(data); // Prepare new product entity
|
||||
return await this.productRepository.save(product); // Save to the database
|
||||
}
|
||||
|
||||
// Update a product
|
||||
async update(id: number, data: Partial<Product>): Promise<Product> {
|
||||
// Check if the product exists before updating
|
||||
const existingProduct = await this.productRepository.findOne({ where: { id } });
|
||||
if (!existingProduct) {
|
||||
throw new ApiError(404, `Product with ID ${id} not found`);
|
||||
}
|
||||
|
||||
// Update the product and return the updated entity
|
||||
await this.productRepository.update(id, data);
|
||||
return this.productRepository.findOneOrFail({ where: { id } });
|
||||
}
|
||||
|
||||
// Find products with pagination
|
||||
async find(limit: number, offset: number): Promise<Product[]> {
|
||||
return await this.productRepository.find({
|
||||
take: limit,
|
||||
skip: offset,
|
||||
order: { id: "ASC" }, // Optional: Add sorting for consistent results
|
||||
});
|
||||
}
|
||||
|
||||
// Find by ID Product
|
||||
async findById(id: number): Promise<Product> {
|
||||
return await this.productRepository.findOneOrFail({ where: { id } });
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,8 @@ class Routes {
|
||||
private initializeDefaultRoutes(): RouteDefinition[] {
|
||||
return [
|
||||
{
|
||||
path: '/products',
|
||||
route: () => import('./productRoutes').then((module) => module.default)
|
||||
path: '/player',
|
||||
route: () => import('./playerRoutes').then((module) => module.default)
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
41
src/routes/playerRoutes.ts
Normal file
41
src/routes/playerRoutes.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import express from 'express';
|
||||
|
||||
import { Mailer } from '../external-libraries/mailer';
|
||||
import { MessageBroker } from '../external-libraries/messageBroker';
|
||||
import { Container } from 'inversify';
|
||||
import { INTERFACE_TYPE } from '../utils';
|
||||
import { IMailer } from '../interfaces/IMailer';
|
||||
import { IMessageBroker } from '../interfaces/IMessageBroker';
|
||||
import { IPlayerRepository} from '../interfaces/IPlayerRepository';
|
||||
import { PlayerRepository } from "../repositories/playerRepository";
|
||||
import { IPlayerInteractor } from '../interfaces/IPlayerInteractor';
|
||||
import { PlayerInteractor} from '../interactors/playerinteracter'
|
||||
import { PlayerController } from '../controllers/playerController';
|
||||
|
||||
/**
|
||||
const repository = new ProductRepository()
|
||||
const mailer = new Mailer();
|
||||
const broker = new MessageBroker()
|
||||
const interactor: IProductInteractor = new ProductInteractor(repository, mailer, broker)
|
||||
const productController = new ProductController(interactor);
|
||||
|
||||
*/
|
||||
|
||||
const container = new Container();
|
||||
|
||||
container.bind<IPlayerRepository>(INTERFACE_TYPE.PlayerRepository).to(PlayerRepository);
|
||||
container.bind<IPlayerInteractor>(INTERFACE_TYPE.PlayerInteractor).to(PlayerInteractor);
|
||||
container.bind<IMailer>(INTERFACE_TYPE.Mailer).to(Mailer);
|
||||
container.bind<IMessageBroker>(INTERFACE_TYPE.MessageBroker).to(MessageBroker);
|
||||
container.bind(INTERFACE_TYPE.PlayerController).to(PlayerController);
|
||||
|
||||
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
const playerController = container.get<PlayerController>(INTERFACE_TYPE.PlayerController);
|
||||
|
||||
router.post('/ ', playerController.onCreatePlayer.bind(playerController));
|
||||
|
||||
|
||||
export default router;
|
||||
@@ -1,41 +0,0 @@
|
||||
import express from 'express';
|
||||
import { ProductController } from '../controllers/productController';
|
||||
import { ProductRepository } from '../repositories/productRepository';
|
||||
import { ProductInteractor } from '../interactors/productInteractor';
|
||||
import { IProductInteractor } from '../interfaces/IProductInteractor';
|
||||
import { Mailer } from '../external-libraries/mailer';
|
||||
import { MessageBroker } from '../external-libraries/messageBroker';
|
||||
import { Container } from 'inversify';
|
||||
import { IProductRepository } from '../interfaces/IProductRepository';
|
||||
import { INTERFACE_TYPE } from '../utils';
|
||||
import { IMailer } from '../interfaces/IMailer';
|
||||
import { IMessageBroker } from '../interfaces/IMessageBroker';
|
||||
|
||||
/**
|
||||
const repository = new ProductRepository()
|
||||
const mailer = new Mailer();
|
||||
const broker = new MessageBroker()
|
||||
const interactor: IProductInteractor = new ProductInteractor(repository, mailer, broker)
|
||||
const productController = new ProductController(interactor);
|
||||
|
||||
*/
|
||||
|
||||
const container = new Container();
|
||||
|
||||
container.bind<IProductRepository>(INTERFACE_TYPE.ProductRepository).to(ProductRepository);
|
||||
container.bind<IProductInteractor>(INTERFACE_TYPE.ProductInteractor).to(ProductInteractor);
|
||||
container.bind<IMailer>(INTERFACE_TYPE.Mailer).to(Mailer);
|
||||
container.bind<IMessageBroker>(INTERFACE_TYPE.MessageBroker).to(MessageBroker);
|
||||
container.bind(INTERFACE_TYPE.ProductController).to(ProductController);
|
||||
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
const productController = container.get<ProductController>(INTERFACE_TYPE.ProductController);
|
||||
|
||||
router.post('/', productController.onCreateProduct.bind(productController));
|
||||
router.get('/', productController.onGetProducts.bind(productController));
|
||||
router.patch('/:id', productController.onUpdateStock.bind(productController));
|
||||
router.get('/:id', productController.onGetByIDProducts.bind(productController));
|
||||
|
||||
export default router;
|
||||
@@ -1,7 +1,12 @@
|
||||
|
||||
|
||||
|
||||
export const INTERFACE_TYPE = {
|
||||
ProductRepository: Symbol.for("ProductRepository"),
|
||||
ProductInteractor: Symbol.for("ProductInteractor"),
|
||||
ProductController: Symbol.for("ProductController"),
|
||||
PlayerController: Symbol.for("PlayerController"),
|
||||
Mailer: Symbol.for("Mailer"),
|
||||
MessageBroker: Symbol.for("MessageBroker"),
|
||||
PlayerRepository: Symbol.for("PlayerRepository"),
|
||||
PlayerInteractor: Symbol.for("PlayerInteractor"),
|
||||
}
|
||||
Reference in New Issue
Block a user