Skip to content

Commit

Permalink
Merge pull request #48 from atlp-rwanda/187530397-bg-swagger-auth
Browse files Browse the repository at this point in the history
[finishes #187530397] adding auth field for swagger apis
  • Loading branch information
patrickhag committed May 6, 2024
2 parents 43d4d75 + 0f887cf commit 91757ec
Show file tree
Hide file tree
Showing 18 changed files with 760 additions and 33 deletions.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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"
Expand All @@ -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({ 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' });
}
};
83 changes: 83 additions & 0 deletions src/controllers/productsController.ts
Original file line number Diff line number Diff line change
@@ -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);
}
};
6 changes: 4 additions & 2 deletions src/database/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
};

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: 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');
},
};
86 changes: 86 additions & 0 deletions src/database/migrations/20240429115230-create-product.js
Original file line number Diff line number Diff line change
@@ -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');
},
};
54 changes: 54 additions & 0 deletions src/database/migrations/20240501004030-create-size.js
Original file line number Diff line number Diff line change
@@ -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');
},
};
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,
}
);
Loading

0 comments on commit 91757ec

Please sign in to comment.