diff --git a/api/src/routes/roles.ts b/api/src/routes/roles.ts index d0f0e68..e131c56 100644 --- a/api/src/routes/roles.ts +++ b/api/src/routes/roles.ts @@ -5,7 +5,8 @@ const ur = express.Router(); import { MemberState } from '@app/shared/types/member'; import pool from '../db'; import { requireLogin, requireMemberState, requireRole } from '../middleware/auth'; -import { assignUserGroup, createGroup } from '../services/rolesService'; +import { assignUserGroup, createGroup, getAllRoles, getRole, getUsersWithRole } from '../services/rolesService'; +import { Request, Response } from 'express'; r.use(requireLogin) ur.use(requireLogin) @@ -44,43 +45,37 @@ ur.delete('/', [requireMemberState(MemberState.Member), requireRole("17th Admini //get all roles r.get('/', [requireMemberState(MemberState.Member)], async (req, res) => { try { - var con = await pool.getConnection(); - - // Get all roles - const roles = await con.query('SELECT * FROM roles;'); - - // Get all members for each role - const membersRoles = await con.query(` - SELECT mr.role_id, v.* - FROM members_roles mr - JOIN view_member_rank_unit_status_latest v ON mr.member_id = v.member_id - `); - - - // Group members by role_id - const roleIdToMembers = {}; - for (const row of membersRoles) { - if (!roleIdToMembers[row.role_id]) roleIdToMembers[row.role_id] = []; - // Remove role_id from member object - const { role_id, ...member } = row; - roleIdToMembers[role_id].push(member); - } - - // Attach members to each role - const result = roles.map(role => ({ - ...role, - members: roleIdToMembers[role.id] || [] - })); - - res.json(result); + const roles = await getAllRoles(); + res.status(200).json(roles); } catch (err) { console.error(err); - res.status(500).json({ error: 'Internal server error' }); - } finally { - con.release(); + res.sendStatus(500); } }); +r.get('/:id/members', [requireMemberState(MemberState.Member)], async (req: Request, res: Response) => { + try { + const members = await getUsersWithRole(Number(req.params.id)); + res.status(200).json(members); + } catch (err) { + console.error(err); + res.sendStatus(500); + } +}) + + +r.get('/:id', [requireMemberState(MemberState.Member)], async (req: Request, res: Response) => { + try { + const role = await getRole(Number(req.params.id)); + res.status(200).json(role); + } catch (err) { + console.error(err); + res.sendStatus(500); + } +}) + + + //create a new role r.post('/', [requireMemberState(MemberState.Member), requireRole("17th Administrator")], async (req, res) => { try { diff --git a/api/src/services/rolesService.ts b/api/src/services/rolesService.ts index f9e724c..9e2275d 100644 --- a/api/src/services/rolesService.ts +++ b/api/src/services/rolesService.ts @@ -1,5 +1,6 @@ +import { MemberLight } from '@app/shared/types/member'; import pool from '../db'; -import { Role } from '@app/shared/types/roles' +import { Role, RoleSummary } from '@app/shared/types/roles' export async function assignUserGroup(userID: number, roleID: number) { @@ -24,4 +25,34 @@ export async function getUserRoles(userID: number): Promise { WHERE mr.member_id = ?;`; return await pool.query(sql, [userID]); +} + +export async function getRole(id: number): Promise { + let res = await pool.query(`SELECT * FROM roles WHERE id = ?`, [id]) + return res[0] as Role; +} + +export async function getAllRoles(): Promise { + return await pool.query(`SELECT id, name, color FROM roles`); +} + +export async function getUsersWithRole(roleId: number): Promise { + const out = await pool.query( + ` + SELECT + m.member_id AS id, + m.member_name AS username, + m.displayName, + u.color + FROM members_roles mr + JOIN view_member_rank_unit_status_latest m + ON m.member_id = mr.member_id + LEFT JOIN units u + ON u.name = m.unit + WHERE mr.role_id = ? + `, + [roleId] + ) + + return out as MemberLight[] } \ No newline at end of file diff --git a/shared/types/roles.ts b/shared/types/roles.ts index a232c52..08ab762 100644 --- a/shared/types/roles.ts +++ b/shared/types/roles.ts @@ -1,6 +1,14 @@ +import { MemberLight } from "./member"; + export interface Role { id: number; name: string; color?: string; description?: string; +} + +export interface RoleSummary { + id: number; + name: string; + color?: string; } \ No newline at end of file diff --git a/ui/src/api/roles.ts b/ui/src/api/roles.ts index 3fb67ee..6ec36e8 100644 --- a/ui/src/api/roles.ts +++ b/ui/src/api/roles.ts @@ -1,10 +1,5 @@ -export type Role = { - id: number; - name: string; - color: string; - description: string | null; - members: any[]; -}; +import { Member, MemberLight } from "@shared/types/member"; +import { Role } from "@shared/types/roles"; // @ts-ignore const addr = import.meta.env.VITE_APIHOST; @@ -22,6 +17,30 @@ export async function getRoles(): Promise { } } +export async function getRoleDetails(id: number): Promise { + const res = await fetch(`${addr}/roles/${id}`, { + credentials: 'include', + }) + + if (res.ok) { + return res.json() as Promise; + } else { + throw new Error("Could not load role"); + } +} + +export async function getRoleMembers(id: number): Promise { + const res = await fetch(`${addr}/roles/${id}/members`, { + credentials: 'include', + }) + + if (res.ok) { + return res.json(); + } else { + throw new Error("Could not load members"); + } +} + export async function createRole(name: string, color: string, description: string | null): Promise { const res = await fetch(`${addr}/roles`, { method: "POST", diff --git a/ui/src/components/roles/roleView.vue b/ui/src/components/roles/roleView.vue new file mode 100644 index 0000000..d6be8c5 --- /dev/null +++ b/ui/src/components/roles/roleView.vue @@ -0,0 +1,111 @@ + + + diff --git a/ui/src/pages/ManageRoles.vue b/ui/src/pages/ManageRoles.vue index cf437ff..6c698b8 100644 --- a/ui/src/pages/ManageRoles.vue +++ b/ui/src/pages/ManageRoles.vue @@ -9,7 +9,7 @@ import { CardTitle, } from '@/components/ui/card' import { onMounted, ref, computed, reactive, watch } from 'vue'; -import { addMemberToRole, createRole, deleteRole, getRoles, removeMemberFromRole, Role } from '@/api/roles'; +import { addMemberToRole, createRole, deleteRole, getRoles, removeMemberFromRole } from '@/api/roles'; import Badge from '@/components/ui/badge/Badge.vue'; import { Dialog, @@ -34,8 +34,11 @@ import { Plus, X } from 'lucide-vue-next'; import Separator from '@/components/ui/separator/Separator.vue'; import Input from '@/components/ui/input/Input.vue'; import Label from '@/components/ui/label/Label.vue'; -import { getMembers } from '@/api/member'; -import { Member } from '@shared/types/member'; +import { getAllLightMembers, getMembers } from '@/api/member'; +import { Member, MemberLight } from '@shared/types/member'; +import { Role } from '@shared/types/roles'; +import RoleView from '@/components/roles/roleView.vue'; +import { useRoute } from 'vue-router'; const roles = ref([]) const activeRole = ref(null) @@ -43,16 +46,9 @@ const showDialog = ref(false); const showCreateGroupDialog = ref(false); const addingMember = ref(false); const memberToAdd = ref(null); +const route = useRoute(); -const allMembers = ref([]) -const availableMembers = computed(() => { - if (!activeRole.value) return []; - return allMembers.value.filter( - member => !activeRole.value!.members.some( - roleMember => roleMember.member_id === member.member_id - ) - ); -}) +const allMembers = ref([]) type RoleDraft = { name: string @@ -137,121 +133,36 @@ async function handleDeleteRole() { onMounted(async () => { roles.value = await getRoles(); - allMembers.value = await getMembers(); + allMembers.value = await getAllLightMembers(); })