From 76e74c237b4ab8aed53f05d8a652beaaa2d28aad Mon Sep 17 00:00:00 2001 From: phil_00 <162841065+crazifil@users.noreply.github.com> Date: Wed, 12 Aug 2026 20:27:06 +0100 Subject: [PATCH 1/3] Add deputy security chief and order security roles --- api/src/services/place/place.service.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/api/src/services/place/place.service.ts b/api/src/services/place/place.service.ts index 6f3c1090..2e54dd5a 100644 --- a/api/src/services/place/place.service.ts +++ b/api/src/services/place/place.service.ts @@ -191,6 +191,7 @@ export class PlaceService { const SecurityInfo = {}; const securityRoles = [ { mapName: 'SecurityChief', roleName: 'Security Chief' }, + { mapName: 'DeputySecurityChief', roleName: 'Deputy Security Chief' }, { mapName: 'SecurityCaptain', roleName: 'Security Captain' }, { mapName: 'SecurityLieutenant', roleName: 'Security Lieutenant' }, { mapName: 'SecuritySergeant', roleName: 'Security Sergeant' }, @@ -198,16 +199,16 @@ export class PlaceService { { mapName: 'JailGuard', roleName: 'Jail Guard' }, ]; try { - await Promise.all(securityRoles.map(async (role) => { - const roleCode = await this.roleRepository.roleMap[role.mapName]; - await this.roleAssignmentRepository.getUsernamesByRoleId(roleCode).then(response => { - const users = []; - response.forEach(row => { - users.push(row.username); - }); - SecurityInfo[role.roleName] = users; - }); - })); + for (const role of securityRoles) { + const roleCode = await this.roleRepository.roleMap[role.mapName]; + const response = await this.roleAssignmentRepository.getUsernamesByRoleId(roleCode); + const users = []; + + response.forEach(row => { + users.push(row.username); + }); + SecurityInfo[role.roleName] = users; + } } catch (error) { console.error(error); } From eca44c6ff284dd6b27de8bf101f275cb4c470518 Mon Sep 17 00:00:00 2001 From: phil_00 <162841065+crazifil@users.noreply.github.com> Date: Wed, 12 Aug 2026 20:34:57 +0100 Subject: [PATCH 2/3] Update security role hierarchy and admin access --- api/src/services/member/member.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/api/src/services/member/member.service.ts b/api/src/services/member/member.service.ts index 8f033d45..cc41eb1e 100644 --- a/api/src/services/member/member.service.ts +++ b/api/src/services/member/member.service.ts @@ -59,6 +59,7 @@ export class MemberService { this.roleRepository.roleMap.Admin, this.roleRepository.roleMap.SecurityCaptain, this.roleRepository.roleMap.SecurityChief, + this.roleRepository.roleMap.DeputySecurityChief, this.roleRepository.roleMap.SecurityLieutenant, this.roleRepository.roleMap.SecurityOfficer, this.roleRepository.roleMap.SecuritySergeant, From ee9ff72f5ee0f04e89717c0b7d2b3d73bc73b25d Mon Sep 17 00:00:00 2001 From: phil_00 <162841065+crazifil@users.noreply.github.com> Date: Tue, 18 Aug 2026 22:50:08 +0100 Subject: [PATCH 3/3] Allow Security Chief & Deputy Chief to manage security roles less SC & DSC --- api/src/controllers/admin.controller.ts | 26 ++- api/src/controllers/member.controller.ts | 13 ++ api/src/routes/member.routes.ts | 3 + api/src/services/member/member.service.ts | 22 +++ spa/src/pages/admin/user/FireRoles.vue | 188 +++++++++++++++------- spa/src/pages/admin/user/HireRoles.vue | 122 +++++++++++--- spa/src/pages/admin/user/SubMenu.vue | 187 +++++++++++++++------ 7 files changed, 429 insertions(+), 132 deletions(-) diff --git a/api/src/controllers/admin.controller.ts b/api/src/controllers/admin.controller.ts index a9298ceb..2ba8c4ce 100644 --- a/api/src/controllers/admin.controller.ts +++ b/api/src/controllers/admin.controller.ts @@ -123,11 +123,18 @@ class AdminController { place_id = parseInt(place_id); } const accessLevel = await this.memberService.getAccessLevel(session.id); - if (accessLevel.includes('admin')) { + const roleId = parseInt(role_id); + const canManageSecurityRoles = + await this.memberService.canManageSecurityRoles(session.id); + const canManageRole = + accessLevel.includes('admin') || + (canManageSecurityRoles && + this.memberService.canSecurityManageRole(roleId)); + if (canManageRole) { try { await this.adminService.fireRole( parseInt(member_id), - parseInt(role_id), + roleId, place_id, ); response.status(200).json({message: 'Role fired successfully'}); @@ -139,7 +146,7 @@ class AdminController { response.status(403).json({error: 'Access Denied'}); } } - + public async getDonor(request: Request, response: Response): Promise { const session = this.memberService.decryptSession(request, response); if (!session) return; @@ -179,12 +186,19 @@ class AdminController { const session = this.memberService.decryptSession(request, response); if (!session) return; const accessLevel = await this.memberService.getAccessLevel(session.id); - if (accessLevel.includes('admin')) { - const {member_id, role_id} = request.body; + const {member_id, role_id} = request.body; + const roleId = parseInt(role_id); + const canManageSecurityRoles = + await this.memberService.canManageSecurityRoles(session.id); + const canManageRole = + accessLevel.includes('admin') || + (canManageSecurityRoles && + this.memberService.canSecurityManageRole(roleId)); + if (canManageRole) { try { await this.adminService.hireRole( parseInt(member_id), - parseInt(role_id), + roleId, ); response.status(200).json({message: 'Role hired successfully'}); } catch(e) { diff --git a/api/src/controllers/member.controller.ts b/api/src/controllers/member.controller.ts index 29dd0e8c..cdbfc19d 100644 --- a/api/src/controllers/member.controller.ts +++ b/api/src/controllers/member.controller.ts @@ -40,6 +40,19 @@ class MemberController { response.status(200).json({ accessLevel }); } + public async canManageSecurityRoles( + request: Request, + response: Response, + ): Promise { + const session = this.memberService.decryptSession(request, response); + if (!session) return; + + const canManage = + await this.memberService.canManageSecurityRoles(session.id); + + response.status(200).json({ canManage }); + } + public async getDonorLevel(request: Request, response: Response): Promise { const session = this.memberService.decryptSession(request, response); if (!session) return; diff --git a/api/src/routes/member.routes.ts b/api/src/routes/member.routes.ts index 906b63c4..2a550115 100644 --- a/api/src/routes/member.routes.ts +++ b/api/src/routes/member.routes.ts @@ -20,6 +20,9 @@ memberRoutes.get('/getrolename', (request, response) => memberRoutes.get('/getadminlevel', (request, response) => memberController.getAdminLevel(request, response), ); +memberRoutes.get('/can-manage-security-roles', (request, response) => + memberController.canManageSecurityRoles(request, response), +); memberRoutes.get('/getdonorlevel', (request, response) => memberController.getDonorLevel(request, response), ); diff --git a/api/src/services/member/member.service.ts b/api/src/services/member/member.service.ts index cc41eb1e..3e090aaf 100644 --- a/api/src/services/member/member.service.ts +++ b/api/src/services/member/member.service.ts @@ -67,6 +67,28 @@ export class MemberService { return !!roleAssignments.find(assignment => ADMIN_ROLES.includes(assignment.role_id)); } + public async canManageSecurityRoles(memberId: number): Promise { + const roleAssignments = + await this.roleAssignmentRepository.getByMemberId(memberId); + const SECURITY_ROLE_MANAGERS = [ + this.roleRepository.roleMap.SecurityChief, + this.roleRepository.roleMap.DeputySecurityChief, + ]; + return !!roleAssignments.find(assignment => SECURITY_ROLE_MANAGERS.includes(assignment.role_id),); + } + + public canSecurityManageRole(roleId: number): boolean { + const SECURITY_ROLES = [ + this.roleRepository.roleMap.SecurityCaptain, + this.roleRepository.roleMap.SecurityLieutenant, + this.roleRepository.roleMap.SecuritySergeant, + this.roleRepository.roleMap.SecurityOfficer, + this.roleRepository.roleMap.SecurityAdvisor, + this.roleRepository.roleMap.JailGuard, + ]; + return SECURITY_ROLES.includes(roleId); + } + public async canLeader(memberId: number): Promise { const roleAssignments = await this.roleAssignmentRepository.getByMemberId(memberId); // Extracted admin roles into a constant for easy management diff --git a/spa/src/pages/admin/user/FireRoles.vue b/spa/src/pages/admin/user/FireRoles.vue index 74bd9ae9..27d67b76 100644 --- a/spa/src/pages/admin/user/FireRoles.vue +++ b/spa/src/pages/admin/user/FireRoles.vue @@ -5,64 +5,98 @@ {{ error }} +
- - - - - - - - - + + + + + + + + + + + +
RolePlace -
{{ id.name }} - City Wide - {{ id.place }} - - - - -
RolePlace +
+ {{ id.name }} + + City Wide + {{ id.place }} + + +
+
-
- +
-

Terminate User?

-
- +
-

Are you sure you want to terminate this user?

+

+ Are you sure you want to terminate this user? +

- -
- - + +
+ + +
+
@@ -71,6 +105,7 @@ diff --git a/spa/src/pages/admin/user/HireRoles.vue b/spa/src/pages/admin/user/HireRoles.vue index 4b5eeb7d..1234e73e 100644 --- a/spa/src/pages/admin/user/HireRoles.vue +++ b/spa/src/pages/admin/user/HireRoles.vue @@ -3,96 +3,170 @@
Hire Roles
-
- This is to give roles to a user. Most roles are title only with no access. However, - Mayor; Deputy Mayor; Colony Leader; and all Security titles come with access rights - to this Admin Panel, so be mindful with who you give what roles to. + +
+ This is to give roles to a user. Most roles are title only with no access. + However, some roles come with access rights to the Admin Panel, so be + mindful with who you give what roles to.
+
- + +
+
- - {{error}} + + {{ error }} - - {{success}} + + + {{ success }} - + +  
+
- +
- + diff --git a/spa/src/pages/admin/user/SubMenu.vue b/spa/src/pages/admin/user/SubMenu.vue index 2362c8ca..c9efee21 100644 --- a/spa/src/pages/admin/user/SubMenu.vue +++ b/spa/src/pages/admin/user/SubMenu.vue @@ -1,65 +1,158 @@ -