Merge pull request 'logging-overhaul-2' (#139) from logging-overhaul-2 into main
All checks were successful
Continuous Integration / Update Development (push) Successful in 3m2s

Reviewed-on: #139
This commit was merged in pull request #139.
This commit is contained in:
2025-12-31 21:46:46 -06:00
25 changed files with 967 additions and 216 deletions

View File

@@ -23,6 +23,9 @@ APPLICATION_VERSION= # Should match release tag
APPLICATION_ENVIRONMENT= # dev / prod
CONFIG_ID= # configures
# Logger
LOG_DEPTH= # normal / verbose / profiling
# Glitchtip
GLITCHTIP_DSN=
DISABLE_GLITCHTIP= # true/false

View File

@@ -1,8 +1,5 @@
// const mariadb = require('mariadb')
import * as mariadb from 'mariadb';
const dotenv = require('dotenv')
dotenv.config();
const pool = mariadb.createPool({
host: process.env.DB_HOST,

View File

@@ -1,31 +1,40 @@
import dotenv = require('dotenv');
dotenv.config();
dotenv.config({ quiet: true });
import express = require('express');
import cors = require('cors');
import morgan = require('morgan');
import { logger, LogHeader, LogPayload } from './services/logging/logger';
const app = express()
app.use(morgan((tokens: morgan.TokenIndexer, req: express.Request, res: express.Response) => {
return JSON.stringify({
type: 'http',
timestamp: new Date().toISOString(),
const head: LogHeader = {
type: 'http',
level: 'info',
depth: 'normal',
timestamp: new Date().toISOString(),
}
const payload: LogPayload = {
message: 'HTTP request completed',
data: {
method: tokens.method(req, res),
path: tokens.url(req, res),
status: Number(tokens.status(req, res)),
response_time_ms: Number(tokens['response-time'](req, res)),
ip: req.ip,
user_id: req.user?.id,
user_name: req.user?.name,
user_agent: req.headers['user-agent'],
},
}
user: req.user
? { id: req.user.id, name: req.user.name }
: null,
});
logger.log(head.level, head.type, payload.message, payload.data, head.depth)
return '';
}, {
skip: (req: express.Request) => {
return req.originalUrl === '/members/me';
return req.originalUrl === '/members/me' || req.originalUrl === '/ping';
}
}))
@@ -43,14 +52,13 @@ const port = process.env.SERVER_PORT;
//glitchtip setup
import sentry = require('@sentry/node');
if (process.env.DISABLE_GLITCHTIP === "true") {
console.log("Glitchtip disabled")
logger.info('app', 'Glitchtip disabled', null, 'normal')
} else {
let dsn = process.env.GLITCHTIP_DSN;
let release = process.env.APPLICATION_VERSION;
let environment = process.env.APPLICATION_ENVIRONMENT;
console.log(release, environment)
sentry.init({ dsn: dsn, release: release, environment: environment, integrations: [sentry.captureConsoleIntegration({ levels: ['error'] })] });
console.log("Glitchtip initialized");
logger.info('app', 'Glitchtip initialized', null, 'normal')
}
//session setup
@@ -110,5 +118,5 @@ app.get('/ping', (req, res) => {
});
app.listen(port, () => {
console.log(`Example app listening on port ${port} `)
logger.info('app', `Example app listening on port ${port} `)
})

View File

@@ -2,59 +2,108 @@ const express = require('express');
const router = express.Router();
import pool from '../db';
import { approveApplication, createApplication, denyApplication, getAllMemberApplications, getApplicationByID, getApplicationComments, getApplicationList, getMemberApplication } from '../services/applicationService';
import { setUserState } from '../services/memberService';
import { approveApplication, createApplication, denyApplication, getAllMemberApplications, getApplicationByID, getApplicationComments, getApplicationList, getMemberApplication } from '../services/db/applicationService';
import { setUserState } from '../services/db/memberService';
import { MemberState } from '@app/shared/types/member';
import { getRankByName, insertMemberRank } from '../services/rankService';
import { getRankByName, insertMemberRank } from '../services/db/rankService';
import { ApplicationFull, CommentRow } from "@app/shared/types/application"
import { assignUserToStatus } from '../services/statusService';
import { assignUserToStatus } from '../services/db/statusService';
import { Request, response, Response } from 'express';
import { getUserRoles } from '../services/rolesService';
import { getUserRoles } from '../services/db/rolesService';
import { requireLogin, requireRole } from '../middleware/auth';
import { logger } from '../services/logging/logger';
//get CoC
router.get('/coc', async (req: Request, res: Response) => {
const output = await fetch(`${process.env.DOC_HOST}/api/pages/714`, {
try {
const response = 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();
if (!response.ok) {
const text = await response.text();
logger.error('app', 'Failed to fetch LOA policy from Bookstack', {
status: response.status,
statusText: response.statusText,
body: text,
});
return res.sendStatus(500);
}
const out = await response.json();
res.status(200).json(out.html);
} else {
console.error("Failed to fetch LOA policy from bookstack");
} catch (error) {
logger.error('app', 'Error fetching LOA policy from Bookstack', {
error: error instanceof Error ? error.message : String(error),
});
res.sendStatus(500);
}
})
});
// POST /application
router.post('/', [requireLogin], async (req, res) => {
try {
const App = req.body?.App || {};
const memberID = req.user.id;
const App = req.body?.App || {};
const appVersion = 1;
await createApplication(memberID, appVersion, JSON.stringify(App))
try {
req.profiler?.start('createApplication');
await createApplication(memberID, appVersion, JSON.stringify(App));
req.profiler?.end('createApplication');
req.profiler?.start('setUserState');
await setUserState(memberID, MemberState.Applicant);
req.profiler?.end('setUserState');
res.sendStatus(201);
// Log full route profiling
const summary = req.profiler?.summary();
if (summary) {
logger.info(
'profiling',
'POST /application completed',
{
memberID,
appVersion,
...summary,
},
'profiling'
);
}
} catch (err) {
console.error('Failed to create application: \n', err);
logger.error(
'app',
'Failed to create application',
{
memberID,
error: err instanceof Error ? err.message : String(err),
stack: err instanceof Error ? err.stack : undefined,
}
);
res.status(500).json({ error: 'Failed to create application' });
}
});
// GET /application/all
router.get('/all', [requireLogin, requireRole("Recruiter")], async (req, res) => {
try {
const rows = await getApplicationList();
res.status(200).json(rows);
} catch (err) {
console.error(err);
logger.error(
'app',
'Failed to get applications',
{
error: err instanceof Error ? err.message : String(err),
stack: err instanceof Error ? err.stack : undefined,
}
);
res.status(500);
}
});
@@ -68,8 +117,16 @@ router.get('/meList', async (req, res) => {
return res.status(200).json(application);
} catch (error) {
console.error('Failed to load applications: \n', error);
return res.status(500).json(error);
logger.error(
'app',
'Failed to get applications for user',
{
user: userID,
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
return res.status(500);
}
})
@@ -94,12 +151,20 @@ router.get('/me', [requireLogin], async (req, res) => {
return res.status(200).json(output);
} catch (error) {
console.error('Failed to load application:', error);
logger.error(
'app',
'Failed to load application',
{
user: userID,
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
return res.status(500).json(error);
}
})
// GET /application/:id
// GET /me/:id
router.get('/me/:id', [requireLogin], async (req: Request, res: Response) => {
let appID = Number(req.params.id);
let member = req.user.id;
@@ -119,9 +184,18 @@ router.get('/me/:id', [requireLogin], async (req: Request, res: Response) => {
}
return res.status(200).json(output);
}
catch (err) {
console.error('Query failed:', err);
return res.status(500).json({ error: 'Failed to load application' });
catch (error) {
logger.error(
'app',
'Failed to load application',
{
application: appID,
user: member,
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
return res.status(500);
}
});
@@ -143,9 +217,17 @@ router.get('/:id', [requireLogin, requireRole("Recruiter")], async (req: Request
}
return res.status(200).json(output);
}
catch (err) {
console.error('Query failed:', err);
return res.status(500).json({ error: 'Failed to load application' });
catch (error) {
logger.error(
'app',
'Failed to load application',
{
application: appID,
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
return res.status(500);
}
});
@@ -163,9 +245,22 @@ router.post('/approve/:id', [requireLogin, requireRole("Recruiter")], async (req
await pool.query('CALL sp_accept_new_recruit_validation(?, ?, ?, ?)', [Number(process.env.CONFIG_ID), app.member_id, approved_by, approved_by])
logger.info('app', "Member application approved", {
application: app.id,
applicant: app.member_id,
approver: approved_by
})
res.sendStatus(200);
} catch (err) {
console.error('Approve failed:', err);
} catch (error) {
logger.error(
'app',
'Failed to approve application',
{
application: appID,
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.status(500).json({ error: 'Failed to approve application' });
}
});
@@ -179,9 +274,23 @@ router.post('/deny/:id', [requireLogin, requireRole("Recruiter")], async (req: R
const app = await getApplicationByID(appID);
await denyApplication(appID, approver);
await setUserState(app.member_id, MemberState.Denied);
logger.info('app', "Member application approved", {
application: app.id,
applicant: app.member_id,
approver: approver
})
res.sendStatus(200);
} catch (err) {
console.error('Approve failed:', err);
} catch (error) {
logger.error(
'app',
'Failed to deny application',
{
application: appID,
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.status(500).json({ error: 'Failed to deny application' });
}
});
@@ -219,10 +328,25 @@ VALUES(?, ?, ?);`
INNER JOIN members AS member ON member.id = app.poster_id
WHERE app.id = ?; `;
const comment = await conn.query(getSQL, [result.insertId])
logger.info('app', "Application comment posted", {
application: appID,
poster: user.id,
comment: result.insertId,
})
res.status(201).json(comment[0]);
} catch (err) {
console.error('Comment failed:', err);
} catch (error) {
logger.error(
'app',
'Failed to post comment',
{
application: appID,
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.status(500).json({ error: 'Could not post comment' });
} finally {
conn.release();
@@ -263,10 +387,24 @@ VALUES(?, ?, ?, 1);`
INNER JOIN members AS member ON member.id = app.poster_id
WHERE app.id = ?; `;
const comment = await conn.query(getSQL, [result.insertId])
res.status(201).json(comment[0]);
} catch (err) {
console.error('Comment failed:', err);
logger.info('app', "Admin application comment posted", {
application: appID,
poster: user.id,
comment: result.insertId,
})
res.status(201).json(comment[0]);
} catch (error) {
logger.error(
'app',
'Failed to post comment',
{
application: appID,
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.status(500).json({ error: 'Could not post comment' });
} finally {
conn.release();
@@ -277,9 +415,22 @@ router.post('/restart', async (req: Request, res: Response) => {
const user = req.user.id;
try {
await setUserState(user, MemberState.Guest);
logger.info('app', "Member restarted application", {
user: user
})
res.sendStatus(200);
} catch (error) {
console.error('Comment failed:', error);
logger.error(
'app',
'Failed to restart application',
{
user: user,
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.status(500).json({ error: 'Could not rester application' });
}
})

View File

@@ -1,7 +1,5 @@
const passport = require('passport');
const OpenIDConnectStrategy = require('passport-openidconnect');
const dotenv = require('dotenv');
dotenv.config();
const express = require('express');
const { param } = require('./applications');
@@ -9,11 +7,14 @@ 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, mapDiscordtoID } from '../services/memberService';
import { getUserRoles } from '../services/db/rolesService';
import { getUserState, mapDiscordtoID } from '../services/db/memberService';
import { MemberState } from '@app/shared/types/member';
import { toDateTime } from '@app/shared/utils/time';
import { logger } from '../services/logging/logger';
const querystring = require('querystring');
import { performance } from 'perf_hooks';
function parseJwt(token) {
return JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
@@ -37,10 +38,10 @@ passport.use(new OpenIDConnectStrategy({
// console.log('profile:', profile);
// console.log('jwt: ', parseJwt(jwtClaims));
// console.log('params:', params);
let con;
try {
var con = await pool.getConnection();
con = await pool.getConnection();
await con.beginTransaction();
@@ -49,7 +50,13 @@ passport.use(new OpenIDConnectStrategy({
let memberId: number | null = null;
//if member exists
if (existing.length > 0) {
//login
memberId = existing[0].id;
logger.info('auth', `Existing member login`, {
memberId,
issuer,
});
} else {
//otherwise: create account mode
const jwt = parseJwt(jwtClaims);
@@ -61,13 +68,17 @@ passport.use(new OpenIDConnectStrategy({
if (discordID && memberId) {
// claim account
console.log("Claiming account");
const result = await con.query(
`UPDATE members SET authentik_sub = ?, authentik_issuer = ? WHERE id = ?;`,
[sub, issuer, memberId]
)
logger.info('auth', `Existing member claimed via Discord`, {
memberId,
discordID,
issuer,
});
} else {
console.log("New Account");
// new account
const username = sub.username;
const result = await con.query(
@@ -75,6 +86,13 @@ passport.use(new OpenIDConnectStrategy({
[username, sub, issuer]
)
memberId = Number(result.insertId);
logger.info('auth', `New member account created`, {
memberId,
username,
issuer,
});
}
}
@@ -83,10 +101,26 @@ passport.use(new OpenIDConnectStrategy({
await con.commit();
return cb(null, { memberId });
} catch (error) {
logger.error('auth', `Authentication transaction failed`, {
issuer,
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
});
if (con) {
try {
await con.rollback();
} catch (rollbackError) {
logger.error('auth', `Rollback failed`, {
error: rollbackError instanceof Error
? rollbackError.message
: String(rollbackError),
});
}
}
return cb(error);
} finally {
con.release();
if (con) con.release();
}
}));
@@ -133,8 +167,12 @@ router.get('/logout', [requireLogin], function (req, res, next) {
client_id: process.env.AUTH_CLIENT_ID,
returnTo: process.env.CLIENT_URL
};
res.redirect(process.env.AUTH_END_SESSION_URI + '?' + querystring.stringify(params));
logger.info('auth', `Member logged out`, {
user: req.user.id,
});
res.redirect(process.env.AUTH_END_SESSION_URI + '?' + querystring.stringify(params));
})
});
});
@@ -146,28 +184,75 @@ passport.serializeUser(function (user, cb) {
});
passport.deserializeUser(function (user, cb) {
const start = performance.now();
const timings: Record<string, number> = {};
process.nextTick(async function () {
const memberID = user.memberId as number;
let con;
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);
let t;
t = performance.now();
con = await pool.getConnection();
timings.getConnection = performance.now() - t;
t = performance.now();
const userResults = await con.query(
`SELECT id, name FROM members WHERE id = ?;`,
[memberID]
);
timings.memberQuery = performance.now() - t;
const userData: {
id: number;
name: string;
roles: Role[];
state: MemberState;
} = userResults[0];
t = performance.now();
const userRoles = await getUserRoles(memberID);
timings.roles = performance.now() - t;
userData.roles = userRoles || [];
t = performance.now();
userData.state = await getUserState(memberID);
} catch (error) {
console.error(error)
} finally {
con.release();
}
timings.state = performance.now() - t;
// 📊 PROFILING LOG
logger.info(
'profiling',
'passport.deserializeUser completed',
{
memberId: memberID,
total_ms: performance.now() - start,
breakdown_ms: timings,
},
'profiling'
);
return cb(null, userData);
} catch (error) {
logger.error(
'profiling',
'passport.deserializeUser failed',
{
memberId: memberID,
error: error instanceof Error ? error.message : String(error),
}
);
return cb(error);
} finally {
if (con) con.release();
}
});
});
declare global {
namespace Express {
interface Request {

View File

@@ -1,8 +1,9 @@
import { Request, Response } from "express";
import { createEvent, getEventAttendance, getEventDetails, getShortEventsInRange, setAttendanceStatus, setEventCancelled, updateEvent } from "../services/calendarService";
import { createEvent, getEventAttendance, getEventDetails, getShortEventsInRange, setAttendanceStatus, setEventCancelled, updateEvent } from "../services/db/calendarService";
import { CalendarAttendance, CalendarEvent } from "@app/shared/types/calendar";
import { requireLogin, requireMemberState, requireRole } from "../middleware/auth";
import { MemberState } from "@app/shared/types/member";
import { logger } from "../services/logging/logger";
const express = require('express');
const r = express.Router();
@@ -28,7 +29,14 @@ r.get('/', async (req, res) => {
res.status(200).json(events);
} catch (error) {
console.error('Error fetching calendar events:', error);
logger.error(
'app',
'Failed to get calendar events',
{
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.status(500).send('Error fetching calendar events');
}
});
@@ -41,9 +49,21 @@ r.post('/:id/cancel', [requireLogin, requireMemberState(MemberState.Member)], as
try {
const eventID = Number(req.params.id);
setEventCancelled(eventID, true);
logger.info('app', 'Calendar event cancelled', {
event: eventID,
user: req.user.id
})
res.sendStatus(200);
} catch (error) {
console.error('Error setting cancel status:', error);
logger.error(
'app',
'Failed to get cancel calendar event',
{
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.status(500).send('Error setting cancel status');
}
})
@@ -51,9 +71,21 @@ r.post('/:id/uncancel', [requireLogin, requireMemberState(MemberState.Member)],
try {
const eventID = Number(req.params.id);
setEventCancelled(eventID, false);
logger.info('app', 'Calendar event un-cancelled', {
event: eventID,
user: req.user.id
})
res.sendStatus(200);
} catch (error) {
console.error('Error setting cancel status:', error);
logger.error(
'app',
'Failed to uncancel calendar event',
{
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.status(500).send('Error setting cancel status');
}
})
@@ -65,12 +97,27 @@ r.post('/:id/attendance', [requireLogin, requireMemberState(MemberState.Member)]
let event = Number(req.params.id);
let state = req.query.state as CalendarAttendance;
setAttendanceStatus(member, event, state);
logger.info('app', 'Member set calendar event attendance', {
event: event,
user: req.user.id,
state: state
})
res.sendStatus(200);
} catch (error) {
console.error('Failed to set attendance:', error);
logger.error(
'app',
'Failed to set attendance',
{
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.status(500).json(error);
}
})
//get event details
r.get('/:id', async (req: Request, res: Response) => {
try {
@@ -79,9 +126,16 @@ r.get('/:id', async (req: Request, res: Response) => {
let details: CalendarEvent = await getEventDetails(eventID);
details.eventSignups = await getEventAttendance(eventID);
res.status(200).json(details);
} catch (err) {
console.error('Insert failed:', err);
res.status(500).json(err);
} catch (error) {
logger.error(
'app',
'Failed to get calendar event details',
{
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.status(500);
}
})
@@ -95,9 +149,22 @@ r.post('/', [requireLogin, requireMemberState(MemberState.Member)], async (req:
event.start = new Date(event.start);
event.end = new Date(event.end);
createEvent(event);
logger.info('app', 'Calendar event posted', {
event: event.id,
user: req.user.id
})
res.sendStatus(200);
} catch (error) {
console.error('Failed to create event:', error);
logger.error(
'app',
'Failed to create calendar event',
{
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.status(500).json(error);
}
})
@@ -108,9 +175,22 @@ r.put('/', [requireLogin, requireMemberState(MemberState.Member)], async (req: R
event.start = new Date(event.start);
event.end = new Date(event.end);
updateEvent(event);
logger.info('app', 'Calendar event updated', {
event: event.id,
user: req.user.id
})
res.sendStatus(200);
} catch (error) {
console.error('Failed to update event:', error);
logger.error(
'app',
'Failed to update calendar event',
{
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.status(500).json(error);
}
})

View File

@@ -1,8 +1,9 @@
import { CourseAttendee, CourseEventDetails } from "@app/shared/types/course";
import { getAllCourses, getCourseEventAttendees, getCourseEventDetails, getCourseEventRoles, getCourseEvents, insertCourseEvent } from "../services/CourseSerivce";
import { getAllCourses, getCourseEventAttendees, getCourseEventDetails, getCourseEventRoles, getCourseEvents, insertCourseEvent } from "../services/db/CourseSerivce";
import { Request, Response, Router } from "express";
import { requireLogin, requireMemberState } from "../middleware/auth";
import { MemberState } from "@app/shared/types/member";
import { logger } from "../services/logging/logger";
const cr = Router();
const er = Router();
@@ -16,9 +17,16 @@ cr.get('/', async (req, res) => {
try {
const courses = await getAllCourses();
res.status(200).json(courses);
} catch (err) {
console.error('failed to fetch courses', err);
res.status(500).json('failed to fetch courses\n' + err);
} catch (error) {
logger.error(
'app',
'Failed to fetch courses',
{
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.status(500).json('failed to fetch courses');
}
})
@@ -26,12 +34,20 @@ cr.get('/roles', async (req, res) => {
try {
const roles = await getCourseEventRoles();
res.status(200).json(roles);
} catch (err) {
console.error('failed to fetch course roles', err);
res.status(500).json('failed to fetch course roles\n' + err);
} catch (error) {
logger.error(
'app',
'Failed to fetch course roles',
{
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.status(500).json('failed to fetch course roles');
}
})
//get event list
er.get('/', async (req: Request, res: Response) => {
try {
const allowedSorts = new Map([
@@ -55,7 +71,14 @@ er.get('/', async (req: Request, res: Response) => {
let events = await getCourseEvents(sortDir, search, page, pageSize);
res.status(200).json(events);
} catch (error) {
console.error('failed to fetch reports', error);
logger.error(
'app',
'Failed to fetch course events',
{
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.status(500).json(error);
}
});
@@ -65,7 +88,14 @@ er.get('/:id', async (req: Request, res: Response) => {
let out = await getCourseEventDetails(Number(req.params.id));
res.status(200).json(out);
} catch (error) {
console.error('failed to fetch report', error);
logger.error(
'app',
'Failed to fetch course event',
{
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.status(500).json(error);
}
});
@@ -74,9 +104,16 @@ er.get('/attendees/:id', async (req: Request, res: Response) => {
try {
const attendees: CourseAttendee[] = await getCourseEventAttendees(Number(req.params.id));
res.status(200).json(attendees);
} catch (err) {
console.error('failed to fetch attendees', err);
res.status(500).json("failed to fetch attendees\n" + err);
} catch (error) {
logger.error(
'app',
'Failed to fetch course event attendees',
{
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.status(500).json("failed to fetch attendees");
}
})
@@ -87,9 +124,19 @@ er.post('/', async (req: Request, res: Response) => {
data.created_by = posterID;
data.event_date = new Date(data.event_date);
const id = await insertCourseEvent(data);
logger.info('app', 'Training report posted', { user: posterID, report: id })
res.status(201).json(id);
} catch (error) {
console.error('failed to post training', error);
logger.error(
'app',
'Failed to post training report',
{
user: posterID,
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.status(500).json("failed to post training\n" + error)
}
})

View File

@@ -3,22 +3,55 @@ const router = express.Router();
import { Request, Response } from 'express';
import { requireLogin } from '../middleware/auth';
import { logger } from '../services/logging/logger';
// GET /welcome
router.get('/welcome', [requireLogin], async (req: Request, res: Response) => {
const output = await fetch(`${process.env.DOC_HOST}/api/pages/717`, {
const t0 = performance.now(); // optional profiling start
try {
const response = 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();
if (!response.ok) {
const text = await response.text();
logger.error('app', 'Failed to fetch welcome page from Bookstack', {
status: response.status,
statusText: response.statusText,
body: text,
userId: req.user?.id,
});
return res.sendStatus(500);
}
const out = await response.json();
res.status(200).json(out.html);
} else {
console.error("Failed to fetch LOA policy from bookstack");
// optional profiling log
const duration = performance.now() - t0;
logger.info(
'profiling',
'GET /welcome completed',
{
userId: req.user?.id,
total_ms: duration,
},
'profiling'
);
} catch (error) {
logger.error('app', 'Error fetching welcome page from Bookstack', {
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
userId: req.user?.id,
});
res.sendStatus(500);
}
})
});
export const docsRouter = router;

View File

@@ -3,9 +3,10 @@ const router = express.Router();
import { Request, Response } from 'express';
import pool from '../db';
import { closeLOA, createNewLOA, getAllLOA, getLOAbyID, getLoaTypes, getUserLOA, setLOAExtension } from '../services/loaService';
import { closeLOA, createNewLOA, getAllLOA, getLOAbyID, getLoaTypes, getUserLOA, setLOAExtension } from '../services/db/loaService';
import { LOARequest } from '@app/shared/types/loa';
import { requireLogin, requireRole } from '../middleware/auth';
import { logger } from '../services/logging/logger';
router.use(requireLogin);
@@ -18,9 +19,17 @@ router.post("/", async (req: Request, res: Response) => {
try {
await createNewLOA(LOARequest);
logger.info('app', 'LOA Posted', { poster: req.user.id, user: LOARequest.member_id })
res.sendStatus(201);
} catch (error) {
console.error(error);
logger.error(
'app',
'Failed to post LOA',
{
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.status(500).send(error);
}
});
@@ -32,10 +41,17 @@ router.post("/admin", [requireRole(['17th Administrator', '17th HQ', '17th Comma
LOARequest.filed_date = new Date();
try {
await createNewLOA(LOARequest);
logger.info('app', 'LOA Posted', { poster: req.user.id, user: LOARequest.member_id })
res.sendStatus(201);
} catch (error) {
console.error(error);
res.status(500).send(error);
logger.error(
'app',
'Failed to post LOA',
{
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
); res.status(500).send(error);
}
});
@@ -46,7 +62,14 @@ router.get("/me", async (req: Request, res: Response) => {
const result = await getUserLOA(user);
res.status(200).json(result)
} catch (error) {
console.error(error);
logger.error(
'app',
'Failed to get user current LOA',
{
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.status(500).send(error);
}
})
@@ -62,7 +85,14 @@ router.get("/history", async (req: Request, res: Response) => {
const result = await getUserLOA(user, page, pageSize);
res.status(200).json(result)
} catch (error) {
console.error(error);
logger.error(
'app',
'Failed to get user LOA history',
{
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.status(500).send(error);
}
})
@@ -74,7 +104,14 @@ router.get('/all', [requireRole(['17th Administrator', '17th HQ', '17th Command'
const result = await getAllLOA(page, pageSize);
res.status(200).json(result)
} catch (error) {
console.error(error);
logger.error(
'app',
'Failed to get full LOA history',
{
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.status(500).send(error);
}
})
@@ -84,8 +121,15 @@ router.get('/types', async (req: Request, res: Response) => {
let out = await getLoaTypes();
res.status(200).json(out);
} catch (error) {
logger.error(
'app',
'Failed to get LOA types',
{
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.status(500).json(error);
console.error(error);
}
})
@@ -99,9 +143,19 @@ router.post('/cancel/:id', async (req: Request, res: Response) => {
}
await closeLOA(Number(req.params.id), closer);
logger.info('app', 'LOA Closed', { closed_by: closer, LOA: id })
res.sendStatus(200);
} catch (error) {
console.error(error);
logger.error(
'app',
'Failed to cancel LOA',
{
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.status(500).json(error);
}
})
@@ -111,14 +165,24 @@ router.post('/adminCancel/:id', [requireRole(['17th Administrator', '17th HQ', '
let closer = req.user.id;
try {
await closeLOA(Number(req.params.id), closer);
logger.info('app', 'LOA Closed', { closed_by: closer, LOA: req.params.id })
res.sendStatus(200);
} catch (error) {
console.error(error);
logger.error(
'app',
'Failed to cancel LOA',
{
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.status(500).json(error);
}
})
// TODO: Enforce admin only
// extend LOA
router.post('/extend/:id', [requireRole(['17th Administrator', '17th HQ', '17th Command'])], async (req: Request, res: Response) => {
const to: Date = req.body.to;
@@ -128,27 +192,71 @@ router.post('/extend/:id', [requireRole(['17th Administrator', '17th HQ', '17th
try {
await setLOAExtension(Number(req.params.id), to);
logger.info('app', 'LOA Extended', { extended_by: req.user.id, LOA: req.params.id })
res.sendStatus(200);
} catch (error) {
console.error(error)
logger.error(
'app',
'Failed to extend LOA',
{
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.status(500).json(error);
}
})
// GET /policy
router.get('/policy', async (req: Request, res: Response) => {
const output = await fetch(`${process.env.DOC_HOST}/api/pages/42`, {
const t0 = performance.now();
try {
const response = await fetch(`${process.env.DOC_HOST}/api/pages/42`, {
headers: {
Authorization: `Token ${process.env.DOC_TOKEN_ID}:${process.env.DOC_TOKEN_SECRET}`,
}
})
},
});
if (output.ok) {
const out = await output.json();
if (!response.ok) {
const text = await response.text();
logger.error('app', 'Failed to fetch policy page from Bookstack', {
pageId: 42,
status: response.status,
statusText: response.statusText,
body: text,
userId: req.user?.id,
});
return res.sendStatus(500);
}
const out = await response.json();
res.status(200).json(out.html);
} else {
console.error("Failed to fetch LOA policy from bookstack");
logger.info(
'profiling',
'GET /policy completed',
{
pageId: 42,
total_ms: performance.now() - t0,
},
'profiling'
);
} catch (error) {
logger.error('app', 'Error fetching policy page from Bookstack', {
pageId: 42,
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
userId: req.user?.id,
});
res.sendStatus(500);
}
})
});
export const loaRouter = router;

View File

@@ -4,11 +4,14 @@ 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 { getAllMembersLite, getMemberSettings, getMembersFull, getMembersLite, getUserData, getUserState, setUserSettings } from '../services/memberService';
import { getUserRoles } from '../services/rolesService';
import { getUserActiveLOA } from '../services/db/loaService';
import { getAllMembersLite, getMemberSettings, getMembersFull, getMembersLite, getUserData, getUserState, setUserSettings } from '../services/db/memberService';
import { getUserRoles } from '../services/db/rolesService';
import { memberSettings, MemberState, myData } from '@app/shared/types/member';
import { Performance } from 'perf_hooks';
import { logger } from '../services/logging/logger';
//get all users
router.get('/', [requireLogin, requireMemberState(MemberState.Member)], async (req, res) => {
try {
@@ -26,29 +29,69 @@ router.get('/', [requireLogin, requireMemberState(MemberState.Member)], async (r
END AS on_loa
FROM view_member_rank_unit_status_latest v;`);
return res.status(200).json(result);
} catch (err) {
console.error('Error fetching users:', err);
} catch (error) {
logger.error(
'app',
'Failed to get all users',
{
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
return res.status(500).json({ error: 'Failed to fetch users' });
}
});
router.get('/me', [requireLogin], async (req, res) => {
if (req.user === undefined)
return res.sendStatus(401)
router.get('/me', [requireLogin], async (req: Request, res) => {
if (!req.user) return res.sendStatus(401);
const routeStart = performance.now();
const timings: Record<string, number> = {};
try {
const memData = await getUserData(req.user.id);
const LOAData = await getUserActiveLOA(req.user.id);
const memState = await getUserState(req.user.id);
const roleData = await getUserRoles(req.user.id);
let t;
t = performance.now();
const memData = await getUserData(req.user.id);
timings.member = performance.now() - t;
t = performance.now();
const LOAData = await getUserActiveLOA(req.user.id);
timings.loa = performance.now() - t;
t = performance.now();
const memState = await getUserState(req.user.id);
timings.state = performance.now() - t;
t = performance.now();
const roleData = await getUserRoles(req.user.id);
timings.roles = performance.now() - t;
const userDataFull: myData = {
member: memData,
LOAs: LOAData,
roles: roleData,
state: memState,
};
const userDataFull: myData = { member: memData, LOAs: LOAData, roles: roleData, state: memState };
res.status(200).json(userDataFull);
logger.info('profiling', 'GET /members/me completed', {
userId: req.user.id,
total_ms: performance.now() - routeStart,
breakdown_ms: timings,
}, 'profiling');
} catch (error) {
console.error('Error fetching user data:', error);
logger.error('profiling', 'GET /members/me failed', {
userId: req.user?.id,
error: error instanceof Error ? error.message : String(error),
});
return res.status(500).json({ error: 'Failed to fetch user data' });
}
})
});
router.get('/settings', [requireLogin], async (req: Request, res: Response) => {
try {
@@ -56,7 +99,14 @@ router.get('/settings', [requireLogin], async (req: Request, res: Response) => {
let output = await getMemberSettings(user);
res.status(200).json(output);
} catch (error) {
console.error(error);
logger.error(
'app',
'Failed to get member settings',
{
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.status(500).json(error);
}
})
@@ -66,10 +116,17 @@ router.put('/settings', [requireLogin], async (req: Request, res: Response) => {
let user = req.user.id;
let settings: memberSettings = req.body;
await setUserSettings(user, settings);
logger.info('app', 'User updated profile settings', { user: user })
res.sendStatus(200);
} catch (error) {
console.error(error);
res.status(500).json(error);
logger.error(
'app',
'Failed to update user settings',
{
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
); res.status(500).json(error);
}
})
@@ -78,7 +135,14 @@ router.get('/lite', [requireLogin], async (req: Request, res: Response) => {
let out = await getAllMembersLite();
res.status(200).json(out);
} catch (error) {
console.error(error);
logger.error(
'app',
'Failed to get lite users',
{
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.status(500).json(error);
}
})
@@ -89,7 +153,14 @@ router.post('/lite/bulk', async (req: Request, res: Response) => {
let out = await getMembersLite(ids);
res.status(200).json(out);
} catch (error) {
console.error(error);
logger.error(
'app',
'Failed to get batch lite users',
{
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.status(500).json(error);
}
})
@@ -101,22 +172,36 @@ router.post('/full/bulk', async (req: Request, res: Response) => {
let out = await getMembersFull(ids);
res.status(200).json(out);
} catch (error) {
console.error(error);
res.status(500).json(error);
logger.error(
'app',
'Failed to get batch full users',
{
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
); res.status(500).json(error);
}
})
router.get('/:id', [requireLogin], async (req, res) => {
try {
const userId = req.params.id;
try {
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' });
}
return res.status(200).json(result.rows[0]);
} catch (err) {
console.error('Error fetching user:', err);
return res.status(500).json({ error: 'Failed to fetch user' });
} catch (error) {
logger.error(
'app',
'Failed to get user',
{
user: userId,
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
); return res.status(500).json({ error: 'Failed to fetch user' });
}
});

View File

@@ -1,9 +1,10 @@
import { MemberState } from "@app/shared/types/member";
import { requireLogin, requireMemberState, requireRole } from "../middleware/auth";
import { batchInsertMemberRank, getAllRanks, getPromotionHistorySummary, getPromotionsOnDay, insertMemberRank } from "../services/rankService";
import { batchInsertMemberRank, getAllRanks, getPromotionHistorySummary, getPromotionsOnDay, insertMemberRank } from "../services/db/rankService";
import { BatchPromotion, BatchPromotionMember } from '@app/shared/schemas/promotionSchema'
import express = require('express');
import { logger } from "../services/logging/logger";
const r = express.Router();
const ur = express.Router();
@@ -19,10 +20,17 @@ ur.post('/', [requireRole(["17th Command", "17th Administrator", "17th HQ"]), re
if (!change) res.sendStatus(400);
await batchInsertMemberRank(change, author);
logger.info('app', 'Promotion batch submitted', { author: author })
res.sendStatus(201);
} catch (err) {
console.error('Insert failed:', err);
} catch (error) {
logger.error(
'app',
'Failed to post rank change',
{
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.status(500).json({ error: 'Failed to update ranks' });
}
});
@@ -30,11 +38,17 @@ ur.post('/', [requireRole(["17th Command", "17th Administrator", "17th HQ"]), re
ur.get('/', async (req: express.Request, res: express.Response) => {
try {
const promos = await getPromotionHistorySummary();
console.log(promos);
res.status(200).json(promos);
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Internal server error' });
} catch (error) {
logger.error(
'app',
'Failed to get rank change history',
{
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.sendStatus(500);
}
});
@@ -42,13 +56,20 @@ ur.get('/:day', async (req: express.Request, res: express.Response) => {
try {
if (!req.params.day) res.sendStatus(400);
let day = new Date(req.params.day)
var day = new Date(req.params.day)
const promos = await getPromotionsOnDay(day);
console.log(promos);
res.status(200).json(promos);
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Internal server error' });
} catch (error) {
logger.error(
'app',
'Failed to get rank change history on day',
{
day: day,
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.sendStatus(500);
}
})
@@ -57,8 +78,15 @@ r.get('/', async (req, res) => {
try {
const ranks = await getAllRanks();
res.json(ranks);
} catch (err) {
console.error(err);
} catch (error) {
logger.error(
'app',
'Failed to get all ranks',
{
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.status(500).json({ error: 'Internal server error' });
}
});

View File

@@ -5,46 +5,71 @@ 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, getAllRoles, getRole, getUsersWithRole } from '../services/rolesService';
import { assignUserGroup, createGroup, getAllRoles, getRole, getUsersWithRole } from '../services/db/rolesService';
import { Request, Response } from 'express';
import { logger } from '../services/logging/logger';
r.use(requireLogin)
ur.use(requireLogin)
//manually assign a member to a group
ur.post('/', [requireMemberState(MemberState.Member), requireRole("17th Administrator")], async (req, res) => {
try {
ur.post('/', [requireMemberState(MemberState.Member), requireRole("17th Administrator")], async (req: Request, res) => {
const body = req.body;
try {
await assignUserGroup(body.member_id, body.role_id);
logger.info('app', 'User assigned role', { user: body.member_id, role: body.role_id, assigner: req.user.id })
res.sendStatus(201);
} catch (err) {
if (err?.code === 'ER_DUP_ENTRY') {
} catch (error) {
if (error?.code === 'ER_DUP_ENTRY') {
return res.status(400).json({
error: 'Member already has this role',
});
}
console.error('Insert failed:', err);
logger.error(
'app',
'Failed to assign role',
{
user: body.member_id,
role: body.role_id,
assigner: req.user.id,
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.status(500).json({ error: 'Failed to add to group' });
}
});
//manually remove member from group
ur.delete('/', [requireMemberState(MemberState.Member), requireRole("17th Administrator")], async (req, res) => {
try {
ur.delete('/', [requireMemberState(MemberState.Member), requireRole("17th Administrator")], async (req: Request, res: Response) => {
const body = req.body;
try {
const sql = 'DELETE FROM members_roles WHERE member_id = ? AND role_id = ?'
await pool.query(sql, [body.member_id, body.role_id])
logger.info('app', 'User removed role', { user: body.member_id, role: body.role_id, assigner: req.user.id })
res.sendStatus(200);
}
catch (err) {
console.error("delete failed: ", err)
catch (error) {
logger.error(
'app',
'Failed to remove role',
{
user: body.member_id,
role: body.role_id,
assigner: req.user.id,
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.status(500).json({ error: 'Failed to remove from group' });
}
})
@@ -52,9 +77,17 @@ ur.delete('/', [requireMemberState(MemberState.Member), requireRole("17th Admini
r.get('/', [requireMemberState(MemberState.Member)], async (req, res) => {
try {
const roles = await getAllRoles();
res.status(200).json(roles);
} catch (err) {
console.error(err);
} catch (error) {
logger.error(
'app',
'Failed to get all roles',
{
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.sendStatus(500);
}
});
@@ -63,8 +96,16 @@ r.get('/:id/members', [requireMemberState(MemberState.Member)], async (req: Requ
try {
const members = await getUsersWithRole(Number(req.params.id));
res.status(200).json(members);
} catch (err) {
console.error(err);
} catch (error) {
logger.error(
'app',
'Failed to get role members',
{
role: req.params.id,
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.sendStatus(500);
}
})
@@ -74,8 +115,16 @@ r.get('/:id', [requireMemberState(MemberState.Member)], async (req: Request, res
try {
const role = await getRole(Number(req.params.id));
res.status(200).json(role);
} catch (err) {
console.error(err);
} catch (error) {
logger.error(
'app',
'Failed to get role members',
{
role: req.params.id,
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.sendStatus(500);
}
})

View File

@@ -4,6 +4,7 @@ const memberStatusR = express.Router();
import pool from '../db';
import { requireLogin } from '../middleware/auth';
import { logger } from '../services/logging/logger';
statusR.use(requireLogin);
memberStatusR.use(requireLogin);
@@ -38,9 +39,16 @@ statusR.get('/', async (req, res) => {
try {
const result = await pool.query('SELECT * FROM statuses;');
res.json(result);
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Internal server error' });
} catch (error) {
logger.error(
'app',
'Failed to get all statuses',
{
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
}
);
res.sendStatus(500);
}
});

View File

@@ -1,4 +1,4 @@
import pool from "../db"
import pool from "../../db"
import { Course, CourseAttendee, CourseAttendeeRole, CourseEventDetails, CourseEventSummary, RawAttendeeRow } from "@app/shared/types/course"
import { PagedData } from "@app/shared/types/pagination";
import { toDateTime } from "@app/shared/utils/time";

View File

@@ -1,5 +1,5 @@
import { ApplicationListRow, ApplicationRow, CommentRow } from "@app/shared/types/application";
import pool from "../db";
import pool from "../../db";
import { error } from "console";
export async function createApplication(memberID: number, appVersion: number, app: string) {

View File

@@ -1,4 +1,4 @@
import pool from '../db';
import pool from '../../db';
import { CalendarEventShort, CalendarSignup, CalendarEvent, CalendarAttendance } from "@app/shared/types/calendar"
import { toDateTime } from "@app/shared/utils/time"

View File

@@ -1,5 +1,5 @@
import { toDateTime } from "@app/shared/utils/time";
import pool from "../db";
import pool from "../../db";
import { LOARequest, LOAType } from '@app/shared/types/loa'
import { PagedData } from '@app/shared/types/pagination'

View File

@@ -1,5 +1,5 @@
import { Role } from "@app/shared/types/roles";
import pool from "../db";
import pool from "../../db";
import { Member, MemberCardDetails, MemberLight, memberSettings, MemberState } from '@app/shared/types/member'
export async function getUserData(userID: number): Promise<Member> {

View File

@@ -1,6 +1,6 @@
import { BatchPromotion, BatchPromotionMember } from "@app/shared/schemas/promotionSchema";
import { PromotionDetails, PromotionSummary } from "@app/shared/types/rank"
import pool from "../db";
import pool from "../../db";
import { PagedData } from "@app/shared/types/pagination";
import { toDateTime } from "@app/shared/utils/time";
@@ -39,7 +39,6 @@ export async function insertMemberRank(member_id: number, rank_id: number, date?
export async function batchInsertMemberRank(promos: BatchPromotionMember[], author: number) {
try {
var con = await pool.getConnection();
console.log(promos);
promos.forEach(p => {
con.query(`CALL sp_update_member_rank(?, ?, ?, ?, ?, ?)`, [p.member_id, p.rank_id, author, author, "Rank Change", toDateTime(new Date(p.start_date))])
});
@@ -70,7 +69,7 @@ export async function getPromotionHistorySummary(page: number = 1, pageSize: num
let promoList: PromotionSummary[] = await pool.query(sql, [pageSize, offset]) as PromotionSummary[];
let loaCount = Number((await pool.query(`SELECT
let rowCount = Number((await pool.query(`SELECT
COUNT(*) AS total_grouped_days_count
FROM
(
@@ -79,10 +78,9 @@ export async function getPromotionHistorySummary(page: number = 1, pageSize: num
WHERE reason = 'Rank Change'
) AS grouped_days;`))[0]);
console.log(loaCount);
let pageCount = loaCount / pageSize;
let pageCount = rowCount / pageSize;
let output: PagedData<PromotionSummary> = { data: promoList, pagination: { page: page, pageSize: pageSize, total: loaCount, totalPages: pageCount } }
let output: PagedData<PromotionSummary> = { data: promoList, pagination: { page: page, pageSize: pageSize, total: rowCount, totalPages: pageCount } }
return output;
}

View File

@@ -1,5 +1,5 @@
import { MemberLight } from '@app/shared/types/member';
import pool from '../db';
import pool from '../../db';
import { Role, RoleSummary } from '@app/shared/types/roles'
export async function assignUserGroup(userID: number, roleID: number) {

View File

@@ -1,4 +1,4 @@
import pool from "../db"
import pool from "../../db"
export async function assignUserToStatus(userID: number, statusID: number) {
const sql = `INSERT INTO members_statuses (member_id, status_id, start_date) VALUES (?, ?, NOW())`

View File

@@ -0,0 +1,72 @@
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
export type LogDepth = 'normal' | 'verbose' | 'profiling';
export type LogType = 'http' | 'app' | 'auth' | 'profiling';
export interface LogHeader {
timestamp: string;
level: LogLevel;
depth: LogDepth;
type: LogType; // 'http', 'app', 'db', etc.
user_id?: number;
}
export interface LogPayload {
message?: string; // short human-friendly description
data?: Record<string, any>; // type-specific rich data
}
// Environment defaults
const CURRENT_DEPTH: LogDepth = (process.env.LOG_DEPTH as LogDepth) || 'normal';
const DEPTH_ORDER: Record<LogDepth, number> = { normal: 0, verbose: 1, profiling: 2 };
function shouldLog(depth: LogDepth) {
let should = DEPTH_ORDER[depth] <= DEPTH_ORDER[CURRENT_DEPTH]
return should;
}
function emitLog(header: LogHeader, payload: LogPayload = {}) {
if (!shouldLog(header.depth)) return;
const logLine = { ...header, ...payload };
if (header.level === 'error')
console.error(JSON.stringify(logLine))
else
console.log(JSON.stringify(logLine));
}
export const logger = {
log(level: LogLevel, type: LogType, message: string, data?: Record<string, any>, depth: LogDepth = 'normal', context?: Partial<LogHeader>) {
const header: LogHeader = {
timestamp: new Date().toISOString(),
level,
depth,
type,
...context,
};
const payload: LogPayload = {
message,
data,
};
emitLog(header, payload);
},
info(type: LogType, message: string, data?: Record<string, any>, depth: LogDepth = 'normal', context?: Partial<LogHeader>) {
this.log('info', type, message, data, depth, context);
},
debug(type: LogType, message: string, data?: Record<string, any>, depth: LogDepth = 'normal', context?: Partial<LogHeader>) {
this.log('debug', type, message, data, depth, context);
},
warn(type: LogType, message: string, data?: Record<string, any>, depth: LogDepth = 'normal', context?: Partial<LogHeader>) {
this.log('warn', type, message, data, depth, context);
},
error(type: LogType, message: string, data?: Record<string, any>, depth: LogDepth = 'normal', context?: Partial<LogHeader>) {
this.log('error', type, message, data, depth, context);
},
}

View File

@@ -7,6 +7,7 @@
"node",
"express"
],
"sourceMap": true,
"paths": {
"@app/shared/*": ["../shared/*"]
}

View File

@@ -61,7 +61,6 @@ export async function getPromoHistory(page?: number, pageSize?: number): Promise
}
export async function getPromotionsOnDay(day: Date): Promise<PromotionDetails[]> {
console.log(day.toISOString());
const res = await fetch(`${addr}/memberRanks/${day.toISOString()}`, {
credentials: 'include',
})

View File

@@ -105,7 +105,6 @@ export const useMemberDirectory = defineStore('memberDirectory', () => {
try {
const res = await getFullMembers(ids);
for (const m of res) {
console.log(m)
full[m.member.member_id] = m;
const waiters = fullWaiters.get(m.member.member_id);