From 79cf77dc6327ee091827a2ecfbd46198fcededb8 Mon Sep 17 00:00:00 2001 From: EagleTrooper Date: Wed, 10 Dec 2025 22:17:45 -0600 Subject: [PATCH 01/30] Added 2 data types unit, unit_date --- ui/src/api/member.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ui/src/api/member.ts b/ui/src/api/member.ts index b97e7ac..4b55d8d 100644 --- a/ui/src/api/member.ts +++ b/ui/src/api/member.ts @@ -3,6 +3,8 @@ export type Member = { member_name: string; rank: string | null; rank_date: string | null; + unit: string | null; + unit_date: string | null; status: string | null; status_date: string | null; on_loa: boolean | null; From a8165e2ae529451f94224c62de15c1711a6fc88f Mon Sep 17 00:00:00 2001 From: EagleTrooper Date: Wed, 10 Dec 2025 22:18:08 -0600 Subject: [PATCH 02/30] Added Unit Table Header and Cell --- ui/src/pages/memberList.vue | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ui/src/pages/memberList.vue b/ui/src/pages/memberList.vue index 5408c26..64ea120 100644 --- a/ui/src/pages/memberList.vue +++ b/ui/src/pages/memberList.vue @@ -65,6 +65,7 @@ const searchedMembers = computed(() => { Member Rank + Unit Status @@ -75,6 +76,7 @@ const searchedMembers = computed(() => { {{ member.member_name }} {{ member.rank }} + {{ member.unit }} {{ member.status }} On LOA From fb64b35807a90c014e66f242da1eecd5dc393838 Mon Sep 17 00:00:00 2001 From: EagleTrooper Date: Wed, 10 Dec 2025 22:18:52 -0600 Subject: [PATCH 03/30] Updated database reference view_member_rank_status_all to view_member_rank_unit_status_latest --- api/src/routes/members.js | 4 ++-- api/src/routes/roles.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/src/routes/members.js b/api/src/routes/members.js index c93f249..ae6de7d 100644 --- a/api/src/routes/members.js +++ b/api/src/routes/members.js @@ -26,7 +26,7 @@ router.get('/', async (req, res) => { AND UTC_TIMESTAMP() BETWEEN l.start_date AND l.end_date ) THEN 1 ELSE 0 END AS on_loa - FROM view_member_rank_status_all v;`); + FROM view_member_rank_unit_status_latest v;`); return res.status(200).json(result); } catch (err) { console.error('Error fetching users:', err); @@ -61,7 +61,7 @@ router.get('/me', async (req, res) => { router.get('/:id', async (req, res) => { try { const userId = req.params.id; - const result = await pool.query('SELECT * FROM view_member_rank_status_all WHERE id = $1;', [userId]); + const result = await pool.query('SELECT * FROM view_member_rank_unit_status_latest WHERE id = $1;', [userId]); if (result.rows.length === 0) { return res.status(404).json({ error: 'User not found' }); } diff --git a/api/src/routes/roles.js b/api/src/routes/roles.js index f1857f6..2a435f0 100644 --- a/api/src/routes/roles.js +++ b/api/src/routes/roles.js @@ -49,7 +49,7 @@ r.get('/', async (req, res) => { const membersRoles = await con.query(` SELECT mr.role_id, v.* FROM members_roles mr - JOIN view_member_rank_status_all v ON mr.member_id = v.member_id + JOIN view_member_rank_unit_status_latest v ON mr.member_id = v.member_id `); From 7a6020febbd8954aa6c81f82fc0bb1244b9c7e33 Mon Sep 17 00:00:00 2001 From: EagleTrooper Date: Thu, 11 Dec 2025 09:22:44 -0600 Subject: [PATCH 04/30] Changed event_date to start_date --- api/src/services/rankService.ts | 4 ++-- api/src/services/statusService.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/src/services/rankService.ts b/api/src/services/rankService.ts index f29a8b3..ada4ea0 100644 --- a/api/src/services/rankService.ts +++ b/api/src/services/rankService.ts @@ -21,8 +21,8 @@ export async function insertMemberRank(member_id: number, rank_id: number): Prom export async function insertMemberRank(member_id: number, rank_id: number, date?: Date): Promise { const sql = date - ? `INSERT INTO members_ranks (member_id, rank_id, event_date) VALUES (?, ?, ?);` - : `INSERT INTO members_ranks (member_id, rank_id, event_date) VALUES (?, ?, NOW());`; + ? `INSERT INTO members_ranks (member_id, rank_id, start_date) VALUES (?, ?, ?);` + : `INSERT INTO members_ranks (member_id, rank_id, start_date) VALUES (?, ?, NOW());`; const params = date ? [member_id, rank_id, date] diff --git a/api/src/services/statusService.ts b/api/src/services/statusService.ts index 7a62f3a..8b8fb28 100644 --- a/api/src/services/statusService.ts +++ b/api/src/services/statusService.ts @@ -1,6 +1,6 @@ import pool from "../db" export async function assignUserToStatus(userID: number, statusID: number) { - const sql = `INSERT INTO members_statuses (member_id, status_id, event_date) VALUES (?, ?, NOW())` + const sql = `INSERT INTO members_statuses (member_id, status_id, start_date) VALUES (?, ?, NOW())` await pool.execute(sql, [userID, statusID]); } From 97119dec97e27c541fcbcea6fc4e0b8563f86a6c Mon Sep 17 00:00:00 2001 From: ajdj100 Date: Thu, 11 Dec 2025 21:22:23 -0500 Subject: [PATCH 05/30] added config_id reference to .env --- api/.env.example | 1 + 1 file changed, 1 insertion(+) diff --git a/api/.env.example b/api/.env.example index 8bfe497..9a1e8da 100644 --- a/api/.env.example +++ b/api/.env.example @@ -21,6 +21,7 @@ CLIENT_URL= # This is whatever URL the client web app is served on CLIENT_DOMAIN= #whatever.com APPLICATION_VERSION= # Should match release tag APPLICATION_ENVIRONMENT= # dev / prod +CONFIG_ID= # configures # Glitchtip GLITCHTIP_DSN= From 0a1704b60bdf2f09b4912241c93a40854d1bf36a Mon Sep 17 00:00:00 2001 From: ajdj100 Date: Thu, 11 Dec 2025 21:43:49 -0500 Subject: [PATCH 06/30] Updated application acceptance system --- api/src/routes/applications.ts | 15 ++++++++------- ui/src/api/application.ts | 4 ++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/api/src/routes/applications.ts b/api/src/routes/applications.ts index c79514e..718a92b 100644 --- a/api/src/routes/applications.ts +++ b/api/src/routes/applications.ts @@ -141,8 +141,9 @@ router.get('/:id', async (req: Request, res: Response) => { }); // POST /application/approve/:id -router.post('/approve/:id', async (req, res) => { - const appID = req.params.id; +router.post('/approve/:id', async (req: Request, res: Response) => { + const appID = Number(req.params.id); + const approved_by = req.user.id; try { const app = await getApplicationByID(appID); @@ -153,14 +154,14 @@ router.post('/approve/:id', async (req, res) => { throw new Error("Something went wrong approving the application"); } - console.log(app.member_id); //update user profile await setUserState(app.member_id, MemberState.Member); - let nextRank = await getRankByName('Recruit') - await insertMemberRank(app.member_id, nextRank.id); - //assign user to "pending basic" - await assignUserToStatus(app.member_id, 1); + await pool.query('CALL sp_accept_new_recruit(?, ?, ?, ?)', [Number(process.env.CONFIG_ID), app.member_id, approved_by, approved_by]) + // let nextRank = await getRankByName('Recruit') + // await insertMemberRank(app.member_id, nextRank.id); + // //assign user to "pending basic" + // await assignUserToStatus(app.member_id, 1); res.sendStatus(200); } catch (err) { console.error('Approve failed:', err); diff --git a/ui/src/api/application.ts b/ui/src/api/application.ts index 8f60f1e..ca1b52b 100644 --- a/ui/src/api/application.ts +++ b/ui/src/api/application.ts @@ -89,7 +89,7 @@ export async function getMyApplication(id: number): Promise { } export async function approveApplication(id: Number) { - const res = await fetch(`${addr}/application/approve/${id}`, { method: 'POST' }) + const res = await fetch(`${addr}/application/approve/${id}`, { method: 'POST', credentials: 'include' }) if (!res.ok) { console.error("Something went wrong approving the application") @@ -97,7 +97,7 @@ export async function approveApplication(id: Number) { } export async function denyApplication(id: Number) { - const res = await fetch(`${addr}/application/deny/${id}`, { method: 'POST' }) + const res = await fetch(`${addr}/application/deny/${id}`, { method: 'POST', credentials: 'include' }) if (!res.ok) { console.error("Something went wrong denying the application") From c74b5b280b5c8893ac48a806fe9ff474b3c31a27 Mon Sep 17 00:00:00 2001 From: ajdj100 Date: Thu, 11 Dec 2025 22:05:46 -0500 Subject: [PATCH 07/30] placeholdered CoC getter until CoC page exists on bookstack --- api/src/routes/applications.ts | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/api/src/routes/applications.ts b/api/src/routes/applications.ts index 718a92b..e6327d9 100644 --- a/api/src/routes/applications.ts +++ b/api/src/routes/applications.ts @@ -7,7 +7,7 @@ import { MemberState, setUserState } from '../services/memberService'; import { getRankByName, insertMemberRank } from '../services/rankService'; import { ApplicationFull, CommentRow } from "@app/shared/types/application" import { assignUserToStatus } from '../services/statusService'; -import { Request, Response } from 'express'; +import { Request, response, Response } from 'express'; import { getUserRoles } from '../services/rolesService'; // POST /application @@ -157,7 +157,7 @@ router.post('/approve/:id', async (req: Request, res: Response) => { //update user profile await setUserState(app.member_id, MemberState.Member); - await pool.query('CALL sp_accept_new_recruit(?, ?, ?, ?)', [Number(process.env.CONFIG_ID), app.member_id, approved_by, approved_by]) + await pool.query('CALL sp_accept_new_recruit_validation(?, ?, ?, ?)', [Number(process.env.CONFIG_ID), app.member_id, approved_by, approved_by]) // let nextRank = await getRankByName('Recruit') // await insertMemberRank(app.member_id, nextRank.id); // //assign user to "pending basic" @@ -283,4 +283,20 @@ router.post('/restart', async (req: Request, res: Response) => { } }) +// router.get('/coc', async (req: Request, res: Response) => { +// const output = await fetch(`${process.env.DOC_HOST}/api/pages/`, { +// headers: { +// Authorization: `Token ${process.env.DOC_TOKEN_ID}:${process.env.DOC_TOKEN_SECRET}`, +// } +// }) + +// if (output.ok) { +// const out = await output.json(); +// res.status(200).json(out.html); +// } else { +// console.error("Failed to fetch LOA policy from bookstack"); +// res.sendStatus(500); +// } +// }) + module.exports = router; From 333bf20d867e5b85dba34a9031ee6cc194ba8d58 Mon Sep 17 00:00:00 2001 From: ajdj100 Date: Fri, 12 Dec 2025 00:39:19 -0500 Subject: [PATCH 08/30] integrated CoC pull from bookstack --- api/src/routes/applications.ts | 33 +++++++------ ui/src/api/application.ts | 16 +++++++ .../application/ApplicationForm.vue | 46 +++++++++++++++++-- 3 files changed, 77 insertions(+), 18 deletions(-) diff --git a/api/src/routes/applications.ts b/api/src/routes/applications.ts index e6327d9..e1c9394 100644 --- a/api/src/routes/applications.ts +++ b/api/src/routes/applications.ts @@ -10,6 +10,24 @@ import { assignUserToStatus } from '../services/statusService'; import { Request, response, Response } from 'express'; import { getUserRoles } from '../services/rolesService'; +//get CoC +router.get('/coc', async (req: Request, res: Response) => { + const output = await fetch(`${process.env.DOC_HOST}/api/pages/714`, { + headers: { + Authorization: `Token ${process.env.DOC_TOKEN_ID}:${process.env.DOC_TOKEN_SECRET}`, + } + }) + + if (output.ok) { + const out = await output.json(); + res.status(200).json(out.html); + } else { + console.error("Failed to fetch LOA policy from bookstack"); + res.sendStatus(500); + } +}) + + // POST /application router.post('/', async (req, res) => { try { @@ -283,20 +301,5 @@ router.post('/restart', async (req: Request, res: Response) => { } }) -// router.get('/coc', async (req: Request, res: Response) => { -// const output = await fetch(`${process.env.DOC_HOST}/api/pages/`, { -// headers: { -// Authorization: `Token ${process.env.DOC_TOKEN_ID}:${process.env.DOC_TOKEN_SECRET}`, -// } -// }) - -// if (output.ok) { -// const out = await output.json(); -// res.status(200).json(out.html); -// } else { -// console.error("Failed to fetch LOA policy from bookstack"); -// res.sendStatus(500); -// } -// }) module.exports = router; diff --git a/ui/src/api/application.ts b/ui/src/api/application.ts index ca1b52b..ec05982 100644 --- a/ui/src/api/application.ts +++ b/ui/src/api/application.ts @@ -113,4 +113,20 @@ export async function restartApplication() { if (!res.ok) { console.error("Something went wrong restarting your application") } +} + +export async function getCoC(): Promise { + const res = await fetch(`${addr}/application/coc`, { + method: "GET", + credentials: 'include', + }); + if (res.ok) { + const out = res.json(); + if (!out) { + return null; + } + return out; + } else { + return null; + } } \ No newline at end of file diff --git a/ui/src/components/application/ApplicationForm.vue b/ui/src/components/application/ApplicationForm.vue index eb1a85f..28ee1ad 100644 --- a/ui/src/components/application/ApplicationForm.vue +++ b/ui/src/components/application/ApplicationForm.vue @@ -13,10 +13,18 @@ import Input from '@/components/ui/input/Input.vue'; import Textarea from '@/components/ui/textarea/Textarea.vue'; import { toTypedSchema } from '@vee-validate/zod'; import { Form } from 'vee-validate'; -import { onMounted, ref } from 'vue'; +import { nextTick, onMounted, ref } from 'vue'; import * as z from 'zod'; import DateInput from '../form/DateInput.vue'; import { ApplicationData } from '@shared/types/application'; +import Dialog from '../ui/dialog/Dialog.vue'; +import DialogTrigger from '../ui/dialog/DialogTrigger.vue'; +import DialogContent from '../ui/dialog/DialogContent.vue'; +import DialogHeader from '../ui/dialog/DialogHeader.vue'; +import DialogTitle from '../ui/dialog/DialogTitle.vue'; +import DialogDescription from '../ui/dialog/DialogDescription.vue'; +import { getCoC } from '@/api/application'; +import { startBrowserTracingPageLoadSpan } from '@sentry/vue'; const regexA = /^https?:\/\/steamcommunity\.com\/id\/[A-Za-z0-9_]+\/?$/; const regexB = /^https?:\/\/steamcommunity\.com\/profiles\/\d+\/?$/; @@ -61,7 +69,7 @@ async function onSubmit(val: any) { emit('submit', val); } -onMounted(() => { +onMounted(async () => { if (props.data !== null) { const parsed = typeof props.data === "string" ? JSON.parse(props.data) @@ -71,8 +79,25 @@ onMounted(() => { } else { initialValues.value = { ...fallbackInitials }; } + + // CoCbox.value.innerHTML = await getCoC() + CoCString.value = await getCoC(); }) +const showCoC = ref(false); +const CoCbox = ref(); +const CoCString = ref(); + +async function onDialogToggle(state: boolean) { + showCoC.value = state; + + if (state) { + await nextTick(); + if (CoCbox.value && CoCString.value) { + CoCbox.value.innerHTML = CoCString.value; + } + } +} @@ -273,7 +298,8 @@ onMounted(() => {
- By checking this box, you accept the .
@@ -286,5 +312,19 @@ onMounted(() => {
+ + + + + Community Code of Conduct + +
+ +
+
+
+
+
+ \ No newline at end of file From 629fd59a7c0d21b1d8a298fbaa206ed2a884ba59 Mon Sep 17 00:00:00 2001 From: ajdj100 Date: Fri, 12 Dec 2025 10:53:33 -0500 Subject: [PATCH 09/30] fixed scrolling behaviour on application management page --- ui/src/assets/base.css | 64 ++++++++++++---- ui/src/pages/ManageApplications.vue | 109 ++++++++++++---------------- 2 files changed, 96 insertions(+), 77 deletions(-) diff --git a/ui/src/assets/base.css b/ui/src/assets/base.css index b29a3bb..f06ddf1 100644 --- a/ui/src/assets/base.css +++ b/ui/src/assets/base.css @@ -168,7 +168,7 @@ } /* Root container */ -.ListRendererV2-container { +.bookstack-container { font-family: var(--font-sans, system-ui), sans-serif; color: var(--foreground); line-height: 1.45; @@ -178,56 +178,53 @@ } /* Headers */ -.ListRendererV2-container h4 { +.bookstack-container h4 { margin: 0.9rem 0 0.4rem 0; font-weight: 600; line-height: 1.35; font-size: 1.05rem; color: var(--foreground); - /* PURE WHITE */ } -.ListRendererV2-container h5 { +.bookstack-container h5 { margin: 0.9rem 0 0.4rem 0; font-weight: 600; line-height: 1.35; font-size: 0.95rem; color: var(--foreground); - /* Still white (change to muted if desired) */ } /* Lists */ -.ListRendererV2-container ul { +.bookstack-container ul { list-style-type: disc; margin-left: 1.1rem; margin-bottom: 0.6rem; padding-left: 0.6rem; color: var(--muted-foreground); - /* dim text */ } /* Nested lists */ -.ListRendererV2-container ul ul { +.bookstack-container ul ul { list-style-type: circle; margin-left: 0.9rem; } /* List items */ -.ListRendererV2-container li { +.bookstack-container li { margin: 0.15rem 0; padding-left: 0.1rem; color: var(--muted-foreground); } /* Bullet color */ -.ListRendererV2-container li::marker { +.bookstack-container li::marker { color: var(--muted-foreground); } /* Inline elements */ -.ListRendererV2-container li p, -.ListRendererV2-container li span, -.ListRendererV2-container p { +.bookstack-container li p, +.bookstack-container li span, +.bookstack-container p { display: inline; margin: 0; padding: 0; @@ -235,6 +232,45 @@ } /* Top-level spacing */ -.ListRendererV2-container>ul>li { +.bookstack-container>ul>li { margin-top: 0.3rem; +} + +/* links */ +.bookstack-container a { + color: var(--color-primary); + margin-top: 0.3rem; +} + +.bookstack-container a:hover { + text-decoration: underline; +} + +/* Scrollbar stuff */ +/* Firefox */ +.scrollbar-themed { + scrollbar-width: thin; + scrollbar-color: #555 #1f1f1f; + padding-right: 6px; +} + +/* Chrome, Edge, Safari */ +.scrollbar-themed::-webkit-scrollbar { + width: 10px; + /* slightly wider to allow padding look */ +} + +.scrollbar-themed::-webkit-scrollbar-track { + background: #1f1f1f; + margin-left: 6px; + /* ❗ adds space between content + scrollbar */ +} + +.scrollbar-themed::-webkit-scrollbar-thumb { + background: #555; + border-radius: 9999px; +} + +.scrollbar-themed::-webkit-scrollbar-thumb:hover { + background: #777; } \ No newline at end of file diff --git a/ui/src/pages/ManageApplications.vue b/ui/src/pages/ManageApplications.vue index a19316b..76b4737 100644 --- a/ui/src/pages/ManageApplications.vue +++ b/ui/src/pages/ManageApplications.vue @@ -1,5 +1,5 @@