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
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:
@@ -23,6 +23,9 @@ APPLICATION_VERSION= # Should match release tag
|
|||||||
APPLICATION_ENVIRONMENT= # dev / prod
|
APPLICATION_ENVIRONMENT= # dev / prod
|
||||||
CONFIG_ID= # configures
|
CONFIG_ID= # configures
|
||||||
|
|
||||||
|
# Logger
|
||||||
|
LOG_DEPTH= # normal / verbose / profiling
|
||||||
|
|
||||||
# Glitchtip
|
# Glitchtip
|
||||||
GLITCHTIP_DSN=
|
GLITCHTIP_DSN=
|
||||||
DISABLE_GLITCHTIP= # true/false
|
DISABLE_GLITCHTIP= # true/false
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
// const mariadb = require('mariadb')
|
// const mariadb = require('mariadb')
|
||||||
import * as mariadb from 'mariadb';
|
import * as mariadb from 'mariadb';
|
||||||
const dotenv = require('dotenv')
|
|
||||||
dotenv.config();
|
|
||||||
|
|
||||||
|
|
||||||
const pool = mariadb.createPool({
|
const pool = mariadb.createPool({
|
||||||
host: process.env.DB_HOST,
|
host: process.env.DB_HOST,
|
||||||
|
|||||||
@@ -1,31 +1,40 @@
|
|||||||
import dotenv = require('dotenv');
|
import dotenv = require('dotenv');
|
||||||
dotenv.config();
|
dotenv.config({ quiet: true });
|
||||||
|
|
||||||
import express = require('express');
|
import express = require('express');
|
||||||
import cors = require('cors');
|
import cors = require('cors');
|
||||||
import morgan = require('morgan');
|
import morgan = require('morgan');
|
||||||
|
import { logger, LogHeader, LogPayload } from './services/logging/logger';
|
||||||
|
|
||||||
const app = express()
|
const app = express()
|
||||||
|
|
||||||
app.use(morgan((tokens: morgan.TokenIndexer, req: express.Request, res: express.Response) => {
|
app.use(morgan((tokens: morgan.TokenIndexer, req: express.Request, res: express.Response) => {
|
||||||
return JSON.stringify({
|
|
||||||
|
const head: LogHeader = {
|
||||||
type: 'http',
|
type: 'http',
|
||||||
|
level: 'info',
|
||||||
|
depth: 'normal',
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
|
}
|
||||||
|
|
||||||
method: tokens.method(req, res),
|
const payload: LogPayload = {
|
||||||
path: tokens.url(req, res),
|
message: 'HTTP request completed',
|
||||||
status: Number(tokens.status(req, res)),
|
data: {
|
||||||
response_time_ms: Number(tokens['response-time'](req, res)),
|
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)),
|
||||||
|
user_id: req.user?.id,
|
||||||
|
user_name: req.user?.name,
|
||||||
|
user_agent: req.headers['user-agent'],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
ip: req.ip,
|
logger.log(head.level, head.type, payload.message, payload.data, head.depth)
|
||||||
user_agent: req.headers['user-agent'],
|
return '';
|
||||||
|
|
||||||
user: req.user
|
|
||||||
? { id: req.user.id, name: req.user.name }
|
|
||||||
: null,
|
|
||||||
});
|
|
||||||
}, {
|
}, {
|
||||||
skip: (req: express.Request) => {
|
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
|
//glitchtip setup
|
||||||
import sentry = require('@sentry/node');
|
import sentry = require('@sentry/node');
|
||||||
if (process.env.DISABLE_GLITCHTIP === "true") {
|
if (process.env.DISABLE_GLITCHTIP === "true") {
|
||||||
console.log("Glitchtip disabled")
|
logger.info('app', 'Glitchtip disabled', null, 'normal')
|
||||||
} else {
|
} else {
|
||||||
let dsn = process.env.GLITCHTIP_DSN;
|
let dsn = process.env.GLITCHTIP_DSN;
|
||||||
let release = process.env.APPLICATION_VERSION;
|
let release = process.env.APPLICATION_VERSION;
|
||||||
let environment = process.env.APPLICATION_ENVIRONMENT;
|
let environment = process.env.APPLICATION_ENVIRONMENT;
|
||||||
console.log(release, environment)
|
|
||||||
sentry.init({ dsn: dsn, release: release, environment: environment, integrations: [sentry.captureConsoleIntegration({ levels: ['error'] })] });
|
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
|
//session setup
|
||||||
@@ -110,5 +118,5 @@ app.get('/ping', (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.listen(port, () => {
|
app.listen(port, () => {
|
||||||
console.log(`Example app listening on port ${port} `)
|
logger.info('app', `Example app listening on port ${port} `)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,59 +2,108 @@ const express = require('express');
|
|||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
import pool from '../db';
|
import pool from '../db';
|
||||||
import { approveApplication, createApplication, denyApplication, getAllMemberApplications, getApplicationByID, getApplicationComments, getApplicationList, getMemberApplication } from '../services/applicationService';
|
import { approveApplication, createApplication, denyApplication, getAllMemberApplications, getApplicationByID, getApplicationComments, getApplicationList, getMemberApplication } from '../services/db/applicationService';
|
||||||
import { setUserState } from '../services/memberService';
|
import { setUserState } from '../services/db/memberService';
|
||||||
import { MemberState } from '@app/shared/types/member';
|
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 { 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 { Request, response, Response } from 'express';
|
||||||
import { getUserRoles } from '../services/rolesService';
|
import { getUserRoles } from '../services/db/rolesService';
|
||||||
import { requireLogin, requireRole } from '../middleware/auth';
|
import { requireLogin, requireRole } from '../middleware/auth';
|
||||||
|
import { logger } from '../services/logging/logger';
|
||||||
|
|
||||||
//get CoC
|
//get CoC
|
||||||
router.get('/coc', async (req: Request, res: Response) => {
|
router.get('/coc', async (req: Request, res: Response) => {
|
||||||
const output = await fetch(`${process.env.DOC_HOST}/api/pages/714`, {
|
try {
|
||||||
headers: {
|
const response = await fetch(`${process.env.DOC_HOST}/api/pages/714`, {
|
||||||
Authorization: `Token ${process.env.DOC_TOKEN_ID}:${process.env.DOC_TOKEN_SECRET}`,
|
headers: {
|
||||||
}
|
Authorization: `Token ${process.env.DOC_TOKEN_ID}:${process.env.DOC_TOKEN_SECRET}`,
|
||||||
})
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (output.ok) {
|
if (!response.ok) {
|
||||||
const out = await output.json();
|
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);
|
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);
|
res.sendStatus(500);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
// POST /application
|
// POST /application
|
||||||
router.post('/', [requireLogin], async (req, res) => {
|
router.post('/', [requireLogin], async (req, res) => {
|
||||||
|
const memberID = req.user.id;
|
||||||
|
const App = req.body?.App || {};
|
||||||
|
const appVersion = 1;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const App = req.body?.App || {};
|
req.profiler?.start('createApplication');
|
||||||
const memberID = req.user.id;
|
await createApplication(memberID, appVersion, JSON.stringify(App));
|
||||||
|
req.profiler?.end('createApplication');
|
||||||
|
|
||||||
const appVersion = 1;
|
req.profiler?.start('setUserState');
|
||||||
|
|
||||||
await createApplication(memberID, appVersion, JSON.stringify(App))
|
|
||||||
await setUserState(memberID, MemberState.Applicant);
|
await setUserState(memberID, MemberState.Applicant);
|
||||||
|
req.profiler?.end('setUserState');
|
||||||
|
|
||||||
res.sendStatus(201);
|
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) {
|
} 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' });
|
res.status(500).json({ error: 'Failed to create application' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// GET /application/all
|
// GET /application/all
|
||||||
router.get('/all', [requireLogin, requireRole("Recruiter")], async (req, res) => {
|
router.get('/all', [requireLogin, requireRole("Recruiter")], async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const rows = await getApplicationList();
|
const rows = await getApplicationList();
|
||||||
res.status(200).json(rows);
|
res.status(200).json(rows);
|
||||||
} catch (err) {
|
} 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);
|
res.status(500);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -68,8 +117,16 @@ router.get('/meList', async (req, res) => {
|
|||||||
|
|
||||||
return res.status(200).json(application);
|
return res.status(200).json(application);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load applications: \n', error);
|
logger.error(
|
||||||
return res.status(500).json(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);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -79,7 +136,7 @@ router.get('/me', [requireLogin], async (req, res) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
let application = await getMemberApplication(userID);
|
let application = await getMemberApplication(userID);
|
||||||
|
|
||||||
if (application === undefined) {
|
if (application === undefined) {
|
||||||
res.sendStatus(204);
|
res.sendStatus(204);
|
||||||
return;
|
return;
|
||||||
@@ -94,12 +151,20 @@ router.get('/me', [requireLogin], async (req, res) => {
|
|||||||
|
|
||||||
return res.status(200).json(output);
|
return res.status(200).json(output);
|
||||||
} catch (error) {
|
} 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);
|
return res.status(500).json(error);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// GET /application/:id
|
// GET /me/:id
|
||||||
router.get('/me/:id', [requireLogin], async (req: Request, res: Response) => {
|
router.get('/me/:id', [requireLogin], async (req: Request, res: Response) => {
|
||||||
let appID = Number(req.params.id);
|
let appID = Number(req.params.id);
|
||||||
let member = req.user.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);
|
return res.status(200).json(output);
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (error) {
|
||||||
console.error('Query failed:', err);
|
logger.error(
|
||||||
return res.status(500).json({ error: 'Failed to load application' });
|
'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);
|
return res.status(200).json(output);
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (error) {
|
||||||
console.error('Query failed:', err);
|
logger.error(
|
||||||
return res.status(500).json({ error: 'Failed to load application' });
|
'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])
|
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);
|
res.sendStatus(200);
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
console.error('Approve failed:', err);
|
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' });
|
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);
|
const app = await getApplicationByID(appID);
|
||||||
await denyApplication(appID, approver);
|
await denyApplication(appID, approver);
|
||||||
await setUserState(app.member_id, MemberState.Denied);
|
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);
|
res.sendStatus(200);
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
console.error('Approve failed:', err);
|
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' });
|
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
|
INNER JOIN members AS member ON member.id = app.poster_id
|
||||||
WHERE app.id = ?; `;
|
WHERE app.id = ?; `;
|
||||||
const comment = await conn.query(getSQL, [result.insertId])
|
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]);
|
res.status(201).json(comment[0]);
|
||||||
|
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
console.error('Comment failed:', err);
|
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' });
|
res.status(500).json({ error: 'Could not post comment' });
|
||||||
} finally {
|
} finally {
|
||||||
conn.release();
|
conn.release();
|
||||||
@@ -263,10 +387,24 @@ VALUES(?, ?, ?, 1);`
|
|||||||
INNER JOIN members AS member ON member.id = app.poster_id
|
INNER JOIN members AS member ON member.id = app.poster_id
|
||||||
WHERE app.id = ?; `;
|
WHERE app.id = ?; `;
|
||||||
const comment = await conn.query(getSQL, [result.insertId])
|
const comment = await conn.query(getSQL, [result.insertId])
|
||||||
res.status(201).json(comment[0]);
|
|
||||||
|
|
||||||
} catch (err) {
|
logger.info('app', "Admin application comment posted", {
|
||||||
console.error('Comment failed:', err);
|
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' });
|
res.status(500).json({ error: 'Could not post comment' });
|
||||||
} finally {
|
} finally {
|
||||||
conn.release();
|
conn.release();
|
||||||
@@ -277,9 +415,22 @@ router.post('/restart', async (req: Request, res: Response) => {
|
|||||||
const user = req.user.id;
|
const user = req.user.id;
|
||||||
try {
|
try {
|
||||||
await setUserState(user, MemberState.Guest);
|
await setUserState(user, MemberState.Guest);
|
||||||
|
|
||||||
|
logger.info('app', "Member restarted application", {
|
||||||
|
user: user
|
||||||
|
})
|
||||||
|
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
} catch (error) {
|
} 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' });
|
res.status(500).json({ error: 'Could not rester application' });
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
const passport = require('passport');
|
const passport = require('passport');
|
||||||
const OpenIDConnectStrategy = require('passport-openidconnect');
|
const OpenIDConnectStrategy = require('passport-openidconnect');
|
||||||
const dotenv = require('dotenv');
|
|
||||||
dotenv.config();
|
|
||||||
|
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const { param } = require('./applications');
|
const { param } = require('./applications');
|
||||||
@@ -9,11 +7,14 @@ const router = express.Router();
|
|||||||
import { Role } from '@app/shared/types/roles';
|
import { Role } from '@app/shared/types/roles';
|
||||||
import pool from '../db';
|
import pool from '../db';
|
||||||
import { requireLogin } from '../middleware/auth';
|
import { requireLogin } from '../middleware/auth';
|
||||||
import { getUserRoles } from '../services/rolesService';
|
import { getUserRoles } from '../services/db/rolesService';
|
||||||
import { getUserState, mapDiscordtoID } from '../services/memberService';
|
import { getUserState, mapDiscordtoID } from '../services/db/memberService';
|
||||||
import { MemberState } from '@app/shared/types/member';
|
import { MemberState } from '@app/shared/types/member';
|
||||||
import { toDateTime } from '@app/shared/utils/time';
|
import { toDateTime } from '@app/shared/utils/time';
|
||||||
|
import { logger } from '../services/logging/logger';
|
||||||
const querystring = require('querystring');
|
const querystring = require('querystring');
|
||||||
|
import { performance } from 'perf_hooks';
|
||||||
|
|
||||||
|
|
||||||
function parseJwt(token) {
|
function parseJwt(token) {
|
||||||
return JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
|
return JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
|
||||||
@@ -37,10 +38,10 @@ passport.use(new OpenIDConnectStrategy({
|
|||||||
// console.log('profile:', profile);
|
// console.log('profile:', profile);
|
||||||
// console.log('jwt: ', parseJwt(jwtClaims));
|
// console.log('jwt: ', parseJwt(jwtClaims));
|
||||||
// console.log('params:', params);
|
// console.log('params:', params);
|
||||||
|
let con;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var con = await pool.getConnection();
|
con = await pool.getConnection();
|
||||||
|
|
||||||
await con.beginTransaction();
|
await con.beginTransaction();
|
||||||
|
|
||||||
@@ -49,7 +50,13 @@ passport.use(new OpenIDConnectStrategy({
|
|||||||
let memberId: number | null = null;
|
let memberId: number | null = null;
|
||||||
//if member exists
|
//if member exists
|
||||||
if (existing.length > 0) {
|
if (existing.length > 0) {
|
||||||
|
//login
|
||||||
memberId = existing[0].id;
|
memberId = existing[0].id;
|
||||||
|
logger.info('auth', `Existing member login`, {
|
||||||
|
memberId,
|
||||||
|
issuer,
|
||||||
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
//otherwise: create account mode
|
//otherwise: create account mode
|
||||||
const jwt = parseJwt(jwtClaims);
|
const jwt = parseJwt(jwtClaims);
|
||||||
@@ -61,13 +68,17 @@ passport.use(new OpenIDConnectStrategy({
|
|||||||
|
|
||||||
if (discordID && memberId) {
|
if (discordID && memberId) {
|
||||||
// claim account
|
// claim account
|
||||||
console.log("Claiming account");
|
|
||||||
const result = await con.query(
|
const result = await con.query(
|
||||||
`UPDATE members SET authentik_sub = ?, authentik_issuer = ? WHERE id = ?;`,
|
`UPDATE members SET authentik_sub = ?, authentik_issuer = ? WHERE id = ?;`,
|
||||||
[sub, issuer, memberId]
|
[sub, issuer, memberId]
|
||||||
)
|
)
|
||||||
|
logger.info('auth', `Existing member claimed via Discord`, {
|
||||||
|
memberId,
|
||||||
|
discordID,
|
||||||
|
issuer,
|
||||||
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
console.log("New Account");
|
|
||||||
// new account
|
// new account
|
||||||
const username = sub.username;
|
const username = sub.username;
|
||||||
const result = await con.query(
|
const result = await con.query(
|
||||||
@@ -75,6 +86,13 @@ passport.use(new OpenIDConnectStrategy({
|
|||||||
[username, sub, issuer]
|
[username, sub, issuer]
|
||||||
)
|
)
|
||||||
memberId = Number(result.insertId);
|
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();
|
await con.commit();
|
||||||
return cb(null, { memberId });
|
return cb(null, { memberId });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await con.rollback();
|
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);
|
return cb(error);
|
||||||
} finally {
|
} 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,
|
client_id: process.env.AUTH_CLIENT_ID,
|
||||||
returnTo: process.env.CLIENT_URL
|
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) {
|
passport.deserializeUser(function (user, cb) {
|
||||||
|
const start = performance.now();
|
||||||
|
const timings: Record<string, number> = {};
|
||||||
|
|
||||||
process.nextTick(async function () {
|
process.nextTick(async function () {
|
||||||
|
|
||||||
const memberID = user.memberId as number;
|
const memberID = user.memberId as number;
|
||||||
|
let con;
|
||||||
|
|
||||||
|
|
||||||
var userData: { id: number, name: string, roles: Role[], state: MemberState };
|
|
||||||
try {
|
try {
|
||||||
var con = await pool.getConnection();
|
let t;
|
||||||
let userResults = await con.query(`SELECT id, name FROM members WHERE id = ?;`, [memberID])
|
|
||||||
userData = userResults[0];
|
t = performance.now();
|
||||||
let userRoles = await getUserRoles(memberID);
|
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 || [];
|
userData.roles = userRoles || [];
|
||||||
|
|
||||||
|
t = performance.now();
|
||||||
userData.state = await getUserState(memberID);
|
userData.state = await getUserState(memberID);
|
||||||
|
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) {
|
} catch (error) {
|
||||||
console.error(error)
|
logger.error(
|
||||||
|
'profiling',
|
||||||
|
'passport.deserializeUser failed',
|
||||||
|
{
|
||||||
|
memberId: memberID,
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return cb(error);
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
con.release();
|
if (con) con.release();
|
||||||
}
|
}
|
||||||
return cb(null, userData);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
namespace Express {
|
namespace Express {
|
||||||
interface Request {
|
interface Request {
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { Request, Response } from "express";
|
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 { CalendarAttendance, CalendarEvent } from "@app/shared/types/calendar";
|
||||||
import { requireLogin, requireMemberState, requireRole } from "../middleware/auth";
|
import { requireLogin, requireMemberState, requireRole } from "../middleware/auth";
|
||||||
import { MemberState } from "@app/shared/types/member";
|
import { MemberState } from "@app/shared/types/member";
|
||||||
|
import { logger } from "../services/logging/logger";
|
||||||
|
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const r = express.Router();
|
const r = express.Router();
|
||||||
@@ -28,7 +29,14 @@ r.get('/', async (req, res) => {
|
|||||||
|
|
||||||
res.status(200).json(events);
|
res.status(200).json(events);
|
||||||
} catch (error) {
|
} 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');
|
res.status(500).send('Error fetching calendar events');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -41,9 +49,21 @@ r.post('/:id/cancel', [requireLogin, requireMemberState(MemberState.Member)], as
|
|||||||
try {
|
try {
|
||||||
const eventID = Number(req.params.id);
|
const eventID = Number(req.params.id);
|
||||||
setEventCancelled(eventID, true);
|
setEventCancelled(eventID, true);
|
||||||
|
|
||||||
|
logger.info('app', 'Calendar event cancelled', {
|
||||||
|
event: eventID,
|
||||||
|
user: req.user.id
|
||||||
|
})
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
} catch (error) {
|
} 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');
|
res.status(500).send('Error setting cancel status');
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -51,9 +71,21 @@ r.post('/:id/uncancel', [requireLogin, requireMemberState(MemberState.Member)],
|
|||||||
try {
|
try {
|
||||||
const eventID = Number(req.params.id);
|
const eventID = Number(req.params.id);
|
||||||
setEventCancelled(eventID, false);
|
setEventCancelled(eventID, false);
|
||||||
|
|
||||||
|
logger.info('app', 'Calendar event un-cancelled', {
|
||||||
|
event: eventID,
|
||||||
|
user: req.user.id
|
||||||
|
})
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
} catch (error) {
|
} 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');
|
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 event = Number(req.params.id);
|
||||||
let state = req.query.state as CalendarAttendance;
|
let state = req.query.state as CalendarAttendance;
|
||||||
setAttendanceStatus(member, event, state);
|
setAttendanceStatus(member, event, state);
|
||||||
|
|
||||||
|
logger.info('app', 'Member set calendar event attendance', {
|
||||||
|
event: event,
|
||||||
|
user: req.user.id,
|
||||||
|
state: state
|
||||||
|
})
|
||||||
|
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
} catch (error) {
|
} 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);
|
res.status(500).json(error);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
//get event details
|
//get event details
|
||||||
r.get('/:id', async (req: Request, res: Response) => {
|
r.get('/:id', async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
@@ -79,9 +126,16 @@ r.get('/:id', async (req: Request, res: Response) => {
|
|||||||
let details: CalendarEvent = await getEventDetails(eventID);
|
let details: CalendarEvent = await getEventDetails(eventID);
|
||||||
details.eventSignups = await getEventAttendance(eventID);
|
details.eventSignups = await getEventAttendance(eventID);
|
||||||
res.status(200).json(details);
|
res.status(200).json(details);
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
console.error('Insert failed:', err);
|
logger.error(
|
||||||
res.status(500).json(err);
|
'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.start = new Date(event.start);
|
||||||
event.end = new Date(event.end);
|
event.end = new Date(event.end);
|
||||||
createEvent(event);
|
createEvent(event);
|
||||||
|
|
||||||
|
logger.info('app', 'Calendar event posted', {
|
||||||
|
event: event.id,
|
||||||
|
user: req.user.id
|
||||||
|
})
|
||||||
|
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
} catch (error) {
|
} 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);
|
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.start = new Date(event.start);
|
||||||
event.end = new Date(event.end);
|
event.end = new Date(event.end);
|
||||||
updateEvent(event);
|
updateEvent(event);
|
||||||
|
|
||||||
|
logger.info('app', 'Calendar event updated', {
|
||||||
|
event: event.id,
|
||||||
|
user: req.user.id
|
||||||
|
})
|
||||||
|
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
} catch (error) {
|
} 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);
|
res.status(500).json(error);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { CourseAttendee, CourseEventDetails } from "@app/shared/types/course";
|
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 { Request, Response, Router } from "express";
|
||||||
import { requireLogin, requireMemberState } from "../middleware/auth";
|
import { requireLogin, requireMemberState } from "../middleware/auth";
|
||||||
import { MemberState } from "@app/shared/types/member";
|
import { MemberState } from "@app/shared/types/member";
|
||||||
|
import { logger } from "../services/logging/logger";
|
||||||
|
|
||||||
const cr = Router();
|
const cr = Router();
|
||||||
const er = Router();
|
const er = Router();
|
||||||
@@ -16,9 +17,16 @@ cr.get('/', async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const courses = await getAllCourses();
|
const courses = await getAllCourses();
|
||||||
res.status(200).json(courses);
|
res.status(200).json(courses);
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
console.error('failed to fetch courses', err);
|
logger.error(
|
||||||
res.status(500).json('failed to fetch courses\n' + err);
|
'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 {
|
try {
|
||||||
const roles = await getCourseEventRoles();
|
const roles = await getCourseEventRoles();
|
||||||
res.status(200).json(roles);
|
res.status(200).json(roles);
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
console.error('failed to fetch course roles', err);
|
logger.error(
|
||||||
res.status(500).json('failed to fetch course roles\n' + err);
|
'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) => {
|
er.get('/', async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const allowedSorts = new Map([
|
const allowedSorts = new Map([
|
||||||
@@ -55,7 +71,14 @@ er.get('/', async (req: Request, res: Response) => {
|
|||||||
let events = await getCourseEvents(sortDir, search, page, pageSize);
|
let events = await getCourseEvents(sortDir, search, page, pageSize);
|
||||||
res.status(200).json(events);
|
res.status(200).json(events);
|
||||||
} catch (error) {
|
} 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);
|
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));
|
let out = await getCourseEventDetails(Number(req.params.id));
|
||||||
res.status(200).json(out);
|
res.status(200).json(out);
|
||||||
} catch (error) {
|
} 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);
|
res.status(500).json(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -74,9 +104,16 @@ er.get('/attendees/:id', async (req: Request, res: Response) => {
|
|||||||
try {
|
try {
|
||||||
const attendees: CourseAttendee[] = await getCourseEventAttendees(Number(req.params.id));
|
const attendees: CourseAttendee[] = await getCourseEventAttendees(Number(req.params.id));
|
||||||
res.status(200).json(attendees);
|
res.status(200).json(attendees);
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
console.error('failed to fetch attendees', err);
|
logger.error(
|
||||||
res.status(500).json("failed to fetch attendees\n" + err);
|
'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.created_by = posterID;
|
||||||
data.event_date = new Date(data.event_date);
|
data.event_date = new Date(data.event_date);
|
||||||
const id = await insertCourseEvent(data);
|
const id = await insertCourseEvent(data);
|
||||||
|
|
||||||
|
logger.info('app', 'Training report posted', { user: posterID, report: id })
|
||||||
res.status(201).json(id);
|
res.status(201).json(id);
|
||||||
} catch (error) {
|
} 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)
|
res.status(500).json("failed to post training\n" + error)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,22 +3,55 @@ const router = express.Router();
|
|||||||
|
|
||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import { requireLogin } from '../middleware/auth';
|
import { requireLogin } from '../middleware/auth';
|
||||||
|
import { logger } from '../services/logging/logger';
|
||||||
|
|
||||||
|
// GET /welcome
|
||||||
router.get('/welcome', [requireLogin], async (req: Request, res: Response) => {
|
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
|
||||||
headers: {
|
|
||||||
Authorization: `Token ${process.env.DOC_TOKEN_ID}:${process.env.DOC_TOKEN_SECRET}`,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (output.ok) {
|
try {
|
||||||
const out = await output.json();
|
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 (!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);
|
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);
|
res.sendStatus(500);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const docsRouter = router;
|
export const docsRouter = router;
|
||||||
@@ -3,9 +3,10 @@ const router = express.Router();
|
|||||||
|
|
||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import pool from '../db';
|
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 { LOARequest } from '@app/shared/types/loa';
|
||||||
import { requireLogin, requireRole } from '../middleware/auth';
|
import { requireLogin, requireRole } from '../middleware/auth';
|
||||||
|
import { logger } from '../services/logging/logger';
|
||||||
|
|
||||||
router.use(requireLogin);
|
router.use(requireLogin);
|
||||||
|
|
||||||
@@ -18,9 +19,17 @@ router.post("/", async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await createNewLOA(LOARequest);
|
await createNewLOA(LOARequest);
|
||||||
|
logger.info('app', 'LOA Posted', { poster: req.user.id, user: LOARequest.member_id })
|
||||||
res.sendStatus(201);
|
res.sendStatus(201);
|
||||||
} catch (error) {
|
} 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);
|
res.status(500).send(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -32,10 +41,17 @@ router.post("/admin", [requireRole(['17th Administrator', '17th HQ', '17th Comma
|
|||||||
LOARequest.filed_date = new Date();
|
LOARequest.filed_date = new Date();
|
||||||
try {
|
try {
|
||||||
await createNewLOA(LOARequest);
|
await createNewLOA(LOARequest);
|
||||||
|
logger.info('app', 'LOA Posted', { poster: req.user.id, user: LOARequest.member_id })
|
||||||
res.sendStatus(201);
|
res.sendStatus(201);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
logger.error(
|
||||||
res.status(500).send(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);
|
const result = await getUserLOA(user);
|
||||||
res.status(200).json(result)
|
res.status(200).json(result)
|
||||||
} catch (error) {
|
} 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);
|
res.status(500).send(error);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -62,7 +85,14 @@ router.get("/history", async (req: Request, res: Response) => {
|
|||||||
const result = await getUserLOA(user, page, pageSize);
|
const result = await getUserLOA(user, page, pageSize);
|
||||||
res.status(200).json(result)
|
res.status(200).json(result)
|
||||||
} catch (error) {
|
} 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);
|
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);
|
const result = await getAllLOA(page, pageSize);
|
||||||
res.status(200).json(result)
|
res.status(200).json(result)
|
||||||
} catch (error) {
|
} 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);
|
res.status(500).send(error);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -84,8 +121,15 @@ router.get('/types', async (req: Request, res: Response) => {
|
|||||||
let out = await getLoaTypes();
|
let out = await getLoaTypes();
|
||||||
res.status(200).json(out);
|
res.status(200).json(out);
|
||||||
} catch (error) {
|
} 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);
|
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);
|
await closeLOA(Number(req.params.id), closer);
|
||||||
|
|
||||||
|
logger.info('app', 'LOA Closed', { closed_by: closer, LOA: id })
|
||||||
|
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
} catch (error) {
|
} 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);
|
res.status(500).json(error);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -111,14 +165,24 @@ router.post('/adminCancel/:id', [requireRole(['17th Administrator', '17th HQ', '
|
|||||||
let closer = req.user.id;
|
let closer = req.user.id;
|
||||||
try {
|
try {
|
||||||
await closeLOA(Number(req.params.id), closer);
|
await closeLOA(Number(req.params.id), closer);
|
||||||
|
|
||||||
|
logger.info('app', 'LOA Closed', { closed_by: closer, LOA: req.params.id })
|
||||||
|
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
} catch (error) {
|
} 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);
|
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) => {
|
router.post('/extend/:id', [requireRole(['17th Administrator', '17th HQ', '17th Command'])], async (req: Request, res: Response) => {
|
||||||
const to: Date = req.body.to;
|
const to: Date = req.body.to;
|
||||||
|
|
||||||
@@ -128,27 +192,71 @@ router.post('/extend/:id', [requireRole(['17th Administrator', '17th HQ', '17th
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await setLOAExtension(Number(req.params.id), to);
|
await setLOAExtension(Number(req.params.id), to);
|
||||||
|
logger.info('app', 'LOA Extended', { extended_by: req.user.id, LOA: req.params.id })
|
||||||
|
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
} catch (error) {
|
} 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);
|
res.status(500).json(error);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// GET /policy
|
||||||
router.get('/policy', async (req: Request, res: Response) => {
|
router.get('/policy', async (req: Request, res: Response) => {
|
||||||
const output = await fetch(`${process.env.DOC_HOST}/api/pages/42`, {
|
const t0 = performance.now();
|
||||||
headers: {
|
|
||||||
Authorization: `Token ${process.env.DOC_TOKEN_ID}:${process.env.DOC_TOKEN_SECRET}`,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (output.ok) {
|
try {
|
||||||
const out = await output.json();
|
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 (!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);
|
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);
|
res.sendStatus(500);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
export const loaRouter = router;
|
export const loaRouter = router;
|
||||||
@@ -4,11 +4,14 @@ const router = express.Router();
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import pool from '../db';
|
import pool from '../db';
|
||||||
import { requireLogin, requireMemberState, requireRole } from '../middleware/auth';
|
import { requireLogin, requireMemberState, requireRole } from '../middleware/auth';
|
||||||
import { getUserActiveLOA } from '../services/loaService';
|
import { getUserActiveLOA } from '../services/db/loaService';
|
||||||
import { getAllMembersLite, getMemberSettings, getMembersFull, getMembersLite, getUserData, getUserState, setUserSettings } from '../services/memberService';
|
import { getAllMembersLite, getMemberSettings, getMembersFull, getMembersLite, getUserData, getUserState, setUserSettings } from '../services/db/memberService';
|
||||||
import { getUserRoles } from '../services/rolesService';
|
import { getUserRoles } from '../services/db/rolesService';
|
||||||
import { memberSettings, MemberState, myData } from '@app/shared/types/member';
|
import { memberSettings, MemberState, myData } from '@app/shared/types/member';
|
||||||
|
|
||||||
|
import { Performance } from 'perf_hooks';
|
||||||
|
import { logger } from '../services/logging/logger';
|
||||||
|
|
||||||
//get all users
|
//get all users
|
||||||
router.get('/', [requireLogin, requireMemberState(MemberState.Member)], async (req, res) => {
|
router.get('/', [requireLogin, requireMemberState(MemberState.Member)], async (req, res) => {
|
||||||
try {
|
try {
|
||||||
@@ -26,29 +29,69 @@ router.get('/', [requireLogin, requireMemberState(MemberState.Member)], async (r
|
|||||||
END AS on_loa
|
END AS on_loa
|
||||||
FROM view_member_rank_unit_status_latest v;`);
|
FROM view_member_rank_unit_status_latest v;`);
|
||||||
return res.status(200).json(result);
|
return res.status(200).json(result);
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
console.error('Error fetching users:', err);
|
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' });
|
return res.status(500).json({ error: 'Failed to fetch users' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/me', [requireLogin], async (req, res) => {
|
router.get('/me', [requireLogin], async (req: Request, res) => {
|
||||||
if (req.user === undefined)
|
if (!req.user) return res.sendStatus(401);
|
||||||
return res.sendStatus(401)
|
|
||||||
|
const routeStart = performance.now();
|
||||||
|
const timings: Record<string, number> = {};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const memData = await getUserData(req.user.id);
|
let t;
|
||||||
const LOAData = await getUserActiveLOA(req.user.id);
|
|
||||||
const memState = await getUserState(req.user.id);
|
t = performance.now();
|
||||||
const roleData = await getUserRoles(req.user.id);
|
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);
|
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) {
|
} 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' });
|
return res.status(500).json({ error: 'Failed to fetch user data' });
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
router.get('/settings', [requireLogin], async (req: Request, res: Response) => {
|
router.get('/settings', [requireLogin], async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
@@ -56,7 +99,14 @@ router.get('/settings', [requireLogin], async (req: Request, res: Response) => {
|
|||||||
let output = await getMemberSettings(user);
|
let output = await getMemberSettings(user);
|
||||||
res.status(200).json(output);
|
res.status(200).json(output);
|
||||||
} catch (error) {
|
} 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);
|
res.status(500).json(error);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -66,10 +116,17 @@ router.put('/settings', [requireLogin], async (req: Request, res: Response) => {
|
|||||||
let user = req.user.id;
|
let user = req.user.id;
|
||||||
let settings: memberSettings = req.body;
|
let settings: memberSettings = req.body;
|
||||||
await setUserSettings(user, settings);
|
await setUserSettings(user, settings);
|
||||||
|
logger.info('app', 'User updated profile settings', { user: user })
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
logger.error(
|
||||||
res.status(500).json(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();
|
let out = await getAllMembersLite();
|
||||||
res.status(200).json(out);
|
res.status(200).json(out);
|
||||||
} catch (error) {
|
} 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);
|
res.status(500).json(error);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -89,7 +153,14 @@ router.post('/lite/bulk', async (req: Request, res: Response) => {
|
|||||||
let out = await getMembersLite(ids);
|
let out = await getMembersLite(ids);
|
||||||
res.status(200).json(out);
|
res.status(200).json(out);
|
||||||
} catch (error) {
|
} 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);
|
res.status(500).json(error);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -101,22 +172,36 @@ router.post('/full/bulk', async (req: Request, res: Response) => {
|
|||||||
let out = await getMembersFull(ids);
|
let out = await getMembersFull(ids);
|
||||||
res.status(200).json(out);
|
res.status(200).json(out);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
logger.error(
|
||||||
res.status(500).json(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) => {
|
router.get('/:id', [requireLogin], async (req, res) => {
|
||||||
|
const userId = req.params.id;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const userId = req.params.id;
|
|
||||||
const result = await pool.query('SELECT * FROM view_member_rank_unit_status_latest 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) {
|
if (result.rows.length === 0) {
|
||||||
return res.status(404).json({ error: 'User not found' });
|
return res.status(404).json({ error: 'User not found' });
|
||||||
}
|
}
|
||||||
return res.status(200).json(result.rows[0]);
|
return res.status(200).json(result.rows[0]);
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
console.error('Error fetching user:', err);
|
logger.error(
|
||||||
return res.status(500).json({ error: 'Failed to fetch user' });
|
'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' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { MemberState } from "@app/shared/types/member";
|
import { MemberState } from "@app/shared/types/member";
|
||||||
import { requireLogin, requireMemberState, requireRole } from "../middleware/auth";
|
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 { BatchPromotion, BatchPromotionMember } from '@app/shared/schemas/promotionSchema'
|
||||||
|
|
||||||
import express = require('express');
|
import express = require('express');
|
||||||
|
import { logger } from "../services/logging/logger";
|
||||||
const r = express.Router();
|
const r = express.Router();
|
||||||
const ur = 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);
|
if (!change) res.sendStatus(400);
|
||||||
|
|
||||||
await batchInsertMemberRank(change, author);
|
await batchInsertMemberRank(change, author);
|
||||||
|
logger.info('app', 'Promotion batch submitted', { author: author })
|
||||||
res.sendStatus(201);
|
res.sendStatus(201);
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
console.error('Insert failed:', err);
|
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' });
|
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) => {
|
ur.get('/', async (req: express.Request, res: express.Response) => {
|
||||||
try {
|
try {
|
||||||
const promos = await getPromotionHistorySummary();
|
const promos = await getPromotionHistorySummary();
|
||||||
console.log(promos);
|
|
||||||
res.status(200).json(promos);
|
res.status(200).json(promos);
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
console.error(err);
|
logger.error(
|
||||||
res.status(500).json({ error: 'Internal server 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 {
|
try {
|
||||||
if (!req.params.day) res.sendStatus(400);
|
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);
|
const promos = await getPromotionsOnDay(day);
|
||||||
console.log(promos);
|
|
||||||
res.status(200).json(promos);
|
res.status(200).json(promos);
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
console.error(err);
|
logger.error(
|
||||||
res.status(500).json({ error: 'Internal server 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 {
|
try {
|
||||||
const ranks = await getAllRanks();
|
const ranks = await getAllRanks();
|
||||||
res.json(ranks);
|
res.json(ranks);
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
console.error(err);
|
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' });
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,46 +5,71 @@ const ur = express.Router();
|
|||||||
import { MemberState } from '@app/shared/types/member';
|
import { MemberState } from '@app/shared/types/member';
|
||||||
import pool from '../db';
|
import pool from '../db';
|
||||||
import { requireLogin, requireMemberState, requireRole } from '../middleware/auth';
|
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 { Request, Response } from 'express';
|
||||||
|
import { logger } from '../services/logging/logger';
|
||||||
|
|
||||||
r.use(requireLogin)
|
r.use(requireLogin)
|
||||||
ur.use(requireLogin)
|
ur.use(requireLogin)
|
||||||
|
|
||||||
//manually assign a member to a group
|
//manually assign a member to a group
|
||||||
ur.post('/', [requireMemberState(MemberState.Member), requireRole("17th Administrator")], async (req, res) => {
|
ur.post('/', [requireMemberState(MemberState.Member), requireRole("17th Administrator")], async (req: Request, res) => {
|
||||||
|
const body = req.body;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const body = req.body;
|
|
||||||
|
|
||||||
await assignUserGroup(body.member_id, body.role_id);
|
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);
|
res.sendStatus(201);
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
if (err?.code === 'ER_DUP_ENTRY') {
|
if (error?.code === 'ER_DUP_ENTRY') {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
error: 'Member already has this role',
|
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' });
|
res.status(500).json({ error: 'Failed to add to group' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
//manually remove member from group
|
//manually remove member from group
|
||||||
ur.delete('/', [requireMemberState(MemberState.Member), requireRole("17th Administrator")], async (req, res) => {
|
ur.delete('/', [requireMemberState(MemberState.Member), requireRole("17th Administrator")], async (req: Request, res: Response) => {
|
||||||
|
const body = req.body;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const body = req.body;
|
|
||||||
|
|
||||||
const sql = 'DELETE FROM members_roles WHERE member_id = ? AND role_id = ?'
|
const sql = 'DELETE FROM members_roles WHERE member_id = ? AND role_id = ?'
|
||||||
await pool.query(sql, [body.member_id, body.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);
|
res.sendStatus(200);
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (error) {
|
||||||
console.error("delete failed: ", err)
|
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' });
|
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) => {
|
r.get('/', [requireMemberState(MemberState.Member)], async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const roles = await getAllRoles();
|
const roles = await getAllRoles();
|
||||||
|
|
||||||
res.status(200).json(roles);
|
res.status(200).json(roles);
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
console.error(err);
|
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);
|
res.sendStatus(500);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -63,8 +96,16 @@ r.get('/:id/members', [requireMemberState(MemberState.Member)], async (req: Requ
|
|||||||
try {
|
try {
|
||||||
const members = await getUsersWithRole(Number(req.params.id));
|
const members = await getUsersWithRole(Number(req.params.id));
|
||||||
res.status(200).json(members);
|
res.status(200).json(members);
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
console.error(err);
|
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);
|
res.sendStatus(500);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -74,8 +115,16 @@ r.get('/:id', [requireMemberState(MemberState.Member)], async (req: Request, res
|
|||||||
try {
|
try {
|
||||||
const role = await getRole(Number(req.params.id));
|
const role = await getRole(Number(req.params.id));
|
||||||
res.status(200).json(role);
|
res.status(200).json(role);
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
console.error(err);
|
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);
|
res.sendStatus(500);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const memberStatusR = express.Router();
|
|||||||
|
|
||||||
import pool from '../db';
|
import pool from '../db';
|
||||||
import { requireLogin } from '../middleware/auth';
|
import { requireLogin } from '../middleware/auth';
|
||||||
|
import { logger } from '../services/logging/logger';
|
||||||
|
|
||||||
statusR.use(requireLogin);
|
statusR.use(requireLogin);
|
||||||
memberStatusR.use(requireLogin);
|
memberStatusR.use(requireLogin);
|
||||||
@@ -38,9 +39,16 @@ statusR.get('/', async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const result = await pool.query('SELECT * FROM statuses;');
|
const result = await pool.query('SELECT * FROM statuses;');
|
||||||
res.json(result);
|
res.json(result);
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
console.error(err);
|
logger.error(
|
||||||
res.status(500).json({ error: 'Internal server 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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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 { Course, CourseAttendee, CourseAttendeeRole, CourseEventDetails, CourseEventSummary, RawAttendeeRow } from "@app/shared/types/course"
|
||||||
import { PagedData } from "@app/shared/types/pagination";
|
import { PagedData } from "@app/shared/types/pagination";
|
||||||
import { toDateTime } from "@app/shared/utils/time";
|
import { toDateTime } from "@app/shared/utils/time";
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ApplicationListRow, ApplicationRow, CommentRow } from "@app/shared/types/application";
|
import { ApplicationListRow, ApplicationRow, CommentRow } from "@app/shared/types/application";
|
||||||
import pool from "../db";
|
import pool from "../../db";
|
||||||
import { error } from "console";
|
import { error } from "console";
|
||||||
|
|
||||||
export async function createApplication(memberID: number, appVersion: number, app: string) {
|
export async function createApplication(memberID: number, appVersion: number, app: string) {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import pool from '../db';
|
import pool from '../../db';
|
||||||
import { CalendarEventShort, CalendarSignup, CalendarEvent, CalendarAttendance } from "@app/shared/types/calendar"
|
import { CalendarEventShort, CalendarSignup, CalendarEvent, CalendarAttendance } from "@app/shared/types/calendar"
|
||||||
import { toDateTime } from "@app/shared/utils/time"
|
import { toDateTime } from "@app/shared/utils/time"
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { toDateTime } from "@app/shared/utils/time";
|
import { toDateTime } from "@app/shared/utils/time";
|
||||||
import pool from "../db";
|
import pool from "../../db";
|
||||||
import { LOARequest, LOAType } from '@app/shared/types/loa'
|
import { LOARequest, LOAType } from '@app/shared/types/loa'
|
||||||
import { PagedData } from '@app/shared/types/pagination'
|
import { PagedData } from '@app/shared/types/pagination'
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Role } from "@app/shared/types/roles";
|
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'
|
import { Member, MemberCardDetails, MemberLight, memberSettings, MemberState } from '@app/shared/types/member'
|
||||||
|
|
||||||
export async function getUserData(userID: number): Promise<Member> {
|
export async function getUserData(userID: number): Promise<Member> {
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { BatchPromotion, BatchPromotionMember } from "@app/shared/schemas/promotionSchema";
|
import { BatchPromotion, BatchPromotionMember } from "@app/shared/schemas/promotionSchema";
|
||||||
import { PromotionDetails, PromotionSummary } from "@app/shared/types/rank"
|
import { PromotionDetails, PromotionSummary } from "@app/shared/types/rank"
|
||||||
import pool from "../db";
|
import pool from "../../db";
|
||||||
import { PagedData } from "@app/shared/types/pagination";
|
import { PagedData } from "@app/shared/types/pagination";
|
||||||
import { toDateTime } from "@app/shared/utils/time";
|
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) {
|
export async function batchInsertMemberRank(promos: BatchPromotionMember[], author: number) {
|
||||||
try {
|
try {
|
||||||
var con = await pool.getConnection();
|
var con = await pool.getConnection();
|
||||||
console.log(promos);
|
|
||||||
promos.forEach(p => {
|
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))])
|
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 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
|
COUNT(*) AS total_grouped_days_count
|
||||||
FROM
|
FROM
|
||||||
(
|
(
|
||||||
@@ -79,10 +78,9 @@ export async function getPromotionHistorySummary(page: number = 1, pageSize: num
|
|||||||
WHERE reason = 'Rank Change'
|
WHERE reason = 'Rank Change'
|
||||||
) AS grouped_days;`))[0]);
|
) AS grouped_days;`))[0]);
|
||||||
|
|
||||||
console.log(loaCount);
|
let pageCount = rowCount / pageSize;
|
||||||
let pageCount = loaCount / 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;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { MemberLight } from '@app/shared/types/member';
|
import { MemberLight } from '@app/shared/types/member';
|
||||||
import pool from '../db';
|
import pool from '../../db';
|
||||||
import { Role, RoleSummary } from '@app/shared/types/roles'
|
import { Role, RoleSummary } from '@app/shared/types/roles'
|
||||||
|
|
||||||
export async function assignUserGroup(userID: number, roleID: number) {
|
export async function assignUserGroup(userID: number, roleID: number) {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import pool from "../db"
|
import pool from "../../db"
|
||||||
|
|
||||||
export async function assignUserToStatus(userID: number, statusID: number) {
|
export async function assignUserToStatus(userID: number, statusID: number) {
|
||||||
const sql = `INSERT INTO members_statuses (member_id, status_id, start_date) VALUES (?, ?, NOW())`
|
const sql = `INSERT INTO members_statuses (member_id, status_id, start_date) VALUES (?, ?, NOW())`
|
||||||
72
api/src/services/logging/logger.ts
Normal file
72
api/src/services/logging/logger.ts
Normal 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);
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
"node",
|
"node",
|
||||||
"express"
|
"express"
|
||||||
],
|
],
|
||||||
|
"sourceMap": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"@app/shared/*": ["../shared/*"]
|
"@app/shared/*": ["../shared/*"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ export async function getPromoHistory(page?: number, pageSize?: number): Promise
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getPromotionsOnDay(day: Date): Promise<PromotionDetails[]> {
|
export async function getPromotionsOnDay(day: Date): Promise<PromotionDetails[]> {
|
||||||
console.log(day.toISOString());
|
|
||||||
const res = await fetch(`${addr}/memberRanks/${day.toISOString()}`, {
|
const res = await fetch(`${addr}/memberRanks/${day.toISOString()}`, {
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -105,7 +105,6 @@ export const useMemberDirectory = defineStore('memberDirectory', () => {
|
|||||||
try {
|
try {
|
||||||
const res = await getFullMembers(ids);
|
const res = await getFullMembers(ids);
|
||||||
for (const m of res) {
|
for (const m of res) {
|
||||||
console.log(m)
|
|
||||||
full[m.member.member_id] = m;
|
full[m.member.member_id] = m;
|
||||||
|
|
||||||
const waiters = fullWaiters.get(m.member.member_id);
|
const waiters = fullWaiters.get(m.member.member_id);
|
||||||
|
|||||||
Reference in New Issue
Block a user