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.
@@ -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;
@@ -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,6 @@
1
+ export const ACTIONS = {
2
+ CREATE: 'CREATE',
3
+ READ: 'READ',
4
+ UPDATE: 'UPDATE',
5
+ DELETE: 'DELETE',
6
+ };
@@ -0,0 +1,4 @@
1
+ export const MODULES = {
2
+ USERS: 'USERS',
3
+ POSTS: 'POSTS', // Example for future
4
+ };
@@ -0,0 +1,4 @@
1
+ export const ROLES = {
2
+ ADMIN: 'ADMIN',
3
+ USER: 'USER',
4
+ };
@@ -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
+ };