Fixed a whole lotta broken stuff by changing state from a string to a number

This commit is contained in:
2026-02-07 13:25:15 -05:00
parent d321c83f49
commit 1101f0eb59
17 changed files with 435 additions and 260 deletions

View File

@@ -54,7 +54,7 @@ router.post('/', [requireLogin], async (req: Request, res: Response) => {
try {
let appID = await createApplication(memberID, appVersion, JSON.stringify(App));
await setUserState(memberID, MemberState.Applicant);
await setUserState(memberID, MemberState.Applicant, "Application Submitted", memberID);
res.sendStatus(201);
@@ -230,7 +230,7 @@ router.post('/approve/:id', [requireLogin, requireRole("Recruiter")], async (req
await approveApplication(appID, approved_by);
//update user profile
await setUserState(app.member_id, MemberState.Member);
await setUserState(app.member_id, MemberState.Member, "Application Accepted", approved_by);
await pool.query('CALL sp_accept_new_recruit_validation(?, ?, ?, ?)', [Number(process.env.CONFIG_ID), app.member_id, approved_by, approved_by])
@@ -262,7 +262,7 @@ router.post('/deny/:id', [requireLogin, requireRole("Recruiter")], async (req: R
try {
const app = await getApplicationByID(appID);
await denyApplication(appID, approver);
await setUserState(app.member_id, MemberState.Denied);
await setUserState(app.member_id, MemberState.Denied, "Application Denied", approver);
logger.info('app', "Member application approved", {
application: app.id,
@@ -403,7 +403,7 @@ VALUES(?, ?, ?, 1);`
router.post('/restart', async (req: Request, res: Response) => {
const user = req.user.id;
try {
await setUserState(user, MemberState.Guest);
await setUserState(user, MemberState.Guest, "Restarted Application", user);
logger.info('app', "Member restarted application", {
user: user

View File

@@ -240,11 +240,12 @@ router.put('/:id/displayname', async (req, res) => {
router.post('/discharge', [requireLogin, requireMemberState(MemberState.Member), requireRole("17th Administrator")], async (req: Request, res: Response) => {
try {
var con = await pool.getConnection();
let author = req.user.id;
con.beginTransaction();
var data: Discharge = req.body;
setUserState(data.userID, MemberState.Retired, con);
setUserState(data.userID, MemberState.Discharged, "Member Discharged", author, con);
cancelLatestRank(data.userID, con);
cancelLatestUnit(data.userID, con);
con.commit();

View File

@@ -6,43 +6,43 @@ 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
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[] = [];
try {
const offset = (page - 1) * pageSize;
const whereClauses: string[] = [];
const params: any[] = [];
if (status && status !== 'all') {
whereClauses.push(`m.state = ?`);
params.push(status);
}
if (status && status !== 'all') {
whereClauses.push(`m.state = ?`);
params.push(status);
}
if (search) {
whereClauses.push(`v.member_name LIKE ?`);
params.push(`%${search}%`);
}
if (search) {
whereClauses.push(`v.member_name LIKE ?`);
params.push(`%${search}%`);
}
if (unitId && unitId !== 'all') {
whereClauses.push(`v.unit = ?`);
params.push(unitId);
}
if (unitId && unitId !== 'all') {
whereClauses.push(`v.unit = ?`);
params.push(unitId);
}
const whereClause = whereClauses.length > 0
? ` WHERE ${whereClauses.join(' AND ')}`
: '';
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;
// 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 = `
// DATA QUERY
const dataQuery = `
SELECT
v.*,
CASE
@@ -60,106 +60,123 @@ export async function getFilteredMembers(
LIMIT ? OFFSET ?
`;
const rows: any[] = await pool.query(dataQuery, [...params, pageSize, 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,
}));
// 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;
}
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 = ?`;
const res: Member = await pool.query(sql, [userID]);
return res[0] ?? null;
const sql = `SELECT * FROM view_member_rank_unit_status_latest WHERE member_id = ?`;
const res: Member = await pool.query(sql, [userID]);
return res[0] ?? null;
}
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 con.query(sql, [state, userID]);
} catch (error) {
logger.error('app', 'Error setting user state', error);
} finally {
memberCache.Invalidate(userID);
}
export async function setUserState(userID: number, state: MemberState, reason: string, creatorID: number, externalCon?: mariadb.Connection | mariadb.PoolConnection) {
const isInternalConn = !externalCon;
const con = (externalCon || await pool.getConnection()) as mariadb.PoolConnection;
try {
if (isInternalConn) await con.beginTransaction();
await endLatestMemberState(userID, con);
const sql = `UPDATE members SET state = ? WHERE id = ?;`;
await con.query(sql, [state, userID]);
const insertHistorySql = `INSERT INTO member_state_history
(member_id, state_id, reason, created_by_id, start_date, end_date)
VALUES (?, ?, ?, ?, NOW(), NULL);`;
await con.query(insertHistorySql, [userID, state, reason, creatorID]);
if (isInternalConn) await con.commit();
} catch (error) {
if (isInternalConn) {
await con.rollback();
}
logger.error('app', 'Error setting user state', error);
throw error;
} finally {
memberCache.Invalidate(userID);
if (isInternalConn && con) con.release();
}
}
export async function getUserState(user: number): Promise<MemberState> {
let out = await pool.query(`SELECT state FROM members WHERE id = ?`, [user]);
return (out[0].state as MemberState);
let out = await pool.query(`SELECT state FROM members WHERE id = ?`, [user]);
return (out[0].state as MemberState);
}
export async function getMemberSettings(id: number): Promise<memberSettings> {
const sql = `SELECT * FROM view_member_settings WHERE id = ?`;
let out: memberSettings[] = await pool.query(sql, [id]);
const sql = `SELECT * FROM view_member_settings WHERE id = ?`;
let out: memberSettings[] = await pool.query(sql, [id]);
if (out.length != 1)
throw new Error("Could not get user settings");
if (out.length != 1)
throw new Error("Could not get user settings");
return out[0];
return out[0];
}
export async function setUserSettings(id: number, settings: memberSettings) {
const sql = `UPDATE view_member_settings SET
const sql = `UPDATE view_member_settings SET
displayName = ?
WHERE id = ?;`;
let result = await pool.query(sql, [settings.displayName, id])
let result = await pool.query(sql, [settings.displayName, id])
}
export async function getMembersLite(ids: number[]): Promise<MemberLight[]> {
const sql = `SELECT m.member_id AS id,
const sql = `SELECT m.member_id AS id,
m.member_name AS username,
m.displayName,
u.color
FROM view_member_rank_unit_status_latest m
LEFT JOIN units u ON u.name = m.unit
WHERE member_id IN (?);`;
const res: MemberLight[] = await pool.query(sql, [ids]);
return res;
const res: MemberLight[] = await pool.query(sql, [ids]);
return res;
}
export async function getAllMembersLite(): Promise<MemberLight[]> {
const sql = `SELECT m.member_id AS id,
const sql = `SELECT m.member_id AS id,
m.member_name AS username,
m.displayName,
u.color
FROM view_member_rank_unit_status_latest m
LEFT JOIN units u ON u.name = m.unit;`;
const res: MemberLight[] = await pool.query(sql);
return res;
const res: MemberLight[] = await pool.query(sql);
return res;
}
export async function getMembersFull(ids: number[]): Promise<MemberCardDetails[]> {
const sql = `
const sql = `
SELECT m.*,
COALESCE(
JSON_ARRAYAGG(
@@ -181,30 +198,60 @@ export async function getMembersFull(ids: number[]): Promise<MemberCardDetails[]
GROUP BY m.member_id;
`;
const rows: any[] = await pool.query(sql, [ids]);
const rows: any[] = await pool.query(sql, [ids]);
return rows.map(row => {
const member: Member = {
member_id: 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,
};
// roles comes as array of strings; parse each one
const roles: Role[] = row.roles;
return rows.map(row => {
const member: Member = {
member_id: 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,
};
// roles comes as array of strings; parse each one
const roles: Role[] = row.roles;
return { member, roles };
});
return { member, roles };
});
}
export async function mapDiscordtoID(id: number): Promise<number | null> {
const sql = `SELECT id FROM members WHERE discord_id = ?;`
let res = await pool.query(sql, [id]);
return res.length > 0 ? res[0].id : null;
const sql = `SELECT id FROM members WHERE discord_id = ?;`
let res = await pool.query(sql, [id]);
return res.length > 0 ? res[0].id : null;
}
export async function endLatestMemberState(memberID: number, con: mariadb.Pool | mariadb.Connection = pool) {
const sql = `UPDATE member_state_history
SET end_date = NOW(),
updated_at = NOW()
WHERE id = (
SELECT id
FROM (
SELECT id
FROM member_state_history
WHERE member_id = ?
AND end_date IS NULL
ORDER BY start_date DESC,
created_at DESC
LIMIT 1
) AS x
);`;
try {
let res = await con.query(sql, [memberID]);
console.log(res);
} catch (error) {
logger.error('app', 'Error ending latest member state', {
error: error instanceof Error ? error.message : String(error),
});
throw error;
}
let res = await pool.query(sql, [memberID]);
console.log(res);
}