Compare commits

..

1 Commits

Author SHA1 Message Date
3848eb939a Tweaked LOA API RBAC to allow full command group access
All checks were successful
Continuous Integration / Update Development (push) Successful in 2m48s
Continuous Deployment / Update Deployment (push) Successful in 2m32s
2025-12-22 21:36:10 -05:00
2 changed files with 10 additions and 51 deletions

View File

@@ -26,7 +26,7 @@ router.post("/", async (req: Request, res: Response) => {
}); });
//admin posts LOA //admin posts LOA
router.post("/admin", [requireRole("17th Administrator")], async (req: Request, res: Response) => { router.post("/admin", [requireRole(['17th Administrator', '17th HQ', '17th Command'])], async (req: Request, res: Response) => {
let LOARequest = req.body as LOARequest; let LOARequest = req.body as LOARequest;
LOARequest.created_by = req.user.id; LOARequest.created_by = req.user.id;
LOARequest.filed_date = new Date(); LOARequest.filed_date = new Date();
@@ -67,7 +67,7 @@ router.get("/history", async (req: Request, res: Response) => {
} }
}) })
router.get('/all', [requireRole("17th Administrator")], async (req: Request, res: Response) => { router.get('/all', [requireRole(['17th Administrator', '17th HQ', '17th Command'])], async (req: Request, res: Response) => {
try { try {
const page = Number(req.query.page) || undefined; const page = Number(req.query.page) || undefined;
const pageSize = Number(req.query.pageSize) || undefined; const pageSize = Number(req.query.pageSize) || undefined;
@@ -107,7 +107,7 @@ router.post('/cancel/:id', async (req: Request, res: Response) => {
}) })
//TODO: enforce admin only //TODO: enforce admin only
router.post('/adminCancel/:id', [requireRole("17th Administrator")], async (req: Request, res: Response) => { router.post('/adminCancel/:id', [requireRole(['17th Administrator', '17th HQ', '17th Command'])], async (req: Request, res: Response) => {
let closer = req.user.id; let closer = req.user.id;
try { try {
await closeLOA(Number(req.params.id), closer); await closeLOA(Number(req.params.id), closer);
@@ -119,7 +119,7 @@ router.post('/adminCancel/:id', [requireRole("17th Administrator")], async (req:
}) })
// TODO: Enforce admin only // TODO: Enforce admin only
router.post('/extend/:id', [requireRole("17th Administrator")], async (req: Request, res: Response) => { router.post('/extend/:id', [requireRole(['17th Administrator', '17th HQ', '17th Command'])], async (req: Request, res: Response) => {
const to: Date = req.body.to; const to: Date = req.body.to;
if (!to) { if (!to) {

View File

@@ -80,19 +80,6 @@ function onSubmit(vals) {
} }
} }
function matchesSearch(text: string, search: string) {
if (!search) return true
const tokens = search
.toLowerCase()
.trim()
.split(/\s+/)
const target = text.toLowerCase()
return tokens.every(token => target.includes(token))
}
const { remove, push, fields } = useFieldArray('attendees'); const { remove, push, fields } = useFieldArray('attendees');
const selectedCourse = computed<Course | undefined>(() => { return trainings.value?.find(c => c.id == values.course_id) }) const selectedCourse = computed<Course | undefined>(() => { return trainings.value?.find(c => c.id == values.course_id) })
@@ -123,24 +110,11 @@ const memberSearch = ref('')
const MAX_RESULTS = 50 const MAX_RESULTS = 50
const filteredMembers = computed(() => { const filteredMembers = computed(() => {
const q = memberSearch.value?.toLowerCase().trim() ?? "" const q = memberSearch?.value?.toLowerCase() ?? ""
if (!q) {
return (members.value ?? []).slice(0, MAX_RESULTS)
}
// Split search into words (handles multiple spaces)
const tokens = q.split(/\s+/)
const results: MemberLight[] = [] const results: MemberLight[] = []
for (const m of members.value ?? []) { for (const m of members.value ?? []) {
const name = (m.displayName || m.username).toLowerCase() if (!q || (m.displayName || m.username).toLowerCase().includes(q)) {
// ALL tokens must be present (order does not matter)
const matches = tokens.every(token => name.includes(token))
if (matches) {
results.push(m) results.push(m)
if (results.length >= MAX_RESULTS) break if (results.length >= MAX_RESULTS) break
} }
@@ -149,17 +123,6 @@ const filteredMembers = computed(() => {
return results return results
}) })
const courseSearch = ref('')
const filteredCourses = computed(() => {
if (!trainings.value) return []
return trainings.value.filter(course =>
matchesSearch(course.name, courseSearch.value)
)
})
</script> </script>
<template> <template>
<form id="trainingForm" @submit.prevent="submitForm" class="flex flex-col gap-5"> <form id="trainingForm" @submit.prevent="submitForm" class="flex flex-col gap-5">
@@ -176,22 +139,18 @@ const filteredCourses = computed(() => {
selectCourse = false selectCourse = false
}" class="w-full"> }" class="w-full">
<ComboboxAnchor class="w-full"> <ComboboxAnchor class="w-full">
<ComboboxInput @focus="selectCourse = true" <ComboboxInput @focus="selectCourse = true" placeholder="Search courses..."
@input="courseSearch = $event.target.value" class="w-full pl-3" :display-value="(id) => {
placeholder="Search courses..."
class="w-full pl-3"
:display-value="(id) => {
const c = trainings?.find(t => t.id === id) const c = trainings?.find(t => t.id === id)
return c ? c.name : ''; return c ? c.name : '';
}" }" />
/>
</ComboboxAnchor> </ComboboxAnchor>
<ComboboxList class="w-full"> <ComboboxList class="w-full">
<ComboboxEmpty class="text-muted-foreground w-full">No results</ComboboxEmpty> <ComboboxEmpty class="text-muted-foreground w-full">No results</ComboboxEmpty>
<ComboboxGroup> <ComboboxGroup>
<div class="max-h-80 overflow-y-scroll scrollbar-themed min-w-md"> <div class="max-h-80 overflow-y-scroll scrollbar-themed min-w-md">
<template v-for="course in filteredCourses" :key="course.id"> <template v-for="course in trainings" :key="course.id">
<ComboboxItem :value="course.id" <ComboboxItem :value="course.id"
class="data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative cursor-pointer select-none px-2 py-1.5 w-full"> class="data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative cursor-pointer select-none px-2 py-1.5 w-full">
{{ course.name }} {{ course.name }}