Merge commit '412001b1b4a85e0dea04f642319e19396955af95' into account-claim
This commit is contained in:
@@ -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=
|
||||
|
||||
@@ -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)
|
||||
49
api/src/middleware/auth.ts
Normal file
49
api/src/middleware/auth.ts
Normal 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);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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]
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user