webupdate

This commit is contained in:
Local Server
2026-01-18 02:22:05 -06:00
parent 6fc159051a
commit 2a2a3d99e5
135 changed files with 54897 additions and 9825 deletions

View 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,
};