diff --git a/api/package-lock.json b/api/package-lock.json index 3e1f4c1..1b35021 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -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", diff --git a/api/package.json b/api/package.json index 9d686bd..261b26a 100644 --- a/api/package.json +++ b/api/package.json @@ -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", diff --git a/api/src/db.ts b/api/src/db.ts index 96158ba..3756f73 100644 --- a/api/src/db.ts +++ b/api/src/db.ts @@ -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, }); diff --git a/api/src/index.ts b/api/src/index.ts index 7230a30..c7e900d 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -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) => { diff --git a/api/src/routes/applications.ts b/api/src/routes/applications.ts index 6183c2a..5a98f07 100644 --- a/api/src/routes/applications.ts +++ b/api/src/routes/applications.ts @@ -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") diff --git a/api/src/routes/auth.ts b/api/src/routes/auth.ts index 3b34c23..9175393 100644 --- a/api/src/routes/auth.ts +++ b/api/src/routes/auth.ts @@ -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)); + + }) }); }); diff --git a/api/src/routes/calendar.ts b/api/src/routes/calendar.ts index 0492501..a5c96be 100644 --- a/api/src/routes/calendar.ts +++ b/api/src/routes/calendar.ts @@ -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) { diff --git a/api/src/routes/course.ts b/api/src/routes/course.ts index 7b10bdd..91a2057 100644 --- a/api/src/routes/course.ts +++ b/api/src/routes/course.ts @@ -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); diff --git a/api/src/routes/docs.ts b/api/src/routes/docs.ts new file mode 100644 index 0000000..13ada87 --- /dev/null +++ b/api/src/routes/docs.ts @@ -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; \ No newline at end of file diff --git a/api/src/routes/loa.ts b/api/src/routes/loa.ts index 17f198a..de218c6 100644 --- a/api/src/routes/loa.ts +++ b/api/src/routes/loa.ts @@ -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); diff --git a/api/src/routes/members.ts b/api/src/routes/members.ts index ea5f99a..c73b8f2 100644 --- a/api/src/routes/members.ts +++ b/api/src/routes/members.ts @@ -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) { diff --git a/api/src/routes/roles.ts b/api/src/routes/roles.ts index 4b692af..d0f0e68 100644 --- a/api/src/routes/roles.ts +++ b/api/src/routes/roles.ts @@ -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); } }) diff --git a/api/src/services/applicationService.ts b/api/src/services/applicationService.ts index 5692b4c..159dbe3 100644 --- a/api/src/services/applicationService.ts +++ b/api/src/services/applicationService.ts @@ -32,6 +32,8 @@ export async function getApplicationByID(appID: number): Promise } export async function getApplicationList(page: number = 1, pageSize: number = 25): Promise { + 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 { diff --git a/api/src/services/calendarService.ts b/api/src/services/calendarService.ts index 7aa2cee..4862650 100644 --- a/api/src/services/calendarService.ts +++ b/api/src/services/calendarService.ts @@ -126,6 +126,5 @@ export async function getEventAttendance(eventID: number): Promise { +export async function getUserLOA(userId: number, page = 1, pageSize = 10): Promise> { + + 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 { 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 = { data: result, pagination: { page: page, pageSize: pageSize, total: loaCount, totalPages: pageCount } } + return output; } export async function getUserActiveLOA(userId: number): Promise { @@ -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 { 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]; diff --git a/api/src/services/memberService.ts b/api/src/services/memberService.ts index 8bc1dd9..844ef33 100644 --- a/api/src/services/memberService.ts +++ b/api/src/services/memberService.ts @@ -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 { diff --git a/api/src/services/rolesService.ts b/api/src/services/rolesService.ts index b847ddc..f9e724c 100644 --- a/api/src/services/rolesService.ts +++ b/api/src/services/rolesService.ts @@ -21,7 +21,7 @@ export async function getUserRoles(userID: number): Promise { 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]); } \ No newline at end of file diff --git a/shared/utils/time.ts b/shared/utils/time.ts index 9c5d03c..e1b5ea8 100644 --- a/shared/utils/time.ts +++ b/shared/utils/time.ts @@ -1,5 +1,4 @@ export function toDateTime(date: Date): string { - console.log(date); if (typeof date === 'string') { date = new Date(date); } diff --git a/ui/public/bg.jpg b/ui/public/bg.jpg new file mode 100644 index 0000000..25c6ace Binary files /dev/null and b/ui/public/bg.jpg differ diff --git a/ui/src/App.vue b/ui/src/App.vue index 8289a75..d27efcd 100644 --- a/ui/src/App.vue +++ b/ui/src/App.vue @@ -22,7 +22,10 @@ const environment = import.meta.env.VITE_ENVIRONMENT;