import { Role } from "@app/shared/types/roles"; import pool from "../../db"; 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 { 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 ? OR v.displayName LIKE ?)`); params.push(`%${search}%`); 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, member_state: row.member_state })); 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 { 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, reason: string, creatorID: number, externalCon?: mariadb.PoolConnection, details: string = "", endPrevious: boolean = true, createHistory: boolean = true) { const isInternalConn = !externalCon; if (isInternalConn) var con = await pool.getConnection(); else var con = externalCon; try { if (isInternalConn) await con.beginTransaction(); if (endPrevious) await endLatestMemberState(userID, con); const sql = `UPDATE members SET state = ? WHERE id = ?;`; await con.query(sql, [state, userID]); if (createHistory) { const insertHistorySql = `INSERT INTO member_state_history (member_id, state_id, reason, created_by_id, start_date, end_date, reason_detailed) VALUES (?, ?, ?, ?, NOW(), NULL, ?);`; await con.query(insertHistorySql, [userID, state, reason, creatorID, details]); } 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 { 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 { const sql = `SELECT * FROM view_member_settings WHERE id = ?`; let out: memberSettings[] = await pool.query(sql, [id]); if (out.length != 1) throw new Error("Could not get user settings"); return out[0]; } export async function setUserSettings(id: number, settings: memberSettings) { const sql = `UPDATE view_member_settings SET displayName = ? WHERE id = ?;`; let result = await pool.query(sql, [settings.displayName, id]) } export async function getMembersLite(ids: number[]): Promise { const sql = `SELECT m.member_id AS id, m.member_name AS username, m.displayName, u.color FROM view_member_rank_unit_status_latest m LEFT JOIN units u ON u.name = m.unit WHERE member_id IN (?);`; const res: MemberLight[] = await pool.query(sql, [ids]); return res; } export async function getAllMembersLite(activeOnly: boolean): Promise { const filter = activeOnly ? `\nWHERE member_state = ${MemberState.Member}` : '' 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 ${filter};`; console.log(sql); const res: MemberLight[] = await pool.query(sql); return res; } export async function getMembersFull(ids: number[]): Promise { const sql = ` SELECT m.*, ( SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT( 'id', r.id, 'name', r.name, 'color', r.color, 'description', r.description )), JSON_ARRAY()) FROM members_roles mr JOIN roles r ON mr.role_id = r.id WHERE mr.member_id = m.member_id ) AS roles FROM view_member_rank_unit_status_latest m WHERE m.member_id IN (?); `; 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[] = typeof row.roles === "string" ? JSON.parse(row.roles) : row.roles; return { member, roles }; }); } export async function mapDiscordtoID(id: number): Promise { 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); return; } 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); } export async function getLastNonSuspendedState(memberID: number): Promise { try { const sql = `SELECT state_id FROM member_state_history WHERE member_id = ? AND state_id != ? ORDER BY start_date DESC, id DESC LIMIT 1;` const res = await pool.query(sql, [memberID, MemberState.Suspended]); console.log(res as MemberState[]) if (res.length) return res[0].state_id as MemberState; } catch (error) { logger.error('app', 'Error ending latest member state', { error: error instanceof Error ? error.message : String(error), }); } }