Skip to content

Commit

Permalink
Feature for enabling product to be created by seller
Browse files Browse the repository at this point in the history
	modified:   package.json
	new file:   public/uploads/mac.jpg
	new file:   public/uploads/to-do-app.jpeg
	new file:   src/controllers/categoriesController.ts
	new file:   src/controllers/productsController.ts
	modified:   src/database/index.ts
	new file:   src/database/migrations/20240426195145-create-category.js
	new file:   src/database/migrations/20240426204430-create-product.js
	new file:   src/database/models/Category.ts
	new file:   src/database/models/Product.ts
	new file:   src/middlewares/multer.ts
	new file:   src/routes/categoryRouter.ts
	modified:   src/routes/index.ts
	new file:   src/routes/productRoutes.ts
	modified:   src/server.ts
  • Loading branch information
patrickhag committed Apr 30, 2024
1 parent 6de33c7 commit 9bd5443
Show file tree
Hide file tree
Showing 21 changed files with 575 additions and 51 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@
"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",
Expand Down Expand Up @@ -91,4 +91,4 @@
"eslint --fix"
]
}
}
}
31 changes: 31 additions & 0 deletions src/controllers/categoriesController.ts
Original file line number Diff line number Diff line change
@@ -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({ status: 201, 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' });
}
};
72 changes: 72 additions & 0 deletions src/controllers/productsController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Request, Response } from 'express';
import { Product, ProductCreationAttributes } from '../database/models/Product';
import logger from '../logs/config';
import uploadImage from '../helpers/claudinary';
import { validateFields } from '../validations';

export const createProduct = async (req: Request, res: Response) => {
try {
const { categoryId } = req.params;
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({
status: 400,
message: 'Product should have at least 4 images',
});
}

const requiredFields = ['name', 'description', 'price'];
const missingFields = validateFields(req, requiredFields);

if (missingFields.length > 0) {
res.status(400).json({
ok: false,
message: `Required fields are missing: ${missingFields.join(', ')}`,
});
}

const { name, description, price, discount } = req.body as ProductCreationAttributes;

const thisProductExists = await Product.findOne({ where: { name } });

if (thisProductExists) {
return res.status(400).json({
status: 400,
ok: false,
message: 'This Product already exists, You can update the stock levels instead.',
data: thisProductExists,
discount,
});
}

const createdProduct = await Product.create({
name,
description,
price,
categoryId,
images: productImages,
});

res
.status(201)
.json({ status: 201, ok: true, data: createdProduct, message: 'Thank you for adding new product in the store!' });
} catch (error) {
if (error instanceof Error) {
res.status(500).json({
status: 500,
ok: false,
message: 'Something went wrong when creating the product',
error: error.message,
});
} else {
logger.error('Unexpected error', error);
}
}
};
4 changes: 2 additions & 2 deletions src/database/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ 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) console.log(error.message);
}
};

Expand Down
5 changes: 5 additions & 0 deletions src/database/migrations/20240425195548-create-user.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ module.exports = {
allowNull: false,
defaultValue: false,
},
status: {
type: Sequelize.ENUM('active', 'inactive'),
allowNull: false,
defaultValue: 'active',
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
Expand Down
40 changes: 40 additions & 0 deletions src/database/migrations/20240426195145-create-category.js
Original file line number Diff line number Diff line change
@@ -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: 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('categories');
},
};
63 changes: 63 additions & 0 deletions src/database/migrations/20240429115230-create-product.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/* 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,
unique: true,
},
name: {
type: Sequelize.STRING,
allowNull: false,
},
description: {
type: Sequelize.TEXT,
allowNull: false,
},
price: {
type: Sequelize.FLOAT,
allowNull: false,
},
discount: {
type: Sequelize.FLOAT,
allowNull: true,
defaultValue: 0, // Default discount is 0
},
images: {
type: Sequelize.ARRAY(Sequelize.STRING),
allowNull: false,
},
categoryId: {
type: Sequelize.UUID,
allowNull: false,
references: {
model: 'categories',
key: 'id',
},
onUpdate: 'CASCADE', // Optional: Update the behavior on category deletion
onDelete: 'CASCADE', // Optional: Delete associated products on category deletion
},
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('products');
},
};
41 changes: 41 additions & 0 deletions src/database/models/Category.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Model, Optional, DataTypes, UUIDV4 } from 'sequelize';
import sequelize from './index';

interface CategoryAttributes {
id: number;
name: string;
description?: string;
}

export interface CategoryCreationAttributes extends Optional<CategoryAttributes, 'id'> {}

export class Category extends Model<CategoryAttributes, CategoryCreationAttributes> implements CategoryAttributes {
public id!: number;
public name!: string;
public description!: 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,
}
);
72 changes: 72 additions & 0 deletions src/database/models/Product.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Model, Optional, DataTypes } from 'sequelize';
import sequelize from './index';
import { UUIDV4 } from 'sequelize';
import { Category } from './Category';

interface ProductAttributes {
id: number;
name: string;
description: string;
price: number;
discount?: number;
categoryId: string;
createdAt?: Date;
updatedAt?: Date;
images: string[];
}

export interface ProductCreationAttributes extends Optional<ProductAttributes, 'id'> {}

export class Product extends Model<ProductAttributes, ProductCreationAttributes> implements ProductAttributes {
public id!: number;
public name!: string;
public description!: string;
public price!: number;
public discount!: number;
public categoryId!: string;
public images!: string[];
public readonly createdAt!: Date | undefined;
public readonly updatedAt!: Date | undefined;
}

Product.init(
{
id: {
type: DataTypes.UUID,
defaultValue: UUIDV4,
autoIncrement: true,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
description: {
type: DataTypes.TEXT,
allowNull: false,
},
price: {
type: DataTypes.FLOAT,
allowNull: false,
},
discount: {
type: DataTypes.FLOAT,
allowNull: true,
defaultValue: 0, // Default discount is 0
},
images: {
type: DataTypes.ARRAY(DataTypes.STRING),
allowNull: false,
},
categoryId: {
type: DataTypes.UUID,
references: {
model: 'Category',
key: 'id',
},
},
},
{ sequelize: sequelize, timestamps: true, modelName: 'Product', tableName: 'products' }
);

Product.belongsTo(Category, { foreignKey: 'categoryId' });
23 changes: 23 additions & 0 deletions src/database/seeders/20240429200629-add-seller-role.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
'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' });
},
};
Loading

0 comments on commit 9bd5443

Please sign in to comment.