26.2 final push

This commit is contained in:
1tzEmerald committed 2026-08-28 17:48:09 +01:00
1 parent 2017c33756
commit a746c468e5
14 files changed
+3048 -163

No files matched your search

+216
View File
@@ -37,6 +37,7 @@ const state = {
profiles: {},
settings: {},
blacklisted_users: {},
suggestions: {},
};
// ----------------------------------------------------
@@ -114,6 +115,16 @@ function initSQLite() {
added_by TEXT,
created_at INTEGER
);
CREATE TABLE IF NOT EXISTS suggestions (
id TEXT PRIMARY KEY,
author_id TEXT,
text TEXT,
status TEXT DEFAULT 'Created',
upvotes TEXT DEFAULT '[]',
downvotes TEXT DEFAULT '[]',
created_at INTEGER
);
`);
loadSQLiteState();
@@ -132,6 +143,10 @@ function loadSQLiteState() {
department: r.department || "general",
priority: r.priority || "medium",
status: r.status || "open",
claimedBy: r.claimed_by || null,
claimed_by: r.claimed_by || null,
claimedAt: r.claimed_at || null,
claimed_at: r.claimed_at || null,
aiModeEnabled: r.ai_mode_enabled,
aiChannelId: r.ai_channel_id,
aiType: r.ai_type,
@@ -190,6 +205,31 @@ function loadSQLiteState() {
created_at: b.created_at,
};
}
const suggestions = sqliteDb.prepare("SELECT * FROM suggestions").all();
for (const s of suggestions) {
let upvotes = [];
let downvotes = [];
try {
upvotes = JSON.parse(s.upvotes || "[]");
} catch {
upvotes = [];
}
try {
downvotes = JSON.parse(s.downvotes || "[]");
} catch {
downvotes = [];
}
state.suggestions[s.id] = {
id: s.id,
author_id: s.author_id,
text: s.text,
status: s.status || "Created",
upvotes: Array.isArray(upvotes) ? upvotes : [],
downvotes: Array.isArray(downvotes) ? downvotes : [],
created_at: Number(s.created_at),
};
}
}
function initMySQL() {
@@ -275,6 +315,18 @@ function initMySQL() {
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
`);
await poolPromise.query(`
CREATE TABLE IF NOT EXISTS suggestions (
id VARCHAR(255) PRIMARY KEY,
author_id VARCHAR(255),
text TEXT,
status VARCHAR(255) DEFAULT 'Created',
upvotes LONGTEXT,
downvotes LONGTEXT,
created_at BIGINT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
`);
// Load initial state from MySQL
const [tickets] = await poolPromise.query("SELECT * FROM tickets");
for (const r of tickets) {
@@ -286,6 +338,10 @@ function initMySQL() {
department: r.department || "general",
priority: r.priority || "medium",
status: r.status || "open",
claimedBy: r.claimed_by || null,
claimed_by: r.claimed_by || null,
claimedAt: r.claimed_at ? Number(r.claimed_at) : null,
claimed_at: r.claimed_at ? Number(r.claimed_at) : null,
aiModeEnabled: r.ai_mode_enabled,
aiChannelId: r.ai_channel_id,
aiType: r.ai_type,
@@ -345,6 +401,31 @@ function initMySQL() {
};
}
const [suggestions] = await poolPromise.query("SELECT * FROM suggestions");
for (const s of suggestions) {
let upvotes = [];
let downvotes = [];
try {
upvotes = JSON.parse(s.upvotes || "[]");
} catch {
upvotes = [];
}
try {
downvotes = JSON.parse(s.downvotes || "[]");
} catch {
downvotes = [];
}
state.suggestions[s.id] = {
id: s.id,
author_id: s.author_id,
text: s.text,
status: s.status || "Created",
upvotes: Array.isArray(upvotes) ? upvotes : [],
downvotes: Array.isArray(downvotes) ? downvotes : [],
created_at: Number(s.created_at),
};
}
mysqlReady = true;
const clr = { reset: "\x1b[0m", gray: "\x1b[90m", green: "\x1b[32m", white: "\x1b[37m" };
console.log(`${clr.green}✓${clr.reset} ${clr.white}Database Connected${clr.reset} ${clr.gray}to MySQL (${mysqlOpts.Host}:${mysqlOpts.Port}/${mysqlOpts.Database})${clr.reset}`);
@@ -639,6 +720,32 @@ function persistBlacklistedUser(userId, action = "add") {
}
}
function persistSuggestion(id) {
const s = state.suggestions[id];
if (!s) return;
const upvotesStr = JSON.stringify(s.upvotes || []);
const downvotesStr = JSON.stringify(s.downvotes || []);
if (isMySQL && mysqlReady && mysqlPool) {
const sql = `
INSERT INTO suggestions (id, author_id, text, status, upvotes, downvotes, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE text=VALUES(text), status=VALUES(status), upvotes=VALUES(upvotes), downvotes=VALUES(downvotes);
`;
mysqlPool.query(sql, [s.id, s.author_id || "", s.text || "", s.status || "Created", upvotesStr, downvotesStr, s.created_at || Date.now()], (err) => {
if (err) console.error("[Database] MySQL persistSuggestion error:", err.message);
});
} else if (sqliteDb) {
const stmt = sqliteDb.prepare(`
INSERT INTO suggestions (id, author_id, text, status, upvotes, downvotes, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(id) DO UPDATE SET text=excluded.text, status=excluded.status, upvotes=excluded.upvotes, downvotes=excluded.downvotes;
`);
stmt.run(s.id, s.author_id || "", s.text || "", s.status || "Created", upvotesStr, downvotesStr, s.created_at || Date.now());
}
}
function ensureProfile(userId) {
if (!state.profiles[userId]) {
state.profiles[userId] = { user_id: userId, bio: "", timezone: "", clockedIn: 0 };
@@ -657,8 +764,15 @@ function normaliseTicketRow(row) {
}
}
const claimedBy = row.claimedBy || row.claimed_by || null;
const claimedAt = row.claimedAt || row.claimed_at || null;
return {
...row,
claimedBy,
claimed_by: claimedBy,
claimedAt,
claimed_at: claimedAt,
aiModeEnabled: Boolean(row.aiModeEnabled),
clockedIn: row.clockedIn !== undefined ? Boolean(row.clockedIn) : row.clockedIn,
questionAnswers,
@@ -882,6 +996,108 @@ const exportsModule = {
return Object.values(state.blacklisted_users);
},
claimTicket(ticketId, userId) {
const t = state.tickets[ticketId];
if (!t) return;
t.claimedBy = userId;
t.claimed_by = userId;
t.claimedAt = Date.now();
t.claimed_at = Date.now();
t.updated_at = Date.now();
persistTicket(ticketId);
},
addSuggestion(id, authorId, text) {
state.suggestions[id] = {
id,
author_id: authorId,
text,
status: "Created",
upvotes: [],
downvotes: [],
created_at: Date.now(),
};
persistSuggestion(id);
},
add(id, authorId, text) {
this.addSuggestion(id, authorId, text);
},
getSuggestion(id) {
const s = state.suggestions[id];
if (!s) return null;
return {
...s,
upvotes: Array.isArray(s.upvotes) ? s.upvotes : [],
downvotes: Array.isArray(s.downvotes) ? s.downvotes : [],
};
},
setSuggestionStatus(id, status) {
const s = state.suggestions[id];
if (!s) return;
s.status = status;
persistSuggestion(id);
},
setStatus(id, status) {
this.setSuggestionStatus(id, status);
},
voteSuggestion(id, userId, voteType) {
let s = state.suggestions[id];
if (!s) {
s = {
id,
author_id: "",
text: "",
status: "Created",
upvotes: [],
downvotes: [],
created_at: Date.now(),
};
state.suggestions[id] = s;
}
if (!Array.isArray(s.upvotes)) s.upvotes = [];
if (!Array.isArray(s.downvotes)) s.downvotes = [];
const hasUpvoted = s.upvotes.includes(userId);
const hasDownvoted = s.downvotes.includes(userId);
if (voteType === "upvote") {
if (hasUpvoted) {
s.upvotes = s.upvotes.filter((uid) => uid !== userId);
} else {
s.upvotes.push(userId);
if (hasDownvoted) {
s.downvotes = s.downvotes.filter((uid) => uid !== userId);
}
}
} else if (voteType === "downvote") {
if (hasDownvoted) {
s.downvotes = s.downvotes.filter((uid) => uid !== userId);
} else {
s.downvotes.push(userId);
if (hasUpvoted) {
s.upvotes = s.upvotes.filter((uid) => uid !== userId);
}
}
} else if (voteType === "removevote") {
s.upvotes = s.upvotes.filter((uid) => uid !== userId);
s.downvotes = s.downvotes.filter((uid) => uid !== userId);
}
persistSuggestion(id);
return {
suggestion: s,
upvoteCount: s.upvotes.length,
downvoteCount: s.downvotes.length,
};
},
getDatabaseInfo() {
if (isMySQL) {
const mysqlOpts = dbConfig.MySQL || {};