Skip to content

Commit

Permalink
Merge pull request #40 from atlp-rwanda/187354255-create-product
Browse files Browse the repository at this point in the history
Feature for enabling product to be created by seller
  • Loading branch information
niyontwali authored May 7, 2024
2 parents 6ea4466 + 436c65b commit 608d497
Show file tree
Hide file tree
Showing 14 changed files with 751 additions and 1 deletion.
19 changes: 19 additions & 0 deletions src/controllers/categoriesController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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;
await Category.create({
name,
description,
});
res.status(201).json({ ok: true, message: 'New category created successully!' });
} catch (error) {
if (error instanceof Error) {
logger.error(error.message);
}
res.status(500).json({ error: 'Failed to create category' });
}
};
71 changes: 71 additions & 0 deletions src/controllers/productsController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Request, Response } from 'express';
import uploadImage from '../helpers/claudinary';
import { sendInternalErrorResponse } from '../validations';
import { Product, ProductAttributes } from '../database/models/Product';
import { Size, SizeAttributes } from '../database/models/Size';

export const createProduct = async (req: Request, res: Response) => {
try {
const { name, description, colors } = req.body as ProductAttributes;
const { categoryId } = req.params;
const seller = (await req.user) as any;
const sellerId = seller.id;

// when products exists
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 = ['asdf', 'asdf', 'asdf', 'asdf'];
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
await Product.create({
sellerId,
name,
description,
categoryId,
colors,
images: productImages,
});

res.status(201).json({
ok: true,
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 { productId } = req.params;
const { size, price, discount, expiryDate } = req.body as SizeAttributes;
await Size.create({ size, price, discount, expiryDate, productId });
res.status(201).json({
ok: true,
message: 'Product size added successfully',
});
} catch (error) {
sendInternalErrorResponse(res, error);
}
};
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: 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');
},
};
68 changes: 68 additions & 0 deletions src/database/migrations/20240429115230-create-product.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/* 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,
},
images: {
type: Sequelize.ARRAY(Sequelize.STRING),
allowNull: false,
},
colors: {
type: Sequelize.ARRAY(Sequelize.STRING),
allowNull: true,
},
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');
},
};
61 changes: 61 additions & 0 deletions src/database/migrations/20240501004030-create-size.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/* 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: {
type: Sequelize.STRING,
allowNull: true,
},
price: {
type: Sequelize.FLOAT,
allowNull: false,
},
quantity: {
type: Sequelize.INTEGER,
defaultValue: 1,
},
discount: {
type: Sequelize.FLOAT,
allowNull: true,
},
expiryDate: {
type: Sequelize.DATE,
allowNull: true,
},
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');
},
};
42 changes: 42 additions & 0 deletions src/database/models/Category.ts
Original file line number Diff line number Diff line change
@@ -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<CategoryAttributes, 'id'> {}

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

export interface ProductAttributes {
id?: string;
sellerId: string;
name: string;
description: string;
images: string[];
colors?: string[];
categoryId: string;
createdAt?: Date;
updatedAt?: Date;
}

export class Product extends Model<ProductAttributes> implements ProductAttributes {
public id!: string;
public sellerId!: string;
public name!: string;
public description!: string;
public categoryId!: string;
public images!: string[];
public colors!: 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,
},
colors: {
type: DataTypes.ARRAY(DataTypes.STRING),
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' });
Loading

0 comments on commit 608d497

Please sign in to comment.