Initial commit

This commit is contained in:
liyaqath
2026-06-30 11:40:07 +05:30
commit bcb937dd26
286 changed files with 15918 additions and 0 deletions
+12
View File
@@ -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
View File
@@ -0,0 +1,11 @@
node_modules/
.env
.DS_Store
dist/
build/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.idea/
.vscode/
*.log
+40
View File
@@ -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;
+37
View File
@@ -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();
+3624
View File
File diff suppressed because it is too large Load Diff
+41
View File
@@ -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';
+8
View File
@@ -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';
+8
View File
@@ -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';
+8
View File
@@ -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();
+38
View File
@@ -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();
+126
View File
@@ -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')
];
+5
View File
@@ -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';
+8
View File
@@ -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')
];
+5
View File
@@ -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';
+8
View File
@@ -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';
+8
View File
@@ -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')
];
+5
View File
@@ -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';
+8
View File
@@ -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;
+8
View File
@@ -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;
+5
View File
@@ -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')
];
+5
View File
@@ -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';
+8
View File
@@ -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')
];
+5
View File
@@ -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';
+8
View File
@@ -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;
+47
View File
@@ -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);
}
+8
View File
@@ -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