From 33d44d765df000d290c6569878c79e543d833422 Mon Sep 17 00:00:00 2001 From: Ryuytyuu-Greseven Date: Sat, 25 Jan 2025 09:27:13 +0530 Subject: [PATCH 1/8] Implementation of orders, products, users completed. --- package.json | 3 + src/app.controller.ts | 9 ++ src/app.module.ts | 8 +- src/main.ts | 4 + .../controllers/orders.controller.spec.ts | 87 ++++++++++++ .../controllers/orders.controller.ts | 27 ++++ src/order/application/dtos/AddOrder.dto.ts | 19 +++ src/order/application/dtos/OrderId.dto.ts | 7 + .../interfaces/order.repository.interface.ts | 7 + .../{order.ts => models/order.model.ts} | 13 +- src/order/domain/order.repository.ts | 6 - .../domain/services/orders.service.spec.ts | 46 +++++++ src/order/domain/services/orders.service.ts | 87 ++++++++++++ .../infrastructure/legacy-order.controller.js | 74 +++++----- .../repositories/order-repository.service.ts | 39 ++++++ src/order/order.module.ts | 7 +- .../controllers/products.controller.spec.ts | 121 +++++++++++++++++ .../controllers/products.controller.ts | 36 +++++ .../application/dtos/AddProduct.dto.ts | 15 ++ src/product/application/dtos/ProductId.dto.ts | 8 ++ .../application/dtos/UpdateProduct.dto.ts | 9 ++ .../product.repository.interface.ts | 9 ++ src/product/domain/models/product.model.ts | 33 +++++ .../domain/services/product.service.ts | 128 ++++++++++++++++++ .../product-repository.service.ts | 56 ++++++++ src/product/product.module.ts | 10 ++ .../controllers/users.controller.spec.ts | 127 +++++++++++++++++ .../controllers/users.controller.ts | 36 +++++ src/users/application /dtos/AddUser.dto.ts | 21 +++ src/users/application /dtos/UpdateUser.dto.ts | 9 ++ src/users/application /dtos/UserId.dto.ts | 8 ++ .../interfaces/user.repository.interface.ts | 9 ++ src/users/domain/models/user.model.ts | 38 ++++++ src/users/domain/services/users.service.ts | 127 +++++++++++++++++ .../repositories/user-repository.service.ts | 59 ++++++++ src/users/users.module.ts | 10 ++ 36 files changed, 1263 insertions(+), 49 deletions(-) create mode 100644 src/app.controller.ts create mode 100644 src/order/application/controllers/orders.controller.spec.ts create mode 100644 src/order/application/controllers/orders.controller.ts create mode 100644 src/order/application/dtos/AddOrder.dto.ts create mode 100644 src/order/application/dtos/OrderId.dto.ts create mode 100644 src/order/domain/interfaces/order.repository.interface.ts rename src/order/domain/{order.ts => models/order.model.ts} (73%) delete mode 100644 src/order/domain/order.repository.ts create mode 100644 src/order/domain/services/orders.service.spec.ts create mode 100644 src/order/domain/services/orders.service.ts create mode 100644 src/order/infrastructure/repositories/order-repository.service.ts create mode 100644 src/product/application/controllers/products.controller.spec.ts create mode 100644 src/product/application/controllers/products.controller.ts create mode 100644 src/product/application/dtos/AddProduct.dto.ts create mode 100644 src/product/application/dtos/ProductId.dto.ts create mode 100644 src/product/application/dtos/UpdateProduct.dto.ts create mode 100644 src/product/domain/interfaces/product.repository.interface.ts create mode 100644 src/product/domain/models/product.model.ts create mode 100644 src/product/domain/services/product.service.ts create mode 100644 src/product/infrastructure/repositories/product-repository.service.ts create mode 100644 src/product/product.module.ts create mode 100644 src/users/application /controllers/users.controller.spec.ts create mode 100644 src/users/application /controllers/users.controller.ts create mode 100644 src/users/application /dtos/AddUser.dto.ts create mode 100644 src/users/application /dtos/UpdateUser.dto.ts create mode 100644 src/users/application /dtos/UserId.dto.ts create mode 100644 src/users/domain/interfaces/user.repository.interface.ts create mode 100644 src/users/domain/models/user.model.ts create mode 100644 src/users/domain/services/users.service.ts create mode 100644 src/users/infrastructure/repositories/user-repository.service.ts create mode 100644 src/users/users.module.ts diff --git a/package.json b/package.json index 894015d..ae24da7 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,9 @@ "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/platform-express": "^10.0.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.1", + "cors": "^2.8.5", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1" }, diff --git a/src/app.controller.ts b/src/app.controller.ts new file mode 100644 index 0000000..d41cbc5 --- /dev/null +++ b/src/app.controller.ts @@ -0,0 +1,9 @@ +import { Controller, Get } from '@nestjs/common'; + +@Controller() +export class AppController { + @Get('') + checkServer() { + return 'Server is up and running'; + } +} diff --git a/src/app.module.ts b/src/app.module.ts index 509207f..4068d68 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,9 +1,13 @@ import { Module } from '@nestjs/common'; import { OrderModule } from './order/order.module'; +import { AppController } from './app.controller'; +import { ProductModule } from './product/product.module'; +import { UsersModule } from './users/users.module'; +import { SharedModule } from './shared/shared.module'; @Module({ - imports: [OrderModule], - controllers: [], + imports: [OrderModule, ProductModule, UsersModule, SharedModule], + controllers: [AppController], providers: [], }) export class AppModule {} diff --git a/src/main.ts b/src/main.ts index 13cad38..ef888aa 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,8 +1,12 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; +import { ValidationPipe } from '@nestjs/common'; +import * as cors from 'cors'; async function bootstrap() { const app = await NestFactory.create(AppModule); + app.useGlobalPipes(new ValidationPipe()) + app.use(cors()); await app.listen(3000); } bootstrap(); diff --git a/src/order/application/controllers/orders.controller.spec.ts b/src/order/application/controllers/orders.controller.spec.ts new file mode 100644 index 0000000..a1359e9 --- /dev/null +++ b/src/order/application/controllers/orders.controller.spec.ts @@ -0,0 +1,87 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { OrdersController } from './orders.controller'; +import { OrdersService } from '../../domain/services/orders.service'; +import { AddOrder } from '../dtos/AddOrder.dto'; + +describe('OrdersController', () => { + let controller: OrdersController; + + const mockOrdersService = { + fetchOrderDetails: jest.fn((payload) => { + return { + success: true, + message: 'Order details fetched successfully', + data: { + id: '1', + productId: '345', + quantity: 4, + userId: 'test_user', + }, + }; + }), + addOrder: jest.fn((body) => { + return { + success: true, + message: 'Order created successfully', + data: { + id: '1', + productId: '345', + quantity: 4, + userId: 'test_user', + }, + }; + }), + fetchAllOrders: jest.fn(() => { + return { + success: true, + message: 'Orders fetched successfully', + data: [{ id: '1', productId: '345', quantity: 4, userId: 'test_user' }], + }; + }), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [OrdersController], + providers: [{ provide: OrdersService, useValue: mockOrdersService }], + }).compile(); + + controller = module.get(OrdersController); + }); + + it('add an order', async () => { + const payload: AddOrder = { + productId: '345', + quantity: 4, + userId: 'test_user', + }; + const response = (await controller.addOrder(payload)) as any; + // console.log(response); + + expect(response.success).toEqual(true); + expect(response.data.id).toEqual('1'); + expect(mockOrdersService.addOrder).toHaveBeenCalled(); + }); + + it('fetch all orders', async () => { + const response = await controller.getAllOrders(); + // console.log(response); + + expect(response.data[0].productId).toEqual('345'); + expect(response.data[0].quantity).toEqual(4); + expect(response.data[0].userId).toEqual('test_user'); + expect(mockOrdersService.fetchAllOrders).toHaveBeenCalled(); + }); + + it('fetching single order details', async () => { + const payload = { + orderId: '1', + }; + + const result: any = await controller.getOrderDetails(payload.orderId); + // console.log(result); + expect(result.success).toEqual(true); + expect(result.data.userId).toEqual('test_user'); + expect(mockOrdersService.fetchOrderDetails).toHaveBeenCalled(); + }); +}); diff --git a/src/order/application/controllers/orders.controller.ts b/src/order/application/controllers/orders.controller.ts new file mode 100644 index 0000000..12e3961 --- /dev/null +++ b/src/order/application/controllers/orders.controller.ts @@ -0,0 +1,27 @@ +import { Body, Controller, Get, Param, Post, Req, Res } from '@nestjs/common'; +import { OrdersService } from '../../domain/services/orders.service'; +import { AddOrder } from '../dtos/AddOrder.dto'; +import { OrderIdDto } from '../dtos/OrderId.dto'; + +@Controller('order') +export class OrdersController { + constructor(private orderService: OrdersService) {} + + @Get('/all') + getAllOrders() { + return this.orderService.fetchAllOrders(); + } + + @Get('/:id') + getOrderDetails(@Param('id') orderId: string) { + const paylod: OrderIdDto = { + orderId, + }; + return this.orderService.fetchOrderDetails(paylod); + } + + @Post('add') + addOrder(@Body() body: AddOrder) { + return this.orderService.addOrder(body); + } +} diff --git a/src/order/application/dtos/AddOrder.dto.ts b/src/order/application/dtos/AddOrder.dto.ts new file mode 100644 index 0000000..5887ca4 --- /dev/null +++ b/src/order/application/dtos/AddOrder.dto.ts @@ -0,0 +1,19 @@ +import { IsNotEmpty, IsNumber, IsString, Min, MinLength } from 'class-validator' + +export class AddOrder { + + @IsNotEmpty() + @IsString() + @MinLength(3) + productId: string; + + @IsNotEmpty() + @IsNumber() + @Min(1) + quantity: number; + + @IsNotEmpty() + @IsString() + @MinLength(5) + userId: string; +} \ No newline at end of file diff --git a/src/order/application/dtos/OrderId.dto.ts b/src/order/application/dtos/OrderId.dto.ts new file mode 100644 index 0000000..9aaeefb --- /dev/null +++ b/src/order/application/dtos/OrderId.dto.ts @@ -0,0 +1,7 @@ +import { IsNotEmpty, IsString } from "class-validator"; + +export class OrderIdDto { + @IsNotEmpty() + @IsString() + orderId: string; +} \ No newline at end of file diff --git a/src/order/domain/interfaces/order.repository.interface.ts b/src/order/domain/interfaces/order.repository.interface.ts new file mode 100644 index 0000000..45e09d6 --- /dev/null +++ b/src/order/domain/interfaces/order.repository.interface.ts @@ -0,0 +1,7 @@ +import { Order } from '../models/order.model'; + +export interface IOrderRepository { + save(order: Order): Promise; + find(): Promise; + findById(id: string): Promise; +} diff --git a/src/order/domain/order.ts b/src/order/domain/models/order.model.ts similarity index 73% rename from src/order/domain/order.ts rename to src/order/domain/models/order.model.ts index d2de237..8da9d39 100644 --- a/src/order/domain/order.ts +++ b/src/order/domain/models/order.model.ts @@ -1,14 +1,14 @@ type OrderConstructorParameters = { id: string; productId: string; - quantity: string; + quantity: number; userId: string; }; export class Order { #id: string; #productId: string; - #quantity: string; + #quantity: number; #userId: string; constructor({ id, productId, quantity, userId }: OrderConstructorParameters) { @@ -30,4 +30,13 @@ export class Order { get userId() { return this.#userId; } + + orderDetails() { + return { + id: this.#id, + productId: this.#productId, + quantity: this.#quantity, + userId: this.#userId, + }; + } } diff --git a/src/order/domain/order.repository.ts b/src/order/domain/order.repository.ts deleted file mode 100644 index ae54a45..0000000 --- a/src/order/domain/order.repository.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Order } from './order'; - -export interface OrderRepository { - save(order: Order): Promise; - findById(id: string): Promise; -} diff --git a/src/order/domain/services/orders.service.spec.ts b/src/order/domain/services/orders.service.spec.ts new file mode 100644 index 0000000..f37cd21 --- /dev/null +++ b/src/order/domain/services/orders.service.spec.ts @@ -0,0 +1,46 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { OrdersService } from './orders.service'; +import { AddOrder } from '../../application/dtos/AddOrder.dto'; +import { OrdersRepositoryService } from '../../infrastructure/repositories/order-repository.service'; + +describe('OrdersService', () => { + let service: OrdersService; + + const mockOrderRepo = { + allOrders: [], + save: jest.fn((order)=>{}), + findById: jest.fn((id)=>{}), + findAll: jest.fn(), + ordersMaxId: 0, + }; + + const addNewOrder : AddOrder = { + productId: '345', + quantity: 4, + userId: 'test_user', + }; + + const orderDetails = { + id: '1', + ...addNewOrder + } + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [], + providers: [ + OrdersService, + { provide: OrdersRepositoryService, useValue: mockOrderRepo }, + ], + }).compile(); + + service = module.get(OrdersService); + }); + + it('add a new order', async () => { + const response = await service.addOrder(addNewOrder); + + expect(response.success).toEqual(true); + expect(mockOrderRepo.save).toHaveBeenCalled() + }); +}); diff --git a/src/order/domain/services/orders.service.ts b/src/order/domain/services/orders.service.ts new file mode 100644 index 0000000..f5bb85e --- /dev/null +++ b/src/order/domain/services/orders.service.ts @@ -0,0 +1,87 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { Order } from '../models/order.model'; +import { AddOrder } from '../../application/dtos/AddOrder.dto'; +import { OrdersRepositoryService } from '../../infrastructure/repositories/order-repository.service'; +import { OrderIdDto } from '../../application/dtos/OrderId.dto'; + +@Injectable() +export class OrdersService { + constructor(private ordersRepo: OrdersRepositoryService) {} + + async fetchOrderDetails(payload: OrderIdDto) { + const result = { + success: true, + message: '', + data: {}, + }; + + const orderDetails = await this.ordersRepo + .findById(payload.orderId) + .catch((error) => { + result.message = 'Unable to process right now'; + result.success = false; + console.log(error); + }); + + console.log('order details', orderDetails); + + if (!orderDetails?.id && result.success) { + result.success = false; + result.message = 'Order not found'; + delete result.data; + throw new NotFoundException(result); + } else if (result.success) { + result.data = orderDetails; + result.message = 'Order details fetched successfully'; + return result; + } else { + return result; + } + } + + async addOrder(body: AddOrder) { + const result = { + success: true, + message: '', + data: {}, + }; + + try { + const id = Math.random().toString(36).substr(2, 9); + const product = new Order({ + id: id, + productId: body.productId, + quantity: body.quantity, + userId: body.userId, + }); + + await this.ordersRepo.save(product); + result.data = product.orderDetails(); + result.message = 'Order created successfully'; + } catch (error) { + result.message = 'Unable to process right now'; + result.success = false; + console.log(error); + } + return result; + } + + async fetchAllOrders() { + const result = { + success: true, + message: '', + data: [], + }; + + try { + const ordersList = await this.ordersRepo.find(); + result.message = 'Orders fetched successfully'; + result.data = ordersList; + } catch (error) { + console.log(error); + result.success = false; + result.message = 'Unable to process right now'; + } + return result; + } +} diff --git a/src/order/infrastructure/legacy-order.controller.js b/src/order/infrastructure/legacy-order.controller.js index 70391ad..852e18e 100644 --- a/src/order/infrastructure/legacy-order.controller.js +++ b/src/order/infrastructure/legacy-order.controller.js @@ -1,37 +1,37 @@ -const express = require('express'); -const app = express(); -const orders = []; - -app.use(express.json()); - -app.post('/order', (req, res) => { - const { productId, quantity, userId } = req.body; - if (!productId || !quantity || !userId) { - return res.status(400).send('Invalid data'); - } - - const order = { - id: orders.length + 1, - productId, - quantity, - userId, - }; - - orders.push(order); - res.status(201).send(order); -}); - -app.get('/order/:id', (req, res) => { - const orderId = parseInt(req.params.id); - const order = orders.find((o) => o.id === orderId); - - if (!order) { - return res.status(404).send('Order not found'); - } - - res.send(order); -}); - -app.listen(3000, () => { - console.log('Server running on port 3000'); -}); +// const express = require('express'); +// const app = express(); +// const orders = []; + +// app.use(express.json()); + +// app.post('/order', (req, res) => { +// const { productId, quantity, userId } = req.body; +// if (!productId || !quantity || !userId) { +// return res.status(400).send('Invalid data'); +// } + +// const order = { +// id: orders.length + 1, +// productId, +// quantity, +// userId, +// }; + +// orders.push(order); +// res.status(201).send(order); +// }); + +// app.get('/order/:id', (req, res) => { +// const orderId = parseInt(req.params.id); +// const order = orders.find((o) => o.id === orderId); + +// if (!order) { +// return res.status(404).send('Order not found'); +// } + +// res.send(order); +// }); + +// app.listen(3000, () => { +// console.log('Server running on port 3000'); +// }); diff --git a/src/order/infrastructure/repositories/order-repository.service.ts b/src/order/infrastructure/repositories/order-repository.service.ts new file mode 100644 index 0000000..117e9a2 --- /dev/null +++ b/src/order/infrastructure/repositories/order-repository.service.ts @@ -0,0 +1,39 @@ +import { Injectable } from '@nestjs/common'; +import { IOrderRepository } from '../../domain/interfaces/order.repository.interface'; +import { Order } from '../../domain/models/order.model'; + +interface OrderDetails { + id: string; + userId: string; + productId: string; + quantity: number; +} + +@Injectable() +export class OrdersRepositoryService implements IOrderRepository { + allOrders: OrderDetails[]; + + constructor() { + this.allOrders = []; + } + + // adding a new record to orders + save(orderDetails: Order): Promise { + console.log(orderDetails); + this.allOrders.push(orderDetails.orderDetails()); + return Promise.resolve(); + } + + // filtering a single order details + findById(id: string): Promise { + const orderDetails = this.allOrders.find( + (singleOrder) => singleOrder.id === id, + ); + return Promise.resolve(orderDetails); + } + + // retriving all the products list + find(): Promise { + return Promise.resolve(this.allOrders); + } +} diff --git a/src/order/order.module.ts b/src/order/order.module.ts index 6013842..145fbff 100644 --- a/src/order/order.module.ts +++ b/src/order/order.module.ts @@ -1,8 +1,11 @@ import { Module } from '@nestjs/common'; +import { OrdersController } from './application/controllers/orders.controller'; +import { OrdersService } from './domain/services/orders.service'; +import { OrdersRepositoryService } from './infrastructure/repositories/order-repository.service'; @Module({ imports: [], - controllers: [], - providers: [], + controllers: [OrdersController], + providers: [OrdersService, OrdersRepositoryService], }) export class OrderModule {} diff --git a/src/product/application/controllers/products.controller.spec.ts b/src/product/application/controllers/products.controller.spec.ts new file mode 100644 index 0000000..a60400b --- /dev/null +++ b/src/product/application/controllers/products.controller.spec.ts @@ -0,0 +1,121 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ProductsController } from '../controllers/products.controller'; +import { ProductService } from '../../domain/services/product.service'; +import { UpdateProductDto } from '../dtos/UpdateProduct.dto'; +import { ProductIdDto } from '../dtos/ProductId.dto'; + +describe('ProductsController', () => { + let controller: ProductsController; + + const mockProductService = { + createProduct: jest.fn(() => { + return { + success: true, + message: 'Product created successfully', + data: { + id: 'xh945mdtb', + name: 'mobiles', + description: 'a simple mobile device', + }, + }; + }), + getProductDetails: jest.fn((id) => { + return { + success: true, + message: 'Product details fetched successfully', + data: { + id: 'xh945mdtb', + name: 'mobiles', + description: 'a simple mobile device', + }, + }; + }), + updateProduct: jest.fn((body: UpdateProductDto) => { + return { + success: true, + message: 'Product updated successfully', + }; + }), + getAllProducts: jest.fn(() => { + return { + success: true, + message: 'Products fetched successfully', + data: [ + { + id: 'xh945mdtb', + name: 'mobiles', + description: 'a simple mobile device', + }, + ], + }; + }), + deleteProduct: jest.fn((body: ProductIdDto) => { + return { + success: true, + message: 'Product deleted successfully' + } + }), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [ProductsController], + providers: [{ provide: ProductService, useValue: mockProductService }], + }).compile(); + + controller = module.get(ProductsController); + }); + + it('add new product', async () => { + const product = { + name: 'mobiles', + description: 'a simple mobile device', + }; + + const response: any = await controller.addProduct(product); + + expect(response.success).toEqual(true); + expect(response.data.id).toEqual('xh945mdtb'); + expect(mockProductService.createProduct).toHaveBeenCalled(); + }); + + it('fetch a product by id', async () => { + const payload = { + id: 'xh945mdtb', + }; + + const response: any = await controller.fetchProductDetails(payload.id); + + expect(response.data.id).toEqual('xh945mdtb'); + expect(mockProductService.getProductDetails).toHaveBeenCalled(); + }); + + it('update a single product with product details',async ()=>{ + const payload : UpdateProductDto= { + productId: 'xh945mdtb', + name: 'Nothing Phone(1)', + description: 'Nothing brand first phone', + } + + const response = await controller.editeProduct(payload); + + expect(response.success).toEqual(true); + expect(mockProductService.updateProduct).toHaveBeenCalled(); + }) + + it('fetching all products', async()=>{ + const response = await controller.fetchAllProducts(); + + expect(response.data.length).toBeGreaterThan(0); + expect(mockProductService.getAllProducts).toHaveBeenCalled(); + }) + + it('delete a product', async ()=>{ + const payload = { productId: 'xh945md'}; + + const result = await controller.deleteProduct(payload); + + expect(result.success).toEqual(true); + expect(mockProductService.deleteProduct).toHaveBeenCalled(); + }) +}); diff --git a/src/product/application/controllers/products.controller.ts b/src/product/application/controllers/products.controller.ts new file mode 100644 index 0000000..5614419 --- /dev/null +++ b/src/product/application/controllers/products.controller.ts @@ -0,0 +1,36 @@ +import { Body, Controller, Get, Param, Post } from '@nestjs/common'; +import { AddProductDto } from '../dtos/AddProduct.dto'; +import { UpdateProductDto } from '../dtos/UpdateProduct.dto'; +import { ProductService } from '../../domain/services/product.service'; +import { ProductIdDto } from '../dtos/ProductId.dto'; + +@Controller('products') +export class ProductsController { + constructor(private readonly productService: ProductService) {} + + @Get('/single-product/:id') + fetchProductDetails(@Param('id') id: string) { + return this.productService.getProductDetails(id); + } + + @Post('create-product') + addProduct(@Body() body: AddProductDto) { + return this.productService.createProduct(body); + } + + @Post('update-product') + editeProduct(@Body() body: UpdateProductDto) { + console.log(body); + return this.productService.updateProduct(body); + } + + @Get('all-products') + fetchAllProducts() { + return this.productService.getAllProducts(); + } + + @Post('delete-product') + deleteProduct(@Body() body: ProductIdDto) { + return this.productService.deleteProduct(body); + } +} diff --git a/src/product/application/dtos/AddProduct.dto.ts b/src/product/application/dtos/AddProduct.dto.ts new file mode 100644 index 0000000..794d3b9 --- /dev/null +++ b/src/product/application/dtos/AddProduct.dto.ts @@ -0,0 +1,15 @@ +import { IsNotEmpty, IsString, IsEmail, MinLength, MaxLength } from 'class-validator'; + +export class AddProductDto { + @IsNotEmpty() + @IsString() + @MinLength(3) + @MaxLength(20) + name: string; + + @IsNotEmpty() + @IsString() + @MinLength(6) + @MaxLength(250) + description: string; +} diff --git a/src/product/application/dtos/ProductId.dto.ts b/src/product/application/dtos/ProductId.dto.ts new file mode 100644 index 0000000..e0db8e3 --- /dev/null +++ b/src/product/application/dtos/ProductId.dto.ts @@ -0,0 +1,8 @@ +import { IsNotEmpty, IsString, MinLength } from 'class-validator'; + +export class ProductIdDto { + @IsNotEmpty() + @IsString() + @MinLength(5) + productId: string; +} \ No newline at end of file diff --git a/src/product/application/dtos/UpdateProduct.dto.ts b/src/product/application/dtos/UpdateProduct.dto.ts new file mode 100644 index 0000000..c9f9d96 --- /dev/null +++ b/src/product/application/dtos/UpdateProduct.dto.ts @@ -0,0 +1,9 @@ +import { IsNotEmpty, IsString, MinLength } from 'class-validator'; +import { AddProductDto } from './AddProduct.dto'; + +export class UpdateProductDto extends AddProductDto { + @IsNotEmpty() + @IsString() + @MinLength(5) + productId: string; +} diff --git a/src/product/domain/interfaces/product.repository.interface.ts b/src/product/domain/interfaces/product.repository.interface.ts new file mode 100644 index 0000000..dcd8884 --- /dev/null +++ b/src/product/domain/interfaces/product.repository.interface.ts @@ -0,0 +1,9 @@ +import { Product } from "../models/product.model"; + +export interface IProductRepository { + save(productDetails: Product): Promise; + update(productDetails: Product): Promise; + find(): Promise; + findById(id: string): Promise; + delete(id: string): Promise; +} \ No newline at end of file diff --git a/src/product/domain/models/product.model.ts b/src/product/domain/models/product.model.ts new file mode 100644 index 0000000..d764f1b --- /dev/null +++ b/src/product/domain/models/product.model.ts @@ -0,0 +1,33 @@ + +export class Product { + #id: string; + #name: string; + #description: string; + + constructor(id: string, name: string, description: string) { + this.#id = id; + this.#name = name; + this.#description = description; + } + + get id() { + return this.id; + } + + get name() { + return this.name; + } + + get description() { + return this.description; + } + + getDetails() { + return { + id: this.#id, + name: this.#name, + description: this.#description, + }; + } + } + \ No newline at end of file diff --git a/src/product/domain/services/product.service.ts b/src/product/domain/services/product.service.ts new file mode 100644 index 0000000..a06e9b7 --- /dev/null +++ b/src/product/domain/services/product.service.ts @@ -0,0 +1,128 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { AddProductDto } from '../../application/dtos/AddProduct.dto'; +import { ProductRepositoryService } from '../../infrastructure/repositories/product-repository.service'; +import { Product } from '../models/product.model'; +import { UpdateProductDto } from '../../application/dtos/UpdateProduct.dto'; +import { ProductIdDto } from '../../application/dtos/ProductId.dto'; + +@Injectable() +export class ProductService { + constructor(private readonly productRepo: ProductRepositoryService) {} + + // fetching a single product details + async getProductDetails(productId: string) { + const result = { + success: true, + message: '', + data: {}, + }; + + const productDetails = await this.productRepo + .findById(productId) + .catch((error) => { + result.message = 'Unable to process right now'; + result.success = false; + console.log(error); + }); + + console.log('product details', productDetails); + + if (!productDetails?.id && result.success) { + result.success = false; + result.message = 'Product not found'; + delete result.data; + throw new NotFoundException(result); + } else if (result.success) { + result.data = productDetails; + result.message = 'Product details fetched successfully'; + return result; + } else { + return result; + } + } + + // add a new record to products + async createProduct(body: AddProductDto) { + const result = { + success: true, + message: '', + data: {}, + }; + + try { + const id = Math.random().toString(36).substr(2, 9); + const product = new Product(id, body.name, body.description); + + await this.productRepo.save(product); + result.data = product.getDetails(); + result.message = 'Product created successfully'; + } catch (error) { + result.message = 'Unable to process right now'; + result.success = false; + console.log(error); + } + return result; + } + + // update a prodduct detail + async updateProduct(body: UpdateProductDto) { + const result = { + success: true, + message: '', + }; + + try { + await this.productRepo.update({ + id: body.productId, + name: body.name, + description: body.description, + }); + result.message = 'Product updated successfully'; + } catch (error) { + console.log(error); + result.success = false; + result.message = 'Unable to process right now'; + } + return result; + } + + // delete a product with productId + async deleteProduct(body: ProductIdDto) { + const result = { + success: true, + message: '', + }; + + try { + const productDetails = await this.productRepo.delete(body.productId); + console.log('Product deleted', productDetails); + result.message = 'Product deleted successfully'; + } catch (error) { + console.log(error); + result.success = false; + result.message = 'Unable to process right now'; + } + return result; + } + + // fetching all product details + async getAllProducts() { + const result = { + success: true, + message: '', + data: [], + }; + + try { + const productsList = await this.productRepo.find(); + result.message = 'Products fetched successfully'; + result.data = productsList; + } catch (error) { + console.log(error); + result.success = false; + result.message = 'Unable to process right now'; + } + + return result; + } +} diff --git a/src/product/infrastructure/repositories/product-repository.service.ts b/src/product/infrastructure/repositories/product-repository.service.ts new file mode 100644 index 0000000..aea3572 --- /dev/null +++ b/src/product/infrastructure/repositories/product-repository.service.ts @@ -0,0 +1,56 @@ +import { Injectable } from '@nestjs/common'; +import { Product } from '../../domain/models/product.model'; + +interface ProductDetails { + id: string; + name: string; + description: string; +} + +@Injectable() +export class ProductRepositoryService { + productsData: ProductDetails[]; + + constructor() { + this.productsData = []; + } + + // adding a new record to products + save(productDetails: Product): Promise { + console.log(productDetails); + this.productsData.push(productDetails.getDetails()); + return Promise.resolve(); + } + + // updating the product details + update(productDetails: ProductDetails): Promise { + console.log(productDetails); + const productIndex = this.productsData.findIndex( + (singleProduct) => singleProduct.id === productDetails.id, + ); + this.productsData[productIndex] = productDetails; + return Promise.resolve(); + } + + // retriving all the products list + find(): Promise { + return Promise.resolve(this.productsData); + } + + // filtering a single product details + findById(id: string): Promise { + const productDetails = this.productsData.find( + (singleProduct) => singleProduct.id === id, + ); + return Promise.resolve(productDetails); + } + + // deleting a product with productid + delete(id: string): Promise { + const productIndex = this.productsData.findIndex( + (singleProduct) => singleProduct.id === id, + ); + const [deletedUser] = this.productsData.splice(productIndex, 1); + return Promise.resolve(deletedUser); + } +} diff --git a/src/product/product.module.ts b/src/product/product.module.ts new file mode 100644 index 0000000..f53eef7 --- /dev/null +++ b/src/product/product.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { ProductsController } from './application/controllers/products.controller'; +import { ProductService } from './domain/services/product.service'; +import { ProductRepositoryService } from './infrastructure/repositories/product-repository.service'; + +@Module({ + controllers: [ProductsController], + providers: [ProductService, ProductRepositoryService] +}) +export class ProductModule {} diff --git a/src/users/application /controllers/users.controller.spec.ts b/src/users/application /controllers/users.controller.spec.ts new file mode 100644 index 0000000..d5ef0f1 --- /dev/null +++ b/src/users/application /controllers/users.controller.spec.ts @@ -0,0 +1,127 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { UsersController } from './users.controller'; +import { UpdateUserDto } from '../dtos/UpdateUser.dto'; +import { UserIdDto } from '../dtos/UserId.dto'; +import { UsersService } from '../../domain/services/users.service'; + +describe('UsersController', () => { + let controller: UsersController; + + const mockUserService = { + createUser: jest.fn(() => { + return { + success: true, + message: 'User created successfully', + data: { + id: 'gvs9athpt', + name: 'mobiles', + email: 'mobiles@test.com', + password: '123456sdfb', + }, + }; + }), + getUserDetails: jest.fn((id) => { + return { + success: true, + message: 'User details fetched successfully', + data: { + id: 'gvs9athpt', + name: 'mobiles', + email: 'mobiles@test.com', + password: '123456sdfb', + }, + }; + }), + updateUser: jest.fn((body: UpdateUserDto) => { + return { + success: true, + message: 'User updated successfully', + }; + }), + getAllUsers: jest.fn(() => { + return { + success: true, + message: 'Users fetched successfully', + data: [ + { + id: 'gvs9athpt', + name: 'mobiles', + email: 'mobiles@test.com', + password: '123456sdfb', + }, + ], + }; + }), + deleteUser: jest.fn((body: UserIdDto) => { + return { + success: true, + message: 'User deleted successfully', + }; + }), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [UsersController], + providers: [{ provide: UsersService, useValue: mockUserService }], + }).compile(); + + controller = module.get(UsersController); + }); + + it('add a new user', async () => { + const product = { + name: 'mobiles', + email: 'mobiles@test.com', + password: '123456sdfb', + }; + + const response: any = await controller.addUser(product); + + expect(response.success).toEqual(true); + expect(response.data.email).toEqual('mobiles@test.com'); + expect(mockUserService.createUser).toHaveBeenCalled(); + }); + + it('fetch a user details by user id', async () => { + const payload = { + id: 'gvs9athpt', + }; + + const response: any = await controller.fetchUserDetails(payload.id); + + expect(response.data.id).toEqual('gvs9athpt'); + expect(response.data.email).toEqual('mobiles@test.com'); + expect(mockUserService.getUserDetails).toHaveBeenCalled(); + }); + + it('update a single user details',async ()=>{ + const payload : UpdateUserDto= { + userId: 'gvs9athpt', + name: 'steve', + email: 'tony@stark.com', + password: '123456sdfb', + } + + const response = await controller.editeUser(payload); + + expect(response.success).toEqual(true); + expect(mockUserService.updateUser).toHaveBeenCalled(); + }) + + it('fetching all users', async()=>{ + const response = await controller.fetchAllUsers(); + + expect(response.data.length).toBeGreaterThan(0); + expect(mockUserService.getAllUsers).toHaveBeenCalled(); + }) + + it('delete a user', async ()=>{ + const payload = { userId: 'gvs9athpt'}; + + const result = await controller.deleteUser(payload); + + expect(result.success).toEqual(true); + expect(mockUserService.deleteUser).toHaveBeenCalled(); + }) +}); diff --git a/src/users/application /controllers/users.controller.ts b/src/users/application /controllers/users.controller.ts new file mode 100644 index 0000000..b4924dc --- /dev/null +++ b/src/users/application /controllers/users.controller.ts @@ -0,0 +1,36 @@ +import { Body, Controller, Get, Param, Post } from '@nestjs/common'; +import { UsersService } from '../../domain/services/users.service'; +import { AddUserDto } from '../dtos/AddUser.dto'; +import { UpdateUserDto } from '../dtos/UpdateUser.dto'; +import { UserIdDto } from '../dtos/UserId.dto'; + +@Controller('users') +export class UsersController { + constructor(private readonly usersService: UsersService) {} + + @Get('/single-user/:id') + fetchUserDetails(@Param('id') id: string) { + return this.usersService.getUserDetails(id); + } + + @Post('create-user') + addUser(@Body() body: AddUserDto) { + return this.usersService.createUser(body); + } + + @Post('update-user') + editeUser(@Body() body: UpdateUserDto) { + console.log(body); + return this.usersService.updateUser(body); + } + + @Get('all-users') + fetchAllUsers() { + return this.usersService.getAllUsers(); + } + + @Post('delete-user') + deleteUser(@Body() body: UserIdDto) { + return this.usersService.deleteUser(body); + } +} diff --git a/src/users/application /dtos/AddUser.dto.ts b/src/users/application /dtos/AddUser.dto.ts new file mode 100644 index 0000000..92486b6 --- /dev/null +++ b/src/users/application /dtos/AddUser.dto.ts @@ -0,0 +1,21 @@ +import { IsNotEmpty, IsString, IsEmail, MinLength, MaxLength } from 'class-validator'; + +export class AddUserDto { + @IsNotEmpty() + @IsString() + @MinLength(3) + @MaxLength(20) + name: string; + + @IsNotEmpty() + @IsEmail() + @MinLength(6) + @MaxLength(25) + email: string; + + @IsNotEmpty() + @IsString() + @MinLength(8) + @MaxLength(20) + password?: string; +} diff --git a/src/users/application /dtos/UpdateUser.dto.ts b/src/users/application /dtos/UpdateUser.dto.ts new file mode 100644 index 0000000..e5814f3 --- /dev/null +++ b/src/users/application /dtos/UpdateUser.dto.ts @@ -0,0 +1,9 @@ +import { IsNotEmpty, IsString, MinLength } from 'class-validator'; +import { AddUserDto } from './AddUser.dto'; + +export class UpdateUserDto extends AddUserDto { + @IsNotEmpty() + @IsString() + @MinLength(5) + userId: string; +} diff --git a/src/users/application /dtos/UserId.dto.ts b/src/users/application /dtos/UserId.dto.ts new file mode 100644 index 0000000..f539829 --- /dev/null +++ b/src/users/application /dtos/UserId.dto.ts @@ -0,0 +1,8 @@ +import { IsNotEmpty, IsString, MinLength } from 'class-validator'; + +export class UserIdDto { + @IsNotEmpty() + @IsString() + @MinLength(5) + userId: string; +} \ No newline at end of file diff --git a/src/users/domain/interfaces/user.repository.interface.ts b/src/users/domain/interfaces/user.repository.interface.ts new file mode 100644 index 0000000..8fc0815 --- /dev/null +++ b/src/users/domain/interfaces/user.repository.interface.ts @@ -0,0 +1,9 @@ +import { User } from "../models/user.model"; + +export interface IUsersRepository{ + save(userDetails: User): Promise; + update(userDetails: User): Promise; + find(): Promise; + findById(id: string): Promise; + delete(id: string): Promise; +} \ No newline at end of file diff --git a/src/users/domain/models/user.model.ts b/src/users/domain/models/user.model.ts new file mode 100644 index 0000000..fab6d17 --- /dev/null +++ b/src/users/domain/models/user.model.ts @@ -0,0 +1,38 @@ +export class User { + #id: string; + #name: string; + #email: string; + #password: string; + + constructor(id: string, name: string, email: string, password: string) { + this.#id = id; + this.#name = name; + this.#email = email; + this.#password = password; + } + + get id() { + return this.id; + } + + get name() { + return this.name; + } + + get email() { + return this.email; + } + + get password() { + return this.password; + } + + getDetails() { + return { + id: this.#id, + name: this.#name, + email: this.#email, + password: this.#password, + }; + } +} diff --git a/src/users/domain/services/users.service.ts b/src/users/domain/services/users.service.ts new file mode 100644 index 0000000..6884755 --- /dev/null +++ b/src/users/domain/services/users.service.ts @@ -0,0 +1,127 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { AddUserDto } from '../../application /dtos/AddUser.dto'; +import { UserRepositoryService } from '../../infrastructure/repositories/user-repository.service'; +import { User } from '../models/user.model'; +import { UpdateUserDto } from '../../application /dtos/UpdateUser.dto'; +import { UserIdDto } from '../../application /dtos/UserId.dto'; + +@Injectable() +export class UsersService { + constructor(private readonly userRepo: UserRepositoryService) {} + + // fetching a single user details + async getUserDetails(userId: string) { + const result = { + success: true, + message: '', + data: {}, + }; + + const userDetails = await this.userRepo.findById(userId).catch((error) => { + result.message = 'Unable to process right now'; + result.success = false; + console.log(error); + }); + + console.log('user details', userDetails); + + if (!userDetails?.id && result.success) { + result.success = false; + result.message = 'User not found'; + delete result.data; + throw new NotFoundException(result); + } else if (result.success) { + result.data = userDetails; + result.message = 'User details fetched successfully'; + return result; + } else { + return result; + } + } + + // add a new record to users + async createUser(body: AddUserDto) { + const result = { + success: true, + message: '', + data: {}, + }; + + try { + const id = Math.random().toString(36).substr(2, 9); + const user = new User(id, body.email, body.name, body.password); + + await this.userRepo.save(user); + result.data = user.getDetails(); + result.message = 'User created successfully'; + } catch (error) { + result.message = 'Unable to process right now'; + result.success = false; + console.log(error); + } + return result; + } + + // update a user details + async updateUser(body: UpdateUserDto) { + const result = { + success: true, + message: '', + }; + + try { + await this.userRepo.update({ + email: body.email, + id: body.userId, + name: body.name, + password: body.password, + }); + result.message = 'User updated successfully'; + } catch (error) { + console.log(error); + result.success = false; + result.message = 'Unable to process right now'; + } + return result; + } + + // delete a user with user id + async deleteUser(body: UserIdDto) { + const result = { + success: true, + message: '', + }; + + try { + const userDetalils = await this.userRepo.delete(body.userId); + console.log('User deleted', userDetalils); + result.message = 'User deleted successfully'; + } catch (error) { + console.log(error); + result.success = false; + result.message = 'Unable to process right now'; + } + return result; + } + + // fetching all users details + async getAllUsers() { + const result = { + success: true, + message: '', + data:[], + }; + + try { + const usersList = await this.userRepo.find(); + result.message = 'Users fetched successfully'; + result.data = usersList; + } catch (error) { + console.log(error); + result.success = false; + result.message = 'Unable to process right now'; + } + + return result; + } +} diff --git a/src/users/infrastructure/repositories/user-repository.service.ts b/src/users/infrastructure/repositories/user-repository.service.ts new file mode 100644 index 0000000..0162b99 --- /dev/null +++ b/src/users/infrastructure/repositories/user-repository.service.ts @@ -0,0 +1,59 @@ +import { Injectable } from '@nestjs/common'; +import { IUsersRepository } from 'src/users/domain/interfaces/user.repository.interface'; +import { User } from 'src/users/domain/models/user.model'; + + +interface UserDetails { + id: string; + name: string; + email: string; + password: string; +} + +@Injectable() +export class UserRepositoryService implements IUsersRepository { + userData: UserDetails[]; + + constructor() { + this.userData = []; + } + + // adding a new record to users + save(userDetails: User): Promise { + console.log(userDetails); + this.userData.push(userDetails.getDetails()); + return Promise.resolve(); + } + + // updating the user details + update(userDetails: UserDetails): Promise { + console.log(userDetails); + const userIndex = this.userData.findIndex( + (singleUser) => singleUser.id === userDetails.id, + ); + this.userData[userIndex] = userDetails; + return Promise.resolve(); + } + + // retriving all the users list + find(): Promise { + return Promise.resolve(this.userData); + } + + // filtering a single user details + findById(id: string): Promise { + const userDetails = this.userData.find( + (singleUser) => singleUser.id === id, + ); + return Promise.resolve(userDetails); + } + + // deleting a user with userid + delete(id: string): Promise { + const userIndex = this.userData.findIndex( + (singleUser) => singleUser.id === id, + ); + const [deletedUser] = this.userData.splice(userIndex, 1); + return Promise.resolve(deletedUser); + } +} diff --git a/src/users/users.module.ts b/src/users/users.module.ts new file mode 100644 index 0000000..7c53618 --- /dev/null +++ b/src/users/users.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { UsersController } from './application /controllers/users.controller'; +import { UsersService } from './domain/services/users.service'; +import { UserRepositoryService } from './infrastructure/repositories/user-repository.service'; + +@Module({ + controllers: [UsersController], + providers: [UsersService, UserRepositoryService], +}) +export class UsersModule {} From 60a20f868196a09022d5eb8ae3fc41532e1b65cc Mon Sep 17 00:00:00 2001 From: Ryuytyuu-Greseven Date: Sat, 25 Jan 2025 09:29:16 +0530 Subject: [PATCH 2/8] removed shared module import. --- src/app.module.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app.module.ts b/src/app.module.ts index 4068d68..e830eb7 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -3,10 +3,9 @@ import { OrderModule } from './order/order.module'; import { AppController } from './app.controller'; import { ProductModule } from './product/product.module'; import { UsersModule } from './users/users.module'; -import { SharedModule } from './shared/shared.module'; @Module({ - imports: [OrderModule, ProductModule, UsersModule, SharedModule], + imports: [OrderModule, ProductModule, UsersModule], controllers: [AppController], providers: [], }) From d222f3971f4d5535873e26d1da9570249fc3281c Mon Sep 17 00:00:00 2001 From: Ryuytyuu-Greseven Date: Sat, 25 Jan 2025 11:17:31 +0530 Subject: [PATCH 3/8] misc fixes! --- src/users/application /controllers/users.controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/users/application /controllers/users.controller.ts b/src/users/application /controllers/users.controller.ts index b4924dc..2145dce 100644 --- a/src/users/application /controllers/users.controller.ts +++ b/src/users/application /controllers/users.controller.ts @@ -13,12 +13,12 @@ export class UsersController { return this.usersService.getUserDetails(id); } - @Post('create-user') + @Post('signup') addUser(@Body() body: AddUserDto) { return this.usersService.createUser(body); } - @Post('update-user') + @Post('update-profile') editeUser(@Body() body: UpdateUserDto) { console.log(body); return this.usersService.updateUser(body); From 66f1135a760aabab4755051b3c42d1c39217fd32 Mon Sep 17 00:00:00 2001 From: Ryuytyuu-Greseven Date: Sat, 25 Jan 2025 11:17:49 +0530 Subject: [PATCH 4/8] Docker file added for containerisation! --- Dockerfile | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0934768 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +from node:20 + +WORKDIR /app + +COPY package.json ./ + +RUN npm i + +COPY . . + +RUN npm run build + +EXPOSE 3000 + +CMD ["npm", "run", "start:prod"] \ No newline at end of file From 50c6fe5d661bb1896b45617d9299f2029954c133 Mon Sep 17 00:00:00 2001 From: Ryuytyuu-Greseven Date: Mon, 27 Jan 2025 16:51:03 +0530 Subject: [PATCH 5/8] adding fly setup files! --- .dockerignore | 58 ++++++++++++++++++++++++++++++++ .github/workflows/fly-deploy.yml | 18 ++++++++++ fly.toml | 22 ++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 .dockerignore create mode 100644 .github/workflows/fly-deploy.yml create mode 100644 fly.toml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..39b7bff --- /dev/null +++ b/.dockerignore @@ -0,0 +1,58 @@ +# flyctl launch added from .gitignore +# compiled output +dist +node_modules +build + +# Logs +**/logs +**/*.log +**/npm-debug.log* +**/pnpm-debug.log* +**/yarn-debug.log* +**/yarn-error.log* +**/lerna-debug.log* + +# OS +**/.DS_Store + +# Tests +coverage +.nyc_output + +# IDEs and editors +.idea +**/.project +**/.classpath +**/.c9 +**/*.launch +**/.settings +**/*.sublime-workspace + +# IDE - VSCode +**/.vscode/* +!**/.vscode/settings.json +!**/.vscode/tasks.json +!**/.vscode/launch.json +!**/.vscode/extensions.json + +# dotenv environment variable files +**/.env +**/.env.development.local +**/.env.test.local +**/.env.production.local +**/.env.local + +# temp directory +**/.temp +**/.tmp + +# Runtime data +**/pids +**/*.pid +**/*.seed +**/*.pid.lock + +# Diagnostic reports (https://nodejs.org/api/report.html) +**/report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json +fly.toml diff --git a/.github/workflows/fly-deploy.yml b/.github/workflows/fly-deploy.yml new file mode 100644 index 0000000..b0c246e --- /dev/null +++ b/.github/workflows/fly-deploy.yml @@ -0,0 +1,18 @@ +# See https://fly.io/docs/app-guides/continuous-deployment-with-github-actions/ + +name: Fly Deploy +on: + push: + branches: + - main +jobs: + deploy: + name: Deploy app + runs-on: ubuntu-latest + concurrency: deploy-group # optional: ensure only one action runs at a time + steps: + - uses: actions/checkout@v4 + - uses: superfly/flyctl-actions/setup-flyctl@master + - run: flyctl deploy --remote-only + env: + FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} diff --git a/fly.toml b/fly.toml new file mode 100644 index 0000000..3310dfd --- /dev/null +++ b/fly.toml @@ -0,0 +1,22 @@ +# fly.toml app configuration file generated for senior-backend-coding-exercise on 2025-01-25T11:26:33+05:30 +# +# See https://fly.io/docs/reference/configuration/ for information about how to use this file. +# + +app = 'senior-backend-coding-exercise' +primary_region = 'mia' + +[build] + +[http_service] + internal_port = 3000 + force_https = true + auto_stop_machines = 'stop' + auto_start_machines = true + min_machines_running = 0 + processes = ['app'] + +[[vm]] + memory = '2gb' + cpu_kind = 'shared' + cpus = 1 From 1ca23bcd71fb036ea72ddd886c8d11988d3075b3 Mon Sep 17 00:00:00 2001 From: Ryuytyuu-Greseven Date: Mon, 27 Jan 2025 17:53:07 +0530 Subject: [PATCH 6/8] Addde sample payloads for the users apis! --- README.md | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/README.md b/README.md index eed725e..f39dce1 100644 --- a/README.md +++ b/README.md @@ -96,3 +96,100 @@ The application will run on `http://localhost:3000`. ### Time Estimate: The exercise should take approximately **20 minutes** to complete. + + +# Senior Backend Coding Exercise Sample Payloads + +```bash + + Signup User + API: https://senior-backend-coding-exercise.fly.dev/users/signup + Method: POST + + Payload: { + "name": "steve", + "email": "abc@test.com", + "password": "encrypted" + } + + Response: { + "success": true, + "message": "User created successfully", + "data": { + "id": "5dew2s8um", + "name": "abc@test.com", + "email": "steve", + "password": "encrypted" + } + } + + Fetch User Details + API: https://senior-backend-coding-exercise.fly.dev/users/single-user/5dew2s8um + Method: GET + + Response: + { + "success": true, + "message": "User details fetched successfully", + "data": + { + "id": "5dew2s8um", + "name": "abc@test.com", + "email": "steve", + "password": "encrypted" + } + } + + Fetch All Users + API: https://senior-backend-coding-exercise.fly.dev/users/all-users + Method: GET + + Response: + { + "success": true, + "message": "Users fetched successfully", + "data": [ + { + "id": "5dew2s8um", + "name": "abc@test.com", + "email": "steve", + "password": "encrypted" + } + ] + } + + Update profile + API: https://senior-backend-coding-exercise.fly.dev/users/update-profile + Method: POST + Payload: + { + "userId":"fqeuzrhee", + "name": "tony", + "email": "tony@stark.in", + "password": "double_enc" + } + + Response: + { + "success": true, + "message": "User updated successfully" + } + + + Delete User + API: https://senior-backend-coding-exercise.fly.dev/users/delete-user + Method: POST + Payload: + { + "userId": "hsgqx70tp" + } + + Response: + { + "success": true, + "message": "User deleted successfully" + } + + + +``` \ No newline at end of file From 15c2217bb92ed2c52e8ef2312be5123b1201e739 Mon Sep 17 00:00:00 2001 From: Ryuytyuu-Greseven Date: Mon, 27 Jan 2025 18:05:43 +0530 Subject: [PATCH 7/8] Added Products and Orders Sample API's --- README.md | 149 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) diff --git a/README.md b/README.md index f39dce1..5044ab9 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,7 @@ The exercise should take approximately **20 minutes** to complete. ```bash Signup User + API: https://senior-backend-coding-exercise.fly.dev/users/signup Method: POST @@ -124,6 +125,7 @@ The exercise should take approximately **20 minutes** to complete. } Fetch User Details + API: https://senior-backend-coding-exercise.fly.dev/users/single-user/5dew2s8um Method: GET @@ -141,6 +143,7 @@ The exercise should take approximately **20 minutes** to complete. } Fetch All Users + API: https://senior-backend-coding-exercise.fly.dev/users/all-users Method: GET @@ -159,6 +162,7 @@ The exercise should take approximately **20 minutes** to complete. } Update profile + API: https://senior-backend-coding-exercise.fly.dev/users/update-profile Method: POST Payload: @@ -177,6 +181,7 @@ The exercise should take approximately **20 minutes** to complete. Delete User + API: https://senior-backend-coding-exercise.fly.dev/users/delete-user Method: POST Payload: @@ -189,7 +194,151 @@ The exercise should take approximately **20 minutes** to complete. "success": true, "message": "User deleted successfully" } +``` + +```bash + Add new Products + API: https://senior-backend-coding-exercise.fly.dev/products/create-product + Method: POST + Payload: + { + "name": "mobiles", + "description": "a simple mobile device" + } + + Response: + { + "success": true, + "message": "Product created successfully", + "data": { + "id": "epefon9gr", + "name": "mobiles", + "description": "a simple mobile device" + } + } + + + Fetch Product Details + API: https://senior-backend-coding-exercise.fly.dev/products/single-product/epefon9gr + Method: GET + + Response: + { + "success": true, + "message": "Product details fetched successfully", + "data": { + "id": "epefon9gr", + "name": "mobiles", + "description": "a simple mobile device" + } + } + + + Fetch All Products + API: https://senior-backend-coding-exercise.fly.dev/products/all-products + Method: GET + + Response: + { + "success": true, + "message": "Products fetched successfully", + "data": [ + { + "id": "epefon9gr", + "name": "mobiles", + "description": "a simple mobile device" + } + ] + } + + + Update Product + API: https://senior-backend-coding-exercise.fly.dev/products/update-product + Method: POST + Payload: + { + "productId":"epefon9gr", + "name": "Nothing Phone (1)", + "description": "A brand new nothing phone (1)(R)" + } + Response: + { + "success": true, + "message": "Product updated successfully" + } + + Delete Product + API: https://senior-backend-coding-exercise.fly.dev/products/delete-product + Method: POST + Payload: + { + "productId": "epefon9gr" + } + Response: + { + "success": true, + "message": "Product deleted successfully" + } + +``` + +```bash + Add Orders + API: https://senior-backend-coding-exercise.fly.dev/order/add + Method: POST + Payload: + { + "productId":"xh945mdtb", + "userId":"gvs9athpt", + "quantity": 2 + } + + Response: + { + "success": true, + "message": "Order created successfully", + "data": { + "id": "842cm5c2x", + "productId": "xh945mdtb", + "quantity": 2, + "userId": "gvs9athpt" + } + } + + + Fetch Product Details + API: https://senior-backend-coding-exercise.fly.dev/order/842cm5c2x + Method: GET + Response: + { + "success": true, + "message": "Order details fetched successfully", + "data": { + "id": "842cm5c2x", + "productId": "xh945mdtb", + "quantity": 2, + "userId": "gvs9athpt" + } + } + + + Fetch All Orders + API: https://senior-backend-coding-exercise.fly.dev/order/all + Method: GET + Response: + { + "success": true, + "message": "Orders fetched successfully", + "data": [ + { + "id": "842cm5c2x", + "productId": "xh945mdtb", + "quantity": 2, + "userId": "gvs9athpt" + } + ] + } ``` \ No newline at end of file From 26def382eb233e18c6d97921cb81597bbe2f29c2 Mon Sep 17 00:00:00 2001 From: Ryuytyuu-Greseven Date: Mon, 27 Jan 2025 18:07:22 +0530 Subject: [PATCH 8/8] tags added! --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 5044ab9..0cbdd8e 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,7 @@ The exercise should take approximately **20 minutes** to complete. # Senior Backend Coding Exercise Sample Payloads +### Users ```bash Signup User @@ -196,6 +197,7 @@ The exercise should take approximately **20 minutes** to complete. } ``` +### Products ```bash Add new Products API: https://senior-backend-coding-exercise.fly.dev/products/create-product @@ -284,6 +286,7 @@ The exercise should take approximately **20 minutes** to complete. ``` +### Orders ```bash Add Orders API: https://senior-backend-coding-exercise.fly.dev/order/add