Merge remote-tracking branch 'Origin/main' into promotions

This commit is contained in:
2025-12-17 23:45:58 -05:00
34 changed files with 385 additions and 128 deletions

13
api/package-lock.json generated
View File

@@ -10,6 +10,7 @@
"license": "ISC",
"dependencies": {
"@sentry/node": "^10.27.0",
"chalk": "^5.6.2",
"connect-sqlite3": "^0.9.16",
"cors": "^2.8.5",
"dotenv": "^17.2.1",
@@ -1314,6 +1315,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/chalk": {
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
"integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
"license": "MIT",
"engines": {
"node": "^12.17.0 || ^14.13 || >=16.0.0"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",

View File

@@ -13,6 +13,7 @@
},
"dependencies": {
"@sentry/node": "^10.27.0",
"chalk": "^5.6.2",
"connect-sqlite3": "^0.9.16",
"cors": "^2.8.5",
"dotenv": "^17.2.1",

View File

@@ -12,7 +12,7 @@ const pool = mariadb.createPool({
connectionLimit: 5,
connectTimeout: 10000, // give it more breathing room
acquireTimeout: 15000,
database: 'ranger_unit_tracker',
database: process.env.DB_DATABASE,
ssl: false,
});

View File

@@ -5,9 +5,27 @@ import express = require('express');
import cors = require('cors');
import morgan = require('morgan');
const app = express()
app.use(morgan('dev', {
skip: (req) => {
return req.path === '/members/me';
import chalk from 'chalk';
app.use(morgan((tokens: morgan.TokenIndexer, req: express.Request, res: express.Response) => {
const status = Number(tokens.status(req, res));
// Colorize status code
const statusColor = status >= 500 ? chalk.red
: status >= 400 ? chalk.yellow
: status >= 300 ? chalk.cyan
: chalk.green;
return [
chalk.gray(`[${new Date().toISOString()}]`),
chalk.blue.bold(tokens.method(req, res)),
tokens.url(req, res),
statusColor(status),
chalk.magenta(tokens['response-time'](req, res) + ' ms'),
chalk.yellow(`- User: ${req.user?.name ? `${req.user.name} (${req.user.id})` : 'Unauthenticated'}`),
].join(' ');
}, {
skip: (req: express.Request) => {
return req.originalUrl === '/members/me';
}
}))
@@ -64,6 +82,7 @@ import { authRouter } from './routes/auth';
import { roles, memberRoles } from './routes/roles';
import { courseRouter, eventRouter } from './routes/course';
import { calendarRouter } from './routes/calendar';
import { docsRouter } from './routes/docs';
app.use('/application', applicationRouter);
app.use('/ranks', ranks);
@@ -77,6 +96,7 @@ app.use('/memberRoles', memberRoles)
app.use('/course', courseRouter)
app.use('/courseEvent', eventRouter)
app.use('/calendar', calendarRouter)
app.use('/docs', docsRouter)
app.use('/', authRouter)
app.get('/ping', (req, res) => {

View File

@@ -105,7 +105,6 @@ router.get('/me/:id', [requireLogin], async (req: Request, res: Response) => {
const application = await getApplicationByID(appID);
if (application === undefined)
return res.sendStatus(204);
console.log(application.member_id, member)
if (application.member_id != member) {
return res.sendStatus(403);
}
@@ -191,8 +190,6 @@ router.post('/:id/comment', [requireLogin], async (req: Request, res: Response)
const data = req.body.message;
const user = req.user;
console.log(user)
const sql = `INSERT INTO application_comments(
application_id,
poster_id,
@@ -205,7 +202,6 @@ VALUES(?, ?, ?);`
var conn = await pool.getConnection();
const result = await conn.query(sql, [appID, user.id, data])
console.log(result)
if (result.affectedRows !== 1) {
conn.release();
throw new Error("Insert Failure")
@@ -237,8 +233,6 @@ router.post('/:id/adminComment', [requireLogin, requireRole("Recruiter")], async
const data = req.body.message;
const user = req.user;
console.log(user)
const sql = `INSERT INTO application_comments(
application_id,
poster_id,
@@ -251,7 +245,6 @@ VALUES(?, ?, ?, 1);`
var conn = await pool.getConnection();
const result = await conn.query(sql, [appID, user.id, data])
console.log(result)
if (result.affectedRows !== 1) {
conn.release();
throw new Error("Insert Failure")

View File

@@ -115,11 +115,24 @@ router.get('/callback', (req, res, next) => {
router.get('/logout', [requireLogin], function (req, res, next) {
req.logout(function (err) {
if (err) { return next(err); }
var params = {
client_id: process.env.AUTH_CLIENT_ID,
returnTo: process.env.CLIENT_URL
};
res.redirect(process.env.AUTH_END_SESSION_URI + '?' + querystring.stringify(params));
req.session.destroy((err) => {
if (err) { return next(err); }
res.clearCookie('connect.sid', {
path: '/',
domain: process.env.CLIENT_DOMAIN,
httpOnly: true,
sameSite: 'lax'
});
var params = {
client_id: process.env.AUTH_CLIENT_ID,
returnTo: process.env.CLIENT_URL
};
res.redirect(process.env.AUTH_END_SESSION_URI + '?' + querystring.stringify(params));
})
});
});

View File

@@ -107,7 +107,6 @@ r.put('/', [requireLogin, requireMemberState(MemberState.Member)], async (req: R
let event: CalendarEvent = req.body;
event.start = new Date(event.start);
event.end = new Date(event.end);
console.log(event);
updateEvent(event);
res.sendStatus(200);
} catch (error) {

View File

@@ -83,7 +83,6 @@ er.get('/attendees/:id', async (req: Request, res: Response) => {
er.post('/', async (req: Request, res: Response) => {
const posterID: number = req.user.id;
try {
console.log();
let data: CourseEventDetails = req.body;
data.created_by = posterID;
data.event_date = new Date(data.event_date);

24
api/src/routes/docs.ts Normal file
View File

@@ -0,0 +1,24 @@
const express = require('express');
const router = express.Router();
import { Request, Response } from 'express';
import { requireLogin } from '../middleware/auth';
router.get('/welcome', [requireLogin], async (req: Request, res: Response) => {
const output = await fetch(`${process.env.DOC_HOST}/api/pages/717`, {
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);
}
})
export const docsRouter = router;

View File

@@ -30,9 +30,6 @@ router.post("/admin", [requireRole("17th Administrator")], async (req: Request,
let LOARequest = req.body as LOARequest;
LOARequest.created_by = req.user.id;
LOARequest.filed_date = new Date();
console.log(LOARequest);
try {
await createNewLOA(LOARequest);
res.sendStatus(201);
@@ -56,9 +53,13 @@ router.get("/me", async (req: Request, res: Response) => {
//get my LOA history
router.get("/history", async (req: Request, res: Response) => {
const user = req.user.id;
try {
const result = await getUserLOA(user);
const user = req.user.id;
const page = Number(req.query.page) || undefined;
const pageSize = Number(req.query.pageSize) || undefined;
const result = await getUserLOA(user, page, pageSize);
res.status(200).json(result)
} catch (error) {
console.error(error);

View File

@@ -53,7 +53,6 @@ router.get('/me', [requireLogin], async (req, res) => {
router.get('/settings', [requireLogin], async (req: Request, res: Response) => {
try {
let user = req.user.id;
console.log(user);
let output = await getMemberSettings(user);
res.status(200).json(output);
} catch (error) {
@@ -66,7 +65,6 @@ router.put('/settings', [requireLogin], async (req: Request, res: Response) => {
try {
let user = req.user.id;
let settings: memberSettings = req.body;
console.log(settings)
await setUserSettings(user, settings);
res.sendStatus(200);
} catch (error) {

View File

@@ -28,7 +28,6 @@ ur.post('/', [requireMemberState(MemberState.Member), requireRole("17th Administ
ur.delete('/', [requireMemberState(MemberState.Member), requireRole("17th Administrator")], async (req, res) => {
try {
const body = req.body;
console.log(body);
const sql = 'DELETE FROM members_roles WHERE member_id = ? AND role_id = ?'
await pool.query(sql, [body.member_id, body.role_id])
@@ -86,7 +85,6 @@ r.get('/', [requireMemberState(MemberState.Member)], async (req, res) => {
r.post('/', [requireMemberState(MemberState.Member), requireRole("17th Administrator")], async (req, res) => {
try {
const { name, color, description } = req.body;
console.log('Creating role:', { name, color, description });
if (!name || !color) {
return res.status(400).json({ error: 'Name and color are required' });
}
@@ -113,7 +111,7 @@ r.delete('/:id', [requireMemberState(MemberState.Member), requireRole("17th Admi
const res = await pool.query(sql, [id]);
res.sendStatus(200);
} catch (error) {
console.log(error);
console.error(error);
res.sendStatus(500);
}
})

View File

@@ -32,6 +32,8 @@ export async function getApplicationByID(appID: number): Promise<ApplicationRow>
}
export async function getApplicationList(page: number = 1, pageSize: number = 25): Promise<ApplicationListRow[]> {
const offset = (page - 1) * pageSize;
const sql = `SELECT
member.name AS member_name,
app.id,
@@ -44,7 +46,7 @@ export async function getApplicationList(page: number = 1, pageSize: number = 25
ORDER BY app.submitted_at DESC
LIMIT ? OFFSET ?;`
const rows: ApplicationListRow[] = await pool.query(sql, [pageSize, page]);
const rows: ApplicationListRow[] = await pool.query(sql, [pageSize, offset]);
return rows;
}
@@ -71,7 +73,6 @@ export async function approveApplication(id: number, approver: number) {
`;
const result = await pool.execute(sql, [approver, id]);
console.log(result);
if (result.affectedRows == 1) {
return
} else {
@@ -89,7 +90,6 @@ export async function denyApplication(id: number, approver: number) {
`;
const result = await pool.execute(sql, [approver, id]);
console.log(result);
if (result.affectedRows == 1) {
return
} else {

View File

@@ -126,6 +126,5 @@ export async function getEventAttendance(eventID: number): Promise<CalendarSignu
const sql = "CALL `sp_GetCalendarEventSignups`(?)"
const res = await pool.query(sql, [eventID]);
console.log(res[0]);
return res[0];
}

View File

@@ -36,7 +36,10 @@ export async function getAllLOA(page = 1, pageSize = 10): Promise<PagedData<LOAR
return output;
}
export async function getUserLOA(userId: number): Promise<LOARequest[]> {
export async function getUserLOA(userId: number, page = 1, pageSize = 10): Promise<PagedData<LOARequest>> {
const offset = (page - 1) * pageSize;
const result: LOARequest[] = await pool.query(`
SELECT loa.*, members.name, t.name AS type_name
FROM leave_of_absences AS loa
@@ -53,8 +56,12 @@ export async function getUserLOA(userId: number): Promise<LOARequest[]> {
WHEN loa.closed IS NOT NULL THEN 4
END,
loa.start_date DESC
`, [userId])
return result;
LIMIT ? OFFSET ?;`, [userId, pageSize, offset])
let loaCount = Number((await pool.query(`SELECT COUNT(*) as count FROM leave_of_absences WHERE member_id = ?;`, [userId]))[0].count);
let pageCount = loaCount / pageSize;
let output: PagedData<LOARequest> = { data: result, pagination: { page: page, pageSize: pageSize, total: loaCount, totalPages: pageCount } }
return output;
}
export async function getUserActiveLOA(userId: number): Promise<LOARequest[]> {
@@ -82,13 +89,11 @@ export async function closeLOA(id: number, closer: number) {
ended_at = NOW()
WHERE leave_of_absences.id = ?`;
let out = await pool.query(sql, [closer, id]);
console.log(out);
return out;
}
export async function getLOAbyID(id: number): Promise<LOARequest> {
let res = await pool.query(`SELECT * FROM leave_of_absences WHERE id = ?`, [id]);
console.log(res);
if (res.length != 1)
throw new Error(`LOA with id ${id} not found`);
return res[0];

View File

@@ -34,7 +34,6 @@ export async function setUserSettings(id: number, settings: memberSettings) {
displayName = ?
WHERE id = ?;`;
let result = await pool.query(sql, [settings.displayName, id])
console.log(result);
}
export async function getMembersLite(ids: number[]): Promise<MemberLight[]> {

View File

@@ -21,7 +21,7 @@ export async function getUserRoles(userID: number): Promise<Role[]> {
const sql = `SELECT r.id, r.name
FROM members_roles mr
INNER JOIN roles r ON mr.role_id = r.id
WHERE mr.member_id = 190;`;
WHERE mr.member_id = ?;`;
return await pool.query(sql, [userID]);
}