farheen-psql 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/migrations/1781264731432_create-users-table.js +37 -0
- package/migrations/1781340925498_add-password-and-role-to-users.js +26 -0
- package/migrations/1781365601015_create-rbac-tables.js +97 -0
- package/package.json +31 -0
- package/scripts/setupDb.js +42 -0
- package/src/app.js +19 -0
- package/src/config/db.js +18 -0
- package/src/constants/actions.js +6 -0
- package/src/constants/modules.js +4 -0
- package/src/constants/roles.js +4 -0
- package/src/controllers/authController.js +25 -0
- package/src/controllers/permissionController.js +69 -0
- package/src/controllers/roleController.js +104 -0
- package/src/controllers/userController.js +76 -0
- package/src/middlewares/authMiddleware.js +19 -0
- package/src/middlewares/authorizeMiddleware.js +20 -0
- package/src/models/Permission.js +27 -0
- package/src/models/Role.js +18 -0
- package/src/models/User.js +33 -0
- package/src/models/index.js +29 -0
- package/src/repositories/permissionRepository.js +26 -0
- package/src/repositories/roleRepository.js +51 -0
- package/src/repositories/userRepository.js +47 -0
- package/src/routes/authRoutes.js +9 -0
- package/src/routes/permissionRoutes.js +20 -0
- package/src/routes/roleRoutes.js +24 -0
- package/src/routes/routes.js +14 -0
- package/src/routes/userRoutes.js +28 -0
- package/src/server.js +30 -0
- package/src/services/authService.js +71 -0
- package/src/services/permissionService.js +22 -0
- package/src/services/roleService.js +44 -0
- package/src/services/userService.js +28 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @type {import('node-pg-migrate').ColumnDefinitions | undefined}
|
|
3
|
+
*/
|
|
4
|
+
export const shorthands = undefined;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param pgm {import('node-pg-migrate').MigrationBuilder}
|
|
8
|
+
* @param run {() => void | undefined}
|
|
9
|
+
* @returns {Promise<void> | void}
|
|
10
|
+
*/
|
|
11
|
+
export const up = (pgm) => {
|
|
12
|
+
pgm.createTable('users', {
|
|
13
|
+
id: 'id',
|
|
14
|
+
name: { type: 'varchar(100)', notNull: true },
|
|
15
|
+
email: { type: 'varchar(100)', notNull: true, unique: true },
|
|
16
|
+
age: { type: 'integer' },
|
|
17
|
+
created_at: {
|
|
18
|
+
type: 'timestamp',
|
|
19
|
+
notNull: true,
|
|
20
|
+
default: pgm.func('current_timestamp'),
|
|
21
|
+
},
|
|
22
|
+
updated_at: {
|
|
23
|
+
type: 'timestamp',
|
|
24
|
+
notNull: true,
|
|
25
|
+
default: pgm.func('current_timestamp'),
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param pgm {import('node-pg-migrate').MigrationBuilder}
|
|
32
|
+
* @param run {() => void | undefined}
|
|
33
|
+
* @returns {Promise<void> | void}
|
|
34
|
+
*/
|
|
35
|
+
export const down = (pgm) => {
|
|
36
|
+
pgm.dropTable('users');
|
|
37
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @type {import('node-pg-migrate').ColumnDefinitions | undefined}
|
|
3
|
+
*/
|
|
4
|
+
export const shorthands = undefined;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param pgm {import('node-pg-migrate').MigrationBuilder}
|
|
8
|
+
* @param run {() => void | undefined}
|
|
9
|
+
* @returns {Promise<void> | void}
|
|
10
|
+
*/
|
|
11
|
+
export const up = (pgm) => {
|
|
12
|
+
pgm.addColumn('users', {
|
|
13
|
+
password: { type: 'varchar(255)' },
|
|
14
|
+
role: { type: 'varchar(50)', default: 'USER', notNull: true }
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param pgm {import('node-pg-migrate').MigrationBuilder}
|
|
20
|
+
* @param run {() => void | undefined}
|
|
21
|
+
* @returns {Promise<void> | void}
|
|
22
|
+
*/
|
|
23
|
+
export const down = (pgm) => {
|
|
24
|
+
pgm.dropColumn('users', 'password');
|
|
25
|
+
pgm.dropColumn('users', 'role');
|
|
26
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @type {import('node-pg-migrate').ColumnDefinitions | undefined}
|
|
3
|
+
*/
|
|
4
|
+
export const shorthands = undefined;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param pgm {import('node-pg-migrate').MigrationBuilder}
|
|
8
|
+
* @param run {() => void | undefined}
|
|
9
|
+
* @returns {Promise<void> | void}
|
|
10
|
+
*/
|
|
11
|
+
export const up = (pgm) => {
|
|
12
|
+
// 1. Create `roles` table
|
|
13
|
+
pgm.createTable('roles', {
|
|
14
|
+
id: { type: 'serial', primaryKey: true },
|
|
15
|
+
name: { type: 'varchar(50)', notNull: true, unique: true },
|
|
16
|
+
created_at: {
|
|
17
|
+
type: 'timestamp',
|
|
18
|
+
notNull: true,
|
|
19
|
+
default: pgm.func('current_timestamp'),
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// 2. Create `permissions` table
|
|
24
|
+
pgm.createTable('permissions', {
|
|
25
|
+
id: { type: 'serial', primaryKey: true },
|
|
26
|
+
module: { type: 'varchar(50)', notNull: true },
|
|
27
|
+
action: { type: 'varchar(50)', notNull: true },
|
|
28
|
+
created_at: {
|
|
29
|
+
type: 'timestamp',
|
|
30
|
+
notNull: true,
|
|
31
|
+
default: pgm.func('current_timestamp'),
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Unique constraint to prevent duplicate permissions for the same module/action
|
|
36
|
+
pgm.addConstraint('permissions', 'unique_module_action', {
|
|
37
|
+
unique: ['module', 'action'],
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// 3. Create `role_permissions` join table
|
|
41
|
+
pgm.createTable('role_permissions', {
|
|
42
|
+
role_id: {
|
|
43
|
+
type: 'integer',
|
|
44
|
+
notNull: true,
|
|
45
|
+
references: '"roles"',
|
|
46
|
+
onDelete: 'CASCADE',
|
|
47
|
+
},
|
|
48
|
+
permission_id: {
|
|
49
|
+
type: 'integer',
|
|
50
|
+
notNull: true,
|
|
51
|
+
references: '"permissions"',
|
|
52
|
+
onDelete: 'CASCADE',
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// A role can only have a specific permission once
|
|
57
|
+
pgm.addConstraint('role_permissions', 'unique_role_permission', {
|
|
58
|
+
unique: ['role_id', 'permission_id'],
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// 4. Update `users` table: add `role_id` and drop `role` string
|
|
62
|
+
pgm.addColumn('users', {
|
|
63
|
+
role_id: {
|
|
64
|
+
type: 'integer',
|
|
65
|
+
notNull: false, // nullable because roles might be empty initially
|
|
66
|
+
references: '"roles"',
|
|
67
|
+
onDelete: 'SET NULL',
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Finally drop the old string column
|
|
72
|
+
pgm.dropColumn('users', 'role');
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @param pgm {import('node-pg-migrate').MigrationBuilder}
|
|
77
|
+
* @param run {() => void | undefined}
|
|
78
|
+
* @returns {Promise<void> | void}
|
|
79
|
+
*/
|
|
80
|
+
export const down = (pgm) => {
|
|
81
|
+
// Add back the `role` column
|
|
82
|
+
pgm.addColumn('users', {
|
|
83
|
+
role: {
|
|
84
|
+
type: 'varchar(50)',
|
|
85
|
+
notNull: false,
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Make it not null and drop role_id
|
|
90
|
+
pgm.alterColumn('users', 'role', { notNull: true, default: 'USER' });
|
|
91
|
+
pgm.dropColumn('users', 'role_id');
|
|
92
|
+
|
|
93
|
+
// Drop tables
|
|
94
|
+
pgm.dropTable('role_permissions');
|
|
95
|
+
pgm.dropTable('permissions');
|
|
96
|
+
pgm.dropTable('roles');
|
|
97
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "farheen-psql",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "server.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
8
|
+
"start": "node src/server.js",
|
|
9
|
+
"dev": "nodemon src/server.js",
|
|
10
|
+
"db:setup": "node scripts/setupDb.js",
|
|
11
|
+
"migrate": "node-pg-migrate",
|
|
12
|
+
"migrate:create": "node-pg-migrate create"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [],
|
|
15
|
+
"author": "",
|
|
16
|
+
"license": "ISC",
|
|
17
|
+
"description": "",
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"bcrypt": "^6.0.0",
|
|
20
|
+
"cors": "^2.8.6",
|
|
21
|
+
"dotenv": "^17.4.2",
|
|
22
|
+
"express": "^5.2.1",
|
|
23
|
+
"jsonwebtoken": "^9.0.3",
|
|
24
|
+
"node-pg-migrate": "^8.0.4",
|
|
25
|
+
"pg": "^8.21.0",
|
|
26
|
+
"sequelize": "^6.37.8"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"nodemon": "^3.1.14"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import pg from 'pg';
|
|
2
|
+
import dotenv from 'dotenv';
|
|
3
|
+
|
|
4
|
+
dotenv.config();
|
|
5
|
+
|
|
6
|
+
const { Client } = pg;
|
|
7
|
+
|
|
8
|
+
const setupDb = async () => {
|
|
9
|
+
// Connect to the default database (e.g., 'postgres') first to create the target db if needed
|
|
10
|
+
const adminClient = new Client({
|
|
11
|
+
user: process.env.DB_USER,
|
|
12
|
+
host: process.env.DB_HOST,
|
|
13
|
+
password: process.env.DB_PASSWORD,
|
|
14
|
+
port: process.env.DB_PORT || 5432,
|
|
15
|
+
database: 'postgres',
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
await adminClient.connect();
|
|
20
|
+
console.log('Connected to admin database.');
|
|
21
|
+
|
|
22
|
+
const dbName = process.env.DB_NAME;
|
|
23
|
+
|
|
24
|
+
// Check if the database exists
|
|
25
|
+
const checkDbQuery = `SELECT 1 FROM pg_database WHERE datname = '${dbName}'`;
|
|
26
|
+
const checkDbResult = await adminClient.query(checkDbQuery);
|
|
27
|
+
|
|
28
|
+
if (checkDbResult.rowCount === 0) {
|
|
29
|
+
console.log(`Database '${dbName}' does not exist. Creating...`);
|
|
30
|
+
await adminClient.query(`CREATE DATABASE ${dbName}`);
|
|
31
|
+
console.log(`Database '${dbName}' created successfully.`);
|
|
32
|
+
} else {
|
|
33
|
+
console.log(`Database '${dbName}' already exists.`);
|
|
34
|
+
}
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error('Error checking/creating database:', error);
|
|
37
|
+
} finally {
|
|
38
|
+
await adminClient.end();
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
setupDb();
|
package/src/app.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import cors from 'cors';
|
|
3
|
+
import routes from './routes/routes.js';
|
|
4
|
+
|
|
5
|
+
const app = express();
|
|
6
|
+
|
|
7
|
+
// Middleware
|
|
8
|
+
app.use(cors());
|
|
9
|
+
app.use(express.json());
|
|
10
|
+
|
|
11
|
+
// Routes
|
|
12
|
+
app.use('/api', routes);
|
|
13
|
+
|
|
14
|
+
// Basic health check endpoint
|
|
15
|
+
app.get('/health', (req, res) => {
|
|
16
|
+
res.status(200).json({ status: 'ok', message: 'Server is healthy' });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export default app;
|
package/src/config/db.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Sequelize } from 'sequelize';
|
|
2
|
+
import dotenv from 'dotenv';
|
|
3
|
+
|
|
4
|
+
dotenv.config();
|
|
5
|
+
|
|
6
|
+
const sequelize = new Sequelize(
|
|
7
|
+
process.env.DB_NAME,
|
|
8
|
+
process.env.DB_USER,
|
|
9
|
+
process.env.DB_PASSWORD,
|
|
10
|
+
{
|
|
11
|
+
host: process.env.DB_HOST,
|
|
12
|
+
dialect: 'postgres',
|
|
13
|
+
port: process.env.DB_PORT || 5432,
|
|
14
|
+
logging: false, // Set to console.log to see SQL queries
|
|
15
|
+
}
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
export default sequelize;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as authService from '../services/authService.js';
|
|
2
|
+
|
|
3
|
+
export const register = async (req, res) => {
|
|
4
|
+
try {
|
|
5
|
+
const user = await authService.register(req.body);
|
|
6
|
+
res.status(201).json(user);
|
|
7
|
+
} catch (error) {
|
|
8
|
+
console.error('Registration error:', error);
|
|
9
|
+
res.status(400).json({ error: error.message || 'Registration failed' });
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const login = async (req, res) => {
|
|
14
|
+
try {
|
|
15
|
+
const { email, password } = req.body;
|
|
16
|
+
if (!email || !password) {
|
|
17
|
+
return res.status(400).json({ error: 'Email and password are required' });
|
|
18
|
+
}
|
|
19
|
+
const result = await authService.login(email, password);
|
|
20
|
+
res.status(200).json(result);
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error('Login error:', error);
|
|
23
|
+
res.status(401).json({ error: error.message || 'Login failed' });
|
|
24
|
+
}
|
|
25
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import * as permissionService from '../services/permissionService.js';
|
|
2
|
+
|
|
3
|
+
export const getAllPermissions = async (req, res) => {
|
|
4
|
+
try {
|
|
5
|
+
const permissions = await permissionService.getAllPermissions();
|
|
6
|
+
res.status(200).json(permissions);
|
|
7
|
+
} catch (error) {
|
|
8
|
+
console.error('Error fetching permissions:', error);
|
|
9
|
+
res.status(500).json({ error: 'Internal Server Error' });
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const getPermissionById = async (req, res) => {
|
|
14
|
+
try {
|
|
15
|
+
const { id } = req.params;
|
|
16
|
+
const permission = await permissionService.getPermissionById(id);
|
|
17
|
+
if (!permission) {
|
|
18
|
+
return res.status(404).json({ error: 'Permission not found' });
|
|
19
|
+
}
|
|
20
|
+
res.status(200).json(permission);
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error('Error fetching permission:', error);
|
|
23
|
+
res.status(500).json({ error: 'Internal Server Error' });
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const createPermission = async (req, res) => {
|
|
28
|
+
try {
|
|
29
|
+
const { module, action } = req.body;
|
|
30
|
+
if (!module || !action) {
|
|
31
|
+
return res.status(400).json({ error: 'module and action are required' });
|
|
32
|
+
}
|
|
33
|
+
const newPermission = await permissionService.createPermission({ module, action });
|
|
34
|
+
res.status(201).json(newPermission);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error('Error creating permission:', error);
|
|
37
|
+
res.status(500).json({ error: 'Internal Server Error' });
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const updatePermission = async (req, res) => {
|
|
42
|
+
try {
|
|
43
|
+
const { id } = req.params;
|
|
44
|
+
const { module, action } = req.body;
|
|
45
|
+
|
|
46
|
+
const updatedPermission = await permissionService.updatePermission(id, { module, action });
|
|
47
|
+
if (!updatedPermission) {
|
|
48
|
+
return res.status(404).json({ error: 'Permission not found' });
|
|
49
|
+
}
|
|
50
|
+
res.status(200).json(updatedPermission);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error('Error updating permission:', error);
|
|
53
|
+
res.status(500).json({ error: 'Internal Server Error' });
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const deletePermission = async (req, res) => {
|
|
58
|
+
try {
|
|
59
|
+
const { id } = req.params;
|
|
60
|
+
const deletedPermission = await permissionService.deletePermission(id);
|
|
61
|
+
if (!deletedPermission) {
|
|
62
|
+
return res.status(404).json({ error: 'Permission not found' });
|
|
63
|
+
}
|
|
64
|
+
res.status(200).json({ message: 'Permission deleted successfully', permission: deletedPermission });
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error('Error deleting permission:', error);
|
|
67
|
+
res.status(500).json({ error: 'Internal Server Error' });
|
|
68
|
+
}
|
|
69
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import * as roleService from '../services/roleService.js';
|
|
2
|
+
|
|
3
|
+
export const getAllRoles = async (req, res) => {
|
|
4
|
+
try {
|
|
5
|
+
const roles = await roleService.getAllRoles();
|
|
6
|
+
res.status(200).json(roles);
|
|
7
|
+
} catch (error) {
|
|
8
|
+
console.error('Error fetching roles:', error);
|
|
9
|
+
res.status(500).json({ error: 'Internal Server Error' });
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const getRoleById = async (req, res) => {
|
|
14
|
+
try {
|
|
15
|
+
const { id } = req.params;
|
|
16
|
+
const role = await roleService.getRoleById(id);
|
|
17
|
+
if (!role) {
|
|
18
|
+
return res.status(404).json({ error: 'Role not found' });
|
|
19
|
+
}
|
|
20
|
+
res.status(200).json(role);
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error('Error fetching role:', error);
|
|
23
|
+
res.status(500).json({ error: 'Internal Server Error' });
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const createRole = async (req, res) => {
|
|
28
|
+
try {
|
|
29
|
+
const { name } = req.body;
|
|
30
|
+
if (!name) {
|
|
31
|
+
return res.status(400).json({ error: 'Role name is required' });
|
|
32
|
+
}
|
|
33
|
+
const newRole = await roleService.createRole({ name });
|
|
34
|
+
res.status(201).json(newRole);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error('Error creating role:', error);
|
|
37
|
+
if (error.message === 'Role name already exists') {
|
|
38
|
+
return res.status(400).json({ error: error.message });
|
|
39
|
+
}
|
|
40
|
+
res.status(500).json({ error: 'Internal Server Error' });
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const updateRole = async (req, res) => {
|
|
45
|
+
try {
|
|
46
|
+
const { id } = req.params;
|
|
47
|
+
const { name } = req.body;
|
|
48
|
+
|
|
49
|
+
const updatedRole = await roleService.updateRole(id, { name });
|
|
50
|
+
if (!updatedRole) {
|
|
51
|
+
return res.status(404).json({ error: 'Role not found' });
|
|
52
|
+
}
|
|
53
|
+
res.status(200).json(updatedRole);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error('Error updating role:', error);
|
|
56
|
+
res.status(500).json({ error: 'Internal Server Error' });
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const deleteRole = async (req, res) => {
|
|
61
|
+
try {
|
|
62
|
+
const { id } = req.params;
|
|
63
|
+
const deletedRole = await roleService.deleteRole(id);
|
|
64
|
+
if (!deletedRole) {
|
|
65
|
+
return res.status(404).json({ error: 'Role not found' });
|
|
66
|
+
}
|
|
67
|
+
res.status(200).json({ message: 'Role deleted successfully', role: deletedRole });
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error('Error deleting role:', error);
|
|
70
|
+
res.status(500).json({ error: 'Internal Server Error' });
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// Custom controller to assign multiple permissions to a single role
|
|
75
|
+
export const assignPermissions = async (req, res) => {
|
|
76
|
+
try {
|
|
77
|
+
const { id } = req.params; // Role ID
|
|
78
|
+
const { permissionIds } = req.body; // Array of Permission IDs
|
|
79
|
+
|
|
80
|
+
if (!Array.isArray(permissionIds)) {
|
|
81
|
+
return res.status(400).json({ error: 'permissionIds must be an array of integers' });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const updatedRolePermissions = await roleService.assignPermissions(id, permissionIds);
|
|
85
|
+
res.status(200).json({ message: 'Permissions assigned', permissions: updatedRolePermissions });
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error('Error assigning permissions:', error);
|
|
88
|
+
if (error.message === 'Role not found') {
|
|
89
|
+
return res.status(404).json({ error: error.message });
|
|
90
|
+
}
|
|
91
|
+
res.status(500).json({ error: 'Internal Server Error' });
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const getRolePermissions = async (req, res) => {
|
|
96
|
+
try {
|
|
97
|
+
const { id } = req.params; // Role ID
|
|
98
|
+
const permissions = await roleService.getRolePermissions(id);
|
|
99
|
+
res.status(200).json(permissions);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.error('Error fetching role permissions:', error);
|
|
102
|
+
res.status(500).json({ error: 'Internal Server Error' });
|
|
103
|
+
}
|
|
104
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as userService from '../services/userService.js';
|
|
2
|
+
|
|
3
|
+
export const getAllUsers = async (req, res) => {
|
|
4
|
+
try {
|
|
5
|
+
const users = await userService.getAllUsers();
|
|
6
|
+
res.status(200).json(users);
|
|
7
|
+
} catch (error) {
|
|
8
|
+
console.error('Error fetching users:', error);
|
|
9
|
+
res.status(500).json({ error: 'Internal Server Error' });
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const getUserById = async (req, res) => {
|
|
14
|
+
try {
|
|
15
|
+
const { id } = req.params;
|
|
16
|
+
const user = await userService.getUserById(id);
|
|
17
|
+
if (!user) {
|
|
18
|
+
return res.status(404).json({ error: 'User not found' });
|
|
19
|
+
}
|
|
20
|
+
res.status(200).json(user);
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error('Error fetching user:', error);
|
|
23
|
+
res.status(500).json({ error: 'Internal Server Error' });
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const createUser = async (req, res) => {
|
|
28
|
+
try {
|
|
29
|
+
const { name, email, age } = req.body;
|
|
30
|
+
if (!name || !email) {
|
|
31
|
+
return res.status(400).json({ error: 'Name and email are required' });
|
|
32
|
+
}
|
|
33
|
+
const newUser = await userService.createUser({ name, email, age });
|
|
34
|
+
res.status(201).json(newUser);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error('Error creating user:', error);
|
|
37
|
+
// Handle specific service errors
|
|
38
|
+
if (error.message === 'Age cannot be negative') {
|
|
39
|
+
return res.status(400).json({ error: error.message });
|
|
40
|
+
}
|
|
41
|
+
res.status(500).json({ error: 'Internal Server Error' });
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const updateUser = async (req, res) => {
|
|
46
|
+
try {
|
|
47
|
+
const { id } = req.params;
|
|
48
|
+
const { name, email, age } = req.body;
|
|
49
|
+
|
|
50
|
+
const updatedUser = await userService.updateUser(id, { name, email, age });
|
|
51
|
+
if (!updatedUser) {
|
|
52
|
+
return res.status(404).json({ error: 'User not found' });
|
|
53
|
+
}
|
|
54
|
+
res.status(200).json(updatedUser);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.error('Error updating user:', error);
|
|
57
|
+
if (error.message === 'Age cannot be negative') {
|
|
58
|
+
return res.status(400).json({ error: error.message });
|
|
59
|
+
}
|
|
60
|
+
res.status(500).json({ error: 'Internal Server Error' });
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const deleteUser = async (req, res) => {
|
|
65
|
+
try {
|
|
66
|
+
const { id } = req.params;
|
|
67
|
+
const deletedUser = await userService.deleteUser(id);
|
|
68
|
+
if (!deletedUser) {
|
|
69
|
+
return res.status(404).json({ error: 'User not found' });
|
|
70
|
+
}
|
|
71
|
+
res.status(200).json({ message: 'User deleted successfully', user: deletedUser });
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error('Error deleting user:', error);
|
|
74
|
+
res.status(500).json({ error: 'Internal Server Error' });
|
|
75
|
+
}
|
|
76
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import jwt from 'jsonwebtoken';
|
|
2
|
+
|
|
3
|
+
export const authMiddleware = (req, res, next) => {
|
|
4
|
+
const authHeader = req.headers.authorization;
|
|
5
|
+
|
|
6
|
+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
7
|
+
return res.status(401).json({ error: 'Unauthorized: No token provided' });
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const token = authHeader.split(' ')[1];
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'secret');
|
|
14
|
+
req.user = decoded;
|
|
15
|
+
next();
|
|
16
|
+
} catch (error) {
|
|
17
|
+
return res.status(401).json({ error: 'Unauthorized: Invalid token' });
|
|
18
|
+
}
|
|
19
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ROLES } from '../constants/roles.js';
|
|
2
|
+
|
|
3
|
+
export const authorizeMiddleware = (moduleName, actionName) => {
|
|
4
|
+
return (req, res, next) => {
|
|
5
|
+
// Ensure the user exists and has permissions attached (from token)
|
|
6
|
+
if (!req.user || !req.user.permissions) {
|
|
7
|
+
return res.status(401).json({ error: 'Unauthorized: User permissions not found' });
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const requiredPermission = `${moduleName}:${actionName}`;
|
|
11
|
+
|
|
12
|
+
// Check if the user's token permissions array includes the required one
|
|
13
|
+
if (!req.user.permissions.includes(requiredPermission)) {
|
|
14
|
+
return res.status(403).json({ error: `Forbidden: Missing '${requiredPermission}' permission` });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Passed all checks!
|
|
18
|
+
next();
|
|
19
|
+
};
|
|
20
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { DataTypes } from 'sequelize';
|
|
2
|
+
import sequelize from '../config/db.js';
|
|
3
|
+
|
|
4
|
+
// This is exactly like mongoose.Schema({ ... })
|
|
5
|
+
const Permission = sequelize.define('Permission', {
|
|
6
|
+
module: {
|
|
7
|
+
type: DataTypes.STRING(50),
|
|
8
|
+
allowNull: false,
|
|
9
|
+
},
|
|
10
|
+
action: {
|
|
11
|
+
type: DataTypes.STRING(50),
|
|
12
|
+
allowNull: false,
|
|
13
|
+
}
|
|
14
|
+
}, {
|
|
15
|
+
tableName: 'permissions',
|
|
16
|
+
timestamps: true,
|
|
17
|
+
createdAt: 'created_at',
|
|
18
|
+
updatedAt: false,
|
|
19
|
+
indexes: [
|
|
20
|
+
{
|
|
21
|
+
unique: true,
|
|
22
|
+
fields: ['module', 'action']
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export default Permission;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { DataTypes } from 'sequelize';
|
|
2
|
+
import sequelize from '../config/db.js';
|
|
3
|
+
|
|
4
|
+
// This is exactly like mongoose.Schema({ ... })
|
|
5
|
+
const Role = sequelize.define('Role', {
|
|
6
|
+
name: {
|
|
7
|
+
type: DataTypes.STRING(50),
|
|
8
|
+
allowNull: false,
|
|
9
|
+
unique: true,
|
|
10
|
+
}
|
|
11
|
+
}, {
|
|
12
|
+
tableName: 'roles',
|
|
13
|
+
timestamps: true, // Adds created_at
|
|
14
|
+
createdAt: 'created_at',
|
|
15
|
+
updatedAt: false, // We didn't define updated_at for roles
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export default Role;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { DataTypes } from 'sequelize';
|
|
2
|
+
import sequelize from '../config/db.js';
|
|
3
|
+
|
|
4
|
+
// ---------------------------------------------------------
|
|
5
|
+
// This is exactly like mongoose.Schema({ ... })
|
|
6
|
+
// ---------------------------------------------------------
|
|
7
|
+
const User = sequelize.define('User', {
|
|
8
|
+
name: {
|
|
9
|
+
type: DataTypes.STRING(100),
|
|
10
|
+
allowNull: false, // This is like required: true in Mongoose
|
|
11
|
+
},
|
|
12
|
+
email: {
|
|
13
|
+
type: DataTypes.STRING(100),
|
|
14
|
+
allowNull: false,
|
|
15
|
+
unique: true, // Same as Mongoose unique: true
|
|
16
|
+
},
|
|
17
|
+
age: {
|
|
18
|
+
type: DataTypes.INTEGER,
|
|
19
|
+
},
|
|
20
|
+
password: {
|
|
21
|
+
type: DataTypes.STRING(255),
|
|
22
|
+
},
|
|
23
|
+
role_id: {
|
|
24
|
+
type: DataTypes.INTEGER,
|
|
25
|
+
}
|
|
26
|
+
}, {
|
|
27
|
+
tableName: 'users',
|
|
28
|
+
timestamps: true, // Auto-manages created_at and updated_at
|
|
29
|
+
createdAt: 'created_at',
|
|
30
|
+
updatedAt: 'updated_at',
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export default User;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import User from './User.js';
|
|
2
|
+
import Role from './Role.js';
|
|
3
|
+
import Permission from './Permission.js';
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------
|
|
6
|
+
// RELATIONSHIPS
|
|
7
|
+
// ---------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
// 1 User belongs to 1 Role
|
|
10
|
+
User.belongsTo(Role, { foreignKey: 'role_id' });
|
|
11
|
+
// 1 Role has many Users
|
|
12
|
+
Role.hasMany(User, { foreignKey: 'role_id' });
|
|
13
|
+
|
|
14
|
+
// Many-to-Many: 1 Role has Many Permissions, and 1 Permission belongs to Many Roles
|
|
15
|
+
Role.belongsToMany(Permission, {
|
|
16
|
+
through: 'role_permissions',
|
|
17
|
+
foreignKey: 'role_id',
|
|
18
|
+
otherKey: 'permission_id',
|
|
19
|
+
timestamps: false
|
|
20
|
+
});
|
|
21
|
+
Permission.belongsToMany(Role, {
|
|
22
|
+
through: 'role_permissions',
|
|
23
|
+
foreignKey: 'permission_id',
|
|
24
|
+
otherKey: 'role_id',
|
|
25
|
+
timestamps: false
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Export all models so they can be imported together from this file
|
|
29
|
+
export { User, Role, Permission };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Permission } from '../models/index.js';
|
|
2
|
+
|
|
3
|
+
export const findAll = async () => {
|
|
4
|
+
return await Permission.findAll({ order: [['created_at', 'DESC']] });
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const findById = async (id) => {
|
|
8
|
+
return await Permission.findByPk(id);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const create = async (permissionData) => {
|
|
12
|
+
return await Permission.create(permissionData);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const update = async (id, permissionData) => {
|
|
16
|
+
const permission = await Permission.findByPk(id);
|
|
17
|
+
if (!permission) return null;
|
|
18
|
+
return await permission.update(permissionData);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const remove = async (id) => {
|
|
22
|
+
const permission = await Permission.findByPk(id);
|
|
23
|
+
if (!permission) return null;
|
|
24
|
+
await permission.destroy();
|
|
25
|
+
return permission;
|
|
26
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Role, Permission } from '../models/index.js';
|
|
2
|
+
|
|
3
|
+
export const findAll = async () => {
|
|
4
|
+
return await Role.findAll({ order: [['created_at', 'DESC']] });
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const findById = async (id) => {
|
|
8
|
+
return await Role.findByPk(id);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const findByName = async (name) => {
|
|
12
|
+
return await Role.findOne({ where: { name } });
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const create = async (roleData) => {
|
|
16
|
+
return await Role.create(roleData);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const update = async (id, roleData) => {
|
|
20
|
+
const role = await Role.findByPk(id);
|
|
21
|
+
if (!role) return null;
|
|
22
|
+
return await role.update(roleData);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const remove = async (id) => {
|
|
26
|
+
const role = await Role.findByPk(id);
|
|
27
|
+
if (!role) return null;
|
|
28
|
+
await role.destroy();
|
|
29
|
+
return role;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const assignPermissionsToRole = async (roleId, permissionIds) => {
|
|
33
|
+
const role = await Role.findByPk(roleId);
|
|
34
|
+
if (!role) return null;
|
|
35
|
+
|
|
36
|
+
// Sequelize auto-generates setPermissions for belongsToMany relationships
|
|
37
|
+
await role.setPermissions(permissionIds);
|
|
38
|
+
return role;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const getRolePermissions = async (roleId) => {
|
|
42
|
+
const role = await Role.findByPk(roleId, {
|
|
43
|
+
include: [{
|
|
44
|
+
model: Permission,
|
|
45
|
+
through: { attributes: [] }
|
|
46
|
+
}]
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (!role) return [];
|
|
50
|
+
return role.Permissions;
|
|
51
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { User } from '../models/index.js';
|
|
2
|
+
|
|
3
|
+
export const findAll = async () => {
|
|
4
|
+
return await User.findAll({
|
|
5
|
+
order: [['created_at', 'DESC']],
|
|
6
|
+
attributes: { exclude: ['password'] }
|
|
7
|
+
});
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const findById = async (id) => {
|
|
11
|
+
return await User.findByPk(id, {
|
|
12
|
+
attributes: { exclude: ['password'] }
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const findByEmail = async (email) => {
|
|
17
|
+
return await User.findOne({ where: { email } });
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const create = async (userData) => {
|
|
21
|
+
const user = await User.create(userData);
|
|
22
|
+
const userObj = user.toJSON();
|
|
23
|
+
delete userObj.password;
|
|
24
|
+
return userObj;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const update = async (id, userData) => {
|
|
28
|
+
const user = await User.findByPk(id);
|
|
29
|
+
if (!user) return null;
|
|
30
|
+
|
|
31
|
+
await user.update(userData);
|
|
32
|
+
|
|
33
|
+
const userObj = user.toJSON();
|
|
34
|
+
delete userObj.password;
|
|
35
|
+
return userObj;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const remove = async (id) => {
|
|
39
|
+
const user = await User.findByPk(id);
|
|
40
|
+
if (!user) return null;
|
|
41
|
+
|
|
42
|
+
await user.destroy();
|
|
43
|
+
|
|
44
|
+
const userObj = user.toJSON();
|
|
45
|
+
delete userObj.password;
|
|
46
|
+
return userObj;
|
|
47
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import * as authController from '../controllers/authController.js';
|
|
3
|
+
|
|
4
|
+
const router = express.Router();
|
|
5
|
+
|
|
6
|
+
router.post('/register', authController.register);
|
|
7
|
+
router.post('/login', authController.login);
|
|
8
|
+
|
|
9
|
+
export default router;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import * as permissionController from '../controllers/permissionController.js';
|
|
3
|
+
import { authMiddleware } from '../middlewares/authMiddleware.js';
|
|
4
|
+
import { authorizeMiddleware } from '../middlewares/authorizeMiddleware.js';
|
|
5
|
+
import { MODULES } from '../constants/modules.js';
|
|
6
|
+
import { ACTIONS } from '../constants/actions.js';
|
|
7
|
+
|
|
8
|
+
const router = express.Router();
|
|
9
|
+
|
|
10
|
+
// Apply authMiddleware to all permission routes
|
|
11
|
+
router.use(authMiddleware);
|
|
12
|
+
|
|
13
|
+
// Restrict all permission management to users who have PERMISSIONS module permissions
|
|
14
|
+
router.get('/', authorizeMiddleware(MODULES.PERMISSIONS, ACTIONS.READ), permissionController.getAllPermissions);
|
|
15
|
+
router.get('/:id', authorizeMiddleware(MODULES.PERMISSIONS, ACTIONS.READ), permissionController.getPermissionById);
|
|
16
|
+
router.post('/', authorizeMiddleware(MODULES.PERMISSIONS, ACTIONS.CREATE), permissionController.createPermission);
|
|
17
|
+
router.put('/:id', authorizeMiddleware(MODULES.PERMISSIONS, ACTIONS.UPDATE), permissionController.updatePermission);
|
|
18
|
+
router.delete('/:id', authorizeMiddleware(MODULES.PERMISSIONS, ACTIONS.DELETE), permissionController.deletePermission);
|
|
19
|
+
|
|
20
|
+
export default router;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import * as roleController from '../controllers/roleController.js';
|
|
3
|
+
import { authMiddleware } from '../middlewares/authMiddleware.js';
|
|
4
|
+
import { authorizeMiddleware } from '../middlewares/authorizeMiddleware.js';
|
|
5
|
+
import { MODULES } from '../constants/modules.js';
|
|
6
|
+
import { ACTIONS } from '../constants/actions.js';
|
|
7
|
+
|
|
8
|
+
const router = express.Router();
|
|
9
|
+
|
|
10
|
+
// Apply authMiddleware to all role routes
|
|
11
|
+
router.use(authMiddleware);
|
|
12
|
+
|
|
13
|
+
// Restrict all role management to users who have ROLES module permissions
|
|
14
|
+
router.get('/', authorizeMiddleware(MODULES.ROLES, ACTIONS.READ), roleController.getAllRoles);
|
|
15
|
+
router.get('/:id', authorizeMiddleware(MODULES.ROLES, ACTIONS.READ), roleController.getRoleById);
|
|
16
|
+
router.post('/', authorizeMiddleware(MODULES.ROLES, ACTIONS.CREATE), roleController.createRole);
|
|
17
|
+
router.put('/:id', authorizeMiddleware(MODULES.ROLES, ACTIONS.UPDATE), roleController.updateRole);
|
|
18
|
+
router.delete('/:id', authorizeMiddleware(MODULES.ROLES, ACTIONS.DELETE), roleController.deleteRole);
|
|
19
|
+
|
|
20
|
+
// Special endpoints for managing permissions of a role
|
|
21
|
+
router.get('/:id/permissions', authorizeMiddleware(MODULES.ROLES, ACTIONS.READ), roleController.getRolePermissions);
|
|
22
|
+
router.post('/:id/permissions', authorizeMiddleware(MODULES.ROLES, ACTIONS.UPDATE), roleController.assignPermissions);
|
|
23
|
+
|
|
24
|
+
export default router;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import authRoutes from './authRoutes.js';
|
|
3
|
+
import userRoutes from './userRoutes.js';
|
|
4
|
+
import roleRoutes from './roleRoutes.js';
|
|
5
|
+
import permissionRoutes from './permissionRoutes.js';
|
|
6
|
+
|
|
7
|
+
const router = express.Router();
|
|
8
|
+
|
|
9
|
+
router.use('/auth', authRoutes);
|
|
10
|
+
router.use('/user', userRoutes);
|
|
11
|
+
router.use('/roles', roleRoutes);
|
|
12
|
+
router.use('/permissions', permissionRoutes);
|
|
13
|
+
|
|
14
|
+
export default router;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import * as userController from '../controllers/userController.js';
|
|
3
|
+
import { authMiddleware } from '../middlewares/authMiddleware.js';
|
|
4
|
+
import { authorizeMiddleware } from '../middlewares/authorizeMiddleware.js';
|
|
5
|
+
import { MODULES } from '../constants/modules.js';
|
|
6
|
+
import { ACTIONS } from '../constants/actions.js';
|
|
7
|
+
|
|
8
|
+
const router = express.Router();
|
|
9
|
+
|
|
10
|
+
// Apply authMiddleware to all user routes
|
|
11
|
+
router.use(authMiddleware);
|
|
12
|
+
|
|
13
|
+
// GET all users
|
|
14
|
+
router.get('/', authorizeMiddleware(MODULES.USERS, ACTIONS.READ), userController.getAllUsers);
|
|
15
|
+
|
|
16
|
+
// GET a user by ID
|
|
17
|
+
router.get('/:id', authorizeMiddleware(MODULES.USERS, ACTIONS.READ), userController.getUserById);
|
|
18
|
+
|
|
19
|
+
// POST a new user
|
|
20
|
+
router.post('/', authorizeMiddleware(MODULES.USERS, ACTIONS.CREATE), userController.createUser);
|
|
21
|
+
|
|
22
|
+
// PUT (update) a user
|
|
23
|
+
router.put('/:id', authorizeMiddleware(MODULES.USERS, ACTIONS.UPDATE), userController.updateUser);
|
|
24
|
+
|
|
25
|
+
// DELETE a user
|
|
26
|
+
router.delete('/:id', authorizeMiddleware(MODULES.USERS, ACTIONS.DELETE), userController.deleteUser);
|
|
27
|
+
|
|
28
|
+
export default router;
|
package/src/server.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import app from './app.js';
|
|
2
|
+
import dotenv from 'dotenv';
|
|
3
|
+
import sequelize from './config/db.js';
|
|
4
|
+
import './models/index.js'; // Import models and relationships so Sequelize knows about them before syncing
|
|
5
|
+
|
|
6
|
+
dotenv.config();
|
|
7
|
+
|
|
8
|
+
const PORT = process.env.PORT || 3000;
|
|
9
|
+
|
|
10
|
+
const startServer = async () => {
|
|
11
|
+
try {
|
|
12
|
+
// Check database connection and sync models automatically
|
|
13
|
+
await sequelize.authenticate();
|
|
14
|
+
console.log('✅ Connected to PostgreSQL Database successfully with Sequelize');
|
|
15
|
+
|
|
16
|
+
// Auto-sync database (creates/alters tables based on Models)
|
|
17
|
+
// Warning: { alter: true } is great for dev, but in production use proper migrations
|
|
18
|
+
await sequelize.sync({ alter: true });
|
|
19
|
+
console.log('✅ Database schemas synchronized');
|
|
20
|
+
|
|
21
|
+
app.listen(PORT, () => {
|
|
22
|
+
console.log(`🚀 Server is running on http://localhost:${PORT}`);
|
|
23
|
+
});
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.error('❌ Unable to connect to the database:', error);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
startServer();
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import bcrypt from 'bcrypt';
|
|
2
|
+
import jwt from 'jsonwebtoken';
|
|
3
|
+
import * as userRepository from '../repositories/userRepository.js';
|
|
4
|
+
import * as roleRepository from '../repositories/roleRepository.js';
|
|
5
|
+
|
|
6
|
+
export const register = async (userData) => {
|
|
7
|
+
const { name, email, password, age, role_id } = userData;
|
|
8
|
+
|
|
9
|
+
const existingUser = await userRepository.findByEmail(email);
|
|
10
|
+
if (existingUser) {
|
|
11
|
+
throw new Error('Email already in use');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Find the ID for the default USER role, if not provided
|
|
15
|
+
let assignedRoleId = role_id;
|
|
16
|
+
if (!assignedRoleId) {
|
|
17
|
+
const defaultRole = await roleRepository.findByName('USER');
|
|
18
|
+
assignedRoleId = defaultRole ? defaultRole.id : null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const hashedPassword = await bcrypt.hash(password, 10);
|
|
22
|
+
|
|
23
|
+
const newUser = await userRepository.create({
|
|
24
|
+
name,
|
|
25
|
+
email,
|
|
26
|
+
password: hashedPassword,
|
|
27
|
+
age,
|
|
28
|
+
role_id: assignedRoleId
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Convert Sequelize model to plain JSON before modifying
|
|
32
|
+
const userObj = newUser.toJSON();
|
|
33
|
+
delete userObj.password;
|
|
34
|
+
return userObj;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const login = async (email, password) => {
|
|
38
|
+
const user = await userRepository.findByEmail(email);
|
|
39
|
+
|
|
40
|
+
if (!user) {
|
|
41
|
+
throw new Error('Invalid email or password');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const isMatch = await bcrypt.compare(password, user.password);
|
|
45
|
+
|
|
46
|
+
if (!isMatch) {
|
|
47
|
+
throw new Error('Invalid email or password');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Fetch the role name and exact permissions from the database
|
|
51
|
+
const permissions = await roleRepository.getRolePermissions(user.role_id);
|
|
52
|
+
|
|
53
|
+
// Format permissions into an array of string identifiers (e.g. ['USERS:READ'])
|
|
54
|
+
const permissionStrings = permissions.map(p => `${p.module}:${p.action}`);
|
|
55
|
+
|
|
56
|
+
const token = jwt.sign(
|
|
57
|
+
{
|
|
58
|
+
id: user.id,
|
|
59
|
+
email: user.email,
|
|
60
|
+
role_id: user.role_id,
|
|
61
|
+
permissions: permissionStrings // Embed DB-driven permissions into the token!
|
|
62
|
+
},
|
|
63
|
+
process.env.JWT_SECRET || 'secret',
|
|
64
|
+
{ expiresIn: '1d' }
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const userObj = user.toJSON();
|
|
68
|
+
delete userObj.password;
|
|
69
|
+
|
|
70
|
+
return { user: userObj, token };
|
|
71
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as permissionRepository from '../repositories/permissionRepository.js';
|
|
2
|
+
|
|
3
|
+
export const getAllPermissions = async () => {
|
|
4
|
+
return await permissionRepository.findAll();
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const getPermissionById = async (id) => {
|
|
8
|
+
return await permissionRepository.findById(id);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const createPermission = async (permissionData) => {
|
|
12
|
+
// In a real app you might want to check if the module+action combo already exists
|
|
13
|
+
return await permissionRepository.create(permissionData);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const updatePermission = async (id, permissionData) => {
|
|
17
|
+
return await permissionRepository.update(id, permissionData);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const deletePermission = async (id) => {
|
|
21
|
+
return await permissionRepository.remove(id);
|
|
22
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as roleRepository from '../repositories/roleRepository.js';
|
|
2
|
+
|
|
3
|
+
export const getAllRoles = async () => {
|
|
4
|
+
return await roleRepository.findAll();
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const getRoleById = async (id) => {
|
|
8
|
+
return await roleRepository.findById(id);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const createRole = async (roleData) => {
|
|
12
|
+
// Prevent duplicate roles
|
|
13
|
+
const existingRole = await roleRepository.findByName(roleData.name);
|
|
14
|
+
if (existingRole) {
|
|
15
|
+
throw new Error('Role name already exists');
|
|
16
|
+
}
|
|
17
|
+
return await roleRepository.create(roleData);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const updateRole = async (id, roleData) => {
|
|
21
|
+
return await roleRepository.update(id, roleData);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const deleteRole = async (id) => {
|
|
25
|
+
return await roleRepository.remove(id);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const assignPermissions = async (roleId, permissionIds) => {
|
|
29
|
+
// Ensure role exists
|
|
30
|
+
const role = await roleRepository.findById(roleId);
|
|
31
|
+
if (!role) {
|
|
32
|
+
throw new Error('Role not found');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Update permissions in join table
|
|
36
|
+
await roleRepository.assignPermissionsToRole(roleId, permissionIds);
|
|
37
|
+
|
|
38
|
+
// Return the updated role with its permissions
|
|
39
|
+
return await roleRepository.getRolePermissions(roleId);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const getRolePermissions = async (roleId) => {
|
|
43
|
+
return await roleRepository.getRolePermissions(roleId);
|
|
44
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as userRepository from '../repositories/userRepository.js';
|
|
2
|
+
|
|
3
|
+
export const getAllUsers = async () => {
|
|
4
|
+
return await userRepository.findAll();
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const getUserById = async (id) => {
|
|
8
|
+
return await userRepository.findById(id);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const createUser = async (userData) => {
|
|
12
|
+
// Example of business logic in service
|
|
13
|
+
if (userData.age && userData.age < 0) {
|
|
14
|
+
throw new Error('Age cannot be negative');
|
|
15
|
+
}
|
|
16
|
+
return await userRepository.create(userData);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const updateUser = async (id, userData) => {
|
|
20
|
+
if (userData.age && userData.age < 0) {
|
|
21
|
+
throw new Error('Age cannot be negative');
|
|
22
|
+
}
|
|
23
|
+
return await userRepository.update(id, userData);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const deleteUser = async (id) => {
|
|
27
|
+
return await userRepository.remove(id);
|
|
28
|
+
};
|