webupdate
This commit is contained in:
227
backend/utils/crudFactory.js
Normal file
227
backend/utils/crudFactory.js
Normal file
@@ -0,0 +1,227 @@
|
||||
/**
|
||||
* CRUD Route Factory
|
||||
* Generates standardized CRUD routes with consistent patterns
|
||||
*/
|
||||
|
||||
const { query } = require("../config/database");
|
||||
const { asyncHandler } = require("../middleware/errorHandler");
|
||||
const { requireAuth } = require("../middleware/auth");
|
||||
const { sendSuccess, sendNotFound } = require("../utils/responseHelpers");
|
||||
const { getById, deleteById, countRecords } = require("./queryHelpers");
|
||||
const { validateRequiredFields, generateSlug } = require("./validation");
|
||||
const { HTTP_STATUS } = require("../config/constants");
|
||||
|
||||
/**
|
||||
* Create standardized CRUD routes for a resource
|
||||
* @param {Object} config - Configuration object
|
||||
* @param {string} config.table - Database table name
|
||||
* @param {string} config.resourceName - Resource name (plural, e.g., 'products')
|
||||
* @param {string} config.singularName - Singular resource name (e.g., 'product')
|
||||
* @param {string[]} config.listFields - Fields to select in list endpoint
|
||||
* @param {string[]} config.requiredFields - Required fields for creation
|
||||
* @param {Function} config.beforeCreate - Hook before creation
|
||||
* @param {Function} config.afterCreate - Hook after creation
|
||||
* @param {Function} config.beforeUpdate - Hook before update
|
||||
* @param {Function} config.afterUpdate - Hook after update
|
||||
* @param {Function} config.cacheInvalidate - Function to invalidate cache
|
||||
* @returns {Object} Object with route handlers
|
||||
*/
|
||||
const createCRUDHandlers = (config) => {
|
||||
const {
|
||||
table,
|
||||
resourceName,
|
||||
singularName,
|
||||
listFields = "*",
|
||||
requiredFields = [],
|
||||
beforeCreate,
|
||||
afterCreate,
|
||||
beforeUpdate,
|
||||
afterUpdate,
|
||||
cacheInvalidate,
|
||||
} = config;
|
||||
|
||||
return {
|
||||
/**
|
||||
* List all resources
|
||||
* GET /:resource
|
||||
*/
|
||||
list: asyncHandler(async (req, res) => {
|
||||
const result = await query(
|
||||
`SELECT ${listFields} FROM ${table} ORDER BY createdat DESC`
|
||||
);
|
||||
sendSuccess(res, { [resourceName]: result.rows });
|
||||
}),
|
||||
|
||||
/**
|
||||
* Get single resource by ID
|
||||
* GET /:resource/:id
|
||||
*/
|
||||
getById: asyncHandler(async (req, res) => {
|
||||
const item = await getById(table, req.params.id);
|
||||
if (!item) {
|
||||
return sendNotFound(res, singularName);
|
||||
}
|
||||
sendSuccess(res, { [singularName]: item });
|
||||
}),
|
||||
|
||||
/**
|
||||
* Create new resource
|
||||
* POST /:resource
|
||||
*/
|
||||
create: asyncHandler(async (req, res) => {
|
||||
// Validate required fields
|
||||
if (requiredFields.length > 0) {
|
||||
validateRequiredFields(req.body, requiredFields);
|
||||
}
|
||||
|
||||
// Run beforeCreate hook if provided
|
||||
let data = { ...req.body };
|
||||
if (beforeCreate) {
|
||||
data = await beforeCreate(data, req);
|
||||
}
|
||||
|
||||
// Build insert query dynamically
|
||||
const fields = Object.keys(data);
|
||||
const placeholders = fields.map((_, i) => `$${i + 1}`).join(", ");
|
||||
const values = fields.map((key) => data[key]);
|
||||
|
||||
const result = await query(
|
||||
`INSERT INTO ${table} (${fields.join(", ")}, createdat)
|
||||
VALUES (${placeholders}, NOW())
|
||||
RETURNING *`,
|
||||
values
|
||||
);
|
||||
|
||||
let created = result.rows[0];
|
||||
|
||||
// Run afterCreate hook if provided
|
||||
if (afterCreate) {
|
||||
created = await afterCreate(created, req);
|
||||
}
|
||||
|
||||
// Invalidate cache if function provided
|
||||
if (cacheInvalidate) {
|
||||
cacheInvalidate();
|
||||
}
|
||||
|
||||
sendSuccess(
|
||||
res,
|
||||
{
|
||||
[singularName]: created,
|
||||
message: `${singularName} created successfully`,
|
||||
},
|
||||
HTTP_STATUS.CREATED
|
||||
);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Update resource by ID
|
||||
* PUT /:resource/:id
|
||||
*/
|
||||
update: asyncHandler(async (req, res) => {
|
||||
// Check if resource exists
|
||||
const existing = await getById(table, req.params.id);
|
||||
if (!existing) {
|
||||
return sendNotFound(res, singularName);
|
||||
}
|
||||
|
||||
// Run beforeUpdate hook if provided
|
||||
let data = { ...req.body };
|
||||
if (beforeUpdate) {
|
||||
data = await beforeUpdate(data, req, existing);
|
||||
}
|
||||
|
||||
// Build update query dynamically
|
||||
const updates = [];
|
||||
const values = [];
|
||||
let paramIndex = 1;
|
||||
|
||||
Object.entries(data).forEach(([key, value]) => {
|
||||
if (value !== undefined) {
|
||||
updates.push(`${key} = $${paramIndex}`);
|
||||
values.push(value);
|
||||
paramIndex++;
|
||||
}
|
||||
});
|
||||
|
||||
if (updates.length === 0) {
|
||||
return sendSuccess(res, {
|
||||
[singularName]: existing,
|
||||
message: "No changes to update",
|
||||
});
|
||||
}
|
||||
|
||||
updates.push(`updatedat = NOW()`);
|
||||
values.push(req.params.id);
|
||||
|
||||
const result = await query(
|
||||
`UPDATE ${table} SET ${updates.join(
|
||||
", "
|
||||
)} WHERE id = $${paramIndex} RETURNING *`,
|
||||
values
|
||||
);
|
||||
|
||||
let updated = result.rows[0];
|
||||
|
||||
// Run afterUpdate hook if provided
|
||||
if (afterUpdate) {
|
||||
updated = await afterUpdate(updated, req, existing);
|
||||
}
|
||||
|
||||
// Invalidate cache if function provided
|
||||
if (cacheInvalidate) {
|
||||
cacheInvalidate();
|
||||
}
|
||||
|
||||
sendSuccess(res, {
|
||||
[singularName]: updated,
|
||||
message: `${singularName} updated successfully`,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* Delete resource by ID
|
||||
* DELETE /:resource/:id
|
||||
*/
|
||||
delete: asyncHandler(async (req, res) => {
|
||||
const deleted = await deleteById(table, req.params.id);
|
||||
if (!deleted) {
|
||||
return sendNotFound(res, singularName);
|
||||
}
|
||||
|
||||
// Invalidate cache if function provided
|
||||
if (cacheInvalidate) {
|
||||
cacheInvalidate();
|
||||
}
|
||||
|
||||
sendSuccess(res, {
|
||||
message: `${singularName} deleted successfully`,
|
||||
});
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Attach CRUD handlers to a router
|
||||
* @param {Router} router - Express router
|
||||
* @param {string} path - Base path for routes
|
||||
* @param {Object} handlers - CRUD handlers object
|
||||
* @param {Function} authMiddleware - Authentication middleware (default: requireAuth)
|
||||
*/
|
||||
const attachCRUDRoutes = (
|
||||
router,
|
||||
path,
|
||||
handlers,
|
||||
authMiddleware = requireAuth
|
||||
) => {
|
||||
router.get(`/${path}`, authMiddleware, handlers.list);
|
||||
router.get(`/${path}/:id`, authMiddleware, handlers.getById);
|
||||
router.post(`/${path}`, authMiddleware, handlers.create);
|
||||
router.put(`/${path}/:id`, authMiddleware, handlers.update);
|
||||
router.delete(`/${path}/:id`, authMiddleware, handlers.delete);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
createCRUDHandlers,
|
||||
attachCRUDRoutes,
|
||||
};
|
||||
Reference in New Issue
Block a user