--- /dev/null
+/**
+ * @file Role controller for handling role-related API requests
+ */
+
+const db = require( '../models' );
+const createError = require( 'http-errors' );
+
+/**
+ * Role controller
+ * @type {Object}
+ */
+module.exports = {
+ /**
+ * Create a new role
+ * @param {Object} req - Express request object
+ * @param {Object} res - Express response object
+ * @param {Function} next - Express next middleware function
+ * @returns {Promise<void>}
+ */
+ async create( req, res, next ) {
+ try {
+ const role_data = req.body;
+ const role = await db.role.create( role_data );
+ res.json( role );
+ } catch (error) {
+ logger.error( `Create role error: ${ error.message }` );
+ next( createError( error.status || 409, error.message ) );
+ }
+ },
+
+ /**
+ * Find role by name
+ * @param {Object} req - Express request object
+ * @param {Object} res - Express response object
+ * @param {Function} next - Express next middleware function
+ * @returns {Promise<void>}
+ */
+ async find_by_name( req, res, next ) {
+ try {
+ const { name } = req.params;
+ const role = await db.role.find_by_name( name );
+ if (!role) return next( createError( 404, 'Role not found' ) );
+ res.json( role );
+ } catch (error) {
+ logger.error( `Find role by name error: ${ error.message }` );
+ next( createError( error.status || 500, error.message ) );
+ }
+ },
+
+ /**
+ * Find one role by ID
+ * @param {Object} req - Express request object
+ * @param {Object} res - Express response object
+ * @param {Function} next - Express next middleware function
+ * @returns {Promise<void>}
+ */
+ async find_one( req, res, next ) {
+ try {
+ const { id } = req.params;
+ const role = await db.role.find_one( { id:parseInt( id ) } );
+ if (!role) return next( createError( 404, 'Role not found' ) );
+ res.json( role );
+ } catch (error) {
+ logger.error( `Find role error: ${ error.message }` );
+ next( createError( error.status || 500, error.message ) );
+ }
+ },
+
+ /**
+ * Find many roles
+ * @param {Object} req - Express request object
+ * @param {Object} res - Express response object
+ * @param {Function} next - Express next middleware function
+ * @returns {Promise<void>}
+ */
+ async find_many( req, res, next ) {
+ try {
+ const { limit = '100', offset = '0', ...where } = req.query;
+ const roles = await db.role.find_many( where, [], null, parseInt( limit ), parseInt( offset ) );
+ res.json( roles );
+ } catch (error) {
+ logger.error( `Find many roles error: ${ error.message }` );
+ next( createError( error.status || 500, error.message ) );
+ }
+ },
+
+ /**
+ * Update a role
+ * @param {Object} req - Express request object
+ * @param {Object} res - Express response object
+ * @param {Function} next - Express next middleware function
+ * @returns {Promise<void>}
+ */
+ async update( req, res, next ) {
+ try {
+ const { id } = req.params;
+ const role_data = req.body;
+ const role = await db.role.instance().find_one( { id:parseInt( id ) } );
+ if (!role) return next( createError( 404, 'Role not found' ) );
+ const updated_role = await role.update( role_data );
+ res.json( updated_role );
+ } catch (error) {
+ logger.error( `Update role error: ${ error.message }` );
+ next( createError( error.status || 400, error.message ) );
+ }
+ },
+
+ /**
+ * Soft delete a role
+ * @param {Object} req - Express request object
+ * @param {Object} res - Express response object
+ * @param {Function} next - Express next middleware function
+ * @returns {Promise<void>}
+ */
+ async soft_delete( req, res, next ) {
+ try {
+ const { id } = req.params;
+ const { deleted_by_id } = req.body;
+ const role = await db.role.instance().find_one( { id:parseInt( id ) } );
+ if (!role) return next( createError( 404, 'Role not found' ) );
+ const deleted_role = await role.soft_delete( deleted_by_id );
+ res.json( deleted_role );
+ } catch (error) {
+ logger.error( `Soft delete role error: ${ error.message }` );
+ next( createError( error.status || 400, error.message ) );
+ }
+ }
+};
\ No newline at end of file
--- /dev/null
+/**
+ * @file User roles controller for handling user role-related API requests
+ */
+
+const db = require( '../models' );
+const createError = require( 'http-errors' );
+
+/**
+ * User roles controller
+ * @type {Object}
+ */
+module.exports = {
+ /**
+ * Add a user-role relation
+ * @param {Object} req - Express request object
+ * @param {Object} res - Express response object
+ * @param {Function} next - Express next middleware function
+ * @returns {Promise<void>}
+ */
+ async add_relation( req, res, next ) {
+ try {
+ const { user_id, role_id } = req.body;
+ const relation = await db.user_roles.add_relation( user_id, role_id );
+ res.json( relation );
+ } catch (error) {
+ logger.error( `Add user-role relation error: ${ error.message }` );
+ next( createError( error.status || 409, error.message ) );
+ }
+ },
+
+ /**
+ * Remove a user-role relation
+ * @param {Object} req - Express request object
+ * @param {Object} res - Express response object
+ * @param {Function} next - Express next middleware function
+ * @returns {Promise<void>}
+ */
+ async remove_relation( req, res, next ) {
+ try {
+ const { role_id, user_id } = req.body;
+ const relation = await db.user_roles.remove_relation( role_id, user_id );
+ if (!relation) return next( createError( 404, 'Relation not found' ) );
+ res.json( relation );
+ } catch (error) {
+ logger.error( `Remove user-role relation error: ${ error.message }` );
+ next( createError( error.status || 400, error.message ) );
+ }
+ },
+
+ /**
+ * Find user-role relation by IDs
+ * @param {Object} req - Express request object
+ * @param {Object} res - Express response object
+ * @param {Function} next - Express next middleware function
+ * @returns {Promise<void>}
+ */
+ async find_by_ids( req, res, next ) {
+ try {
+ const { user_id, role_id } = req.params;
+ const relation = await db.user_roles.find_by_ids( parseInt( user_id ), parseInt( role_id ) );
+ if (!relation) return next( createError( 404, 'Relation not found' ) );
+ res.json( relation );
+ } catch (error) {
+ logger.error( `Find user-role by IDs error: ${ error.message }` );
+ next( createError( error.status || 500, error.message ) );
+ }
+ },
+
+ /**
+ * Find user-role relations by user ID
+ * @param {Object} req - Express request object
+ * @param {Object} res - Express response object
+ * @param {Function} next - Express next middleware function
+ * @returns {Promise<void>}
+ */
+ async find_by_user_id( req, res, next ) {
+ try {
+ const { user_id } = req.params;
+ const { limit = '100', offset = '0' } = req.query;
+ const relations = await db.user_roles.find_by_user_id( parseInt( user_id ), [], null, parseInt( limit ), parseInt( offset ) );
+ res.json( relations );
+ } catch (error) {
+ logger.error( `Find user-roles by user ID error: ${ error.message }` );
+ next( createError( error.status || 500, error.message ) );
+ }
+ }
+};
\ No newline at end of file
*/
const get_authentication_model = () => get_model( 'authentication' );
+/**
+ * @returns {Function} Role model class
+ */
+const get_role_model = () => get_model( 'role' );
+
+/**
+ * @returns {Function} User roles model class
+ */
+const get_user_roles_model = () => get_model( 'user_roles' );
+
/**
* Unified database interface
* @type {Object}
* @returns {Authentication} Authentication instance
*/
instance:() => new (get_authentication_model())()
+ },
+ role:{
+ /**
+ * Create a new role
+ * @param {Object} role_data - Role data
+ * @returns {Promise<Role>} Created role instance
+ * @throws {ValidationError} If required fields are missing
+ * @throws {FailedToCreateError} If creation fails
+ */
+ async create( role_data ) {
+ try {
+ return await get_role_model().create( role_data );
+ } catch (error) {
+ logger.error( `Failed to create role: ${ error.message }` );
+ throw error;
+ }
+ },
+ /**
+ * Find role by name
+ * @param {string} name - Role name to search for
+ * @param {string[]} [excludes] - Fields to exclude
+ * @returns {Promise<Role|null>} Role instance or null
+ */
+ async find_by_name( name, excludes ) {
+ try {
+ return await get_role_model().find_by_name( name, excludes );
+ } catch (error) {
+ logger.error( `Failed to find role by name: ${ error.message }` );
+ throw error;
+ }
+ },
+ /**
+ * Find one role
+ * @param {Object} where - Conditions
+ * @param {string[]} [excludes] - Fields to exclude
+ * @returns {Promise<Role|null>} Role instance or null
+ */
+ async find_one( where, excludes ) {
+ try {
+ return await new (get_role_model())().find_one( where, excludes );
+ } catch (error) {
+ logger.error( `Failed to find one role: ${ error.message }` );
+ throw error;
+ }
+ },
+ /**
+ * Find many roles
+ * @param {Object} [where] - Conditions
+ * @param {string[]} [excludes] - Fields to exclude
+ * @param {{name: string, direction?: 'asc'|'desc'}||null} [order_by] - Order by configuration
+ * @param {number} [limit] - Maximum number of records
+ * @param {number} [offset] - Number of records to skip
+ * @returns {Promise<Role[]>} Array of roles
+ */
+ async find_many( where, excludes, order_by, limit, offset ) {
+ try {
+ return await new (get_role_model())().find_many( where, excludes, order_by, limit, offset );
+ } catch (error) {
+ logger.error( `Failed to find many roles: ${ error.message }` );
+ throw error;
+ }
+ },
+ /**
+ * Create a new Role instance
+ * @returns {Role} Role instance
+ */
+ instance:() => new (get_role_model())()
+ },
+ user_roles:{
+ /**
+ * Add a user-role relation
+ * @param {number|string} user_id - User ID
+ * @param {number|string} role_id - Role ID
+ * @returns {Promise<UserRole>} Created relation instance
+ * @throws {ValidationError} If IDs are invalid
+ * @throws {FailedToCreateError} If creation fails
+ */
+ async add_relation( user_id, role_id ) {
+ try {
+ return await get_user_roles_model().add_relation( user_id, role_id );
+ } catch (error) {
+ logger.error( `Failed to add user-role relation: ${ error.message }` );
+ throw error;
+ }
+ },
+ /**
+ * Remove a user-role relation
+ * @param {number|string} role_id - Role ID
+ * @param {number|string|undefined} [user_id] - User ID
+ * @returns {Promise<UserRole|null>} Deleted relation instance or null
+ * @throws {ValidationError} If IDs are invalid
+ */
+ async remove_relation( role_id, user_id ) {
+ try {
+ return await get_user_roles_model().remove_relation( role_id, user_id );
+ } catch (error) {
+ logger.error( `Failed to remove user-role relation: ${ error.message }` );
+ throw error;
+ }
+ },
+ /**
+ * Find user-role relation by IDs
+ * @param {number|string} user_id - User ID
+ * @param {number|string} role_id - Role ID
+ * @param {string[]} [excludes] - Fields to exclude
+ * @returns {Promise<UserRole|null>} Relation instance or null
+ */
+ async find_by_ids( user_id, role_id, excludes ) {
+ try {
+ return await get_user_roles_model().find_by_ids( user_id, role_id, excludes );
+ } catch (error) {
+ logger.error( `Failed to find user-role by IDs: ${ error.message }` );
+ throw error;
+ }
+ },
+ /**
+ * Find user-role relations by user ID
+ * @param {number|string} user_id - User ID
+ * @param {string[]} [excludes] - Fields to exclude
+ * @param {{name: string, direction?: 'asc'|'desc'}||null} [order_by] - Order by configuration
+ * @param {number} [limit] - Maximum number of records
+ * @param {number} [offset] - Number of records to skip
+ * @returns {Promise<UserRole[]>} Array of relations
+ */
+ async find_by_user_id( user_id, excludes, order_by, limit, offset ) {
+ try {
+ return await get_user_roles_model().find_by_user_id( user_id, excludes, order_by, limit, offset );
+ } catch (error) {
+ logger.error( `Failed to find user-roles by user ID: ${ error.message }` );
+ throw error;
+ }
+ },
+ /**
+ * Create a new UserRole instance
+ * @returns {UserRole} UserRole instance
+ */
+ instance:() => new (get_user_roles_model())()
}
};
--- /dev/null
+/**
+ * @file Role model for phase.roles table
+ */
+
+const { Model, NotFoundError, ValidationError, FailedToCreateError } = require( './model' );
+
+/**
+ * @typedef {Object} Role
+ * @property {number} id - Role ID (primary key)
+ * @property {string} name - Role name
+ * @property {number} created_by_id - ID of user who created this role
+ * @property {Date} created_at - Creation timestamp
+ * @property {boolean} is_deleted - Soft delete flag
+ * @property {number|null} deleted_by_id - ID of user who deleted this role
+ * @property {Date|null} deleted_at - Deletion timestamp
+ */
+
+/**
+ * Role model class
+ * @extends Model
+ */
+class Role extends Model {
+ /**
+ * Create a Role instance
+ * @param {Partial<Role>} [props] - Role properties
+ */
+ constructor( props ) {
+ super( props );
+ this.table = 'phase.roles';
+ this.prepend = 'r';
+ this.default_columns = [
+ 'id', 'name', 'created_by_id', 'created_at',
+ 'is_deleted', 'deleted_by_id', 'deleted_at'
+ ];
+ this.update_exclude_columns = ['id', 'created_at', 'is_deleted', 'deleted_at', 'deleted_by_id'];
+ this.base_query = `
+ SELECT r.id,
+ r.name,
+ r.created_by_id,
+ r.created_at,
+ r.is_deleted,
+ r.deleted_by_id,
+ r.deleted_at
+ FROM phase.roles r
+ WHERE r.is_deleted = false
+ `;
+ this.base_list_query = `
+ SELECT r.id, r.name, r.created_by_id, r.created_at
+ FROM phase.roles r
+ WHERE r.is_deleted = false
+ `;
+ this.default_order_by = 'ORDER BY r.name ASC';
+ this.instance = _props => new Role( _props );
+ }
+
+ /**
+ * Create a new role
+ * @param {Omit<Role, 'id'|'created_at'|'is_deleted'|'deleted_at'|'deleted_by_id'>} role_data - Role data
+ * @returns {Promise<Role>} Created role instance
+ * @throws {ValidationError} If required fields are missing
+ * @throws {FailedToCreateError} If creation fails
+ */
+ static async create( role_data ) {
+ const { name, created_by_id } = role_data;
+ if (!name || !created_by_id) {
+ throw new ValidationError( 'Missing required fields: name, created_by_id' );
+ }
+ const query_str = `
+ INSERT INTO phase.roles (name, created_by_id)
+ VALUES ($1, $2)
+ RETURNING *;
+ `;
+ const values = [name, created_by_id];
+ const result = await phsdb.query( query_str, values, { plain:true } );
+ if (!result) throw new FailedToCreateError( 'Failed to create role' );
+ return new Role( result );
+ }
+
+ /**
+ * Find role by name
+ * @param {string} name - Role name to search for
+ * @param {string[]} [excludes] - Fields to exclude from result
+ * @returns {Promise<Role|null>} Role instance or null
+ */
+ static async find_by_name( name, excludes = [] ) {
+ return await new Role().find_one( { name }, excludes );
+ }
+
+ /**
+ * Soft delete role
+ * @param {number|string} deleted_by_id - ID of user performing deletion
+ * @returns {Promise<Role>} Updated role instance
+ * @throws {ValidationError} If deleted_by_id is invalid
+ * @throws {NotFoundError} If record not found
+ */
+ async soft_delete( deleted_by_id ) {
+ const deleted_by_id_int = parseInt( deleted_by_id, 10 );
+ if (isNaN( deleted_by_id_int )) {
+ throw new ValidationError( 'deleted_by_id must be a valid integer' );
+ }
+ return await this.update( {
+ is_deleted:true,
+ deleted_at:new Date().toISOString(),
+ deleted_by_id:deleted_by_id_int
+ } );
+ }
+}
+
+module.exports = Role;
\ No newline at end of file
--- /dev/null
+/**
+ * @file User roles model for phase.user_roles table
+ */
+
+const { Model, ValidationError, FailedToCreateError } = require( './model' );
+
+/**
+ * @typedef {Object} UserRole
+ * @property {number} user_id - User ID (foreign key)
+ * @property {number} role_id - Role ID (foreign key)
+ */
+
+/**
+ * User role model class
+ * @extends Model
+ */
+class UserRole extends Model {
+ /**
+ * Create a UserRole instance
+ * @param {Partial<UserRole>} [props] - User role properties
+ */
+ constructor( props ) {
+ super( props );
+ this.table = 'phase.user_roles';
+ this.prepend = 'ur';
+ this.default_columns = ['user_id', 'role_id'];
+ this.update_exclude_columns = ['user_id', 'role_id'];
+ this.base_query = `
+ SELECT ur.user_id, ur.role_id
+ FROM phase.user_roles ur
+ `;
+ this.base_list_query = this.base_query;
+ this.default_order_by = 'ORDER BY ur.user_id ASC';
+ this.instance = _props => new UserRole( _props );
+ }
+
+ /**
+ * Add a user-role relation
+ * @param {number|string} user_id - User ID
+ * @param {number|string} role_id - Role ID
+ * @returns {Promise<UserRole>} Created relation instance
+ * @throws {ValidationError} If IDs are invalid
+ * @throws {FailedToCreateError} If creation fails
+ */
+ static async add_relation( user_id, role_id ) {
+ const user_id_int = parseInt( user_id, 10 );
+ const role_id_int = parseInt( role_id, 10 );
+ if (isNaN( user_id_int ) || isNaN( role_id_int )) {
+ throw new ValidationError( 'user_id and role_id must be valid integers' );
+ }
+ const query_str = `
+ INSERT INTO phase.user_roles (user_id, role_id)
+ VALUES ($1, $2)
+ RETURNING *;
+ `;
+ const values = [user_id_int, role_id_int];
+ const result = await phsdb.query( query_str, values, { plain:true } );
+ if (!result) throw new FailedToCreateError( 'Failed to add user-role relation' );
+ return new UserRole( result );
+ }
+
+ /**
+ * Remove a user-role relation
+ * @param {number|string} role_id - Role ID
+ * @param {number|string|undefined} [user_id=undefined] - User ID
+ * @returns {Promise<UserRole|null>} Deleted relation instance or null
+ * @throws {ValidationError} If IDs are invalid
+ */
+ static async remove_relation( role_id, user_id = undefined ) {
+ const user_id_int = parseInt( user_id, 10 );
+ const role_id_int = parseInt( role_id, 10 );
+ if (isNaN( role_id_int )) {
+ throw new ValidationError( 'role_id must be a valid integer' );
+ }
+ let query_str = `
+ DELETE
+ FROM phase.user_roles
+ WHERE role_id = $1 ${ user_id_int ? 'AND user_id = $2' : '' }
+ RETURNING *;
+ `;
+ const values = [role_id_int];
+ if (!isNaN( user_id_int )) values.push( user_id_int );
+ const result = await phsdb.query( query_str, values, { plain:!isNaN( user_id_int ) } );
+ return result ? new UserRole( result ) : null;
+ }
+
+ /**
+ * Find user-role relation by IDs
+ * @param {number|string} user_id - User ID
+ * @param {number|string} role_id - Role ID
+ * @param {string[]} [excludes] - Fields to exclude from result
+ * @returns {Promise<UserRole|null>} Relation instance or null
+ */
+ static async find_by_ids( user_id, role_id, excludes = [] ) {
+ return await new UserRole().find_one(
+ { user_id, role_id },
+ excludes
+ );
+ }
+
+ /**
+ * Find user-role relations by user ID
+ * @param {number|string} user_id - User ID
+ * @param {string[]} [excludes] - Fields to exclude from result
+ * @param {{name: string, direction?: 'asc'|'desc'}||null} [order_by] - Order by configuration
+ * @param {number} [limit=100] - Maximum number of records
+ * @param {number} [offset=0] - Number of records to skip
+ * @returns {Promise<UserRole[]>} Array of relations
+ */
+ static async find_by_user_id( user_id, excludes = [], order_by = null, limit = 100, offset = 0 ) {
+ return await new UserRole().find_many(
+ { user_id },
+ excludes,
+ order_by,
+ limit,
+ offset
+ );
+ }
+}
+
+module.exports = UserRole;
\ No newline at end of file
router.use( '/git', require( './git.routes' )( passport ) );
router.use( '/docker', require( './docker.routes' )( passport ) );
router.use( '/vpn', require( './vpn.routes' )( passport ) );
- router.use( '/users', require( './user.routes' )( passport ) );
+ router.use( '/user', require( './user.routes' )( passport ) );
router.use( '/phone_number', require( './phone_number.routes' )( passport ) );
router.use( '/user_phone_numbers', require( './user_phone_numbers.routes' )( passport ) );
router.use( '/address', require( './address.routes' )( passport ) );
router.use( '/user_addresses', require( './user_addresses.routes' )( passport ) );
router.use( '/authentication', require( './authentication.routes' )( passport ) );
+ router.use( '/role', require( './role.routes' )( passport ) );
+ router.use( '/user_roles', require( './user_roles.routes' )( passport ) );
return router;
};
--- /dev/null
+/**
+ * @file Role routes configuration
+ */
+
+const express = require( 'express' );
+const router = express.Router();
+const { validateAuth } = require( '../middleware/routeHelpers' );
+const role_controller = require( '../controllers/role.controller' );
+
+/**
+ * Configure role routes
+ * @param {Object} passport - Passport instance for authentication
+ * @returns {Object} Express router with role routes
+ */
+module.exports = ( passport ) => {
+ router.post( '/create', validateAuth( passport ), role_controller.create );
+ router.get( '/name/:name', validateAuth( passport ), role_controller.find_by_name );
+ router.get( '/:id', validateAuth( passport ), role_controller.find_one );
+ router.get( '/', validateAuth( passport ), role_controller.find_many );
+ router.put( '/:id', validateAuth( passport ), role_controller.update );
+ router.put( '/:id/soft_delete', validateAuth( passport ), role_controller.soft_delete );
+ return router;
+};
\ No newline at end of file
--- /dev/null
+/**
+ * @file User roles routes configuration
+ */
+
+const express = require( 'express' );
+const router = express.Router();
+const { validateAuth } = require( '../middleware/routeHelpers' );
+const user_roles_controller = require( '../controllers/user_roles.controller' );
+
+/**
+ * Configure user roles routes
+ * @param {Object} passport - Passport instance for authentication
+ * @returns {Object} Express router with user roles routes
+ */
+module.exports = ( passport ) => {
+ router.post( '/add', validateAuth( passport ), user_roles_controller.add_relation );
+ router.delete( '/remove', validateAuth( passport ), user_roles_controller.remove_relation );
+ router.get( '/ids/:user_id/:role_id', validateAuth( passport ), user_roles_controller.find_by_ids );
+ router.get( '/user/:user_id', validateAuth( passport ), user_roles_controller.find_by_user_id );
+ return router;
+};
\ No newline at end of file