bug fixes

This commit is contained in:
2025-10-10 18:51:23 -04:00
parent 8236c1e0e7
commit 61ab1e1d9e
15 changed files with 4463 additions and 7 deletions

View File

@@ -57,6 +57,19 @@ async function ensureSchema() {
timestamp TIMESTAMP WITH TIME ZONE DEFAULT now()
);
`);
await p.query(`
CREATE TABLE IF NOT EXISTS reaction_roles (
id SERIAL PRIMARY KEY,
guild_id TEXT NOT NULL,
channel_id TEXT NOT NULL,
message_id TEXT, -- message created in channel (optional until created)
name TEXT NOT NULL,
embed JSONB NOT NULL,
buttons JSONB NOT NULL, -- array of { customId, label, roleId }
created_at TIMESTAMP WITH TIME ZONE DEFAULT now()
);
`);
}
// Servers
@@ -132,6 +145,116 @@ async function deleteAllAdminLogs(guildId) {
await p.query('DELETE FROM admin_logs WHERE guild_id = $1', [guildId]);
}
// Reaction Roles
async function listReactionRoles(guildId) {
const p = initPool();
const res = await p.query('SELECT id, guild_id, channel_id, message_id, name, embed, buttons, created_at FROM reaction_roles WHERE guild_id = $1 ORDER BY created_at DESC', [guildId]);
return res.rows;
}
async function getReactionRole(id) {
const p = initPool();
const res = await p.query('SELECT id, guild_id, channel_id, message_id, name, embed, buttons, created_at FROM reaction_roles WHERE id = $1', [id]);
return res.rows[0] || null;
}
async function createReactionRole(rr) {
const p = initPool();
const q = `INSERT INTO reaction_roles(guild_id, channel_id, message_id, name, embed, buttons) VALUES($1,$2,$3,$4,$5,$6) RETURNING *`;
// Ensure embed/buttons are proper JSON objects/arrays (some clients may send them as JSON strings)
let embed = rr.embed || {};
let buttons = rr.buttons || [];
// If the payload is double-encoded (string containing a JSON string), keep parsing until it's a non-string
try {
while (typeof embed === 'string') {
embed = JSON.parse(embed);
}
} catch (e) {
// fall through and let Postgres reject invalid JSON if it's still malformed
}
try {
while (typeof buttons === 'string') {
buttons = JSON.parse(buttons);
}
// If buttons is an array but elements are themselves JSON strings, parse each element
if (Array.isArray(buttons)) {
buttons = buttons.map(b => {
if (typeof b === 'string') {
try {
let parsed = b;
while (typeof parsed === 'string') {
parsed = JSON.parse(parsed);
}
return parsed;
} catch (e) {
return b; // leave as-is
}
}
return b;
});
}
} catch (e) {
// leave as-is
}
// Validate shapes before inserting to DB to avoid Postgres JSON errors
if (!embed || typeof embed !== 'object' || Array.isArray(embed)) {
throw new Error('Invalid reaction role payload: `embed` must be a JSON object');
}
if (!Array.isArray(buttons) || buttons.length === 0 || !buttons.every(b => b && typeof b === 'object')) {
throw new Error('Invalid reaction role payload: `buttons` must be a non-empty array of objects');
}
const res = await p.query(q, [rr.guildId, rr.channelId, rr.messageId || null, rr.name, embed, buttons]);
return res.rows[0];
}
async function updateReactionRole(id, updates) {
const p = initPool();
const parts = [];
const vals = [];
let idx = 1;
for (const k of ['channel_id','message_id','name','embed','buttons']) {
if (typeof updates[k] !== 'undefined') {
parts.push(`${k} = $${idx}`);
// coerce JSON strings to objects for JSONB columns
if ((k === 'embed' || k === 'buttons') && typeof updates[k] === 'string') {
try {
vals.push(JSON.parse(updates[k]));
} catch (e) {
vals.push(updates[k]);
}
} else {
vals.push(updates[k]);
}
idx++;
}
}
if (parts.length === 0) return getReactionRole(id);
const q = `UPDATE reaction_roles SET ${parts.join(', ')} WHERE id = $${idx} RETURNING *`;
vals.push(id);
// Validate embed/buttons if they are being updated
if (typeof updates.embed !== 'undefined') {
const embed = vals[parts.indexOf('embed = $' + (parts.findIndex(p => p.startsWith('embed')) + 1))];
if (!embed || typeof embed !== 'object' || Array.isArray(embed)) {
throw new Error('Invalid reaction role payload: `embed` must be a JSON object');
}
}
if (typeof updates.buttons !== 'undefined') {
const buttons = vals[parts.indexOf('buttons = $' + (parts.findIndex(p => p.startsWith('buttons')) + 1))];
if (!Array.isArray(buttons) || buttons.length === 0 || !buttons.every(b => b && typeof b === 'object')) {
throw new Error('Invalid reaction role payload: `buttons` must be a non-empty array of objects');
}
}
const res = await p.query(q, vals);
return res.rows[0] || null;
}
async function deleteReactionRole(id) {
const p = initPool();
await p.query('DELETE FROM reaction_roles WHERE id = $1', [id]);
}
// Users
async function getUserData(discordId) {
const p = initPool();
@@ -145,4 +268,5 @@ async function upsertUserData(discordId, data) {
await p.query(`INSERT INTO users(discord_id, data) VALUES($1, $2) ON CONFLICT (discord_id) DO UPDATE SET data = $2`, [discordId, data]);
}
module.exports = { initPool, ensureSchema, getServerSettings, upsertServerSettings, listInvites, addInvite, deleteInvite, getUserData, upsertUserData, addAdminLog, getAdminLogs, getAdminLogsByAction, deleteAdminLog, deleteAllAdminLogs };
module.exports = { initPool, ensureSchema, getServerSettings, upsertServerSettings, listInvites, addInvite, deleteInvite, getUserData, upsertUserData, addAdminLog, getAdminLogs, getAdminLogsByAction, deleteAdminLog, deleteAllAdminLogs, listReactionRoles, getReactionRole, createReactionRole, updateReactionRole, deleteReactionRole };