Merge commit '412001b1b4a85e0dea04f642319e19396955af95' into account-claim

This commit is contained in:
2025-12-14 17:20:56 -05:00
40 changed files with 1140 additions and 283 deletions

View File

@@ -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=

View File

@@ -1,11 +1,15 @@
const dotenv = require('dotenv')
import dotenv = require('dotenv');
dotenv.config();
const express = require('express')
const cors = require('cors')
const morgan = require('morgan')
import express = require('express');
import cors = require('cors');
import morgan = require('morgan');
const app = express()
app.use(morgan('dev'))
app.use(morgan('dev', {
skip: (req) => {
return req.path === '/members/me';
}
}))
app.use(cors({
origin: [process.env.CLIENT_URL], // your SPA origins
@@ -19,7 +23,7 @@ app.set('trust proxy', 1);
const port = process.env.SERVER_PORT;
//glitchtip setup
const sentry = require('@sentry/node');
import sentry = require('@sentry/node');
if (process.env.DISABLE_GLITCHTIP === "true") {
console.log("Glitchtip disabled")
} else {
@@ -27,14 +31,14 @@ if (process.env.DISABLE_GLITCHTIP === "true") {
let release = process.env.APPLICATION_VERSION;
let environment = process.env.APPLICATION_ENVIRONMENT;
console.log(release, environment)
sentry.init({ dsn: dsn, release: release, environment: environment });
sentry.init({ dsn: dsn, release: release, environment: environment, integrations: [sentry.captureConsoleIntegration({ levels: ['error'] })] });
console.log("Glitchtip initialized");
}
//session setup
const path = require('path')
const session = require('express-session')
const passport = require('passport')
import path = require('path');
import session = require('express-session');
import passport = require('passport');
const SQLiteStore = require('connect-sqlite3')(session);
app.use(session({
@@ -51,23 +55,21 @@ app.use(session({
app.use(passport.authenticate('session'));
// Mount route modules
const applicationsRouter = require('./routes/applications');
const { memberRanks, ranks } = require('./routes/ranks');
const members = require('./routes/members');
const loaHandler = require('./routes/loa')
const { status, memberStatus } = require('./routes/statuses')
const authRouter = require('./routes/auth')
const { roles, memberRoles } = require('./routes/roles');
const { courseRouter, eventRouter } = require('./routes/course');
const { calendarRouter } = require('./routes/calendar')
const morgan = require('morgan');
const { env } = require('process');
import { applicationRouter } from './routes/applications';
import { memberRanks, ranks } from './routes/ranks';
import { memberRouter } from './routes/members';
import { loaRouter } from './routes/loa';
import { status, memberStatus } from './routes/statuses';
import { authRouter } from './routes/auth';
import { roles, memberRoles } from './routes/roles';
import { courseRouter, eventRouter } from './routes/course';
import { calendarRouter } from './routes/calendar';
app.use('/application', applicationsRouter);
app.use('/application', applicationRouter);
app.use('/ranks', ranks);
app.use('/memberRanks', memberRanks);
app.use('/members', members);
app.use('/loa', loaHandler);
app.use('/members', memberRouter);
app.use('/loa', loaRouter);
app.use('/status', status)
app.use('/memberStatus', memberStatus)
app.use('/roles', roles)

View File

@@ -0,0 +1,49 @@
import { MemberState } from "@app/shared/types/member";
import { NextFunction, Request, Response } from "express";
import { stat } from "fs";
export const requireLogin = function (req: Request, res: Response, next: NextFunction) {
if (req.user?.id)
next();
else
res.sendStatus(401)
}
export function requireMemberState(state: MemberState) {
return function (req: Request, res: Response, next: NextFunction) {
if (req.user?.state === state)
next();
else
res.status(403).send(`You must be a ${state} of the 17th RBN to access this resource`);
}
}
export function requireRole(requiredRoles: string | string[]) {
// Normalize the input to always be an array of lowercase required roles
const normalizedRequiredRoles: string[] = Array.isArray(requiredRoles)
? requiredRoles.map(role => role.toLowerCase())
: [requiredRoles.toLowerCase()];
const DEV_ROLE = 'dev';
return function (req: Request, res: Response, next: NextFunction) {
if (!req.user || !req.user.roles) {
// User is not authenticated or has no roles array
return res.sendStatus(401);
}
const userRolesLowercase = req.user.roles.map(role => role.name.toLowerCase());
// Check if the user has *any* of the required roles OR the 'dev' role
const hasAccess = userRolesLowercase.some(userRole =>
userRole === DEV_ROLE || normalizedRequiredRoles.includes(userRole)
);
if (hasAccess) {
return next();
} else {
// User is authenticated but does not have the necessary permissions
return res.sendStatus(403);
}
};
}

View File

@@ -3,15 +3,35 @@ const router = express.Router();
import pool from '../db';
import { approveApplication, createApplication, denyApplication, getAllMemberApplications, getApplicationByID, getApplicationComments, getApplicationList, getMemberApplication } from '../services/applicationService';
import { MemberState, setUserState } from '../services/memberService';
import { setUserState } from '../services/memberService';
import { MemberState } from '@app/shared/types/member';
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';
import { requireLogin, requireRole } from '../middleware/auth';
//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) => {
router.post('/', [requireLogin], async (req, res) => {
try {
const App = req.body?.App || {};
const memberID = req.user.id;
@@ -29,7 +49,7 @@ router.post('/', async (req, res) => {
});
// GET /application/all
router.get('/all', async (req, res) => {
router.get('/all', [requireLogin, requireRole("Recruiter")], async (req, res) => {
try {
const rows = await getApplicationList();
res.status(200).json(rows);
@@ -53,7 +73,7 @@ router.get('/meList', async (req, res) => {
}
})
router.get('/me', async (req, res) => {
router.get('/me', [requireLogin], async (req, res) => {
let userID = req.user.id;
@@ -78,7 +98,7 @@ router.get('/me', async (req, res) => {
})
// GET /application/:id
router.get('/me/:id', async (req: Request, res: Response) => {
router.get('/me/:id', [requireLogin], async (req: Request, res: Response) => {
let appID = Number(req.params.id);
let member = req.user.id;
try {
@@ -105,22 +125,10 @@ router.get('/me/:id', async (req: Request, res: Response) => {
});
// GET /application/:id
router.get('/:id', async (req: Request, res: Response) => {
router.get('/:id', [requireLogin, requireRole("Recruiter")], async (req: Request, res: Response) => {
let appID = Number(req.params.id);
let asAdmin = !!req.query.admin || false;
let user = req.user.id;
//TODO: Replace this with bigger authorization system eventually
if (asAdmin) {
let allowed = (await getUserRoles(user)).some((role) =>
role.name.toLowerCase() === 'dev' ||
role.name.toLowerCase() === 'recruiter' ||
role.name.toLowerCase() === 'administrator')
console.log(allowed)
if (!allowed) {
return res.sendStatus(403)
}
}
try {
const application = await getApplicationByID(appID);
if (application === undefined)
@@ -141,8 +149,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', [requireLogin, requireRole("Recruiter")], 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 +162,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_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"
// await assignUserToStatus(app.member_id, 1);
res.sendStatus(200);
} catch (err) {
console.error('Approve failed:', err);
@@ -169,7 +178,7 @@ router.post('/approve/:id', async (req, res) => {
});
// POST /application/deny/:id
router.post('/deny/:id', async (req, res) => {
router.post('/deny/:id', [requireLogin, requireRole("Recruiter")], async (req, res) => {
const appID = req.params.id;
try {
@@ -184,7 +193,7 @@ router.post('/deny/:id', async (req, res) => {
});
// POST /application/:id/comment
router.post('/:id/comment', async (req: Request, res: Response) => {
router.post('/:id/comment', [requireLogin], async (req: Request, res: Response) => {
const appID = req.params.id;
const data = req.body.message;
const user = req.user;
@@ -198,8 +207,9 @@ router.post('/:id/comment', async (req: Request, res: Response) => {
)
VALUES(?, ?, ?);`
try {
const conn = await pool.getConnection();
var conn = await pool.getConnection();
const result = await conn.query(sql, [appID, user.id, data])
console.log(result)
@@ -223,11 +233,13 @@ VALUES(?, ?, ?);`
} catch (err) {
console.error('Comment failed:', err);
res.status(500).json({ error: 'Could not post comment' });
} finally {
conn.release();
}
});
// POST /application/:id/comment
router.post('/:id/adminComment', async (req: Request, res: Response) => {
router.post('/:id/adminComment', [requireLogin, requireRole("Recruiter")], async (req: Request, res: Response) => {
const appID = req.params.id;
const data = req.body.message;
const user = req.user;
@@ -243,7 +255,7 @@ router.post('/:id/adminComment', async (req: Request, res: Response) => {
VALUES(?, ?, ?, 1);`
try {
const conn = await pool.getConnection();
var conn = await pool.getConnection();
const result = await conn.query(sql, [appID, user.id, data])
console.log(result)
@@ -268,6 +280,8 @@ VALUES(?, ?, ?, 1);`
} catch (err) {
console.error('Comment failed:', err);
res.status(500).json({ error: 'Could not post comment' });
} finally {
conn.release();
}
});
@@ -282,4 +296,4 @@ router.post('/restart', async (req: Request, res: Response) => {
}
})
module.exports = router;
export const applicationRouter = router;

View File

@@ -6,7 +6,12 @@ dotenv.config();
const express = require('express');
const { param } = require('./applications');
const router = express.Router();
import { Role } from '@app/shared/types/roles';
import pool from '../db';
import { requireLogin } from '../middleware/auth';
import { getUserRoles } from '../services/rolesService';
import { getUserState } from '../services/memberService';
import { MemberState } from '@app/shared/types/member';
const querystring = require('querystring');
@@ -29,8 +34,9 @@ passport.use(new OpenIDConnectStrategy({
console.log('id_token claims:', JSON.stringify(jwtClaims, null, 2));
console.log('preferred_username:', jwtClaims?.preferred_username);
const con = await pool.getConnection();
try {
var con = await pool.getConnection();
await con.beginTransaction();
//lookup existing user
@@ -66,12 +72,6 @@ router.get('/login', (req, res, next) => {
next();
}, passport.authenticate('openidconnect'));
// router.get('/callback', (req, res, next) => {
// passport.authenticate('openidconnect', {
// successRedirect: req.session.redirectTo,
// failureRedirect: process.env.CLIENT_URL
// })
// });
router.get('/callback', (req, res, next) => {
const redirectURI = req.session.redirectTo;
@@ -90,7 +90,7 @@ router.get('/callback', (req, res, next) => {
})(req, res, next);
});
router.get('/logout', function (req, res, next) {
router.get('/logout', [requireLogin], function (req, res, next) {
req.logout(function (err) {
if (err) { return next(err); }
var params = {
@@ -110,15 +110,18 @@ passport.serializeUser(function (user, cb) {
passport.deserializeUser(function (user, cb) {
process.nextTick(async function () {
const memberID = user.memberId;
const memberID = user.memberId as number;
const con = await pool.getConnection();
var userData;
var userData: { id: number, name: string, roles: Role[], state: MemberState };
try {
var con = await pool.getConnection();
let userResults = await con.query(`SELECT id, name FROM members WHERE id = ?;`, [memberID])
userData = userResults[0];
let userRoles = await getUserRoles(memberID);
userData.roles = userRoles;
userData.state = await getUserState(memberID);
} catch (error) {
console.error(error)
} finally {
@@ -128,5 +131,18 @@ passport.deserializeUser(function (user, cb) {
});
});
declare global {
namespace Express {
interface Request {
user: {
id: number;
name: string;
roles: Role[];
state: MemberState;
};
}
}
}
module.exports = router;
export const authRouter = router;

View File

@@ -1,6 +1,8 @@
import { Request, Response } from "express";
import { createEvent, getEventAttendance, getEventDetails, getShortEventsInRange, setAttendanceStatus, setEventCancelled, updateEvent } from "../services/calendarService";
import { CalendarAttendance, CalendarEvent } from "@app/shared/types/calendar";
import { requireLogin, requireMemberState, requireRole } from "../middleware/auth";
import { MemberState } from "@app/shared/types/member";
const express = require('express');
const r = express.Router();
@@ -35,7 +37,7 @@ r.get('/upcoming', async (req, res) => {
res.sendStatus(501);
})
r.post('/:id/cancel', async (req: Request, res: Response) => {
r.post('/:id/cancel', [requireLogin, requireMemberState(MemberState.Member)], async (req: Request, res: Response) => {
try {
const eventID = Number(req.params.id);
setEventCancelled(eventID, true);
@@ -45,7 +47,7 @@ r.post('/:id/cancel', async (req: Request, res: Response) => {
res.status(500).send('Error setting cancel status');
}
})
r.post('/:id/uncancel', async (req: Request, res: Response) => {
r.post('/:id/uncancel', [requireLogin, requireMemberState(MemberState.Member)], async (req: Request, res: Response) => {
try {
const eventID = Number(req.params.id);
setEventCancelled(eventID, false);
@@ -57,7 +59,7 @@ r.post('/:id/uncancel', async (req: Request, res: Response) => {
})
r.post('/:id/attendance', async (req: Request, res: Response) => {
r.post('/:id/attendance', [requireLogin, requireMemberState(MemberState.Member)], async (req: Request, res: Response) => {
try {
let member = req.user.id;
let event = Number(req.params.id);
@@ -85,7 +87,7 @@ r.get('/:id', async (req: Request, res: Response) => {
//post a new calendar event
r.post('/', async (req: Request, res: Response) => {
r.post('/', [requireLogin, requireMemberState(MemberState.Member)], async (req: Request, res: Response) => {
try {
const member = req.user.id;
let event: CalendarEvent = req.body;
@@ -100,7 +102,7 @@ r.post('/', async (req: Request, res: Response) => {
}
})
r.put('/', async (req: Request, res: Response) => {
r.put('/', [requireLogin, requireMemberState(MemberState.Member)], async (req: Request, res: Response) => {
try {
let event: CalendarEvent = req.body;
event.start = new Date(event.start);
@@ -114,5 +116,4 @@ r.put('/', async (req: Request, res: Response) => {
}
})
module.exports.calendarRouter = r;
export const calendarRouter = r;

View File

@@ -1,11 +1,18 @@
import { CourseAttendee, CourseEventDetails } from "@app/shared/types/course";
import { getAllCourses, getCourseEventAttendees, getCourseEventDetails, getCourseEventRoles, getCourseEvents, insertCourseEvent } from "../services/CourseSerivce";
import { Request, Response, Router } from "express";
import { requireLogin, requireMemberState } from "../middleware/auth";
import { MemberState } from "@app/shared/types/member";
const courseRouter = Router();
const eventRouter = Router();
const cr = Router();
const er = Router();
courseRouter.get('/', async (req, res) => {
cr.use(requireLogin)
er.use(requireLogin)
cr.use(requireMemberState(MemberState.Member))
er.use(requireMemberState(MemberState.Member))
cr.get('/', async (req, res) => {
try {
const courses = await getAllCourses();
res.status(200).json(courses);
@@ -15,7 +22,7 @@ courseRouter.get('/', async (req, res) => {
}
})
courseRouter.get('/roles', async (req, res) => {
cr.get('/roles', async (req, res) => {
try {
const roles = await getCourseEventRoles();
res.status(200).json(roles);
@@ -25,7 +32,7 @@ courseRouter.get('/roles', async (req, res) => {
}
})
eventRouter.get('/', async (req: Request, res: Response) => {
er.get('/', async (req: Request, res: Response) => {
const allowedSorts = new Map([
["ascending", "ASC"],
["descending", "DESC"]
@@ -50,7 +57,7 @@ eventRouter.get('/', async (req: Request, res: Response) => {
}
});
eventRouter.get('/:id', async (req: Request, res: Response) => {
er.get('/:id', async (req: Request, res: Response) => {
try {
let out = await getCourseEventDetails(Number(req.params.id));
res.status(200).json(out);
@@ -60,7 +67,7 @@ eventRouter.get('/:id', async (req: Request, res: Response) => {
}
});
eventRouter.get('/attendees/:id', async (req: Request, res: Response) => {
er.get('/attendees/:id', async (req: Request, res: Response) => {
try {
const attendees: CourseAttendee[] = await getCourseEventAttendees(Number(req.params.id));
res.status(200).json(attendees);
@@ -70,7 +77,7 @@ eventRouter.get('/attendees/:id', async (req: Request, res: Response) => {
}
})
eventRouter.post('/', async (req: Request, res: Response) => {
er.post('/', async (req: Request, res: Response) => {
const posterID: number = req.user.id;
try {
console.log();
@@ -85,5 +92,5 @@ eventRouter.post('/', async (req: Request, res: Response) => {
}
})
module.exports.courseRouter = courseRouter;
module.exports.eventRouter = eventRouter;
export const courseRouter = cr;
export const eventRouter = er;

View File

@@ -5,6 +5,9 @@ import { Request, Response } from 'express';
import pool from '../db';
import { closeLOA, createNewLOA, getAllLOA, getLOAbyID, getLoaTypes, getUserLOA, setLOAExtension } from '../services/loaService';
import { LOARequest } from '@app/shared/types/loa';
import { requireLogin, requireRole } from '../middleware/auth';
router.use(requireLogin);
//member posts LOA
router.post("/", async (req: Request, res: Response) => {
@@ -23,7 +26,7 @@ router.post("/", async (req: Request, res: Response) => {
});
//admin posts LOA
router.post("/admin", async (req: Request, res: Response) => {
router.post("/admin", [requireRole("17th Administrator")], async (req: Request, res: Response) => {
let LOARequest = req.body as LOARequest;
LOARequest.created_by = req.user.id;
LOARequest.filed_date = new Date();
@@ -63,7 +66,7 @@ router.get("/history", async (req: Request, res: Response) => {
}
})
router.get('/all', async (req, res) => {
router.get('/all', [requireRole("17th Administrator")], async (req, res) => {
try {
const result = await getAllLOA();
res.status(200).json(result)
@@ -101,7 +104,7 @@ router.post('/cancel/:id', async (req: Request, res: Response) => {
})
//TODO: enforce admin only
router.post('/adminCancel/:id', async (req: Request, res: Response) => {
router.post('/adminCancel/:id', [requireRole("17th Administrator")], async (req: Request, res: Response) => {
let closer = req.user.id;
try {
await closeLOA(Number(req.params.id), closer);
@@ -113,7 +116,7 @@ router.post('/adminCancel/:id', async (req: Request, res: Response) => {
})
// TODO: Enforce admin only
router.post('/extend/:id', async (req: Request, res: Response) => {
router.post('/extend/:id', [requireRole("17th Administrator")], async (req: Request, res: Response) => {
const to: Date = req.body.to;
if (!to) {
@@ -145,4 +148,4 @@ router.get('/policy', async (req: Request, res: Response) => {
}
})
module.exports = router;
export const loaRouter = router;

View File

@@ -1,19 +1,16 @@
const express = require('express');
const router = express.Router();
import { Request, Response } from 'express';
import pool from '../db';
import { requireLogin, requireMemberState, requireRole } from '../middleware/auth';
import { getUserActiveLOA } from '../services/loaService';
import { getUserData } from '../services/memberService';
import { getMemberSettings, getMembersFull, getMembersLite, getUserData, setUserSettings } from '../services/memberService';
import { getUserRoles } from '../services/rolesService';
router.use((req, res, next) => {
console.log(req.user);
console.log('Time:', Date.now())
next()
})
import { memberSettings, MemberState } from '@app/shared/types/member';
//get all users
router.get('/', async (req, res) => {
router.get('/', [requireLogin, requireMemberState(MemberState.Member)], async (req, res) => {
try {
const result = await pool.query(
`SELECT
@@ -27,7 +24,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);
@@ -35,7 +32,7 @@ router.get('/', async (req, res) => {
}
});
router.get('/me', async (req, res) => {
router.get('/me', [requireLogin], async (req, res) => {
if (req.user === undefined)
return res.sendStatus(401)
@@ -60,10 +57,57 @@ router.get('/me', async (req, res) => {
}
})
router.get('/:id', 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) {
console.error(error);
res.status(500).json(error);
}
})
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) {
console.error(error);
res.status(500).json(error);
}
})
router.post('/lite/bulk', async (req: Request, res: Response) => {
try {
let ids = req.body.ids;
let out = await getMembersLite(ids);
res.status(200).json(out);
} catch (error) {
console.error(error);
res.status(500).json(error);
}
})
router.post('/full/bulk', async (req: Request, res: Response) => {
try {
let ids = req.body.ids;
let out = await getMembersFull(ids);
res.status(200).json(out);
} catch (error) {
console.error(error);
res.status(500).json(error);
}
})
router.get('/:id', [requireLogin], 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' });
}
@@ -77,10 +121,8 @@ router.get('/:id', async (req, res) => {
//update a user's display name (stub)
router.put('/:id/displayname', async (req, res) => {
// Stub: not implemented yet
return res.status(501).json({ error: 'Update display name not implemented' });
return res.status(501);
});
module.exports = router;
export const memberRouter = router;

View File

@@ -1,10 +1,18 @@
const express = require('express');
import { MemberState } from "@app/shared/types/member";
import { requireLogin, requireMemberState, requireRole } from "../middleware/auth";
import { getAllRanks, insertMemberRank } from "../services/rankService";
import express = require('express');
const r = express.Router();
const ur = express.Router();
const { getAllRanks, insertMemberRank } = require('../services/rankService')
r.use(requireLogin)
ur.use(requireLogin)
//insert a new latest rank for a user
ur.post('/', async (req, res) => {3
ur.post('/', [requireRole(["17th Command", "17th Administrator", "17th HQ"]), requireMemberState(MemberState.Member)], async (req, res) => {
3
try {
const change = req.body?.change;
await insertMemberRank(change.member_id, change.rank_id, change.date);
@@ -27,5 +35,5 @@ r.get('/', async (req, res) => {
}
});
module.exports.ranks = r;
module.exports.memberRanks = ur;
export const ranks = r;
export const memberRanks = ur;

View File

@@ -2,11 +2,16 @@ const express = require('express');
const r = express.Router();
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';
r.use(requireLogin)
ur.use(requireLogin)
//manually assign a member to a group
ur.post('/', async (req, res) => {
ur.post('/', [requireMemberState(MemberState.Member), requireRole("17th Administrator")], async (req, res) => {
try {
const body = req.body;
@@ -20,7 +25,7 @@ ur.post('/', async (req, res) => {
});
//manually remove member from group
ur.delete('/', async (req, res) => {
ur.delete('/', [requireMemberState(MemberState.Member), requireRole("17th Administrator")], async (req, res) => {
try {
const body = req.body;
console.log(body);
@@ -38,9 +43,9 @@ ur.delete('/', async (req, res) => {
})
//get all roles
r.get('/', async (req, res) => {
r.get('/', [requireMemberState(MemberState.Member)], async (req, res) => {
try {
const con = await pool.getConnection();
var con = await pool.getConnection();
// Get all roles
const roles = await con.query('SELECT * FROM roles;');
@@ -49,7 +54,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
`);
@@ -68,16 +73,17 @@ r.get('/', async (req, res) => {
members: roleIdToMembers[role.id] || []
}));
con.release();
res.json(result);
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Internal server error' });
} finally {
con.release();
}
});
//create a new role
r.post('/', 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 });
@@ -99,7 +105,7 @@ r.post('/', async (req, res) => {
}
})
r.delete('/:id', async (req, res) => {
r.delete('/:id', [requireMemberState(MemberState.Member), requireRole("17th Administrator")], async (req, res) => {
try {
const id = req.params.id;
@@ -112,5 +118,5 @@ r.delete('/:id', async (req, res) => {
}
})
module.exports.roles = r;
module.exports.memberRoles = ur;
export const roles = r;
export const memberRoles = ur;

View File

@@ -1,11 +1,15 @@
const express = require('express');
const status = express.Router();
const memberStatus = express.Router();
import express = require('express');
const statusR = express.Router();
const memberStatusR = express.Router();
import pool from '../db';
import { requireLogin } from '../middleware/auth';
statusR.use(requireLogin);
memberStatusR.use(requireLogin);
//insert a new latest rank for a user
memberStatus.post('/', async (req, res) => {
memberStatusR.post('/', async (req, res) => {
// try {
// const App = req.body?.App || {};
@@ -30,7 +34,7 @@ memberStatus.post('/', async (req, res) => {
});
//get all statuses
status.get('/', async (req, res) => {
statusR.get('/', async (req, res) => {
try {
const result = await pool.query('SELECT * FROM statuses;');
res.json(result);
@@ -40,7 +44,8 @@ status.get('/', async (req, res) => {
}
});
module.exports.status = status;
module.exports.memberStatus = memberStatus;
export const status = statusR;
export const memberStatus = memberStatusR;
// TODO, implement get all ranks route with SQL stirng SELECT id, name, short_name, category, sort_id FROM ranks;

View File

@@ -79,9 +79,9 @@ export async function getCourseEventDetails(id: number): Promise<CourseEventDeta
}
export async function insertCourseEvent(event: CourseEventDetails): Promise<number> {
console.log(event);
const con = await pool.getConnection();
try {
var con = await pool.getConnection();
await con.beginTransaction();
const res = await con.query("INSERT INTO course_events (course_id, event_date, remarks, created_by) VALUES (?, ?, ?, ?);", [event.course_id, toDateTime(event.event_date), event.remarks, event.created_by]);
var eventID: number = res.insertId;
@@ -98,12 +98,12 @@ export async function insertCourseEvent(event: CourseEventDetails): Promise<numb
VALUES (?, ?, ?, ?, ?, ?);`, [attendee.attendee_id, eventID, attendee.attendee_role_id, attendee.passed_bookwork, attendee.passed_qual, attendee.remarks]);
}
await con.commit();
await con.release();
return Number(eventID);
} catch (error) {
await con.rollback();
await con.release();
if (con) await con.rollback();
throw error;
} finally {
if (con) await con.release();
}
}

View File

@@ -1,34 +1,57 @@
import pool from "../db";
export enum MemberState {
Guest = "guest",
Applicant = "applicant",
Member = "member",
Retired = "retired",
Banned = "banned",
Denied = "denied"
}
import { Member, MemberLight, memberSettings, MemberState } from '@app/shared/types/member'
export async function getUserData(userID: number) {
const sql = `SELECT * FROM members WHERE id = ?`;
const res = await pool.query(sql, [userID]);
return res[0] ?? null;
const sql = `SELECT * FROM members WHERE id = ?`;
const res = await pool.query(sql, [userID]);
return res[0] ?? null;
}
export async function setUserState(userID: number, state: MemberState) {
const sql = `UPDATE members
const sql = `UPDATE members
SET state = ?
WHERE id = ?;`;
return await pool.query(sql, [state, userID]);
return await pool.query(sql, [state, userID]);
}
declare global {
namespace Express {
interface Request {
user: {
id: number;
name: string;
};
}
}
export async function getUserState(user: number): Promise<MemberState> {
let out = await pool.query(`SELECT state FROM members WHERE id = ?`, [user]);
console.log('hi')
return (out[0].state as MemberState);
}
export async function getMemberSettings(id: number): Promise<memberSettings> {
const sql = `SELECT * FROM view_member_settings WHERE id = ?`;
let out: memberSettings[] = await pool.query(sql, [id]);
if (out.length != 1)
throw new Error("Could not get user settings");
return out[0];
}
export async function setUserSettings(id: number, settings: memberSettings) {
const sql = `UPDATE view_member_settings SET
displayName = ?
WHERE id = ?;`;
let result = await pool.query(sql, [settings.displayName, id])
console.log(result);
}
export async function getMembersLite(ids: number[]): Promise<MemberLight[]> {
const sql = `SELECT m.member_id AS id,
m.member_name AS username,
m.displayName,
u.color
FROM view_member_rank_unit_status_latest m
LEFT JOIN units u ON u.name = m.unit
WHERE member_id IN (?);`;
const res: MemberLight[] = await pool.query(sql, [ids]);
return res;
}
export async function getMembersFull(ids: number[]): Promise<Member[]> {
const sql = `SELECT * FROM view_member_rank_unit_status_latest WHERE member_id IN (?);`;
const res: Member[] = await pool.query(sql, [ids]);
return res;
}

View File

@@ -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<void> {
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]

View File

@@ -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]);
}