3 Commits
main ... main

Author SHA1 Message Date
Paritosh Chudasama
55fd1c4c8d feat: add player deletion and update functionality, enhance player controller 2024-12-26 15:45:03 +05:30
Paritosh Chudasama
d79392ca8c fix: correct player creation route and clean up code formatting 2024-12-23 17:42:23 +05:30
Paritosh Chudasama
cde9fe7be1 [feat] - implement player management interfaces, entities, and routes 2024-12-23 17:13:45 +05:30
26 changed files with 7741 additions and 869 deletions

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ import logger from './config/logger';
import error from './middleware/error';
import routes from './routes';
import ApiError from './utils/helper/ApiError';
class App {
private app: Application;
@@ -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"));
})
}

View File

@@ -0,0 +1,46 @@
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(req.body);
const newPlayer= await this.interactor.createPlayer(data);
res.status(201).json(new ApiResponse(201, newPlayer, 'Successfully created'));
}
@AsyncHandler()
async onDeletePlayer(req: Request, res: Response) {
const id: number = parseInt(req.params.id, 10);
console.log(req.params.id);
await this.interactor.deletePlayer(id);
res.status(200).json(new ApiResponse(200, null, 'Successfully deleted'));
}
@AsyncHandler()
async onUpdatePlayer(req: Request, res: Response) {
const id: number = parseInt(req.params.id, 10);
const data: Partial<Player> = req.body;
console.log(data);
const updatedPlayer = await this.interactor.updatePlayer(id, data);
res.status(200).json(new ApiResponse(200, updatedPlayer, 'Successfully updated'));
}
}

View File

@@ -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'));
}
}

View 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;
}

View File

@@ -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
View 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
View 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[];
}

View File

@@ -1,20 +1,15 @@
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
}

View File

@@ -0,0 +1,43 @@
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];
}
async deletePlayer(id: number): Promise<void> {
await this.repository.delete(id);
}
async updatePlayer(id: number, data: Partial<Player>): Promise<Player> {
const player = await this.repository.update(id, data);
return player;
}
}

View File

@@ -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)
}
}

View File

@@ -1,3 +1,15 @@
export interface IMessageBroker {
NotifyToPromotionService(product: unknown): Promise<boolean>;
}
}

View File

@@ -0,0 +1,8 @@
import { Player } from "../entities/principletype";
export interface IPlayerInteractor {
createPlayer(input: Partial<Player>): Promise<Player>;
deletePlayer(id: number): Promise<void>;
updatePlayer(id: number, input: Partial<Player>): Promise<Player>;
}

View File

@@ -0,0 +1,9 @@
import { Player } from "../entities/principletype";
export interface IPlayerRepository {
create(data: Player): Promise<Player[]>;
delete(id: number): Promise<void>;
update(id: number, data: Partial<Player>): Promise<Player>;
}

View File

@@ -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>;
}

View File

@@ -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>;
}

View File

@@ -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,

View File

@@ -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();
})

View File

@@ -0,0 +1,38 @@
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];
}
// Delete a player
async delete(id: number): Promise<void> {
await this.playerRepository.delete(id);
}
// Update a product
async update(id: number, data: Partial<Player>): Promise<Player> {
await this.playerRepository.update(id, data);
const player = await this.playerRepository.findOne({ where: { id } });
if (!player) {
throw new Error("Player not found");
}
return player;
}
}

View File

@@ -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 } });
}
}

View File

@@ -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)
}
];
}
@@ -67,7 +67,7 @@ class Routes {
console.error(`Failed to load route at path: ${path}`, error);
}
}
// Return the router instance
public getRouter(): Router {
return this.router;

View File

@@ -0,0 +1,45 @@
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('/create', playerController.onCreatePlayer.bind(playerController));
router.delete('/delete/:id', playerController.onDeletePlayer.bind(playerController));
router.put('/update/:id', playerController.onUpdatePlayer.bind(playerController));
export default router;

View File

@@ -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;

View File

@@ -1,7 +1,24 @@
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"),
}

1268
yarn.lock

File diff suppressed because it is too large Load Diff