Initial commit
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
PORT=5000
|
||||
NODE_ENV=development
|
||||
CORS_ORIGIN=http://localhost:3000
|
||||
JWT_SECRET=supersecretjwtkeythatislongandsecure
|
||||
JWT_EXPIRES_IN=7d
|
||||
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=5432
|
||||
DB_USER=postgres
|
||||
DB_PASS=postgres
|
||||
DB_NAME=maskan_pim
|
||||
DB_DIALECT=postgres
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
node_modules/
|
||||
.env
|
||||
.DS_Store
|
||||
dist/
|
||||
build/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.idea/
|
||||
.vscode/
|
||||
*.log
|
||||
@@ -0,0 +1,40 @@
|
||||
import express from 'express';
|
||||
import helmet from 'helmet';
|
||||
import cors from 'cors';
|
||||
import compression from 'compression';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import morgan from 'morgan';
|
||||
import swaggerUi from 'swagger-ui-express';
|
||||
import swaggerSpec from './src/shared/config/swagger.config.js';
|
||||
import { errorMiddleware } from './src/shared/middleware/error.middleware.js';
|
||||
import registerRoutes from './src/features/index.js';
|
||||
|
||||
const app = express();
|
||||
|
||||
// Middlewares
|
||||
app.use(helmet());
|
||||
app.use(cors({ origin: process.env.CORS_ORIGIN || '*' }));
|
||||
app.use(compression());
|
||||
app.use(cookieParser());
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
app.use(morgan('dev'));
|
||||
}
|
||||
|
||||
// Swagger setup
|
||||
app.use('/api/docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
|
||||
|
||||
// Base Health Check
|
||||
app.get('/health', (req, res) => {
|
||||
res.status(200).json({ status: 'OK', timestamp: new Date() });
|
||||
});
|
||||
|
||||
// Centralized Feature Router Loader
|
||||
registerRoutes(app);
|
||||
|
||||
// Global Error Handler
|
||||
app.use(errorMiddleware);
|
||||
|
||||
export default app;
|
||||
@@ -0,0 +1,37 @@
|
||||
import dotenv from 'dotenv';
|
||||
import http from 'http';
|
||||
import app from './app.js';
|
||||
import { connectDatabase } from './src/shared/database/connection.js';
|
||||
import { SocketService } from './src/shared/services/socket.service.js';
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config();
|
||||
|
||||
const PORT = process.env.PORT || 5000;
|
||||
|
||||
async function bootstrap() {
|
||||
try {
|
||||
// Connect database
|
||||
await connectDatabase();
|
||||
|
||||
// Create HTTP Server
|
||||
const server = http.createServer(app);
|
||||
|
||||
// Initialize Socket.io service
|
||||
SocketService.init(server);
|
||||
|
||||
server.listen(PORT, () => {
|
||||
console.log(`==================================================`);
|
||||
console.log(` Enterprise PIM Backend Server Started `);
|
||||
console.log(` Port: ${PORT} `);
|
||||
console.log(` Env: ${process.env.NODE_ENV || 'development'} `);
|
||||
console.log(` Docs: http://localhost:${PORT}/api/docs `);
|
||||
console.log(`==================================================`);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Bootstrap error occurred:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
bootstrap();
|
||||
Generated
+3624
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "maskan-pim-backend",
|
||||
"version": "1.0.0",
|
||||
"description": "Enterprise Product Information Management (PIM) Backend Service",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node index.js",
|
||||
"dev": "nodemon index.js",
|
||||
"db:migrate": "sequelize-cli db:migrate --config src/shared/config/database.config.cjs --migrations-path src/migrations --models-path src/shared/database",
|
||||
"db:seed": "sequelize-cli db:seed:all --config src/shared/config/database.config.cjs --seeders-path src/seeders --models-path src/shared/database",
|
||||
"db:rollback": "sequelize-cli db:migrate:undo --config src/shared/config/database.config.cjs --migrations-path src/migrations --models-path src/shared/database"
|
||||
},
|
||||
"dependencies": {
|
||||
"bcrypt": "^6.0.0",
|
||||
"compression": "^1.7.5",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.7",
|
||||
"express": "^4.21.2",
|
||||
"express-validator": "^7.1.0",
|
||||
"helmet": "^8.0.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"morgan": "^1.10.0",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"pg": "^8.13.1",
|
||||
"pg-hstore": "^2.3.4",
|
||||
"sequelize": "^6.37.5",
|
||||
"socket.io": "^4.8.1",
|
||||
"swagger-jsdoc": "^6.2.8",
|
||||
"swagger-ui-express": "^5.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.1.9",
|
||||
"sequelize-cli": "^6.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import service from './attribute.service.js';
|
||||
|
||||
export class AttributeController {
|
||||
async getAll(req, res, next) {
|
||||
try {
|
||||
const records = await service.getAll(req.query);
|
||||
return res.status(200).json({ success: true, data: records });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getById(req, res, next) {
|
||||
try {
|
||||
const record = await service.getById(req.params.id);
|
||||
return res.status(200).json({ success: true, data: record });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async create(req, res, next) {
|
||||
try {
|
||||
const record = await service.create(req.body, req.user);
|
||||
return res.status(201).json({ success: true, data: record });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async update(req, res, next) {
|
||||
try {
|
||||
const record = await service.update(req.params.id, req.body, req.user);
|
||||
return res.status(200).json({ success: true, data: record });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async delete(req, res, next) {
|
||||
try {
|
||||
await service.delete(req.params.id, req.user);
|
||||
return res.status(200).json({ success: true, message: 'Attribute deleted successfully' });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new AttributeController();
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
|
||||
export class Attribute extends Model {
|
||||
static associate(models) {
|
||||
// Define associations here
|
||||
}
|
||||
}
|
||||
|
||||
export default (sequelize) => {
|
||||
Attribute.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
allowNull: false
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: 'active'
|
||||
},
|
||||
metadata: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: true
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'Attribute',
|
||||
tableName: 'attributes',
|
||||
timestamps: true,
|
||||
underscored: true
|
||||
});
|
||||
|
||||
return Attribute;
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Attribute } from './attribute.model.js';
|
||||
|
||||
export class AttributeRepository {
|
||||
async findAll(options = {}) {
|
||||
return await Attribute.findAll(options);
|
||||
}
|
||||
|
||||
async findById(id, options = {}) {
|
||||
return await Attribute.findByPk(id, options);
|
||||
}
|
||||
|
||||
async create(data, options = {}) {
|
||||
return await Attribute.create(data, options);
|
||||
}
|
||||
|
||||
async update(id, data, options = {}) {
|
||||
const record = await this.findById(id, options);
|
||||
if (!record) return null;
|
||||
return await record.update(data, options);
|
||||
}
|
||||
|
||||
async delete(id, options = {}) {
|
||||
const record = await this.findById(id, options);
|
||||
if (!record) return false;
|
||||
await record.destroy(options);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default new AttributeRepository();
|
||||
@@ -0,0 +1,126 @@
|
||||
import { Router } from 'express';
|
||||
import controller from './attribute.controller.js';
|
||||
import { authenticate } from '../../../shared/middleware/auth.middleware.js';
|
||||
import { validate } from '../../../shared/middleware/validation.middleware.js';
|
||||
import { authorize } from '../../../shared/middleware/permission.middleware.js';
|
||||
import { audit } from '../../../shared/middleware/audit.middleware.js';
|
||||
import {
|
||||
createValidation,
|
||||
updateValidation,
|
||||
deleteValidation,
|
||||
getByIdValidation
|
||||
} from './attribute.validation.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/attributes:
|
||||
* get:
|
||||
* summary: Retrieve all attributes
|
||||
* tags: [Attributes]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.get(
|
||||
'/',
|
||||
authenticate,
|
||||
authorize(['read:attributes']),
|
||||
controller.getAll
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/attributes/{id}:
|
||||
* get:
|
||||
* summary: Retrieve a single attribute
|
||||
* tags: [Attributes]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.get(
|
||||
'/:id',
|
||||
authenticate,
|
||||
authorize(['read:attributes']),
|
||||
getByIdValidation,
|
||||
validate,
|
||||
controller.getById
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/attributes:
|
||||
* post:
|
||||
* summary: Create a attribute
|
||||
* tags: [Attributes]
|
||||
* responses:
|
||||
* 201:
|
||||
* description: Success
|
||||
*/
|
||||
router.post(
|
||||
'/',
|
||||
authenticate,
|
||||
authorize(['write:attributes']),
|
||||
createValidation,
|
||||
validate,
|
||||
audit('CREATE_ATTRIBUTE'),
|
||||
controller.create
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/attributes/{id}:
|
||||
* put:
|
||||
* summary: Update a attribute
|
||||
* tags: [Attributes]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.put(
|
||||
'/:id',
|
||||
authenticate,
|
||||
authorize(['write:attributes']),
|
||||
updateValidation,
|
||||
validate,
|
||||
audit('UPDATE_ATTRIBUTE'),
|
||||
controller.update
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/attributes/{id}:
|
||||
* delete:
|
||||
* summary: Delete a attribute
|
||||
* tags: [Attributes]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.delete(
|
||||
'/:id',
|
||||
authenticate,
|
||||
authorize(['write:attributes']),
|
||||
deleteValidation,
|
||||
validate,
|
||||
audit('DELETE_ATTRIBUTE'),
|
||||
controller.delete
|
||||
);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,75 @@
|
||||
import repository from './attribute.repository.js';
|
||||
import { SocketService } from '../../../shared/services/socket.service.js';
|
||||
import { AuditService } from '../../../shared/services/audit.service.js';
|
||||
|
||||
export class AttributeService {
|
||||
async getAll(query = {}) {
|
||||
// Add business logic filtering, pagination, etc.
|
||||
return await repository.findAll();
|
||||
}
|
||||
|
||||
async getById(id) {
|
||||
const record = await repository.findById(id);
|
||||
if (!record) {
|
||||
throw new Error('Attribute not found');
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
async create(data, userContext = {}) {
|
||||
const record = await repository.create(data);
|
||||
|
||||
// Broadcast event
|
||||
SocketService.broadcast('attribute:created', record);
|
||||
|
||||
// Log audit
|
||||
await AuditService.log({
|
||||
action: 'CREATE',
|
||||
resource: 'Attribute',
|
||||
resourceId: record.id,
|
||||
userId: userContext.id || 'system',
|
||||
details: data
|
||||
});
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
async update(id, data, userContext = {}) {
|
||||
const record = await repository.update(id, data);
|
||||
if (!record) {
|
||||
throw new Error('Attribute not found');
|
||||
}
|
||||
|
||||
SocketService.broadcast('attribute:updated', record);
|
||||
|
||||
await AuditService.log({
|
||||
action: 'UPDATE',
|
||||
resource: 'Attribute',
|
||||
resourceId: id,
|
||||
userId: userContext.id || 'system',
|
||||
details: data
|
||||
});
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
async delete(id, userContext = {}) {
|
||||
const deleted = await repository.delete(id);
|
||||
if (!deleted) {
|
||||
throw new Error('Attribute not found');
|
||||
}
|
||||
|
||||
SocketService.broadcast('attribute:deleted', { id });
|
||||
|
||||
await AuditService.log({
|
||||
action: 'DELETE',
|
||||
resource: 'Attribute',
|
||||
resourceId: id,
|
||||
userId: userContext.id || 'system'
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default new AttributeService();
|
||||
@@ -0,0 +1,32 @@
|
||||
import { body, param } from 'express-validator';
|
||||
|
||||
export const createValidation = [
|
||||
body('name')
|
||||
.optional()
|
||||
.isString()
|
||||
.trim()
|
||||
.withMessage('Name must be a string')
|
||||
];
|
||||
|
||||
export const updateValidation = [
|
||||
param('id')
|
||||
.isUUID()
|
||||
.withMessage('Valid UUID is required'),
|
||||
body('name')
|
||||
.optional()
|
||||
.isString()
|
||||
.trim()
|
||||
.withMessage('Name must be a string')
|
||||
];
|
||||
|
||||
export const deleteValidation = [
|
||||
param('id')
|
||||
.isUUID()
|
||||
.withMessage('Valid UUID is required')
|
||||
];
|
||||
|
||||
export const getByIdValidation = [
|
||||
param('id')
|
||||
.isUUID()
|
||||
.withMessage('Valid UUID is required')
|
||||
];
|
||||
@@ -0,0 +1,5 @@
|
||||
export { default as attributeRoutes } from './attribute.routes.js';
|
||||
export { default as attributeController } from './attribute.controller.js';
|
||||
export { default as attributeService } from './attribute.service.js';
|
||||
export { default as attributeRepository } from './attribute.repository.js';
|
||||
export { default as attributeModel } from './attribute.model.js';
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Router } from 'express';
|
||||
import attributesRouter from './attributes/attribute.routes.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.use('/attributes', attributesRouter);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,50 @@
|
||||
import service from './auditLog.service.js';
|
||||
|
||||
export class AuditLogController {
|
||||
async getAll(req, res, next) {
|
||||
try {
|
||||
const records = await service.getAll(req.query);
|
||||
return res.status(200).json({ success: true, data: records });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getById(req, res, next) {
|
||||
try {
|
||||
const record = await service.getById(req.params.id);
|
||||
return res.status(200).json({ success: true, data: record });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async create(req, res, next) {
|
||||
try {
|
||||
const record = await service.create(req.body, req.user);
|
||||
return res.status(201).json({ success: true, data: record });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async update(req, res, next) {
|
||||
try {
|
||||
const record = await service.update(req.params.id, req.body, req.user);
|
||||
return res.status(200).json({ success: true, data: record });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async delete(req, res, next) {
|
||||
try {
|
||||
await service.delete(req.params.id, req.user);
|
||||
return res.status(200).json({ success: true, message: 'AuditLog deleted successfully' });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new AuditLogController();
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
|
||||
export class AuditLog extends Model {
|
||||
static associate(models) {
|
||||
// Define associations here
|
||||
}
|
||||
}
|
||||
|
||||
export default (sequelize) => {
|
||||
AuditLog.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
allowNull: false
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: 'active'
|
||||
},
|
||||
metadata: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: true
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'AuditLog',
|
||||
tableName: 'auditlogs',
|
||||
timestamps: true,
|
||||
underscored: true
|
||||
});
|
||||
|
||||
return AuditLog;
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import { AuditLog } from './auditLog.model.js';
|
||||
|
||||
export class AuditLogRepository {
|
||||
async findAll(options = {}) {
|
||||
return await AuditLog.findAll(options);
|
||||
}
|
||||
|
||||
async findById(id, options = {}) {
|
||||
return await AuditLog.findByPk(id, options);
|
||||
}
|
||||
|
||||
async create(data, options = {}) {
|
||||
return await AuditLog.create(data, options);
|
||||
}
|
||||
|
||||
async update(id, data, options = {}) {
|
||||
const record = await this.findById(id, options);
|
||||
if (!record) return null;
|
||||
return await record.update(data, options);
|
||||
}
|
||||
|
||||
async delete(id, options = {}) {
|
||||
const record = await this.findById(id, options);
|
||||
if (!record) return false;
|
||||
await record.destroy(options);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default new AuditLogRepository();
|
||||
@@ -0,0 +1,126 @@
|
||||
import { Router } from 'express';
|
||||
import controller from './auditLog.controller.js';
|
||||
import { authenticate } from '../../../shared/middleware/auth.middleware.js';
|
||||
import { validate } from '../../../shared/middleware/validation.middleware.js';
|
||||
import { authorize } from '../../../shared/middleware/permission.middleware.js';
|
||||
import { audit } from '../../../shared/middleware/audit.middleware.js';
|
||||
import {
|
||||
createValidation,
|
||||
updateValidation,
|
||||
deleteValidation,
|
||||
getByIdValidation
|
||||
} from './auditLog.validation.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/auditLogs:
|
||||
* get:
|
||||
* summary: Retrieve all auditLogs
|
||||
* tags: [AuditLogs]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.get(
|
||||
'/',
|
||||
authenticate,
|
||||
authorize(['read:auditLogs']),
|
||||
controller.getAll
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/auditLogs/{id}:
|
||||
* get:
|
||||
* summary: Retrieve a single auditLog
|
||||
* tags: [AuditLogs]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.get(
|
||||
'/:id',
|
||||
authenticate,
|
||||
authorize(['read:auditLogs']),
|
||||
getByIdValidation,
|
||||
validate,
|
||||
controller.getById
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/auditLogs:
|
||||
* post:
|
||||
* summary: Create a auditLog
|
||||
* tags: [AuditLogs]
|
||||
* responses:
|
||||
* 201:
|
||||
* description: Success
|
||||
*/
|
||||
router.post(
|
||||
'/',
|
||||
authenticate,
|
||||
authorize(['write:auditLogs']),
|
||||
createValidation,
|
||||
validate,
|
||||
audit('CREATE_AUDITLOG'),
|
||||
controller.create
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/auditLogs/{id}:
|
||||
* put:
|
||||
* summary: Update a auditLog
|
||||
* tags: [AuditLogs]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.put(
|
||||
'/:id',
|
||||
authenticate,
|
||||
authorize(['write:auditLogs']),
|
||||
updateValidation,
|
||||
validate,
|
||||
audit('UPDATE_AUDITLOG'),
|
||||
controller.update
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/auditLogs/{id}:
|
||||
* delete:
|
||||
* summary: Delete a auditLog
|
||||
* tags: [AuditLogs]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.delete(
|
||||
'/:id',
|
||||
authenticate,
|
||||
authorize(['write:auditLogs']),
|
||||
deleteValidation,
|
||||
validate,
|
||||
audit('DELETE_AUDITLOG'),
|
||||
controller.delete
|
||||
);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,75 @@
|
||||
import repository from './auditLog.repository.js';
|
||||
import { SocketService } from '../../../shared/services/socket.service.js';
|
||||
import { AuditService } from '../../../shared/services/audit.service.js';
|
||||
|
||||
export class AuditLogService {
|
||||
async getAll(query = {}) {
|
||||
// Add business logic filtering, pagination, etc.
|
||||
return await repository.findAll();
|
||||
}
|
||||
|
||||
async getById(id) {
|
||||
const record = await repository.findById(id);
|
||||
if (!record) {
|
||||
throw new Error('AuditLog not found');
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
async create(data, userContext = {}) {
|
||||
const record = await repository.create(data);
|
||||
|
||||
// Broadcast event
|
||||
SocketService.broadcast('auditLog:created', record);
|
||||
|
||||
// Log audit
|
||||
await AuditService.log({
|
||||
action: 'CREATE',
|
||||
resource: 'AuditLog',
|
||||
resourceId: record.id,
|
||||
userId: userContext.id || 'system',
|
||||
details: data
|
||||
});
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
async update(id, data, userContext = {}) {
|
||||
const record = await repository.update(id, data);
|
||||
if (!record) {
|
||||
throw new Error('AuditLog not found');
|
||||
}
|
||||
|
||||
SocketService.broadcast('auditLog:updated', record);
|
||||
|
||||
await AuditService.log({
|
||||
action: 'UPDATE',
|
||||
resource: 'AuditLog',
|
||||
resourceId: id,
|
||||
userId: userContext.id || 'system',
|
||||
details: data
|
||||
});
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
async delete(id, userContext = {}) {
|
||||
const deleted = await repository.delete(id);
|
||||
if (!deleted) {
|
||||
throw new Error('AuditLog not found');
|
||||
}
|
||||
|
||||
SocketService.broadcast('auditLog:deleted', { id });
|
||||
|
||||
await AuditService.log({
|
||||
action: 'DELETE',
|
||||
resource: 'AuditLog',
|
||||
resourceId: id,
|
||||
userId: userContext.id || 'system'
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default new AuditLogService();
|
||||
@@ -0,0 +1,32 @@
|
||||
import { body, param } from 'express-validator';
|
||||
|
||||
export const createValidation = [
|
||||
body('name')
|
||||
.optional()
|
||||
.isString()
|
||||
.trim()
|
||||
.withMessage('Name must be a string')
|
||||
];
|
||||
|
||||
export const updateValidation = [
|
||||
param('id')
|
||||
.isUUID()
|
||||
.withMessage('Valid UUID is required'),
|
||||
body('name')
|
||||
.optional()
|
||||
.isString()
|
||||
.trim()
|
||||
.withMessage('Name must be a string')
|
||||
];
|
||||
|
||||
export const deleteValidation = [
|
||||
param('id')
|
||||
.isUUID()
|
||||
.withMessage('Valid UUID is required')
|
||||
];
|
||||
|
||||
export const getByIdValidation = [
|
||||
param('id')
|
||||
.isUUID()
|
||||
.withMessage('Valid UUID is required')
|
||||
];
|
||||
@@ -0,0 +1,5 @@
|
||||
export { default as auditLogRoutes } from './auditLog.routes.js';
|
||||
export { default as auditLogController } from './auditLog.controller.js';
|
||||
export { default as auditLogService } from './auditLog.service.js';
|
||||
export { default as auditLogRepository } from './auditLog.repository.js';
|
||||
export { default as auditLogModel } from './auditLog.model.js';
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Router } from 'express';
|
||||
import auditLogsRouter from './auditLogs/auditLog.routes.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.use('/auditlogs', auditLogsRouter);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,79 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
|
||||
export class PermissionNode extends Model {
|
||||
static associate(models) {}
|
||||
}
|
||||
|
||||
export default (sequelize) => {
|
||||
PermissionNode.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
node_code: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true
|
||||
},
|
||||
node_name: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false
|
||||
},
|
||||
node_type: {
|
||||
type: DataTypes.STRING(20)
|
||||
},
|
||||
parent_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true
|
||||
},
|
||||
node_level: {
|
||||
type: DataTypes.INTEGER,
|
||||
defaultValue: 1
|
||||
},
|
||||
display_order: {
|
||||
type: DataTypes.INTEGER,
|
||||
defaultValue: 0
|
||||
},
|
||||
can_view: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false
|
||||
},
|
||||
can_create: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false
|
||||
},
|
||||
can_edit: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false
|
||||
},
|
||||
can_delete: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false
|
||||
},
|
||||
can_alter: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false
|
||||
},
|
||||
can_export: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false
|
||||
},
|
||||
can_import: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'PermissionNode',
|
||||
tableName: 'permission_nodes',
|
||||
timestamps: true,
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at'
|
||||
});
|
||||
|
||||
return PermissionNode;
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
|
||||
export class Role extends Model {
|
||||
static associate(models) {}
|
||||
}
|
||||
|
||||
export default (sequelize) => {
|
||||
Role.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
tenant_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true
|
||||
},
|
||||
role_code: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false
|
||||
},
|
||||
role_name: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT
|
||||
},
|
||||
role_type: {
|
||||
type: DataTypes.STRING(20),
|
||||
defaultValue: 'tenant',
|
||||
allowNull: false
|
||||
},
|
||||
is_system_role: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: true
|
||||
},
|
||||
created_by: {
|
||||
type: DataTypes.INTEGER
|
||||
},
|
||||
updated_by: {
|
||||
type: DataTypes.INTEGER
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'Role',
|
||||
tableName: 'roles',
|
||||
timestamps: true,
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at'
|
||||
});
|
||||
|
||||
return Role;
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
|
||||
export class RolePermission extends Model {
|
||||
static associate(models) {}
|
||||
}
|
||||
|
||||
export default (sequelize) => {
|
||||
RolePermission.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
role_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false
|
||||
},
|
||||
node_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false
|
||||
},
|
||||
can_view: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false
|
||||
},
|
||||
can_create: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false
|
||||
},
|
||||
can_edit: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false
|
||||
},
|
||||
can_delete: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false
|
||||
},
|
||||
can_alter: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false
|
||||
},
|
||||
can_export: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false
|
||||
},
|
||||
can_import: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'RolePermission',
|
||||
tableName: 'role_permissions',
|
||||
timestamps: true,
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at'
|
||||
});
|
||||
|
||||
return RolePermission;
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
|
||||
export class UserRole extends Model {
|
||||
static associate(models) {}
|
||||
}
|
||||
|
||||
export default (sequelize) => {
|
||||
UserRole.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
user_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false
|
||||
},
|
||||
role_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: true
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'UserRole',
|
||||
tableName: 'user_roles',
|
||||
timestamps: true,
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at'
|
||||
});
|
||||
|
||||
return UserRole;
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
import authService from './auth.service.js';
|
||||
|
||||
export class AuthController {
|
||||
async login(req, res, next) {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
const result = await authService.login({ email, password });
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: 'Login successful',
|
||||
data: result
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async refreshToken(req, res, next) {
|
||||
try {
|
||||
const { refreshToken } = req.body;
|
||||
const result = await authService.refreshToken(refreshToken);
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: 'Tokens refreshed successfully',
|
||||
data: result
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new AuthController();
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
|
||||
export class Auth extends Model {
|
||||
static associate(models) {
|
||||
// Define associations here
|
||||
}
|
||||
}
|
||||
|
||||
export default (sequelize) => {
|
||||
Auth.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
allowNull: false
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: 'active'
|
||||
},
|
||||
metadata: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: true
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'Auth',
|
||||
tableName: 'auths',
|
||||
timestamps: true,
|
||||
underscored: true
|
||||
});
|
||||
|
||||
return Auth;
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Auth } from './auth.model.js';
|
||||
|
||||
export class AuthRepository {
|
||||
async findAll(options = {}) {
|
||||
return await Auth.findAll(options);
|
||||
}
|
||||
|
||||
async findById(id, options = {}) {
|
||||
return await Auth.findByPk(id, options);
|
||||
}
|
||||
|
||||
async create(data, options = {}) {
|
||||
return await Auth.create(data, options);
|
||||
}
|
||||
|
||||
async update(id, data, options = {}) {
|
||||
const record = await this.findById(id, options);
|
||||
if (!record) return null;
|
||||
return await record.update(data, options);
|
||||
}
|
||||
|
||||
async delete(id, options = {}) {
|
||||
const record = await this.findById(id, options);
|
||||
if (!record) return false;
|
||||
await record.destroy(options);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default new AuthRepository();
|
||||
@@ -0,0 +1,67 @@
|
||||
import { Router } from 'express';
|
||||
import controller from './auth.controller.js';
|
||||
import { loginValidation, refreshTokenValidation } from './auth.validation.js';
|
||||
import { validate } from '../../../shared/middleware/validation.middleware.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/auth/login:
|
||||
* post:
|
||||
* summary: Login user
|
||||
* tags: [Authentication]
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - email
|
||||
* - password
|
||||
* properties:
|
||||
* email:
|
||||
* type: string
|
||||
* password:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.post(
|
||||
'/login',
|
||||
loginValidation,
|
||||
validate,
|
||||
controller.login
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/auth/refresh-token:
|
||||
* post:
|
||||
* summary: Refresh JWT token
|
||||
* tags: [Authentication]
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - refreshToken
|
||||
* properties:
|
||||
* refreshToken:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.post(
|
||||
'/refresh-token',
|
||||
refreshTokenValidation,
|
||||
validate,
|
||||
controller.refreshToken
|
||||
);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,134 @@
|
||||
import { models } from '../../../shared/database/models.js';
|
||||
const { User, Role, PermissionNode, Tenant } = models;
|
||||
import { generateToken, generateRefreshToken, verifyRefreshToken } from '../../../utils/helpers/jwt.utils.js';
|
||||
import { ApiError } from '../../../utils/helpers/ApiError.utils.js';
|
||||
|
||||
const formatPermissions = (roles) => {
|
||||
const permissionMap = {};
|
||||
|
||||
roles.forEach(role => {
|
||||
if (!role.permissions) return;
|
||||
role.permissions.forEach(p => {
|
||||
const code = p.node_code;
|
||||
if (!permissionMap[code]) {
|
||||
permissionMap[code] = {
|
||||
view: false, create: false, edit: false, delete: false, alter: false, import: false, export: false
|
||||
};
|
||||
}
|
||||
permissionMap[code].view = permissionMap[code].view || p.RolePermission.can_view;
|
||||
permissionMap[code].create = permissionMap[code].create || p.RolePermission.can_create;
|
||||
permissionMap[code].edit = permissionMap[code].edit || p.RolePermission.can_edit;
|
||||
permissionMap[code].delete = permissionMap[code].delete || p.RolePermission.can_delete;
|
||||
permissionMap[code].alter = permissionMap[code].alter || p.RolePermission.can_alter;
|
||||
permissionMap[code].import = permissionMap[code].import || p.RolePermission.can_import;
|
||||
permissionMap[code].export = permissionMap[code].export || p.RolePermission.can_export;
|
||||
});
|
||||
});
|
||||
|
||||
return permissionMap;
|
||||
};
|
||||
|
||||
export const login = async ({ email, password }) => {
|
||||
const user = await User.findOne({
|
||||
where: { email },
|
||||
include: [
|
||||
{
|
||||
model: Role,
|
||||
as: 'roles',
|
||||
include: [
|
||||
{
|
||||
model: PermissionNode,
|
||||
as: 'permissions',
|
||||
through: { attributes: ['can_view', 'can_create', 'can_edit', 'can_delete', 'can_alter', 'can_import', 'can_export'] }
|
||||
}
|
||||
],
|
||||
through: { attributes: ['status'] }
|
||||
},
|
||||
{
|
||||
model: Tenant,
|
||||
as: 'tenant'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new ApiError(401, 'Invalid email or password');
|
||||
}
|
||||
|
||||
const isMatch = await user.validatePassword(password);
|
||||
if (!isMatch) {
|
||||
throw new ApiError(401, 'Invalid email or password');
|
||||
}
|
||||
|
||||
const roleIds = user.roles.map(r => r.id);
|
||||
|
||||
const payload = {
|
||||
user_id: user.id,
|
||||
tenant_id: user.tenant_id,
|
||||
user_type: user.user_type,
|
||||
role_ids: roleIds
|
||||
};
|
||||
|
||||
const accessToken = generateToken(payload);
|
||||
const refreshToken = generateRefreshToken(payload);
|
||||
|
||||
user.last_login_at = new Date();
|
||||
await user.save();
|
||||
|
||||
const permissions = formatPermissions(user.roles);
|
||||
|
||||
return {
|
||||
user: {
|
||||
id: user.id,
|
||||
name: user.user_name,
|
||||
email: user.email,
|
||||
type: user.user_type,
|
||||
tenant: user.tenant ? { id: user.tenant.id, name: user.tenant.tenant_name } : null,
|
||||
roles: user.roles.filter(r => r.UserRole && r.UserRole.status === true).map(r => ({ id: r.id, name: r.role_name, code: r.role_code }))
|
||||
},
|
||||
accessToken,
|
||||
refreshToken,
|
||||
permissions
|
||||
};
|
||||
};
|
||||
|
||||
export const refreshTokenAuth = async (oldRefreshToken) => {
|
||||
if (!oldRefreshToken) {
|
||||
throw new ApiError(401, 'Refresh token is required');
|
||||
}
|
||||
|
||||
const decoded = verifyRefreshToken(oldRefreshToken);
|
||||
|
||||
const user = await User.findByPk(decoded.user_id, {
|
||||
include: [
|
||||
{
|
||||
model: Role,
|
||||
as: 'roles',
|
||||
through: { attributes: ['status'] }
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new ApiError(401, 'Invalid refresh token');
|
||||
}
|
||||
|
||||
const roleIds = user.roles.filter(r => r.UserRole.status).map(r => r.id);
|
||||
|
||||
const payload = {
|
||||
user_id: user.id,
|
||||
tenant_id: user.tenant_id,
|
||||
user_type: user.user_type,
|
||||
role_ids: roleIds
|
||||
};
|
||||
|
||||
const accessToken = generateToken(payload);
|
||||
const refreshToken = generateRefreshToken(payload);
|
||||
|
||||
return { accessToken, refreshToken };
|
||||
};
|
||||
|
||||
export default {
|
||||
login,
|
||||
refreshToken: refreshTokenAuth
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
import { body } from 'express-validator';
|
||||
|
||||
export const loginValidation = [
|
||||
body('email').isEmail().withMessage('Valid email is required'),
|
||||
body('password').notEmpty().withMessage('Password is required')
|
||||
];
|
||||
|
||||
export const refreshTokenValidation = [
|
||||
body('refreshToken').notEmpty().withMessage('Refresh token is required')
|
||||
];
|
||||
@@ -0,0 +1,5 @@
|
||||
export { default as authRoutes } from './auth.routes.js';
|
||||
export { default as authController } from './auth.controller.js';
|
||||
export { default as authService } from './auth.service.js';
|
||||
export { default as authRepository } from './auth.repository.js';
|
||||
export { default as authModel } from './auth.model.js';
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Router } from 'express';
|
||||
import authRouter from './auth/auth.routes.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.use('/auth', authRouter);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,88 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import bcrypt from 'bcrypt';
|
||||
|
||||
export class User extends Model {
|
||||
static associate(models) {}
|
||||
async validatePassword(password) {
|
||||
return await bcrypt.compare(password, this.password_hash);
|
||||
}
|
||||
}
|
||||
|
||||
export default (sequelize) => {
|
||||
User.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
tenant_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true
|
||||
},
|
||||
email: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
validate: {
|
||||
isEmail: true
|
||||
}
|
||||
},
|
||||
phone: {
|
||||
type: DataTypes.STRING(20)
|
||||
},
|
||||
password_hash: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: false
|
||||
},
|
||||
user_code: {
|
||||
type: DataTypes.STRING(50)
|
||||
},
|
||||
user_name: {
|
||||
type: DataTypes.STRING(100)
|
||||
},
|
||||
profile_picture: {
|
||||
type: DataTypes.STRING(255)
|
||||
},
|
||||
last_login_at: {
|
||||
type: DataTypes.DATE
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: true
|
||||
},
|
||||
created_by: {
|
||||
type: DataTypes.INTEGER
|
||||
},
|
||||
updated_by: {
|
||||
type: DataTypes.INTEGER
|
||||
},
|
||||
user_type: {
|
||||
type: DataTypes.STRING,
|
||||
get() {
|
||||
return this.tenant_id ? 'tenant' : 'platform';
|
||||
}
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'User',
|
||||
tableName: 'users',
|
||||
timestamps: true,
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at',
|
||||
hooks: {
|
||||
beforeCreate: async (user) => {
|
||||
if (user.password_hash) {
|
||||
const salt = await bcrypt.genSalt(10);
|
||||
user.password_hash = await bcrypt.hash(user.password_hash, salt);
|
||||
}
|
||||
},
|
||||
beforeUpdate: async (user) => {
|
||||
if (user.changed('password_hash')) {
|
||||
const salt = await bcrypt.genSalt(10);
|
||||
user.password_hash = await bcrypt.hash(user.password_hash, salt);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return User;
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
import service from './brand.service.js';
|
||||
|
||||
export class BrandController {
|
||||
async getAll(req, res, next) {
|
||||
try {
|
||||
const records = await service.getAll(req.query);
|
||||
return res.status(200).json({ success: true, data: records });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getById(req, res, next) {
|
||||
try {
|
||||
const record = await service.getById(req.params.id);
|
||||
return res.status(200).json({ success: true, data: record });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async create(req, res, next) {
|
||||
try {
|
||||
const record = await service.create(req.body, req.user);
|
||||
return res.status(201).json({ success: true, data: record });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async update(req, res, next) {
|
||||
try {
|
||||
const record = await service.update(req.params.id, req.body, req.user);
|
||||
return res.status(200).json({ success: true, data: record });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async delete(req, res, next) {
|
||||
try {
|
||||
await service.delete(req.params.id, req.user);
|
||||
return res.status(200).json({ success: true, message: 'Brand deleted successfully' });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new BrandController();
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
|
||||
export class Brand extends Model {
|
||||
static associate(models) {
|
||||
// Define associations here
|
||||
}
|
||||
}
|
||||
|
||||
export default (sequelize) => {
|
||||
Brand.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
allowNull: false
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: 'active'
|
||||
},
|
||||
metadata: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: true
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'Brand',
|
||||
tableName: 'brands',
|
||||
timestamps: true,
|
||||
underscored: true
|
||||
});
|
||||
|
||||
return Brand;
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Brand } from './brand.model.js';
|
||||
|
||||
export class BrandRepository {
|
||||
async findAll(options = {}) {
|
||||
return await Brand.findAll(options);
|
||||
}
|
||||
|
||||
async findById(id, options = {}) {
|
||||
return await Brand.findByPk(id, options);
|
||||
}
|
||||
|
||||
async create(data, options = {}) {
|
||||
return await Brand.create(data, options);
|
||||
}
|
||||
|
||||
async update(id, data, options = {}) {
|
||||
const record = await this.findById(id, options);
|
||||
if (!record) return null;
|
||||
return await record.update(data, options);
|
||||
}
|
||||
|
||||
async delete(id, options = {}) {
|
||||
const record = await this.findById(id, options);
|
||||
if (!record) return false;
|
||||
await record.destroy(options);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default new BrandRepository();
|
||||
@@ -0,0 +1,126 @@
|
||||
import { Router } from 'express';
|
||||
import controller from './brand.controller.js';
|
||||
import { authenticate } from '../../../shared/middleware/auth.middleware.js';
|
||||
import { validate } from '../../../shared/middleware/validation.middleware.js';
|
||||
import { authorize } from '../../../shared/middleware/permission.middleware.js';
|
||||
import { audit } from '../../../shared/middleware/audit.middleware.js';
|
||||
import {
|
||||
createValidation,
|
||||
updateValidation,
|
||||
deleteValidation,
|
||||
getByIdValidation
|
||||
} from './brand.validation.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/brands:
|
||||
* get:
|
||||
* summary: Retrieve all brands
|
||||
* tags: [Brands]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.get(
|
||||
'/',
|
||||
authenticate,
|
||||
authorize(['read:brands']),
|
||||
controller.getAll
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/brands/{id}:
|
||||
* get:
|
||||
* summary: Retrieve a single brand
|
||||
* tags: [Brands]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.get(
|
||||
'/:id',
|
||||
authenticate,
|
||||
authorize(['read:brands']),
|
||||
getByIdValidation,
|
||||
validate,
|
||||
controller.getById
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/brands:
|
||||
* post:
|
||||
* summary: Create a brand
|
||||
* tags: [Brands]
|
||||
* responses:
|
||||
* 201:
|
||||
* description: Success
|
||||
*/
|
||||
router.post(
|
||||
'/',
|
||||
authenticate,
|
||||
authorize(['write:brands']),
|
||||
createValidation,
|
||||
validate,
|
||||
audit('CREATE_BRAND'),
|
||||
controller.create
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/brands/{id}:
|
||||
* put:
|
||||
* summary: Update a brand
|
||||
* tags: [Brands]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.put(
|
||||
'/:id',
|
||||
authenticate,
|
||||
authorize(['write:brands']),
|
||||
updateValidation,
|
||||
validate,
|
||||
audit('UPDATE_BRAND'),
|
||||
controller.update
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/brands/{id}:
|
||||
* delete:
|
||||
* summary: Delete a brand
|
||||
* tags: [Brands]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.delete(
|
||||
'/:id',
|
||||
authenticate,
|
||||
authorize(['write:brands']),
|
||||
deleteValidation,
|
||||
validate,
|
||||
audit('DELETE_BRAND'),
|
||||
controller.delete
|
||||
);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,75 @@
|
||||
import repository from './brand.repository.js';
|
||||
import { SocketService } from '../../../shared/services/socket.service.js';
|
||||
import { AuditService } from '../../../shared/services/audit.service.js';
|
||||
|
||||
export class BrandService {
|
||||
async getAll(query = {}) {
|
||||
// Add business logic filtering, pagination, etc.
|
||||
return await repository.findAll();
|
||||
}
|
||||
|
||||
async getById(id) {
|
||||
const record = await repository.findById(id);
|
||||
if (!record) {
|
||||
throw new Error('Brand not found');
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
async create(data, userContext = {}) {
|
||||
const record = await repository.create(data);
|
||||
|
||||
// Broadcast event
|
||||
SocketService.broadcast('brand:created', record);
|
||||
|
||||
// Log audit
|
||||
await AuditService.log({
|
||||
action: 'CREATE',
|
||||
resource: 'Brand',
|
||||
resourceId: record.id,
|
||||
userId: userContext.id || 'system',
|
||||
details: data
|
||||
});
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
async update(id, data, userContext = {}) {
|
||||
const record = await repository.update(id, data);
|
||||
if (!record) {
|
||||
throw new Error('Brand not found');
|
||||
}
|
||||
|
||||
SocketService.broadcast('brand:updated', record);
|
||||
|
||||
await AuditService.log({
|
||||
action: 'UPDATE',
|
||||
resource: 'Brand',
|
||||
resourceId: id,
|
||||
userId: userContext.id || 'system',
|
||||
details: data
|
||||
});
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
async delete(id, userContext = {}) {
|
||||
const deleted = await repository.delete(id);
|
||||
if (!deleted) {
|
||||
throw new Error('Brand not found');
|
||||
}
|
||||
|
||||
SocketService.broadcast('brand:deleted', { id });
|
||||
|
||||
await AuditService.log({
|
||||
action: 'DELETE',
|
||||
resource: 'Brand',
|
||||
resourceId: id,
|
||||
userId: userContext.id || 'system'
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default new BrandService();
|
||||
@@ -0,0 +1,32 @@
|
||||
import { body, param } from 'express-validator';
|
||||
|
||||
export const createValidation = [
|
||||
body('name')
|
||||
.optional()
|
||||
.isString()
|
||||
.trim()
|
||||
.withMessage('Name must be a string')
|
||||
];
|
||||
|
||||
export const updateValidation = [
|
||||
param('id')
|
||||
.isUUID()
|
||||
.withMessage('Valid UUID is required'),
|
||||
body('name')
|
||||
.optional()
|
||||
.isString()
|
||||
.trim()
|
||||
.withMessage('Name must be a string')
|
||||
];
|
||||
|
||||
export const deleteValidation = [
|
||||
param('id')
|
||||
.isUUID()
|
||||
.withMessage('Valid UUID is required')
|
||||
];
|
||||
|
||||
export const getByIdValidation = [
|
||||
param('id')
|
||||
.isUUID()
|
||||
.withMessage('Valid UUID is required')
|
||||
];
|
||||
@@ -0,0 +1,5 @@
|
||||
export { default as brandRoutes } from './brand.routes.js';
|
||||
export { default as brandController } from './brand.controller.js';
|
||||
export { default as brandService } from './brand.service.js';
|
||||
export { default as brandRepository } from './brand.repository.js';
|
||||
export { default as brandModel } from './brand.model.js';
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Router } from 'express';
|
||||
import brandsRouter from './brands/brand.routes.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.use('/brands', brandsRouter);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,50 @@
|
||||
import service from './catalog.service.js';
|
||||
|
||||
export class CatalogController {
|
||||
async getAll(req, res, next) {
|
||||
try {
|
||||
const records = await service.getAll(req.query);
|
||||
return res.status(200).json({ success: true, data: records });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getById(req, res, next) {
|
||||
try {
|
||||
const record = await service.getById(req.params.id);
|
||||
return res.status(200).json({ success: true, data: record });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async create(req, res, next) {
|
||||
try {
|
||||
const record = await service.create(req.body, req.user);
|
||||
return res.status(201).json({ success: true, data: record });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async update(req, res, next) {
|
||||
try {
|
||||
const record = await service.update(req.params.id, req.body, req.user);
|
||||
return res.status(200).json({ success: true, data: record });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async delete(req, res, next) {
|
||||
try {
|
||||
await service.delete(req.params.id, req.user);
|
||||
return res.status(200).json({ success: true, message: 'Catalog deleted successfully' });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new CatalogController();
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
|
||||
export class Catalog extends Model {
|
||||
static associate(models) {
|
||||
// Define associations here
|
||||
}
|
||||
}
|
||||
|
||||
export default (sequelize) => {
|
||||
Catalog.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
allowNull: false
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: 'active'
|
||||
},
|
||||
metadata: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: true
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'Catalog',
|
||||
tableName: 'catalogs',
|
||||
timestamps: true,
|
||||
underscored: true
|
||||
});
|
||||
|
||||
return Catalog;
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Catalog } from './catalog.model.js';
|
||||
|
||||
export class CatalogRepository {
|
||||
async findAll(options = {}) {
|
||||
return await Catalog.findAll(options);
|
||||
}
|
||||
|
||||
async findById(id, options = {}) {
|
||||
return await Catalog.findByPk(id, options);
|
||||
}
|
||||
|
||||
async create(data, options = {}) {
|
||||
return await Catalog.create(data, options);
|
||||
}
|
||||
|
||||
async update(id, data, options = {}) {
|
||||
const record = await this.findById(id, options);
|
||||
if (!record) return null;
|
||||
return await record.update(data, options);
|
||||
}
|
||||
|
||||
async delete(id, options = {}) {
|
||||
const record = await this.findById(id, options);
|
||||
if (!record) return false;
|
||||
await record.destroy(options);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default new CatalogRepository();
|
||||
@@ -0,0 +1,126 @@
|
||||
import { Router } from 'express';
|
||||
import controller from './catalog.controller.js';
|
||||
import { authenticate } from '../../../shared/middleware/auth.middleware.js';
|
||||
import { validate } from '../../../shared/middleware/validation.middleware.js';
|
||||
import { authorize } from '../../../shared/middleware/permission.middleware.js';
|
||||
import { audit } from '../../../shared/middleware/audit.middleware.js';
|
||||
import {
|
||||
createValidation,
|
||||
updateValidation,
|
||||
deleteValidation,
|
||||
getByIdValidation
|
||||
} from './catalog.validation.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/catalogs:
|
||||
* get:
|
||||
* summary: Retrieve all catalogs
|
||||
* tags: [Catalogs]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.get(
|
||||
'/',
|
||||
authenticate,
|
||||
authorize(['read:catalogs']),
|
||||
controller.getAll
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/catalogs/{id}:
|
||||
* get:
|
||||
* summary: Retrieve a single catalog
|
||||
* tags: [Catalogs]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.get(
|
||||
'/:id',
|
||||
authenticate,
|
||||
authorize(['read:catalogs']),
|
||||
getByIdValidation,
|
||||
validate,
|
||||
controller.getById
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/catalogs:
|
||||
* post:
|
||||
* summary: Create a catalog
|
||||
* tags: [Catalogs]
|
||||
* responses:
|
||||
* 201:
|
||||
* description: Success
|
||||
*/
|
||||
router.post(
|
||||
'/',
|
||||
authenticate,
|
||||
authorize(['write:catalogs']),
|
||||
createValidation,
|
||||
validate,
|
||||
audit('CREATE_CATALOG'),
|
||||
controller.create
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/catalogs/{id}:
|
||||
* put:
|
||||
* summary: Update a catalog
|
||||
* tags: [Catalogs]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.put(
|
||||
'/:id',
|
||||
authenticate,
|
||||
authorize(['write:catalogs']),
|
||||
updateValidation,
|
||||
validate,
|
||||
audit('UPDATE_CATALOG'),
|
||||
controller.update
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/catalogs/{id}:
|
||||
* delete:
|
||||
* summary: Delete a catalog
|
||||
* tags: [Catalogs]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.delete(
|
||||
'/:id',
|
||||
authenticate,
|
||||
authorize(['write:catalogs']),
|
||||
deleteValidation,
|
||||
validate,
|
||||
audit('DELETE_CATALOG'),
|
||||
controller.delete
|
||||
);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,75 @@
|
||||
import repository from './catalog.repository.js';
|
||||
import { SocketService } from '../../../shared/services/socket.service.js';
|
||||
import { AuditService } from '../../../shared/services/audit.service.js';
|
||||
|
||||
export class CatalogService {
|
||||
async getAll(query = {}) {
|
||||
// Add business logic filtering, pagination, etc.
|
||||
return await repository.findAll();
|
||||
}
|
||||
|
||||
async getById(id) {
|
||||
const record = await repository.findById(id);
|
||||
if (!record) {
|
||||
throw new Error('Catalog not found');
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
async create(data, userContext = {}) {
|
||||
const record = await repository.create(data);
|
||||
|
||||
// Broadcast event
|
||||
SocketService.broadcast('catalog:created', record);
|
||||
|
||||
// Log audit
|
||||
await AuditService.log({
|
||||
action: 'CREATE',
|
||||
resource: 'Catalog',
|
||||
resourceId: record.id,
|
||||
userId: userContext.id || 'system',
|
||||
details: data
|
||||
});
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
async update(id, data, userContext = {}) {
|
||||
const record = await repository.update(id, data);
|
||||
if (!record) {
|
||||
throw new Error('Catalog not found');
|
||||
}
|
||||
|
||||
SocketService.broadcast('catalog:updated', record);
|
||||
|
||||
await AuditService.log({
|
||||
action: 'UPDATE',
|
||||
resource: 'Catalog',
|
||||
resourceId: id,
|
||||
userId: userContext.id || 'system',
|
||||
details: data
|
||||
});
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
async delete(id, userContext = {}) {
|
||||
const deleted = await repository.delete(id);
|
||||
if (!deleted) {
|
||||
throw new Error('Catalog not found');
|
||||
}
|
||||
|
||||
SocketService.broadcast('catalog:deleted', { id });
|
||||
|
||||
await AuditService.log({
|
||||
action: 'DELETE',
|
||||
resource: 'Catalog',
|
||||
resourceId: id,
|
||||
userId: userContext.id || 'system'
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default new CatalogService();
|
||||
@@ -0,0 +1,32 @@
|
||||
import { body, param } from 'express-validator';
|
||||
|
||||
export const createValidation = [
|
||||
body('name')
|
||||
.optional()
|
||||
.isString()
|
||||
.trim()
|
||||
.withMessage('Name must be a string')
|
||||
];
|
||||
|
||||
export const updateValidation = [
|
||||
param('id')
|
||||
.isUUID()
|
||||
.withMessage('Valid UUID is required'),
|
||||
body('name')
|
||||
.optional()
|
||||
.isString()
|
||||
.trim()
|
||||
.withMessage('Name must be a string')
|
||||
];
|
||||
|
||||
export const deleteValidation = [
|
||||
param('id')
|
||||
.isUUID()
|
||||
.withMessage('Valid UUID is required')
|
||||
];
|
||||
|
||||
export const getByIdValidation = [
|
||||
param('id')
|
||||
.isUUID()
|
||||
.withMessage('Valid UUID is required')
|
||||
];
|
||||
@@ -0,0 +1,5 @@
|
||||
export { default as catalogRoutes } from './catalog.routes.js';
|
||||
export { default as catalogController } from './catalog.controller.js';
|
||||
export { default as catalogService } from './catalog.service.js';
|
||||
export { default as catalogRepository } from './catalog.repository.js';
|
||||
export { default as catalogModel } from './catalog.model.js';
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Router } from 'express';
|
||||
import catalogsRouter from './catalogs/catalog.routes.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.use('/catalogs', catalogsRouter);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,50 @@
|
||||
import service from './categorie.service.js';
|
||||
|
||||
export class CategorieController {
|
||||
async getAll(req, res, next) {
|
||||
try {
|
||||
const records = await service.getAll(req.query);
|
||||
return res.status(200).json({ success: true, data: records });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getById(req, res, next) {
|
||||
try {
|
||||
const record = await service.getById(req.params.id);
|
||||
return res.status(200).json({ success: true, data: record });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async create(req, res, next) {
|
||||
try {
|
||||
const record = await service.create(req.body, req.user);
|
||||
return res.status(201).json({ success: true, data: record });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async update(req, res, next) {
|
||||
try {
|
||||
const record = await service.update(req.params.id, req.body, req.user);
|
||||
return res.status(200).json({ success: true, data: record });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async delete(req, res, next) {
|
||||
try {
|
||||
await service.delete(req.params.id, req.user);
|
||||
return res.status(200).json({ success: true, message: 'Categorie deleted successfully' });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new CategorieController();
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
|
||||
export class Categorie extends Model {
|
||||
static associate(models) {
|
||||
// Define associations here
|
||||
}
|
||||
}
|
||||
|
||||
export default (sequelize) => {
|
||||
Categorie.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
allowNull: false
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: 'active'
|
||||
},
|
||||
metadata: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: true
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'Categorie',
|
||||
tableName: 'categories',
|
||||
timestamps: true,
|
||||
underscored: true
|
||||
});
|
||||
|
||||
return Categorie;
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Categorie } from './categorie.model.js';
|
||||
|
||||
export class CategorieRepository {
|
||||
async findAll(options = {}) {
|
||||
return await Categorie.findAll(options);
|
||||
}
|
||||
|
||||
async findById(id, options = {}) {
|
||||
return await Categorie.findByPk(id, options);
|
||||
}
|
||||
|
||||
async create(data, options = {}) {
|
||||
return await Categorie.create(data, options);
|
||||
}
|
||||
|
||||
async update(id, data, options = {}) {
|
||||
const record = await this.findById(id, options);
|
||||
if (!record) return null;
|
||||
return await record.update(data, options);
|
||||
}
|
||||
|
||||
async delete(id, options = {}) {
|
||||
const record = await this.findById(id, options);
|
||||
if (!record) return false;
|
||||
await record.destroy(options);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default new CategorieRepository();
|
||||
@@ -0,0 +1,126 @@
|
||||
import { Router } from 'express';
|
||||
import controller from './categorie.controller.js';
|
||||
import { authenticate } from '../../../shared/middleware/auth.middleware.js';
|
||||
import { validate } from '../../../shared/middleware/validation.middleware.js';
|
||||
import { authorize } from '../../../shared/middleware/permission.middleware.js';
|
||||
import { audit } from '../../../shared/middleware/audit.middleware.js';
|
||||
import {
|
||||
createValidation,
|
||||
updateValidation,
|
||||
deleteValidation,
|
||||
getByIdValidation
|
||||
} from './categorie.validation.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/categories:
|
||||
* get:
|
||||
* summary: Retrieve all categories
|
||||
* tags: [Categories]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.get(
|
||||
'/',
|
||||
authenticate,
|
||||
authorize(['read:categories']),
|
||||
controller.getAll
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/categories/{id}:
|
||||
* get:
|
||||
* summary: Retrieve a single categorie
|
||||
* tags: [Categories]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.get(
|
||||
'/:id',
|
||||
authenticate,
|
||||
authorize(['read:categories']),
|
||||
getByIdValidation,
|
||||
validate,
|
||||
controller.getById
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/categories:
|
||||
* post:
|
||||
* summary: Create a categorie
|
||||
* tags: [Categories]
|
||||
* responses:
|
||||
* 201:
|
||||
* description: Success
|
||||
*/
|
||||
router.post(
|
||||
'/',
|
||||
authenticate,
|
||||
authorize(['write:categories']),
|
||||
createValidation,
|
||||
validate,
|
||||
audit('CREATE_CATEGORIE'),
|
||||
controller.create
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/categories/{id}:
|
||||
* put:
|
||||
* summary: Update a categorie
|
||||
* tags: [Categories]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.put(
|
||||
'/:id',
|
||||
authenticate,
|
||||
authorize(['write:categories']),
|
||||
updateValidation,
|
||||
validate,
|
||||
audit('UPDATE_CATEGORIE'),
|
||||
controller.update
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/categories/{id}:
|
||||
* delete:
|
||||
* summary: Delete a categorie
|
||||
* tags: [Categories]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.delete(
|
||||
'/:id',
|
||||
authenticate,
|
||||
authorize(['write:categories']),
|
||||
deleteValidation,
|
||||
validate,
|
||||
audit('DELETE_CATEGORIE'),
|
||||
controller.delete
|
||||
);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,75 @@
|
||||
import repository from './categorie.repository.js';
|
||||
import { SocketService } from '../../../shared/services/socket.service.js';
|
||||
import { AuditService } from '../../../shared/services/audit.service.js';
|
||||
|
||||
export class CategorieService {
|
||||
async getAll(query = {}) {
|
||||
// Add business logic filtering, pagination, etc.
|
||||
return await repository.findAll();
|
||||
}
|
||||
|
||||
async getById(id) {
|
||||
const record = await repository.findById(id);
|
||||
if (!record) {
|
||||
throw new Error('Categorie not found');
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
async create(data, userContext = {}) {
|
||||
const record = await repository.create(data);
|
||||
|
||||
// Broadcast event
|
||||
SocketService.broadcast('categorie:created', record);
|
||||
|
||||
// Log audit
|
||||
await AuditService.log({
|
||||
action: 'CREATE',
|
||||
resource: 'Categorie',
|
||||
resourceId: record.id,
|
||||
userId: userContext.id || 'system',
|
||||
details: data
|
||||
});
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
async update(id, data, userContext = {}) {
|
||||
const record = await repository.update(id, data);
|
||||
if (!record) {
|
||||
throw new Error('Categorie not found');
|
||||
}
|
||||
|
||||
SocketService.broadcast('categorie:updated', record);
|
||||
|
||||
await AuditService.log({
|
||||
action: 'UPDATE',
|
||||
resource: 'Categorie',
|
||||
resourceId: id,
|
||||
userId: userContext.id || 'system',
|
||||
details: data
|
||||
});
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
async delete(id, userContext = {}) {
|
||||
const deleted = await repository.delete(id);
|
||||
if (!deleted) {
|
||||
throw new Error('Categorie not found');
|
||||
}
|
||||
|
||||
SocketService.broadcast('categorie:deleted', { id });
|
||||
|
||||
await AuditService.log({
|
||||
action: 'DELETE',
|
||||
resource: 'Categorie',
|
||||
resourceId: id,
|
||||
userId: userContext.id || 'system'
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default new CategorieService();
|
||||
@@ -0,0 +1,32 @@
|
||||
import { body, param } from 'express-validator';
|
||||
|
||||
export const createValidation = [
|
||||
body('name')
|
||||
.optional()
|
||||
.isString()
|
||||
.trim()
|
||||
.withMessage('Name must be a string')
|
||||
];
|
||||
|
||||
export const updateValidation = [
|
||||
param('id')
|
||||
.isUUID()
|
||||
.withMessage('Valid UUID is required'),
|
||||
body('name')
|
||||
.optional()
|
||||
.isString()
|
||||
.trim()
|
||||
.withMessage('Name must be a string')
|
||||
];
|
||||
|
||||
export const deleteValidation = [
|
||||
param('id')
|
||||
.isUUID()
|
||||
.withMessage('Valid UUID is required')
|
||||
];
|
||||
|
||||
export const getByIdValidation = [
|
||||
param('id')
|
||||
.isUUID()
|
||||
.withMessage('Valid UUID is required')
|
||||
];
|
||||
@@ -0,0 +1,5 @@
|
||||
export { default as categorieRoutes } from './categorie.routes.js';
|
||||
export { default as categorieController } from './categorie.controller.js';
|
||||
export { default as categorieService } from './categorie.service.js';
|
||||
export { default as categorieRepository } from './categorie.repository.js';
|
||||
export { default as categorieModel } from './categorie.model.js';
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Router } from 'express';
|
||||
import categoriesRouter from './categories/categorie.routes.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.use('/categories', categoriesRouter);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,50 @@
|
||||
import service from './channel.service.js';
|
||||
|
||||
export class ChannelController {
|
||||
async getAll(req, res, next) {
|
||||
try {
|
||||
const records = await service.getAll(req.query);
|
||||
return res.status(200).json({ success: true, data: records });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getById(req, res, next) {
|
||||
try {
|
||||
const record = await service.getById(req.params.id);
|
||||
return res.status(200).json({ success: true, data: record });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async create(req, res, next) {
|
||||
try {
|
||||
const record = await service.create(req.body, req.user);
|
||||
return res.status(201).json({ success: true, data: record });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async update(req, res, next) {
|
||||
try {
|
||||
const record = await service.update(req.params.id, req.body, req.user);
|
||||
return res.status(200).json({ success: true, data: record });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async delete(req, res, next) {
|
||||
try {
|
||||
await service.delete(req.params.id, req.user);
|
||||
return res.status(200).json({ success: true, message: 'Channel deleted successfully' });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new ChannelController();
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
|
||||
export class Channel extends Model {
|
||||
static associate(models) {
|
||||
// Define associations here
|
||||
}
|
||||
}
|
||||
|
||||
export default (sequelize) => {
|
||||
Channel.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
allowNull: false
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: 'active'
|
||||
},
|
||||
metadata: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: true
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'Channel',
|
||||
tableName: 'channels',
|
||||
timestamps: true,
|
||||
underscored: true
|
||||
});
|
||||
|
||||
return Channel;
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Channel } from './channel.model.js';
|
||||
|
||||
export class ChannelRepository {
|
||||
async findAll(options = {}) {
|
||||
return await Channel.findAll(options);
|
||||
}
|
||||
|
||||
async findById(id, options = {}) {
|
||||
return await Channel.findByPk(id, options);
|
||||
}
|
||||
|
||||
async create(data, options = {}) {
|
||||
return await Channel.create(data, options);
|
||||
}
|
||||
|
||||
async update(id, data, options = {}) {
|
||||
const record = await this.findById(id, options);
|
||||
if (!record) return null;
|
||||
return await record.update(data, options);
|
||||
}
|
||||
|
||||
async delete(id, options = {}) {
|
||||
const record = await this.findById(id, options);
|
||||
if (!record) return false;
|
||||
await record.destroy(options);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default new ChannelRepository();
|
||||
@@ -0,0 +1,126 @@
|
||||
import { Router } from 'express';
|
||||
import controller from './channel.controller.js';
|
||||
import { authenticate } from '../../../shared/middleware/auth.middleware.js';
|
||||
import { validate } from '../../../shared/middleware/validation.middleware.js';
|
||||
import { authorize } from '../../../shared/middleware/permission.middleware.js';
|
||||
import { audit } from '../../../shared/middleware/audit.middleware.js';
|
||||
import {
|
||||
createValidation,
|
||||
updateValidation,
|
||||
deleteValidation,
|
||||
getByIdValidation
|
||||
} from './channel.validation.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/channels:
|
||||
* get:
|
||||
* summary: Retrieve all channels
|
||||
* tags: [Channels]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.get(
|
||||
'/',
|
||||
authenticate,
|
||||
authorize(['read:channels']),
|
||||
controller.getAll
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/channels/{id}:
|
||||
* get:
|
||||
* summary: Retrieve a single channel
|
||||
* tags: [Channels]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.get(
|
||||
'/:id',
|
||||
authenticate,
|
||||
authorize(['read:channels']),
|
||||
getByIdValidation,
|
||||
validate,
|
||||
controller.getById
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/channels:
|
||||
* post:
|
||||
* summary: Create a channel
|
||||
* tags: [Channels]
|
||||
* responses:
|
||||
* 201:
|
||||
* description: Success
|
||||
*/
|
||||
router.post(
|
||||
'/',
|
||||
authenticate,
|
||||
authorize(['write:channels']),
|
||||
createValidation,
|
||||
validate,
|
||||
audit('CREATE_CHANNEL'),
|
||||
controller.create
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/channels/{id}:
|
||||
* put:
|
||||
* summary: Update a channel
|
||||
* tags: [Channels]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.put(
|
||||
'/:id',
|
||||
authenticate,
|
||||
authorize(['write:channels']),
|
||||
updateValidation,
|
||||
validate,
|
||||
audit('UPDATE_CHANNEL'),
|
||||
controller.update
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/channels/{id}:
|
||||
* delete:
|
||||
* summary: Delete a channel
|
||||
* tags: [Channels]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.delete(
|
||||
'/:id',
|
||||
authenticate,
|
||||
authorize(['write:channels']),
|
||||
deleteValidation,
|
||||
validate,
|
||||
audit('DELETE_CHANNEL'),
|
||||
controller.delete
|
||||
);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,75 @@
|
||||
import repository from './channel.repository.js';
|
||||
import { SocketService } from '../../../shared/services/socket.service.js';
|
||||
import { AuditService } from '../../../shared/services/audit.service.js';
|
||||
|
||||
export class ChannelService {
|
||||
async getAll(query = {}) {
|
||||
// Add business logic filtering, pagination, etc.
|
||||
return await repository.findAll();
|
||||
}
|
||||
|
||||
async getById(id) {
|
||||
const record = await repository.findById(id);
|
||||
if (!record) {
|
||||
throw new Error('Channel not found');
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
async create(data, userContext = {}) {
|
||||
const record = await repository.create(data);
|
||||
|
||||
// Broadcast event
|
||||
SocketService.broadcast('channel:created', record);
|
||||
|
||||
// Log audit
|
||||
await AuditService.log({
|
||||
action: 'CREATE',
|
||||
resource: 'Channel',
|
||||
resourceId: record.id,
|
||||
userId: userContext.id || 'system',
|
||||
details: data
|
||||
});
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
async update(id, data, userContext = {}) {
|
||||
const record = await repository.update(id, data);
|
||||
if (!record) {
|
||||
throw new Error('Channel not found');
|
||||
}
|
||||
|
||||
SocketService.broadcast('channel:updated', record);
|
||||
|
||||
await AuditService.log({
|
||||
action: 'UPDATE',
|
||||
resource: 'Channel',
|
||||
resourceId: id,
|
||||
userId: userContext.id || 'system',
|
||||
details: data
|
||||
});
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
async delete(id, userContext = {}) {
|
||||
const deleted = await repository.delete(id);
|
||||
if (!deleted) {
|
||||
throw new Error('Channel not found');
|
||||
}
|
||||
|
||||
SocketService.broadcast('channel:deleted', { id });
|
||||
|
||||
await AuditService.log({
|
||||
action: 'DELETE',
|
||||
resource: 'Channel',
|
||||
resourceId: id,
|
||||
userId: userContext.id || 'system'
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default new ChannelService();
|
||||
@@ -0,0 +1,32 @@
|
||||
import { body, param } from 'express-validator';
|
||||
|
||||
export const createValidation = [
|
||||
body('name')
|
||||
.optional()
|
||||
.isString()
|
||||
.trim()
|
||||
.withMessage('Name must be a string')
|
||||
];
|
||||
|
||||
export const updateValidation = [
|
||||
param('id')
|
||||
.isUUID()
|
||||
.withMessage('Valid UUID is required'),
|
||||
body('name')
|
||||
.optional()
|
||||
.isString()
|
||||
.trim()
|
||||
.withMessage('Name must be a string')
|
||||
];
|
||||
|
||||
export const deleteValidation = [
|
||||
param('id')
|
||||
.isUUID()
|
||||
.withMessage('Valid UUID is required')
|
||||
];
|
||||
|
||||
export const getByIdValidation = [
|
||||
param('id')
|
||||
.isUUID()
|
||||
.withMessage('Valid UUID is required')
|
||||
];
|
||||
@@ -0,0 +1,5 @@
|
||||
export { default as channelRoutes } from './channel.routes.js';
|
||||
export { default as channelController } from './channel.controller.js';
|
||||
export { default as channelService } from './channel.service.js';
|
||||
export { default as channelRepository } from './channel.repository.js';
|
||||
export { default as channelModel } from './channel.model.js';
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Router } from 'express';
|
||||
import channelsRouter from './channels/channel.routes.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.use('/channels', channelsRouter);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Router } from 'express';
|
||||
import metricsRouter from './metrics/metric.routes.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.use('/metrics', metricsRouter);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,5 @@
|
||||
export { default as metricRoutes } from './metric.routes.js';
|
||||
export { default as metricController } from './metric.controller.js';
|
||||
export { default as metricService } from './metric.service.js';
|
||||
export { default as metricRepository } from './metric.repository.js';
|
||||
export { default as metricModel } from './metric.model.js';
|
||||
@@ -0,0 +1,50 @@
|
||||
import service from './metric.service.js';
|
||||
|
||||
export class MetricController {
|
||||
async getAll(req, res, next) {
|
||||
try {
|
||||
const records = await service.getAll(req.query);
|
||||
return res.status(200).json({ success: true, data: records });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getById(req, res, next) {
|
||||
try {
|
||||
const record = await service.getById(req.params.id);
|
||||
return res.status(200).json({ success: true, data: record });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async create(req, res, next) {
|
||||
try {
|
||||
const record = await service.create(req.body, req.user);
|
||||
return res.status(201).json({ success: true, data: record });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async update(req, res, next) {
|
||||
try {
|
||||
const record = await service.update(req.params.id, req.body, req.user);
|
||||
return res.status(200).json({ success: true, data: record });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async delete(req, res, next) {
|
||||
try {
|
||||
await service.delete(req.params.id, req.user);
|
||||
return res.status(200).json({ success: true, message: 'Metric deleted successfully' });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new MetricController();
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
|
||||
export class Metric extends Model {
|
||||
static associate(models) {
|
||||
// Define associations here
|
||||
}
|
||||
}
|
||||
|
||||
export default (sequelize) => {
|
||||
Metric.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
allowNull: false
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: 'active'
|
||||
},
|
||||
metadata: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: true
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'Metric',
|
||||
tableName: 'metrics',
|
||||
timestamps: true,
|
||||
underscored: true
|
||||
});
|
||||
|
||||
return Metric;
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Metric } from './metric.model.js';
|
||||
|
||||
export class MetricRepository {
|
||||
async findAll(options = {}) {
|
||||
return await Metric.findAll(options);
|
||||
}
|
||||
|
||||
async findById(id, options = {}) {
|
||||
return await Metric.findByPk(id, options);
|
||||
}
|
||||
|
||||
async create(data, options = {}) {
|
||||
return await Metric.create(data, options);
|
||||
}
|
||||
|
||||
async update(id, data, options = {}) {
|
||||
const record = await this.findById(id, options);
|
||||
if (!record) return null;
|
||||
return await record.update(data, options);
|
||||
}
|
||||
|
||||
async delete(id, options = {}) {
|
||||
const record = await this.findById(id, options);
|
||||
if (!record) return false;
|
||||
await record.destroy(options);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default new MetricRepository();
|
||||
@@ -0,0 +1,126 @@
|
||||
import { Router } from 'express';
|
||||
import controller from './metric.controller.js';
|
||||
import { authenticate } from '../../../shared/middleware/auth.middleware.js';
|
||||
import { validate } from '../../../shared/middleware/validation.middleware.js';
|
||||
import { authorize } from '../../../shared/middleware/permission.middleware.js';
|
||||
import { audit } from '../../../shared/middleware/audit.middleware.js';
|
||||
import {
|
||||
createValidation,
|
||||
updateValidation,
|
||||
deleteValidation,
|
||||
getByIdValidation
|
||||
} from './metric.validation.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/metrics:
|
||||
* get:
|
||||
* summary: Retrieve all metrics
|
||||
* tags: [Metrics]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.get(
|
||||
'/',
|
||||
authenticate,
|
||||
authorize(['read:metrics']),
|
||||
controller.getAll
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/metrics/{id}:
|
||||
* get:
|
||||
* summary: Retrieve a single metric
|
||||
* tags: [Metrics]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.get(
|
||||
'/:id',
|
||||
authenticate,
|
||||
authorize(['read:metrics']),
|
||||
getByIdValidation,
|
||||
validate,
|
||||
controller.getById
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/metrics:
|
||||
* post:
|
||||
* summary: Create a metric
|
||||
* tags: [Metrics]
|
||||
* responses:
|
||||
* 201:
|
||||
* description: Success
|
||||
*/
|
||||
router.post(
|
||||
'/',
|
||||
authenticate,
|
||||
authorize(['write:metrics']),
|
||||
createValidation,
|
||||
validate,
|
||||
audit('CREATE_METRIC'),
|
||||
controller.create
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/metrics/{id}:
|
||||
* put:
|
||||
* summary: Update a metric
|
||||
* tags: [Metrics]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.put(
|
||||
'/:id',
|
||||
authenticate,
|
||||
authorize(['write:metrics']),
|
||||
updateValidation,
|
||||
validate,
|
||||
audit('UPDATE_METRIC'),
|
||||
controller.update
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/metrics/{id}:
|
||||
* delete:
|
||||
* summary: Delete a metric
|
||||
* tags: [Metrics]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.delete(
|
||||
'/:id',
|
||||
authenticate,
|
||||
authorize(['write:metrics']),
|
||||
deleteValidation,
|
||||
validate,
|
||||
audit('DELETE_METRIC'),
|
||||
controller.delete
|
||||
);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,75 @@
|
||||
import repository from './metric.repository.js';
|
||||
import { SocketService } from '../../../shared/services/socket.service.js';
|
||||
import { AuditService } from '../../../shared/services/audit.service.js';
|
||||
|
||||
export class MetricService {
|
||||
async getAll(query = {}) {
|
||||
// Add business logic filtering, pagination, etc.
|
||||
return await repository.findAll();
|
||||
}
|
||||
|
||||
async getById(id) {
|
||||
const record = await repository.findById(id);
|
||||
if (!record) {
|
||||
throw new Error('Metric not found');
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
async create(data, userContext = {}) {
|
||||
const record = await repository.create(data);
|
||||
|
||||
// Broadcast event
|
||||
SocketService.broadcast('metric:created', record);
|
||||
|
||||
// Log audit
|
||||
await AuditService.log({
|
||||
action: 'CREATE',
|
||||
resource: 'Metric',
|
||||
resourceId: record.id,
|
||||
userId: userContext.id || 'system',
|
||||
details: data
|
||||
});
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
async update(id, data, userContext = {}) {
|
||||
const record = await repository.update(id, data);
|
||||
if (!record) {
|
||||
throw new Error('Metric not found');
|
||||
}
|
||||
|
||||
SocketService.broadcast('metric:updated', record);
|
||||
|
||||
await AuditService.log({
|
||||
action: 'UPDATE',
|
||||
resource: 'Metric',
|
||||
resourceId: id,
|
||||
userId: userContext.id || 'system',
|
||||
details: data
|
||||
});
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
async delete(id, userContext = {}) {
|
||||
const deleted = await repository.delete(id);
|
||||
if (!deleted) {
|
||||
throw new Error('Metric not found');
|
||||
}
|
||||
|
||||
SocketService.broadcast('metric:deleted', { id });
|
||||
|
||||
await AuditService.log({
|
||||
action: 'DELETE',
|
||||
resource: 'Metric',
|
||||
resourceId: id,
|
||||
userId: userContext.id || 'system'
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default new MetricService();
|
||||
@@ -0,0 +1,32 @@
|
||||
import { body, param } from 'express-validator';
|
||||
|
||||
export const createValidation = [
|
||||
body('name')
|
||||
.optional()
|
||||
.isString()
|
||||
.trim()
|
||||
.withMessage('Name must be a string')
|
||||
];
|
||||
|
||||
export const updateValidation = [
|
||||
param('id')
|
||||
.isUUID()
|
||||
.withMessage('Valid UUID is required'),
|
||||
body('name')
|
||||
.optional()
|
||||
.isString()
|
||||
.trim()
|
||||
.withMessage('Name must be a string')
|
||||
];
|
||||
|
||||
export const deleteValidation = [
|
||||
param('id')
|
||||
.isUUID()
|
||||
.withMessage('Valid UUID is required')
|
||||
];
|
||||
|
||||
export const getByIdValidation = [
|
||||
param('id')
|
||||
.isUUID()
|
||||
.withMessage('Valid UUID is required')
|
||||
];
|
||||
@@ -0,0 +1,50 @@
|
||||
import service from './export.service.js';
|
||||
|
||||
export class ExportController {
|
||||
async getAll(req, res, next) {
|
||||
try {
|
||||
const records = await service.getAll(req.query);
|
||||
return res.status(200).json({ success: true, data: records });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getById(req, res, next) {
|
||||
try {
|
||||
const record = await service.getById(req.params.id);
|
||||
return res.status(200).json({ success: true, data: record });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async create(req, res, next) {
|
||||
try {
|
||||
const record = await service.create(req.body, req.user);
|
||||
return res.status(201).json({ success: true, data: record });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async update(req, res, next) {
|
||||
try {
|
||||
const record = await service.update(req.params.id, req.body, req.user);
|
||||
return res.status(200).json({ success: true, data: record });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async delete(req, res, next) {
|
||||
try {
|
||||
await service.delete(req.params.id, req.user);
|
||||
return res.status(200).json({ success: true, message: 'Export deleted successfully' });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new ExportController();
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
|
||||
export class Export extends Model {
|
||||
static associate(models) {
|
||||
// Define associations here
|
||||
}
|
||||
}
|
||||
|
||||
export default (sequelize) => {
|
||||
Export.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
allowNull: false
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: 'active'
|
||||
},
|
||||
metadata: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: true
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'Export',
|
||||
tableName: 'exports',
|
||||
timestamps: true,
|
||||
underscored: true
|
||||
});
|
||||
|
||||
return Export;
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Export } from './export.model.js';
|
||||
|
||||
export class ExportRepository {
|
||||
async findAll(options = {}) {
|
||||
return await Export.findAll(options);
|
||||
}
|
||||
|
||||
async findById(id, options = {}) {
|
||||
return await Export.findByPk(id, options);
|
||||
}
|
||||
|
||||
async create(data, options = {}) {
|
||||
return await Export.create(data, options);
|
||||
}
|
||||
|
||||
async update(id, data, options = {}) {
|
||||
const record = await this.findById(id, options);
|
||||
if (!record) return null;
|
||||
return await record.update(data, options);
|
||||
}
|
||||
|
||||
async delete(id, options = {}) {
|
||||
const record = await this.findById(id, options);
|
||||
if (!record) return false;
|
||||
await record.destroy(options);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default new ExportRepository();
|
||||
@@ -0,0 +1,126 @@
|
||||
import { Router } from 'express';
|
||||
import controller from './export.controller.js';
|
||||
import { authenticate } from '../../../shared/middleware/auth.middleware.js';
|
||||
import { validate } from '../../../shared/middleware/validation.middleware.js';
|
||||
import { authorize } from '../../../shared/middleware/permission.middleware.js';
|
||||
import { audit } from '../../../shared/middleware/audit.middleware.js';
|
||||
import {
|
||||
createValidation,
|
||||
updateValidation,
|
||||
deleteValidation,
|
||||
getByIdValidation
|
||||
} from './export.validation.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/exports:
|
||||
* get:
|
||||
* summary: Retrieve all exports
|
||||
* tags: [Exports]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.get(
|
||||
'/',
|
||||
authenticate,
|
||||
authorize(['read:exports']),
|
||||
controller.getAll
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/exports/{id}:
|
||||
* get:
|
||||
* summary: Retrieve a single export
|
||||
* tags: [Exports]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.get(
|
||||
'/:id',
|
||||
authenticate,
|
||||
authorize(['read:exports']),
|
||||
getByIdValidation,
|
||||
validate,
|
||||
controller.getById
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/exports:
|
||||
* post:
|
||||
* summary: Create a export
|
||||
* tags: [Exports]
|
||||
* responses:
|
||||
* 201:
|
||||
* description: Success
|
||||
*/
|
||||
router.post(
|
||||
'/',
|
||||
authenticate,
|
||||
authorize(['write:exports']),
|
||||
createValidation,
|
||||
validate,
|
||||
audit('CREATE_EXPORT'),
|
||||
controller.create
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/exports/{id}:
|
||||
* put:
|
||||
* summary: Update a export
|
||||
* tags: [Exports]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.put(
|
||||
'/:id',
|
||||
authenticate,
|
||||
authorize(['write:exports']),
|
||||
updateValidation,
|
||||
validate,
|
||||
audit('UPDATE_EXPORT'),
|
||||
controller.update
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/exports/{id}:
|
||||
* delete:
|
||||
* summary: Delete a export
|
||||
* tags: [Exports]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.delete(
|
||||
'/:id',
|
||||
authenticate,
|
||||
authorize(['write:exports']),
|
||||
deleteValidation,
|
||||
validate,
|
||||
audit('DELETE_EXPORT'),
|
||||
controller.delete
|
||||
);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,75 @@
|
||||
import repository from './export.repository.js';
|
||||
import { SocketService } from '../../../shared/services/socket.service.js';
|
||||
import { AuditService } from '../../../shared/services/audit.service.js';
|
||||
|
||||
export class ExportService {
|
||||
async getAll(query = {}) {
|
||||
// Add business logic filtering, pagination, etc.
|
||||
return await repository.findAll();
|
||||
}
|
||||
|
||||
async getById(id) {
|
||||
const record = await repository.findById(id);
|
||||
if (!record) {
|
||||
throw new Error('Export not found');
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
async create(data, userContext = {}) {
|
||||
const record = await repository.create(data);
|
||||
|
||||
// Broadcast event
|
||||
SocketService.broadcast('export:created', record);
|
||||
|
||||
// Log audit
|
||||
await AuditService.log({
|
||||
action: 'CREATE',
|
||||
resource: 'Export',
|
||||
resourceId: record.id,
|
||||
userId: userContext.id || 'system',
|
||||
details: data
|
||||
});
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
async update(id, data, userContext = {}) {
|
||||
const record = await repository.update(id, data);
|
||||
if (!record) {
|
||||
throw new Error('Export not found');
|
||||
}
|
||||
|
||||
SocketService.broadcast('export:updated', record);
|
||||
|
||||
await AuditService.log({
|
||||
action: 'UPDATE',
|
||||
resource: 'Export',
|
||||
resourceId: id,
|
||||
userId: userContext.id || 'system',
|
||||
details: data
|
||||
});
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
async delete(id, userContext = {}) {
|
||||
const deleted = await repository.delete(id);
|
||||
if (!deleted) {
|
||||
throw new Error('Export not found');
|
||||
}
|
||||
|
||||
SocketService.broadcast('export:deleted', { id });
|
||||
|
||||
await AuditService.log({
|
||||
action: 'DELETE',
|
||||
resource: 'Export',
|
||||
resourceId: id,
|
||||
userId: userContext.id || 'system'
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default new ExportService();
|
||||
@@ -0,0 +1,32 @@
|
||||
import { body, param } from 'express-validator';
|
||||
|
||||
export const createValidation = [
|
||||
body('name')
|
||||
.optional()
|
||||
.isString()
|
||||
.trim()
|
||||
.withMessage('Name must be a string')
|
||||
];
|
||||
|
||||
export const updateValidation = [
|
||||
param('id')
|
||||
.isUUID()
|
||||
.withMessage('Valid UUID is required'),
|
||||
body('name')
|
||||
.optional()
|
||||
.isString()
|
||||
.trim()
|
||||
.withMessage('Name must be a string')
|
||||
];
|
||||
|
||||
export const deleteValidation = [
|
||||
param('id')
|
||||
.isUUID()
|
||||
.withMessage('Valid UUID is required')
|
||||
];
|
||||
|
||||
export const getByIdValidation = [
|
||||
param('id')
|
||||
.isUUID()
|
||||
.withMessage('Valid UUID is required')
|
||||
];
|
||||
@@ -0,0 +1,5 @@
|
||||
export { default as exportRoutes } from './export.routes.js';
|
||||
export { default as exportController } from './export.controller.js';
|
||||
export { default as exportService } from './export.service.js';
|
||||
export { default as exportRepository } from './export.repository.js';
|
||||
export { default as exportModel } from './export.model.js';
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Router } from 'express';
|
||||
import exportsRouter from './exports/export.routes.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.use('/exports', exportsRouter);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,50 @@
|
||||
import service from './import.service.js';
|
||||
|
||||
export class ImportController {
|
||||
async getAll(req, res, next) {
|
||||
try {
|
||||
const records = await service.getAll(req.query);
|
||||
return res.status(200).json({ success: true, data: records });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getById(req, res, next) {
|
||||
try {
|
||||
const record = await service.getById(req.params.id);
|
||||
return res.status(200).json({ success: true, data: record });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async create(req, res, next) {
|
||||
try {
|
||||
const record = await service.create(req.body, req.user);
|
||||
return res.status(201).json({ success: true, data: record });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async update(req, res, next) {
|
||||
try {
|
||||
const record = await service.update(req.params.id, req.body, req.user);
|
||||
return res.status(200).json({ success: true, data: record });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async delete(req, res, next) {
|
||||
try {
|
||||
await service.delete(req.params.id, req.user);
|
||||
return res.status(200).json({ success: true, message: 'Import deleted successfully' });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new ImportController();
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
|
||||
export class Import extends Model {
|
||||
static associate(models) {
|
||||
// Define associations here
|
||||
}
|
||||
}
|
||||
|
||||
export default (sequelize) => {
|
||||
Import.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
allowNull: false
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: 'active'
|
||||
},
|
||||
metadata: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: true
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'Import',
|
||||
tableName: 'imports',
|
||||
timestamps: true,
|
||||
underscored: true
|
||||
});
|
||||
|
||||
return Import;
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Import } from './import.model.js';
|
||||
|
||||
export class ImportRepository {
|
||||
async findAll(options = {}) {
|
||||
return await Import.findAll(options);
|
||||
}
|
||||
|
||||
async findById(id, options = {}) {
|
||||
return await Import.findByPk(id, options);
|
||||
}
|
||||
|
||||
async create(data, options = {}) {
|
||||
return await Import.create(data, options);
|
||||
}
|
||||
|
||||
async update(id, data, options = {}) {
|
||||
const record = await this.findById(id, options);
|
||||
if (!record) return null;
|
||||
return await record.update(data, options);
|
||||
}
|
||||
|
||||
async delete(id, options = {}) {
|
||||
const record = await this.findById(id, options);
|
||||
if (!record) return false;
|
||||
await record.destroy(options);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default new ImportRepository();
|
||||
@@ -0,0 +1,126 @@
|
||||
import { Router } from 'express';
|
||||
import controller from './import.controller.js';
|
||||
import { authenticate } from '../../../shared/middleware/auth.middleware.js';
|
||||
import { validate } from '../../../shared/middleware/validation.middleware.js';
|
||||
import { authorize } from '../../../shared/middleware/permission.middleware.js';
|
||||
import { audit } from '../../../shared/middleware/audit.middleware.js';
|
||||
import {
|
||||
createValidation,
|
||||
updateValidation,
|
||||
deleteValidation,
|
||||
getByIdValidation
|
||||
} from './import.validation.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/imports:
|
||||
* get:
|
||||
* summary: Retrieve all imports
|
||||
* tags: [Imports]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.get(
|
||||
'/',
|
||||
authenticate,
|
||||
authorize(['read:imports']),
|
||||
controller.getAll
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/imports/{id}:
|
||||
* get:
|
||||
* summary: Retrieve a single import
|
||||
* tags: [Imports]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.get(
|
||||
'/:id',
|
||||
authenticate,
|
||||
authorize(['read:imports']),
|
||||
getByIdValidation,
|
||||
validate,
|
||||
controller.getById
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/imports:
|
||||
* post:
|
||||
* summary: Create a import
|
||||
* tags: [Imports]
|
||||
* responses:
|
||||
* 201:
|
||||
* description: Success
|
||||
*/
|
||||
router.post(
|
||||
'/',
|
||||
authenticate,
|
||||
authorize(['write:imports']),
|
||||
createValidation,
|
||||
validate,
|
||||
audit('CREATE_IMPORT'),
|
||||
controller.create
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/imports/{id}:
|
||||
* put:
|
||||
* summary: Update a import
|
||||
* tags: [Imports]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.put(
|
||||
'/:id',
|
||||
authenticate,
|
||||
authorize(['write:imports']),
|
||||
updateValidation,
|
||||
validate,
|
||||
audit('UPDATE_IMPORT'),
|
||||
controller.update
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/imports/{id}:
|
||||
* delete:
|
||||
* summary: Delete a import
|
||||
* tags: [Imports]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.delete(
|
||||
'/:id',
|
||||
authenticate,
|
||||
authorize(['write:imports']),
|
||||
deleteValidation,
|
||||
validate,
|
||||
audit('DELETE_IMPORT'),
|
||||
controller.delete
|
||||
);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,75 @@
|
||||
import repository from './import.repository.js';
|
||||
import { SocketService } from '../../../shared/services/socket.service.js';
|
||||
import { AuditService } from '../../../shared/services/audit.service.js';
|
||||
|
||||
export class ImportService {
|
||||
async getAll(query = {}) {
|
||||
// Add business logic filtering, pagination, etc.
|
||||
return await repository.findAll();
|
||||
}
|
||||
|
||||
async getById(id) {
|
||||
const record = await repository.findById(id);
|
||||
if (!record) {
|
||||
throw new Error('Import not found');
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
async create(data, userContext = {}) {
|
||||
const record = await repository.create(data);
|
||||
|
||||
// Broadcast event
|
||||
SocketService.broadcast('import:created', record);
|
||||
|
||||
// Log audit
|
||||
await AuditService.log({
|
||||
action: 'CREATE',
|
||||
resource: 'Import',
|
||||
resourceId: record.id,
|
||||
userId: userContext.id || 'system',
|
||||
details: data
|
||||
});
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
async update(id, data, userContext = {}) {
|
||||
const record = await repository.update(id, data);
|
||||
if (!record) {
|
||||
throw new Error('Import not found');
|
||||
}
|
||||
|
||||
SocketService.broadcast('import:updated', record);
|
||||
|
||||
await AuditService.log({
|
||||
action: 'UPDATE',
|
||||
resource: 'Import',
|
||||
resourceId: id,
|
||||
userId: userContext.id || 'system',
|
||||
details: data
|
||||
});
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
async delete(id, userContext = {}) {
|
||||
const deleted = await repository.delete(id);
|
||||
if (!deleted) {
|
||||
throw new Error('Import not found');
|
||||
}
|
||||
|
||||
SocketService.broadcast('import:deleted', { id });
|
||||
|
||||
await AuditService.log({
|
||||
action: 'DELETE',
|
||||
resource: 'Import',
|
||||
resourceId: id,
|
||||
userId: userContext.id || 'system'
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default new ImportService();
|
||||
@@ -0,0 +1,32 @@
|
||||
import { body, param } from 'express-validator';
|
||||
|
||||
export const createValidation = [
|
||||
body('name')
|
||||
.optional()
|
||||
.isString()
|
||||
.trim()
|
||||
.withMessage('Name must be a string')
|
||||
];
|
||||
|
||||
export const updateValidation = [
|
||||
param('id')
|
||||
.isUUID()
|
||||
.withMessage('Valid UUID is required'),
|
||||
body('name')
|
||||
.optional()
|
||||
.isString()
|
||||
.trim()
|
||||
.withMessage('Name must be a string')
|
||||
];
|
||||
|
||||
export const deleteValidation = [
|
||||
param('id')
|
||||
.isUUID()
|
||||
.withMessage('Valid UUID is required')
|
||||
];
|
||||
|
||||
export const getByIdValidation = [
|
||||
param('id')
|
||||
.isUUID()
|
||||
.withMessage('Valid UUID is required')
|
||||
];
|
||||
@@ -0,0 +1,5 @@
|
||||
export { default as importRoutes } from './import.routes.js';
|
||||
export { default as importController } from './import.controller.js';
|
||||
export { default as importService } from './import.service.js';
|
||||
export { default as importRepository } from './import.repository.js';
|
||||
export { default as importModel } from './import.model.js';
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Router } from 'express';
|
||||
import importsRouter from './imports/import.routes.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.use('/imports', importsRouter);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,47 @@
|
||||
import authenticationRouter from './authentication/index.js';
|
||||
import organizationRouter from './organization/index.js';
|
||||
import dashboardRouter from './dashboard/index.js';
|
||||
import productsRouter from './products/index.js';
|
||||
import catalogsRouter from './catalogs/index.js';
|
||||
import categoriesRouter from './categories/index.js';
|
||||
import brandsRouter from './brands/index.js';
|
||||
import attributesRouter from './attributes/index.js';
|
||||
import mediaRouter from './media/index.js';
|
||||
import pricingRouter from './pricing/index.js';
|
||||
import inventoryRouter from './inventory/index.js';
|
||||
import suppliersRouter from './suppliers/index.js';
|
||||
import localizationRouter from './localization/index.js';
|
||||
import workflowsRouter from './workflows/index.js';
|
||||
import integrationsRouter from './integrations/index.js';
|
||||
import channelsRouter from './channels/index.js';
|
||||
import importsRouter from './imports/index.js';
|
||||
import exportsRouter from './exports/index.js';
|
||||
import notificationsRouter from './notifications/index.js';
|
||||
import reportsRouter from './reports/index.js';
|
||||
import settingsRouter from './settings/index.js';
|
||||
import auditLogsRouter from './auditLogs/index.js';
|
||||
|
||||
export default function registerRoutes(app) {
|
||||
app.use('/api/v1/auth', authenticationRouter);
|
||||
app.use('/api/v1/organization', organizationRouter);
|
||||
app.use('/api/v1/dashboard', dashboardRouter);
|
||||
app.use('/api/v1/products', productsRouter);
|
||||
app.use('/api/v1/catalogs', catalogsRouter);
|
||||
app.use('/api/v1/categories', categoriesRouter);
|
||||
app.use('/api/v1/brands', brandsRouter);
|
||||
app.use('/api/v1/attributes', attributesRouter);
|
||||
app.use('/api/v1/media', mediaRouter);
|
||||
app.use('/api/v1/pricing', pricingRouter);
|
||||
app.use('/api/v1/inventory', inventoryRouter);
|
||||
app.use('/api/v1/suppliers', suppliersRouter);
|
||||
app.use('/api/v1/localization', localizationRouter);
|
||||
app.use('/api/v1/workflows', workflowsRouter);
|
||||
app.use('/api/v1/integrations', integrationsRouter);
|
||||
app.use('/api/v1/channels', channelsRouter);
|
||||
app.use('/api/v1/imports', importsRouter);
|
||||
app.use('/api/v1/exports', exportsRouter);
|
||||
app.use('/api/v1/notifications', notificationsRouter);
|
||||
app.use('/api/v1/reports', reportsRouter);
|
||||
app.use('/api/v1/settings', settingsRouter);
|
||||
app.use('/api/v1/auditLogs', auditLogsRouter);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Router } from 'express';
|
||||
import integrationsRouter from './integrations/integration.routes.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.use('/integrations', integrationsRouter);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,5 @@
|
||||
export { default as integrationRoutes } from './integration.routes.js';
|
||||
export { default as integrationController } from './integration.controller.js';
|
||||
export { default as integrationService } from './integration.service.js';
|
||||
export { default as integrationRepository } from './integration.repository.js';
|
||||
export { default as integrationModel } from './integration.model.js';
|
||||
@@ -0,0 +1,50 @@
|
||||
import service from './integration.service.js';
|
||||
|
||||
export class IntegrationController {
|
||||
async getAll(req, res, next) {
|
||||
try {
|
||||
const records = await service.getAll(req.query);
|
||||
return res.status(200).json({ success: true, data: records });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getById(req, res, next) {
|
||||
try {
|
||||
const record = await service.getById(req.params.id);
|
||||
return res.status(200).json({ success: true, data: record });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async create(req, res, next) {
|
||||
try {
|
||||
const record = await service.create(req.body, req.user);
|
||||
return res.status(201).json({ success: true, data: record });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async update(req, res, next) {
|
||||
try {
|
||||
const record = await service.update(req.params.id, req.body, req.user);
|
||||
return res.status(200).json({ success: true, data: record });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async delete(req, res, next) {
|
||||
try {
|
||||
await service.delete(req.params.id, req.user);
|
||||
return res.status(200).json({ success: true, message: 'Integration deleted successfully' });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new IntegrationController();
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
|
||||
export class Integration extends Model {
|
||||
static associate(models) {
|
||||
// Define associations here
|
||||
}
|
||||
}
|
||||
|
||||
export default (sequelize) => {
|
||||
Integration.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
allowNull: false
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: 'active'
|
||||
},
|
||||
metadata: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: true
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'Integration',
|
||||
tableName: 'integrations',
|
||||
timestamps: true,
|
||||
underscored: true
|
||||
});
|
||||
|
||||
return Integration;
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Integration } from './integration.model.js';
|
||||
|
||||
export class IntegrationRepository {
|
||||
async findAll(options = {}) {
|
||||
return await Integration.findAll(options);
|
||||
}
|
||||
|
||||
async findById(id, options = {}) {
|
||||
return await Integration.findByPk(id, options);
|
||||
}
|
||||
|
||||
async create(data, options = {}) {
|
||||
return await Integration.create(data, options);
|
||||
}
|
||||
|
||||
async update(id, data, options = {}) {
|
||||
const record = await this.findById(id, options);
|
||||
if (!record) return null;
|
||||
return await record.update(data, options);
|
||||
}
|
||||
|
||||
async delete(id, options = {}) {
|
||||
const record = await this.findById(id, options);
|
||||
if (!record) return false;
|
||||
await record.destroy(options);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default new IntegrationRepository();
|
||||
@@ -0,0 +1,126 @@
|
||||
import { Router } from 'express';
|
||||
import controller from './integration.controller.js';
|
||||
import { authenticate } from '../../../shared/middleware/auth.middleware.js';
|
||||
import { validate } from '../../../shared/middleware/validation.middleware.js';
|
||||
import { authorize } from '../../../shared/middleware/permission.middleware.js';
|
||||
import { audit } from '../../../shared/middleware/audit.middleware.js';
|
||||
import {
|
||||
createValidation,
|
||||
updateValidation,
|
||||
deleteValidation,
|
||||
getByIdValidation
|
||||
} from './integration.validation.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/integrations:
|
||||
* get:
|
||||
* summary: Retrieve all integrations
|
||||
* tags: [Integrations]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.get(
|
||||
'/',
|
||||
authenticate,
|
||||
authorize(['read:integrations']),
|
||||
controller.getAll
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/integrations/{id}:
|
||||
* get:
|
||||
* summary: Retrieve a single integration
|
||||
* tags: [Integrations]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.get(
|
||||
'/:id',
|
||||
authenticate,
|
||||
authorize(['read:integrations']),
|
||||
getByIdValidation,
|
||||
validate,
|
||||
controller.getById
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/integrations:
|
||||
* post:
|
||||
* summary: Create a integration
|
||||
* tags: [Integrations]
|
||||
* responses:
|
||||
* 201:
|
||||
* description: Success
|
||||
*/
|
||||
router.post(
|
||||
'/',
|
||||
authenticate,
|
||||
authorize(['write:integrations']),
|
||||
createValidation,
|
||||
validate,
|
||||
audit('CREATE_INTEGRATION'),
|
||||
controller.create
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/integrations/{id}:
|
||||
* put:
|
||||
* summary: Update a integration
|
||||
* tags: [Integrations]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.put(
|
||||
'/:id',
|
||||
authenticate,
|
||||
authorize(['write:integrations']),
|
||||
updateValidation,
|
||||
validate,
|
||||
audit('UPDATE_INTEGRATION'),
|
||||
controller.update
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/integrations/{id}:
|
||||
* delete:
|
||||
* summary: Delete a integration
|
||||
* tags: [Integrations]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
*/
|
||||
router.delete(
|
||||
'/:id',
|
||||
authenticate,
|
||||
authorize(['write:integrations']),
|
||||
deleteValidation,
|
||||
validate,
|
||||
audit('DELETE_INTEGRATION'),
|
||||
controller.delete
|
||||
);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,75 @@
|
||||
import repository from './integration.repository.js';
|
||||
import { SocketService } from '../../../shared/services/socket.service.js';
|
||||
import { AuditService } from '../../../shared/services/audit.service.js';
|
||||
|
||||
export class IntegrationService {
|
||||
async getAll(query = {}) {
|
||||
// Add business logic filtering, pagination, etc.
|
||||
return await repository.findAll();
|
||||
}
|
||||
|
||||
async getById(id) {
|
||||
const record = await repository.findById(id);
|
||||
if (!record) {
|
||||
throw new Error('Integration not found');
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
async create(data, userContext = {}) {
|
||||
const record = await repository.create(data);
|
||||
|
||||
// Broadcast event
|
||||
SocketService.broadcast('integration:created', record);
|
||||
|
||||
// Log audit
|
||||
await AuditService.log({
|
||||
action: 'CREATE',
|
||||
resource: 'Integration',
|
||||
resourceId: record.id,
|
||||
userId: userContext.id || 'system',
|
||||
details: data
|
||||
});
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
async update(id, data, userContext = {}) {
|
||||
const record = await repository.update(id, data);
|
||||
if (!record) {
|
||||
throw new Error('Integration not found');
|
||||
}
|
||||
|
||||
SocketService.broadcast('integration:updated', record);
|
||||
|
||||
await AuditService.log({
|
||||
action: 'UPDATE',
|
||||
resource: 'Integration',
|
||||
resourceId: id,
|
||||
userId: userContext.id || 'system',
|
||||
details: data
|
||||
});
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
async delete(id, userContext = {}) {
|
||||
const deleted = await repository.delete(id);
|
||||
if (!deleted) {
|
||||
throw new Error('Integration not found');
|
||||
}
|
||||
|
||||
SocketService.broadcast('integration:deleted', { id });
|
||||
|
||||
await AuditService.log({
|
||||
action: 'DELETE',
|
||||
resource: 'Integration',
|
||||
resourceId: id,
|
||||
userId: userContext.id || 'system'
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default new IntegrationService();
|
||||
@@ -0,0 +1,32 @@
|
||||
import { body, param } from 'express-validator';
|
||||
|
||||
export const createValidation = [
|
||||
body('name')
|
||||
.optional()
|
||||
.isString()
|
||||
.trim()
|
||||
.withMessage('Name must be a string')
|
||||
];
|
||||
|
||||
export const updateValidation = [
|
||||
param('id')
|
||||
.isUUID()
|
||||
.withMessage('Valid UUID is required'),
|
||||
body('name')
|
||||
.optional()
|
||||
.isString()
|
||||
.trim()
|
||||
.withMessage('Name must be a string')
|
||||
];
|
||||
|
||||
export const deleteValidation = [
|
||||
param('id')
|
||||
.isUUID()
|
||||
.withMessage('Valid UUID is required')
|
||||
];
|
||||
|
||||
export const getByIdValidation = [
|
||||
param('id')
|
||||
.isUUID()
|
||||
.withMessage('Valid UUID is required')
|
||||
];
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user