Merge remote-tracking branch 'Origin/main' into devcontainers
This commit is contained in:
@@ -102,6 +102,7 @@ import { roles, memberRoles } from './routes/roles';
|
||||
import { courseRouter, eventRouter } from './routes/course';
|
||||
import { calendarRouter } from './routes/calendar';
|
||||
import { docsRouter } from './routes/docs';
|
||||
import { units } from './routes/units';
|
||||
|
||||
app.use('/application', applicationRouter);
|
||||
app.use('/ranks', ranks);
|
||||
@@ -115,6 +116,7 @@ app.use('/memberRoles', memberRoles)
|
||||
app.use('/course', courseRouter)
|
||||
app.use('/courseEvent', eventRouter)
|
||||
app.use('/calendar', calendarRouter)
|
||||
app.use('/units', units)
|
||||
app.use('/docs', docsRouter)
|
||||
app.use('/', authRouter)
|
||||
|
||||
|
||||
@@ -5,12 +5,16 @@ import { Request, Response } from 'express';
|
||||
import pool from '../db';
|
||||
import { requireLogin, requireMemberState, requireRole } from '../middleware/auth';
|
||||
import { getUserActiveLOA } from '../services/db/loaService';
|
||||
import { getAllMembersLite, getMemberSettings, getMembersFull, getMembersLite, getUserData, getUserState, setUserSettings } from '../services/db/memberService';
|
||||
import { getAllMembersLite, getMemberSettings, getMembersFull, getMembersLite, getUserData, getUserState, setUserSettings, getFilteredMembers, setUserState } from '../services/db/memberService';
|
||||
import { getUserRoles } from '../services/db/rolesService';
|
||||
import { memberSettings, MemberState, myData } from '@app/shared/types/member';
|
||||
import { Discharge } from '@app/shared/schemas/dischargeSchema';
|
||||
|
||||
import { Performance } from 'perf_hooks';
|
||||
import { logger } from '../services/logging/logger';
|
||||
import { memberCache } from './auth';
|
||||
import { cancelLatestRank } from '../services/db/rankService';
|
||||
import { cancelLatestUnit } from '../services/db/unitService';
|
||||
|
||||
//get all users
|
||||
router.get('/', [requireLogin, requireMemberState(MemberState.Member)], async (req, res) => {
|
||||
@@ -42,6 +46,27 @@ router.get('/', [requireLogin, requireMemberState(MemberState.Member)], async (r
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/filtered', [requireLogin, requireMemberState(MemberState.Member)], async (req, res) => {
|
||||
try {
|
||||
// Extract Query Parameters
|
||||
const page = parseInt(req.query.page as string) || 1;
|
||||
const pageSize = parseInt(req.query.pageSize as string) || 15;
|
||||
const search = req.query.search as string | undefined;
|
||||
const status = req.query.status as string | undefined;
|
||||
const unitId = req.query.unitId as string | undefined;
|
||||
|
||||
// Call the service function
|
||||
const result = await getFilteredMembers(page, pageSize, search, status, unitId);
|
||||
|
||||
return res.status(200).json(result);
|
||||
} catch (error) {
|
||||
logger.error('app', 'Failed to get filtered users', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
return res.status(500).json({ error: 'Failed to fetch users' });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/me', [requireLogin], async (req: Request, res) => {
|
||||
if (!req.user) return res.sendStatus(401);
|
||||
|
||||
@@ -211,5 +236,32 @@ router.put('/:id/displayname', async (req, res) => {
|
||||
return res.status(501);
|
||||
});
|
||||
|
||||
//discharge member
|
||||
router.post('/discharge', [requireLogin, requireMemberState(MemberState.Member), requireRole("17th Administrator")], async (req: Request, res: Response) => {
|
||||
try {
|
||||
var con = await pool.getConnection();
|
||||
|
||||
con.beginTransaction();
|
||||
|
||||
var data: Discharge = req.body;
|
||||
setUserState(data.userID, MemberState.Retired, con);
|
||||
cancelLatestRank(data.userID, con);
|
||||
cancelLatestUnit(data.userID, con);
|
||||
con.commit();
|
||||
memberCache.Invalidate(data.userID);
|
||||
|
||||
res.sendStatus(200);
|
||||
} catch (error) {
|
||||
logger.error('app', 'Failed to discharge user', {
|
||||
data: data,
|
||||
caller: req.user.id,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
} finally {
|
||||
if (con)
|
||||
con.release();
|
||||
}
|
||||
});
|
||||
|
||||
export const memberRouter = router;
|
||||
|
||||
29
api/src/routes/units.ts
Normal file
29
api/src/routes/units.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import express = require('express');
|
||||
const unitsRouter = express.Router();
|
||||
|
||||
import pool from '../db';
|
||||
import { requireLogin } from '../middleware/auth';
|
||||
import { logger } from '../services/logging/logger';
|
||||
import { Unit } from '@app/shared/types/units';
|
||||
|
||||
unitsRouter.use(requireLogin);
|
||||
|
||||
//get all units
|
||||
unitsRouter.get('/', async (req, res) => {
|
||||
try {
|
||||
const result: Unit[] = await pool.query('SELECT * FROM units WHERE active = 1;');
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
'app',
|
||||
'Failed to get all units',
|
||||
{
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
}
|
||||
);
|
||||
res.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
export const units = unitsRouter;
|
||||
@@ -1,8 +1,97 @@
|
||||
import { Role } from "@app/shared/types/roles";
|
||||
import pool from "../../db";
|
||||
import { Member, MemberCardDetails, MemberLight, memberSettings, MemberState } from '@app/shared/types/member'
|
||||
import { Member, MemberCardDetails, MemberLight, memberSettings, MemberState, PaginatedMembers } from '@app/shared/types/member'
|
||||
import { logger } from "../logging/logger";
|
||||
import { memberCache } from "../../routes/auth";
|
||||
import * as mariadb from 'mariadb';
|
||||
|
||||
export async function getFilteredMembers(
|
||||
page: number = 1,
|
||||
pageSize: number = 15,
|
||||
search?: string,
|
||||
status?: string,
|
||||
unitId?: string
|
||||
): Promise<PaginatedMembers> {
|
||||
try {
|
||||
const offset = (page - 1) * pageSize;
|
||||
const whereClauses: string[] = [];
|
||||
const params: any[] = [];
|
||||
|
||||
if (status && status !== 'all') {
|
||||
whereClauses.push(`m.state = ?`);
|
||||
params.push(status);
|
||||
}
|
||||
|
||||
if (search) {
|
||||
whereClauses.push(`v.member_name LIKE ?`);
|
||||
params.push(`%${search}%`);
|
||||
}
|
||||
|
||||
if (unitId && unitId !== 'all') {
|
||||
whereClauses.push(`v.unit = ?`);
|
||||
params.push(unitId);
|
||||
}
|
||||
|
||||
const whereClause = whereClauses.length > 0
|
||||
? ` WHERE ${whereClauses.join(' AND ')}`
|
||||
: '';
|
||||
|
||||
// COUNT QUERY
|
||||
const countQuery = `SELECT COUNT(*) as total FROM view_member_rank_unit_status_latest v INNER JOIN members m ON v.member_id = m.id ${whereClause}`;
|
||||
const [countResults]: any[] = await pool.query(countQuery, params);
|
||||
const total = Number(countResults?.total) || 0;
|
||||
|
||||
// DATA QUERY
|
||||
const dataQuery = `
|
||||
SELECT
|
||||
v.*,
|
||||
CASE
|
||||
WHEN EXISTS (
|
||||
SELECT 1 FROM leave_of_absences l
|
||||
WHERE l.member_id = v.member_id
|
||||
AND l.deleted = 0
|
||||
AND UTC_TIMESTAMP() BETWEEN l.start_date AND l.end_date
|
||||
) THEN 1 ELSE 0
|
||||
END AS on_loa
|
||||
FROM view_member_rank_unit_status_latest v
|
||||
INNER JOIN members m ON v.member_id = m.id
|
||||
${whereClause} -- Added back correctly
|
||||
ORDER BY v.member_name ASC
|
||||
LIMIT ? OFFSET ?
|
||||
`;
|
||||
|
||||
const rows: any[] = await pool.query(dataQuery, [...params, pageSize, offset]);
|
||||
|
||||
// Map rows to Member type
|
||||
const members: Member[] = rows.map(row => ({
|
||||
member_id: Number(row.member_id),
|
||||
member_name: row.member_name,
|
||||
displayName: row.displayName,
|
||||
rank: row.rank,
|
||||
rank_date: row.rank_date,
|
||||
unit: row.unit,
|
||||
unit_date: row.unit_date,
|
||||
status: row.status,
|
||||
status_date: row.status_date,
|
||||
loa_until: row.loa_until ? new Date(row.loa_until) : undefined,
|
||||
}));
|
||||
|
||||
return {
|
||||
data: members,
|
||||
pagination: {
|
||||
page,
|
||||
pageSize,
|
||||
total,
|
||||
totalPages: Math.ceil(total / pageSize),
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('app', 'Error fetching filtered members', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getUserData(userID: number): Promise<Member> {
|
||||
const sql = `SELECT * FROM view_member_rank_unit_status_latest WHERE member_id = ?`;
|
||||
@@ -10,12 +99,12 @@ export async function getUserData(userID: number): Promise<Member> {
|
||||
return res[0] ?? null;
|
||||
}
|
||||
|
||||
export async function setUserState(userID: number, state: MemberState) {
|
||||
export async function setUserState(userID: number, state: MemberState, con: mariadb.Pool | mariadb.Connection = pool) {
|
||||
try {
|
||||
const sql = `UPDATE members
|
||||
SET state = ?
|
||||
WHERE id = ?;`;
|
||||
return await pool.query(sql, [state, userID]);
|
||||
return await con.query(sql, [state, userID]);
|
||||
} catch (error) {
|
||||
logger.error('app', 'Error setting user state', error);
|
||||
} finally {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { PromotionDetails, PromotionSummary } from "@app/shared/types/rank"
|
||||
import pool from "../../db";
|
||||
import { PagedData } from "@app/shared/types/pagination";
|
||||
import { toDate, toDateIgnoreZone, toDateTime } from "@app/shared/utils/time";
|
||||
import * as mariadb from 'mariadb';
|
||||
|
||||
export async function getAllRanks() {
|
||||
const rows = await pool.query(
|
||||
@@ -105,4 +106,14 @@ export async function getPromotionsOnDay(day: Date): Promise<PromotionDetails[]>
|
||||
let batchPromotion = await pool.query(sql, [dayString]) as PromotionDetails[];
|
||||
|
||||
return batchPromotion;
|
||||
}
|
||||
|
||||
export async function cancelLatestRank(userID: number, con: mariadb.Pool | mariadb.Connection = pool): Promise<boolean> {
|
||||
try {
|
||||
let sql = `CALL sp_end_member_rank(?,NOW())`;
|
||||
con.query(sql, [userID]);
|
||||
return true;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
13
api/src/services/db/unitService.ts
Normal file
13
api/src/services/db/unitService.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import pool from "../../db";
|
||||
import * as mariadb from 'mariadb';
|
||||
|
||||
|
||||
export async function cancelLatestUnit(userID: number, con: mariadb.Pool | mariadb.Connection = pool): Promise<boolean> {
|
||||
try {
|
||||
let sql = `CALL sp_end_member_unit(?,NOW())`;
|
||||
con.query(sql, [userID]);
|
||||
return true;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user