diff --git a/package.json b/package.json index 140615de..f6627c4b 100644 --- a/package.json +++ b/package.json @@ -34,14 +34,15 @@ "glob": "^10.3.12", "jsonwebtoken": "^9.0.2", "mailgen": "^2.0.28", + "multer": "^1.4.5-lts.1", "nodemailer": "^6.9.13", "passport": "^0.7.0", "passport-google-oauth20": "^2.0.0", "passport-jwt": "^4.0.1", - "multer": "^1.4.5-lts.1", "pg": "^8.11.5", "pg-hstore": "^2.3.4", "sequelize": "^6.37.2", + "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.0", "uuid": "^9.0.1", "winston": "^3.13.0", @@ -80,7 +81,6 @@ "prettier": "^3.2.5", "sequelize-cli": "^6.6.2", "supertest": "^6.3.4", - "swagger-jsdoc": "^6.2.8", "ts-jest": "^29.1.2", "ts-node": "^10.9.2", "typescript": "^5.4.3" @@ -91,4 +91,4 @@ "eslint --fix" ] } -} \ No newline at end of file +} diff --git a/src/controllers/categoriesController.ts b/src/controllers/categoriesController.ts new file mode 100644 index 00000000..6c70f821 --- /dev/null +++ b/src/controllers/categoriesController.ts @@ -0,0 +1,31 @@ +import { Request, Response } from 'express'; +import { Category, CategoryCreationAttributes } from '../database/models/Category'; +import logger from '../logs/config'; + +export const createCategory = async (req: Request, res: Response) => { + try { + const { name, description } = req.body as CategoryCreationAttributes; + const newCategory = await Category.create({ + name, + description, + }); + res.status(201).json({ ok: true, message: 'New category created successully!', data: newCategory }); + } catch (error) { + if (error instanceof Error) { + logger.error(error.message); + } + res.status(500).json({ error: 'Failed to create category' }); + } +}; + +export const getCategory = async (req: Request, res: Response) => { + try { + const categories = await Category.findAll(); + res.status(200).json({ ok: true, data: categories }); + } catch (error) { + if (error instanceof Error) { + logger.error(error.message); + } + res.status(500).json({ error: 'Failed to fetch categories' }); + } +}; diff --git a/src/controllers/productsController.ts b/src/controllers/productsController.ts new file mode 100644 index 00000000..1d2be87b --- /dev/null +++ b/src/controllers/productsController.ts @@ -0,0 +1,83 @@ +import { Request, Response } from 'express'; +import uploadImage from '../helpers/claudinary'; +import { sendInternalErrorResponse, validateFields } from '../validations'; +import { Size, SizeCreationAttributes } from '../database/models/Size'; +import { Product, ProductAttributes } from '../database/models/Product'; + +export const createProduct = async (req: Request, res: Response) => { + try { + // when products exists + const name = req.body.name; + const thisProductExists = await Product.findOne({ where: { name } }); + + if (thisProductExists) { + return res.status(400).json({ + ok: false, + message: 'This Product already exists, You can update the stock levels instead.', + data: thisProductExists, + }); + } + // handle images + const productImages = []; + const images: unknown = req.files; + if (images instanceof Array && images.length > 3) { + for (const image of images) { + const imageBuffer: Buffer = image.buffer; + const url = await uploadImage(imageBuffer); + productImages.push(url); + } + } else { + return res.status(400).json({ + message: 'Product should have at least 4 images', + }); + } + + // Create product + const categoryId = req.params.categoryId; + const requiredFields = ['name', 'description', 'sellerId']; + const missingFields = validateFields(req, requiredFields); + + if (missingFields.length > 0) { + res.status(400).json({ + ok: false, + message: `Required fields are missing: ${missingFields.join(', ')}`, + }); + } + + const { description, discount, sellerId, expiryDate, price, quantity } = req.body as ProductAttributes; + + const createdProduct = await Product.create({ + sellerId, + name, + description, + discount, + categoryId, + expiryDate, + price, + quantity, + images: productImages, + }); + + res.status(201).json({ + ok: true, + data: createdProduct, + message: 'Thank you for adding new product in the store!', + }); + } catch (error) { + sendInternalErrorResponse(res, error); + } +}; + +export const createSize = async (req: Request, res: Response) => { + try { + const { size_name, size_number, price, productId } = req.body as SizeCreationAttributes; + const createdSize = await Size.create({ size_name, size_number, price, productId }); + res.status(201).json({ + ok: true, + message: 'Product size created successfully', + data: createdSize, + }); + } catch (error) { + sendInternalErrorResponse(res, error); + } +}; diff --git a/src/database/index.ts b/src/database/index.ts index 647e9f76..e73174b6 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -6,8 +6,10 @@ const databaseConnection = async () => { try { await sequelize.authenticate(); logger.info('connected to the database'); - } catch (error: any) { - logger.error(error.message); + } catch (error: unknown) { + if (error instanceof Error) { + logger.error(error.message); + } } }; diff --git a/src/database/migrations/20240426195145-create-category.js b/src/database/migrations/20240426195145-create-category.js new file mode 100644 index 00000000..3f39ece2 --- /dev/null +++ b/src/database/migrations/20240426195145-create-category.js @@ -0,0 +1,40 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +'use strict'; + +const sequelize = require('sequelize'); + +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + async up(queryInterface, Sequelize) { + await queryInterface.createTable('categories', { + id: { + allowNull: false, + primaryKey: true, + type: Sequelize.UUID, + defaultValue: sequelize.UUIDV4, + unique: true, + }, + name: { + type: Sequelize.STRING, + allowNull: false, + }, + description: { + type: Sequelize.TEXT, + allowNull: true, + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE, + defaultValue: Sequelize.literal('NOW()'), + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE, + defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'), + }, + }); + }, + async down(queryInterface) { + await queryInterface.dropTable('categories'); + }, +}; diff --git a/src/database/migrations/20240429115230-create-product.js b/src/database/migrations/20240429115230-create-product.js new file mode 100644 index 00000000..689bc681 --- /dev/null +++ b/src/database/migrations/20240429115230-create-product.js @@ -0,0 +1,86 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +'use strict'; + +const sequelize = require('sequelize'); + +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + async up(queryInterface, Sequelize) { + await queryInterface.createTable('products', { + id: { + allowNull: false, + primaryKey: true, + type: Sequelize.UUID, + defaultValue: sequelize.UUIDV4, + }, + name: { + type: Sequelize.STRING, + allowNull: false, + }, + description: { + type: Sequelize.TEXT, + allowNull: false, + }, + discount: { + type: Sequelize.FLOAT, + allowNull: true, + defaultValue: 1, + }, + images: { + type: Sequelize.ARRAY(Sequelize.STRING), + allowNull: false, + }, + color: { + type: Sequelize.ARRAY(Sequelize.STRING), + allowNull: true, + }, + expiryDate: { + type: Sequelize.DATE, + allowNull: true, + }, + price: { + type: Sequelize.FLOAT, + allowNull: true, + }, + quantity: { + type: Sequelize.INTEGER, + defaultValue: 1, + allowNull: false, + }, + sellerId: { + type: Sequelize.UUID, + references: { + model: 'Users', + key: 'id', + }, + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + allowNull: false, + }, + categoryId: { + type: Sequelize.UUID, + allowNull: false, + references: { + model: 'categories', + key: 'id', + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE', + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE, + defaultValue: Sequelize.literal('NOW()'), + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE, + defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'), + }, + }); + }, + async down(queryInterface) { + await queryInterface.dropTable('product_sizes'); + await queryInterface.dropTable('products'); + }, +}; diff --git a/src/database/migrations/20240501004030-create-size.js b/src/database/migrations/20240501004030-create-size.js new file mode 100644 index 00000000..dd4b8459 --- /dev/null +++ b/src/database/migrations/20240501004030-create-size.js @@ -0,0 +1,54 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +'use strict'; + +const sequelize = require('sequelize'); + +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + async up(queryInterface, Sequelize) { + await queryInterface.createTable('sizes', { + id: { + allowNull: false, + primaryKey: true, + type: Sequelize.UUID, + defaultValue: sequelize.UUIDV4, + }, + size_name: { + type: Sequelize.STRING, + allowNull: true, + }, + size_number: { type: Sequelize.INTEGER, allowNull: true }, + price: { + type: Sequelize.FLOAT, + allowNull: false, + }, + quantity: { + type: Sequelize.INTEGER, + defaultValue: 1, + }, + productId: { + type: Sequelize.UUID, + references: { + model: 'products', + key: 'id', + }, + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + allowNull: false, + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE, + defaultValue: Sequelize.literal('NOW()'), + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE, + defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'), + }, + }); + }, + async down(queryInterface) { + await queryInterface.dropTable('sizes'); + }, +}; diff --git a/src/database/models/Category.ts b/src/database/models/Category.ts new file mode 100644 index 00000000..02aa27c3 --- /dev/null +++ b/src/database/models/Category.ts @@ -0,0 +1,42 @@ +import { Model, Optional, DataTypes, UUIDV4 } from 'sequelize'; +import sequelize from './index'; + +interface CategoryAttributes { + id: number; + name: string; + description?: string; +} + +export interface CategoryCreationAttributes extends Optional {} + +export class Category extends Model implements CategoryAttributes { + public id!: number; + public name!: string; + public description!: string; + public sizes!: string[]; +} + +Category.init( + { + id: { + type: DataTypes.UUID, + defaultValue: UUIDV4, + primaryKey: true, + autoIncrement: true, + }, + name: { + type: DataTypes.STRING, + allowNull: false, + }, + description: { + type: DataTypes.TEXT, + allowNull: true, + }, + }, + { + sequelize, + modelName: 'Category', + tableName: 'categories', + timestamps: true, + } +); diff --git a/src/database/models/Product.ts b/src/database/models/Product.ts new file mode 100644 index 00000000..e92f8218 --- /dev/null +++ b/src/database/models/Product.ts @@ -0,0 +1,105 @@ +import { Model, DataTypes } from 'sequelize'; +import sequelize from './index'; +import { UUIDV4 } from 'sequelize'; +import { Size } from './Size'; +import { Category } from './Category'; + +export interface ProductAttributes { + id?: number; + sellerId: string; + name: string; + description: string; + images: string[]; + color?: string[]; + discount?: number; + price?: number; + quantity?: number; + expiryDate?: Date; + categoryId: string; + createdAt?: Date; + updatedAt?: Date; +} + +export class Product extends Model implements ProductAttributes { + public id!: number; + public sellerId!: string; + public name!: string; + public description!: string; + public discount!: number; + public quantity!: number; + public price!: number; + public expiryDate!: Date; + public categoryId!: string; + public images!: string[]; + public color!: string[]; + public readonly createdAt!: Date | undefined; + public readonly updatedAt!: Date | undefined; +} + +Product.init( + { + id: { + type: DataTypes.UUID, + defaultValue: UUIDV4, + primaryKey: true, + unique: true, + }, + name: { + type: DataTypes.STRING, + allowNull: false, + }, + description: { + type: DataTypes.TEXT, + allowNull: false, + }, + color: { + type: DataTypes.ARRAY(DataTypes.STRING), + allowNull: true, + }, + discount: { + type: DataTypes.FLOAT, + allowNull: true, + defaultValue: 0, + }, + price: { + type: DataTypes.FLOAT, + allowNull: true, + }, + quantity: { + type: DataTypes.INTEGER, + defaultValue: 1, + allowNull: false, + }, + expiryDate: { + type: DataTypes.DATE, + allowNull: true, + }, + images: { + type: DataTypes.ARRAY(DataTypes.STRING), + allowNull: false, + }, + + sellerId: { + type: DataTypes.UUID, + references: { + model: 'Users', + key: 'id', + }, + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + allowNull: false, + }, + categoryId: { + type: DataTypes.UUID, + references: { + model: 'Category', + key: 'id', + }, + }, + }, + { sequelize: sequelize, timestamps: true, modelName: 'Product', tableName: 'products' } +); + +Product.belongsTo(Category, { foreignKey: 'categoryId' }); + +Product.hasMany(Size, { foreignKey: 'productId' }); diff --git a/src/database/models/Size.ts b/src/database/models/Size.ts new file mode 100644 index 00000000..37180de7 --- /dev/null +++ b/src/database/models/Size.ts @@ -0,0 +1,62 @@ +import { Model, Optional, DataTypes, UUIDV4 } from 'sequelize'; +import sequelize from './index'; + +export interface SizeAttributes { + id: number; + size_name?: string; + size_number?: number; + price: number; + quantity?: number; + productId: string; +} + +export interface SizeCreationAttributes extends Optional {} + +export class Size extends Model implements SizeAttributes { + public id!: number; + public size_name!: string; + public size_number!: number; + public price!: number; + public quantity!: number; + public productId!: string; +} + +Size.init( + { + id: { + type: DataTypes.UUID, + defaultValue: UUIDV4, + primaryKey: true, + autoIncrement: true, + }, + size_name: { + type: DataTypes.STRING, + allowNull: true, + }, + size_number: { type: DataTypes.INTEGER, allowNull: true }, + price: { + type: DataTypes.FLOAT, + allowNull: false, + }, + quantity: { + type: DataTypes.INTEGER, + defaultValue: 1, + }, + productId: { + type: DataTypes.UUID, + references: { + model: 'products', + key: 'id', + }, + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + allowNull: false, + }, + }, + { + sequelize, + modelName: 'Size', + tableName: 'sizes', + timestamps: true, + } +); diff --git a/src/database/seeders/20240429200629-add-seller-role.js b/src/database/seeders/20240429200629-add-seller-role.js new file mode 100644 index 00000000..fe1c1301 --- /dev/null +++ b/src/database/seeders/20240429200629-add-seller-role.js @@ -0,0 +1,22 @@ +'use strict'; +const { v4: uuidv4 } = require('uuid'); + +/** @type {import('sequelize-cli').Migration} */ + +module.exports = { + up: async (queryInterface, Sequelize) => { + return queryInterface.bulkInsert('Roles', [ + { + id: uuidv4(), + name: 'seller', + displayName: 'Seller Role', + createdAt: new Date(), + updatedAt: new Date(), + }, + ]); + }, + + down: async (queryInterface, Sequelize) => { + return queryInterface.bulkDelete('Roles', { name: 'seller' }); + }, +}; diff --git a/src/docs/manageUserStatusDocs.yaml b/src/docs/manageUserStatusDocs.yaml index a461b6e4..75ca8aa7 100644 --- a/src/docs/manageUserStatusDocs.yaml +++ b/src/docs/manageUserStatusDocs.yaml @@ -3,6 +3,8 @@ paths: put: summary: Deactivate a user account description: Deactivates a user account and sends an email notification. + security: + - bearerAuth: [] parameters: - in: path name: userId @@ -10,7 +12,7 @@ paths: description: The ID of the user to deactivate schema: type: string - example: "123456789" + example: '123456789' responses: '200': description: Success response @@ -26,8 +28,8 @@ paths: type: string description: The status of the operation ('OK' for success) example: - message: "User deactivated successfully" - status: "OK" + message: 'User deactivated successfully' + status: 'OK' '400': description: Bad request content: @@ -39,7 +41,7 @@ paths: type: string description: A message indicating the error example: - message: "Invalid request parameters" + message: 'Invalid request parameters' '500': description: Internal server error content: @@ -51,11 +53,13 @@ paths: type: string description: A message indicating the error example: - message: "Internal server error occurred" + message: 'Internal server error occurred' /api/users/activate/{userId}: put: summary: Activate a user account + sercurity: + - bearerAuth: [] description: Activates a user account and sends an email notification. parameters: - in: path @@ -64,7 +68,7 @@ paths: description: The ID of the user to activate schema: type: string - example: "123456789" + example: '123456789' responses: '200': description: Success response @@ -80,8 +84,8 @@ paths: type: string description: The status of the operation ('OK' for success) example: - message: "User activated successfully" - status: "OK" + message: 'User activated successfully' + status: 'OK' '400': description: Bad request content: @@ -93,7 +97,7 @@ paths: type: string description: A message indicating the error example: - message: "Invalid request parameters" + message: 'Invalid request parameters' '500': description: Internal server error content: @@ -105,4 +109,12 @@ paths: type: string description: A message indicating the error example: - message: "Internal server error occurred" + message: 'Internal server error occurred' +security: + - bearerAuth: [] +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT diff --git a/src/docs/products.yaml b/src/docs/products.yaml new file mode 100644 index 00000000..a632348f --- /dev/null +++ b/src/docs/products.yaml @@ -0,0 +1,124 @@ +tags: + - name: Product + description: Operations related to products + +paths: + /api/{categoryId}/products: + post: + summary: Create a new product + tags: + - Product + description: Create a new product in the store + parameters: + - in: path + name: categoryId + required: true + schema: + type: string + description: ID of the category to which the product belongs + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + name *: + type: string + description: Name of the product to which the product belongs + sellerId *: + type: string + description: Name of the seller to which the product belongs to + description *: + type: string + description: Description to which the product + price *: + type: number + description: Price of the product + quantity: + type: number + description: Quantity of the product(by default it's 1) + expiryDate: + type: string + format: date + description: For products that might end + images *: + type: array + items: + type: string + format: binary + description: Array of product images (minimum 4 images required) + responses: + 201: + description: Product created successfully + content: + application/json: + schema: + type: object + properties: + status: + type: integer + example: 201 + ok: + type: boolean + example: true + data: + $ref: '#/components/schemas/Product' + message: + type: string + example: Thank you for adding a new product in the store! + 400: + description: Bad request, invalid parameters provided + content: + application/json: + schema: + type: object + properties: + status: + type: integer + example: 400 + ok: + type: boolean + example: false + message: + type: string + example: Product should have at least 4 images + 500: + description: Internal Server Error + content: + application/json: + schema: + type: object + properties: + status: + type: integer + example: 500 + ok: + type: boolean + example: false + message: + type: string + example: Something went wrong when creating the product +components: + schemas: + Product: + type: object + properties: + name: + type: string + description: Name of the product + description: + type: string + description: Description of the product + price: + type: number + format: float + description: Price of the product + categoryId: + type: string + description: ID of the category to which the product belongs + images: + type: array + items: + type: string + description: Array of URLs of product images diff --git a/src/docs/roles.yaml b/src/docs/roles.yaml index 635f3757..b4780d7b 100644 --- a/src/docs/roles.yaml +++ b/src/docs/roles.yaml @@ -1,10 +1,12 @@ -tags: +tags: - name: Roles description: The role API test paths: /api/roles: get: summary: Get a list of all roles + security: + - bearerAuth: [] tags: - Roles description: This tests the get request of roles @@ -12,10 +14,12 @@ paths: 200: description: Role successfully found 500: - description: Role can't be found + description: Role can't be found post: summary: Add the role + security: + - bearerAuth: [] tags: - Roles description: This add a new role to the role list @@ -39,6 +43,8 @@ paths: /api/roles/{id}: get: summary: Get a single role + security: + - bearerAuth: [] tags: - Roles parameters: @@ -47,12 +53,14 @@ paths: required: true type: string responses: - 404: + 404: description: Role could not be found 200: description: Role successfully found delete: summary: Find a role and delete it by id + security: + - bearerAuth: [] tags: - Roles parameters: @@ -61,14 +69,16 @@ paths: required: true type: string responses: - 404: + 404: description: Role could not be found 200: description: Role successfully deleted 500: - description: Role can't be deleted successfully + description: Role can't be deleted successfully patch: summary: Find a role by id and update it + security: + - bearerAuth: [] tags: - Roles parameters: @@ -77,9 +87,17 @@ paths: required: true type: string responses: - 404: + 404: description: Role could not be found 200: description: Role successfully updated 500: - description: Role can't be updated successfully + description: Role can't be updated successfully +security: + - bearerAuth: [] +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT diff --git a/src/docs/users.yaml b/src/docs/users.yaml index 99c0ab00..a3d3cf0e 100644 --- a/src/docs/users.yaml +++ b/src/docs/users.yaml @@ -1,10 +1,10 @@ -tags: +tags: - name: User description: The role API test paths: /api/users/signup: post: - summary: "Create a new user" + summary: 'Create a new user' tags: - User description: This create a new user @@ -37,6 +37,8 @@ paths: /api/users/{page}: get: summary: Get all users + security: + - bearerAuth: [] tags: - User parameters: @@ -52,6 +54,8 @@ paths: /api/users/edit/{id}: patch: summary: Edit a user data + security: + - bearerAuth: [] tags: - User parameters: @@ -86,10 +90,11 @@ paths: 500: description: Internal Server Error - /api/users/{id}: delete: summary: Delete a user + security: + - bearerAuth: [] tags: - User description: Delete a user @@ -109,6 +114,8 @@ paths: /api/users/role{id}: put: summary: Change a user's role + security: + - bearerAuth: [] tags: - User description: Change a user role @@ -128,6 +135,8 @@ paths: /api/users/user/{id}: get: summary: Get a single user + security: + - bearerAuth: [] tags: - User parameters: @@ -137,11 +146,11 @@ paths: type: string responses: 200: - description: "Successfully Get User" + description: 'Successfully Get User' 404: - description: "User with this ID does not exits" + description: 'User with this ID does not exits' 500: - description: "Internal Server Error" + description: 'Internal Server Error' /api/users/{token}/verify-email: get: @@ -155,13 +164,13 @@ paths: type: string responses: 201: - description: "Account verified, Login to continue." + description: 'Account verified, Login to continue.' 400: - description: "Verification failed. Try again later" + description: 'Verification failed. Try again later' 403: - description: "Verification link has expired. Please request a new one." + description: 'Verification link has expired. Please request a new one.' 500: - description: "Internal Server Error" + description: 'Internal Server Error' /api/users/resend-verify: post: @@ -185,4 +194,12 @@ paths: 400: description: Email is already used, Login to continuue 500: - description: "Internal Server Error" \ No newline at end of file + description: 'Internal Server Error' +security: + - bearerAuth: [] +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT diff --git a/src/routes/categoryRouter.ts b/src/routes/categoryRouter.ts new file mode 100644 index 00000000..9853930b --- /dev/null +++ b/src/routes/categoryRouter.ts @@ -0,0 +1,8 @@ +/* eslint-disable @typescript-eslint/no-misused-promises */ +import express from 'express'; +import { checkUserRoles, isAuthenticated } from '../middlewares/authMiddlewares'; +import { createCategory } from '../controllers/categoriesController'; + +export const categoryRouter = express.Router(); + +categoryRouter.post('/create-category', isAuthenticated, checkUserRoles('admin'), createCategory); diff --git a/src/routes/index.ts b/src/routes/index.ts index 64663233..aa592525 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -2,11 +2,15 @@ import { Router } from 'express'; import userRoute from './userRoute'; import authRoute from './authRoute'; import roleRoute from './roleRoute'; +import { productRouter } from './productRoutes'; +import { categoryRouter } from './categoryRouter'; const router = Router(); router.use('/users', userRoute); router.use('/auth', authRoute); router.use('/roles', roleRoute); +router.use('/products', productRouter); +router.use('/category', categoryRouter); export default router; diff --git a/src/routes/productRoutes.ts b/src/routes/productRoutes.ts new file mode 100644 index 00000000..2639f52c --- /dev/null +++ b/src/routes/productRoutes.ts @@ -0,0 +1,17 @@ +/* eslint-disable @typescript-eslint/no-misused-promises */ +import express from 'express'; +import { createProduct, createSize } from '../controllers/productsController'; +import multerUpload from '../helpers/multer'; +import { checkUserRoles, isAuthenticated } from '../middlewares/authMiddlewares'; + +export const productRouter = express.Router(); + +productRouter.post( + '/:categoryId/create-product/', + isAuthenticated, + checkUserRoles('seller'), + multerUpload.array('images', 8), + createProduct +); + +productRouter.post('/add-size', isAuthenticated, checkUserRoles('seller'), createSize);