Skip to content

Commit

Permalink
chore: file upload crud
Browse files Browse the repository at this point in the history
  • Loading branch information
ahmadlaiq committed Apr 24, 2024
1 parent c3def77 commit 9029dff
Show file tree
Hide file tree
Showing 15 changed files with 405 additions and 3 deletions.
5 changes: 5 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,26 @@ const dotenv = require('dotenv');
const cors = require('cors');
const CategoriesRouter = require('./routes/catagories');
const AuthRouter = require('./routes/auth');
const ProductRouter = require('./routes/product');
const morgan = require('morgan');
const {errorHandler, notFound} = require('./middleware/errorMiddleware');
const cookieParser = require('cookie-parser');
const path = require('path');

dotenv.config();

// Middleware
app.use(express.json()); // Parse JSON bodies
app.use(express.urlencoded({ extended: true })); // Parse URL-encoded bodies
app.use(cookieParser()); // Parse cookies
app.use(morgan('dev')); // Logging middleware
app.use(cors()); // Enable CORS
app.use('/public/uploads', express.static(path.join(__dirname + '/public/uploads'))); // Serve static files

// Routing
app.use('/api/v1/categories', CategoriesRouter);
app.use('/api/v1/auth', AuthRouter);
app.use('/api/v1/products', ProductRouter);

// Error handling
app.use(notFound);
Expand Down
63 changes: 63 additions & 0 deletions controllers/productController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
const asyncHandle = require('../middleware/asyncHandle');
const {
Product
} = require("../models");

exports.createProduct = asyncHandle(async (req, res) => {
let {
name,
description,
price,
categoryId,
stock,
} = req.body;

const file = req.file;

if (!file) {
res.status(400);
throw new Error("No image uploaded");
}

const fileName = file.filename;
const pathFile = `${req.protocol}://${req.get("host")}/public/uploads/${fileName}`;

const newProduct = await Product.create({
name,
description,
price,
categoryId,
stock,
image: pathFile
});

res.status(201).json({
status: "success",
message: "Product added successfully",
product: newProduct
});
});

// Route for reading all products
exports.getProducts = asyncHandle(async (req, res) => {
const products = await Product.findAll();
return res.status(200).json({
data: products
});
});

// Route for getting details of a single product by ID
exports.getProductById = asyncHandle(async (req, res) => {
const id = req.params.id;
const productData = await Product.findByPk(id);

if (!productData) {
return res.status(404).json({
error: "Product not found"
});
}

return res.status(200).json({
data: productData
});
});
60 changes: 60 additions & 0 deletions migrations/20240423094907-create-product.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
'use strict';
/** @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,
unique: true
},
description: {
type: Sequelize.STRING
},
price: {
type: Sequelize.INTEGER,
allowNull: false,

},
categoryId: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: 'Categories',
key: 'id'
},
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
},
image: {
type: Sequelize.STRING,
allowNull: false
},
stock: {
type: Sequelize.INTEGER,
defaultValue: 0
},
countReview: {
type: Sequelize.INTEGER,
defaultValue: 0
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
async down(queryInterface, Sequelize) {
await queryInterface.dropTable('Products');
}
};
96 changes: 96 additions & 0 deletions models/product.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
'use strict';
const {
Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class Product extends Model {
/**
* Helper method for defining associations.
* This method is not a part of Sequelize lifecycle.
* The `models/index` file will call this method automatically.
*/
static associate(models) {
// define association here
}
}
Product.init({
id: {
type: DataTypes.UUID,
primaryKey: true,
defaultValue: DataTypes.UUIDV4
},
name: {
type: DataTypes.STRING,
allowNull: false,
unique: {
args: true,
msg: 'Nama sudah digunakan'
},
validate: {
notNull: {
msg: 'Nama tidak boleh kosong'
}
}
},
description: DataTypes.STRING,
price: {
type: DataTypes.INTEGER,
allowNull: false,
validate: {
notNull: {
msg: 'Harga tidak boleh kosong'
},
isNumeric: {
msg: 'Harga harus berupa angka'
},
min: {
args: [1],
msg: 'Harga minimal 1'
}
}
},
categoryId: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: 'Categories',
key: 'id'
},
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
validate: {
notNull: {
msg: 'Kategori tidak boleh kosong'
},
isExist(value) {
return sequelize.models.Category.findByPk(value).then((el) => {
if (!el) {
throw new Error('Kategori tidak ditemukan')
}
})
}
}
},
image: {
type: DataTypes.STRING,
allowNull: false,
validate: {
notNull: {
msg: 'Image tidak boleh kosong'
}
}
},
stock: {
type: DataTypes.INTEGER,
defaultValue: 0,
},
countReview: {
type: DataTypes.INTEGER,
defaultValue: 0,
}
}, {
sequelize,
modelName: 'Product',
});
return Product;
};
Loading

0 comments on commit 9029dff

Please sign in to comment.