From a7598d26aad8460432f255b474d8727a64aab5e9 Mon Sep 17 00:00:00 2001 From: 1tzEmerald Date: Thu, 6 Aug 2026 17:29:37 +0100 Subject: [PATCH] Fix BETTERSQLLITE ISSUE --- Structures/AIDatabase.js | 431 +++++++--------------------------- Structures/Database.js | 485 ++++++++++----------------------------- 2 files changed, 201 insertions(+), 715 deletions(-) diff --git a/Structures/AIDatabase.js b/Structures/AIDatabase.js index 0a672a1..fb9de50 100644 --- a/Structures/AIDatabase.js +++ b/Structures/AIDatabase.js @@ -1,438 +1,167 @@ -const Database = require("better-sqlite3"); -const path = require("path"); +const fs = require('fs'); +const path = require('path'); -const db = new Database(path.join(__dirname, "../Data/ai-memory.db")); +const DB_PATH = path.join(__dirname, '../Data/ai-memory.json'); -db.pragma("journal_mode = WAL"); +function load() { + try { + const raw = fs.readFileSync(DB_PATH, 'utf8'); + return JSON.parse(raw); + } catch (e) { + return { + ai_conversations: [], + ai_summaries: [], + ai_facts: [], + ai_knowledge: [], + ai_sessions: [], + }; + } +} -db.prepare( - ` - CREATE TABLE IF NOT EXISTS ai_conversations ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - guild_id TEXT, - channel_id TEXT, - user_id TEXT, - role TEXT NOT NULL, - content TEXT NOT NULL, - created_at INTEGER NOT NULL - ) -`, -).run(); +function save(state) { + try { + fs.mkdirSync(path.dirname(DB_PATH), { recursive: true }); + fs.writeFileSync(DB_PATH, JSON.stringify(state, null, 2), 'utf8'); + } catch (e) { + console.error('Failed to persist ai-memory:', e); + } +} -db.prepare( - ` - CREATE TABLE IF NOT EXISTS ai_summaries ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - guild_id TEXT, - channel_id TEXT, - summary TEXT NOT NULL, - created_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL - ) -`, -).run(); +const state = load(); -db.prepare( - ` - CREATE TABLE IF NOT EXISTS ai_facts ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - guild_id TEXT, - channel_id TEXT, - user_id TEXT, - fact_type TEXT NOT NULL, - fact_key TEXT NOT NULL, - fact_value TEXT NOT NULL, - created_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL - ) -`, -).run(); - -db.prepare( - ` - CREATE TABLE IF NOT EXISTS ai_knowledge ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - source_type TEXT NOT NULL, - source_name TEXT NOT NULL, - title TEXT NOT NULL, - content TEXT NOT NULL, - created_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL - ) -`, -).run(); - -db.prepare( - ` - CREATE TABLE IF NOT EXISTS ai_sessions ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - guild_id TEXT, - channel_id TEXT, - user_id TEXT, - model TEXT, - mode TEXT, - last_message_at INTEGER, - created_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL - ) -`, -).run(); - -try { - db.prepare(`ALTER TABLE ai_facts ADD COLUMN user_id TEXT`).run(); -} catch (e) { - if (!String(e).includes("duplicate column name")) throw e; +function nextId(arr) { + return arr.length === 0 ? 1 : Math.max(...arr.map(a => a.id || 0)) + 1; } module.exports = { saveUserFact({ guildId, userId, factType, factKey, factValue }) { - const existing = db - .prepare( - ` - SELECT id FROM ai_facts - WHERE guild_id IS ? AND user_id IS ? AND fact_type = ? AND fact_key = ? - LIMIT 1 - `, - ) - .get(guildId || null, userId || null, factType, factKey); - + const existing = state.ai_facts.find(f => f.guild_id === (guildId || null) && f.user_id === (userId || null) && f.fact_type === factType && f.fact_key === factKey); if (existing) { - db.prepare( - ` - UPDATE ai_facts - SET fact_value = ?, updated_at = ? - WHERE id = ? - `, - ).run(factValue, Date.now(), existing.id); + existing.fact_value = factValue; + existing.updated_at = Date.now(); + save(state); return; } - db.prepare( - ` - INSERT INTO ai_facts ( - guild_id, channel_id, user_id, fact_type, fact_key, fact_value, created_at, updated_at - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) - `, - ).run( - guildId || null, - null, - userId || null, - factType, - factKey, - factValue, - Date.now(), - Date.now(), - ); + const id = nextId(state.ai_facts); + state.ai_facts.push({ id, guild_id: guildId || null, channel_id: null, user_id: userId || null, fact_type: factType, fact_key: factKey, fact_value: factValue, created_at: Date.now(), updated_at: Date.now() }); + save(state); }, getUserFacts(guildId, userId) { - return db - .prepare( - ` - SELECT * FROM ai_facts - WHERE guild_id IS ? AND user_id IS ? - ORDER BY updated_at DESC - `, - ) - .all(guildId || null, userId || null); + return state.ai_facts.filter(f => f.guild_id === (guildId || null) && f.user_id === (userId || null)).sort((a,b) => b.updated_at - a.updated_at); }, getUserFactsByScope({ guildId = null, userId = null, scope = "guild" }) { if (!userId) return []; - if (scope === "global") { - return db - .prepare( - ` - SELECT * FROM ai_facts - WHERE user_id IS ? - ORDER BY updated_at DESC - `, - ) - .all(userId || null); + return state.ai_facts.filter(f => f.user_id === (userId || null)).sort((a,b) => b.updated_at - a.updated_at); } - return db - .prepare( - ` - SELECT * FROM ai_facts - WHERE guild_id IS ? AND user_id IS ? - ORDER BY updated_at DESC - `, - ) - .all(guildId || null, userId || null); + return state.ai_facts.filter(f => f.guild_id === (guildId || null) && f.user_id === (userId || null)).sort((a,b) => b.updated_at - a.updated_at); }, addMessage({ guildId, channelId, userId, role, content }) { - db.prepare( - ` - INSERT INTO ai_conversations ( - guild_id, channel_id, user_id, role, content, created_at - ) VALUES (?, ?, ?, ?, ?, ?) - `, - ).run( - guildId || null, - channelId || null, - userId || null, - role, - content, - Date.now(), - ); + const id = nextId(state.ai_conversations); + state.ai_conversations.push({ id, guild_id: guildId || null, channel_id: channelId || null, user_id: userId || null, role, content, created_at: Date.now() }); + save(state); }, getRecentMessages(channelId, limit = 15) { - return db - .prepare( - ` - SELECT * FROM ai_conversations - WHERE channel_id = ? - ORDER BY created_at DESC - LIMIT ? - `, - ) - .all(channelId, limit) - .reverse(); + const msgs = state.ai_conversations.filter(m => m.channel_id === channelId).sort((a,b) => b.created_at - a.created_at).slice(0, limit).reverse(); + return msgs; }, getRecentMessagesByScope({ guildId = null, channelId = null, limit = 15, scope = "guild" }) { if (scope === "channel") { - return db - .prepare( - ` - SELECT * FROM ai_conversations - WHERE channel_id = ? - ORDER BY created_at DESC - LIMIT ? - `, - ) - .all(channelId || null, limit) - .reverse(); + return state.ai_conversations.filter(m => m.channel_id === (channelId || null)).sort((a,b) => b.created_at - a.created_at).slice(0, limit).reverse(); } if (scope === "global") { - return db - .prepare( - ` - SELECT * FROM ai_conversations - ORDER BY created_at DESC - LIMIT ? - `, - ) - .all(limit) - .reverse(); + return state.ai_conversations.sort((a,b) => b.created_at - a.created_at).slice(0, limit).reverse(); } - return db - .prepare( - ` - SELECT * FROM ai_conversations - WHERE guild_id IS ? - ORDER BY created_at DESC - LIMIT ? - `, - ) - .all(guildId || null, limit) - .reverse(); + return state.ai_conversations.filter(m => m.guild_id === (guildId || null)).sort((a,b) => b.created_at - a.created_at).slice(0, limit).reverse(); }, saveSummary({ guildId, channelId, summary }) { - const existing = db - .prepare( - ` - SELECT id FROM ai_summaries - WHERE guild_id IS ? AND channel_id IS ? - ORDER BY updated_at DESC - LIMIT 1 - `, - ) - .get(guildId || null, channelId || null); - + const existing = state.ai_summaries.find(s => s.guild_id === (guildId || null) && s.channel_id === (channelId || null)); if (existing) { - db.prepare( - ` - UPDATE ai_summaries - SET summary = ?, updated_at = ? - WHERE id = ? - `, - ).run(summary, Date.now(), existing.id); + existing.summary = summary; + existing.updated_at = Date.now(); + save(state); return; } - - db.prepare( - ` - INSERT INTO ai_summaries ( - guild_id, channel_id, summary, created_at, updated_at - ) VALUES (?, ?, ?, ?, ?) - `, - ).run(guildId || null, channelId || null, summary, Date.now(), Date.now()); + const id = nextId(state.ai_summaries); + state.ai_summaries.push({ id, guild_id: guildId || null, channel_id: channelId || null, summary, created_at: Date.now(), updated_at: Date.now() }); + save(state); }, getLatestSummary(channelId) { - return db - .prepare( - ` - SELECT * FROM ai_summaries - WHERE channel_id = ? - ORDER BY updated_at DESC - LIMIT 1 - `, - ) - .get(channelId); + const arr = state.ai_summaries.filter(s => s.channel_id === channelId).sort((a,b) => b.updated_at - a.updated_at); + return arr.length ? arr[0] : null; }, getLatestSummaryByScope({ guildId = null, channelId = null, scope = "guild" }) { if (scope === "channel") { - return db - .prepare( - ` - SELECT * FROM ai_summaries - WHERE channel_id = ? - ORDER BY updated_at DESC - LIMIT 1 - `, - ) - .get(channelId || null); + const arr = state.ai_summaries.filter(s => s.channel_id === (channelId || null)).sort((a,b) => b.updated_at - a.updated_at); + return arr.length ? arr[0] : null; } - if (scope === "global") { - return db - .prepare( - ` - SELECT * FROM ai_summaries - ORDER BY updated_at DESC - LIMIT 1 - `, - ) - .get(); + const arr = state.ai_summaries.sort((a,b) => b.updated_at - a.updated_at); + return arr.length ? arr[0] : null; } - return db - .prepare( - ` - SELECT * FROM ai_summaries - WHERE guild_id IS ? - ORDER BY updated_at DESC - LIMIT 1 - `, - ) - .get(guildId || null); + const arr = state.ai_summaries.filter(s => s.guild_id === (guildId || null)).sort((a,b) => b.updated_at - a.updated_at); + return arr.length ? arr[0] : null; }, saveFact({ guildId, channelId, factType, factKey, factValue }) { - const existing = db - .prepare( - ` - SELECT id FROM ai_facts - WHERE guild_id IS ? AND channel_id IS ? AND fact_type = ? AND fact_key = ? - LIMIT 1 - `, - ) - .get(guildId || null, channelId || null, factType, factKey); - + const existing = state.ai_facts.find(f => f.guild_id === (guildId || null) && f.channel_id === (channelId || null) && f.fact_type === factType && f.fact_key === factKey); if (existing) { - db.prepare( - ` - UPDATE ai_facts - SET fact_value = ?, updated_at = ? - WHERE id = ? - `, - ).run(factValue, Date.now(), existing.id); + existing.fact_value = factValue; + existing.updated_at = Date.now(); + save(state); return; } - - db.prepare( - ` - INSERT INTO ai_facts ( - guild_id, channel_id, fact_type, fact_key, fact_value, created_at, updated_at - ) VALUES (?, ?, ?, ?, ?, ?, ?) - `, - ).run( - guildId || null, - channelId || null, - factType, - factKey, - factValue, - Date.now(), - Date.now(), - ); + const id = nextId(state.ai_facts); + state.ai_facts.push({ id, guild_id: guildId || null, channel_id: channelId || null, user_id: null, fact_type: factType, fact_key: factKey, fact_value: factValue, created_at: Date.now(), updated_at: Date.now() }); + save(state); }, getFacts(channelId) { - return db - .prepare( - ` - SELECT * FROM ai_facts - WHERE channel_id = ? - ORDER BY updated_at DESC - `, - ) - .all(channelId); + return state.ai_facts.filter(f => f.channel_id === channelId).sort((a,b) => b.updated_at - a.updated_at); }, getFactsByScope({ guildId = null, channelId = null, scope = "guild" }) { if (scope === "channel") { - return db - .prepare( - ` - SELECT * FROM ai_facts - WHERE channel_id = ? AND user_id IS NULL - ORDER BY updated_at DESC - `, - ) - .all(channelId || null); + return state.ai_facts.filter(f => f.channel_id === (channelId || null) && f.user_id == null).sort((a,b) => b.updated_at - a.updated_at); } - if (scope === "global") { - return db - .prepare( - ` - SELECT * FROM ai_facts - WHERE user_id IS NULL - ORDER BY updated_at DESC - `, - ) - .all(); + return state.ai_facts.filter(f => f.user_id == null).sort((a,b) => b.updated_at - a.updated_at); } - return db - .prepare( - ` - SELECT * FROM ai_facts - WHERE guild_id IS ? AND user_id IS NULL - ORDER BY updated_at DESC - `, - ) - .all(guildId || null); + return state.ai_facts.filter(f => f.guild_id === (guildId || null) && f.user_id == null).sort((a,b) => b.updated_at - a.updated_at); }, addKnowledge({ sourceType, sourceName, title, content }) { - db.prepare( - ` - INSERT INTO ai_knowledge ( - source_type, source_name, title, content, created_at, updated_at - ) VALUES (?, ?, ?, ?, ?, ?) - `, - ).run(sourceType, sourceName, title, content, Date.now(), Date.now()); + const id = nextId(state.ai_knowledge); + state.ai_knowledge.push({ id, source_type: sourceType, source_name: sourceName, title, content, created_at: Date.now(), updated_at: Date.now() }); + save(state); }, getKnowledge(limit = 25) { - return db - .prepare( - ` - SELECT * FROM ai_knowledge - ORDER BY updated_at DESC - LIMIT ? - `, - ) - .all(limit); + return state.ai_knowledge.sort((a,b) => b.updated_at - a.updated_at).slice(0, limit); }, clearKnowledge() { - db.prepare(`DELETE FROM ai_knowledge`).run(); + state.ai_knowledge = []; + save(state); }, clearConversations(channelId) { if (!channelId) return; - db.prepare(`DELETE FROM ai_conversations WHERE channel_id = ?`).run(channelId); + state.ai_conversations = state.ai_conversations.filter(c => c.channel_id !== channelId); + save(state); }, }; \ No newline at end of file diff --git a/Structures/Database.js b/Structures/Database.js index 532bd35..d7b190e 100644 --- a/Structures/Database.js +++ b/Structures/Database.js @@ -1,120 +1,46 @@ -// SupportBot | Emerald Services -const Database = require("better-sqlite3"); +// SupportBot | JSON fallback adapter for environments without native addons (Pterodactyl) +const fs = require("fs"); const path = require("path"); -const db = new Database(path.join(__dirname, "../Data/supportbot.db")); +const DB_PATH = path.join(__dirname, "../Data/supportbot.json"); -db.prepare( - ` - CREATE TABLE IF NOT EXISTS suggestions ( - thread_id TEXT PRIMARY KEY, - author_id TEXT, - text TEXT, - status TEXT DEFAULT 'Created' - ) -`, -).run(); - -db.prepare( - ` - CREATE TABLE IF NOT EXISTS profiles ( - user_id TEXT PRIMARY KEY, - bio TEXT, - timezone TEXT, - clockedIn INTEGER DEFAULT 0 - ) -`, -).run(); - -db.prepare( - ` - CREATE TABLE IF NOT EXISTS tickets ( - ticket_id TEXT PRIMARY KEY, - user_id TEXT, - status TEXT DEFAULT 'open', - created_at INTEGER, - updated_at INTEGER, - subject TEXT, - description TEXT - ) -`, -).run(); - -// Safe column upgrades -const safeAlter = (sql) => { +function load() { try { - db.prepare(sql).run(); + const raw = fs.readFileSync(DB_PATH, "utf8"); + return JSON.parse(raw); } catch (e) { - if (!String(e).includes("duplicate column name")) throw e; + return { + suggestions: {}, + profiles: {}, + tickets: {}, + ticket_panel: [], + ticket_users: {}, + settings: {}, + }; } -}; +} -safeAlter(`ALTER TABLE tickets ADD COLUMN voiceChannelId TEXT`); -safeAlter(`ALTER TABLE tickets ADD COLUMN department TEXT DEFAULT 'general'`); -safeAlter(`ALTER TABLE tickets ADD COLUMN priority TEXT DEFAULT 'medium'`); -safeAlter(`ALTER TABLE tickets ADD COLUMN aiModeEnabled INTEGER DEFAULT 0`); -safeAlter(`ALTER TABLE tickets ADD COLUMN aiChannelId TEXT`); -safeAlter(`ALTER TABLE tickets ADD COLUMN aiType TEXT`); -safeAlter(`ALTER TABLE tickets ADD COLUMN aiEnabledBy TEXT`); -safeAlter(`ALTER TABLE tickets ADD COLUMN aiEnabledAt INTEGER`); -safeAlter(`ALTER TABLE tickets ADD COLUMN questionAnswers TEXT`); -safeAlter(`ALTER TABLE tickets ADD COLUMN ticketName TEXT`); -safeAlter(`ALTER TABLE tickets ADD COLUMN ticketNumber TEXT`); +function save(state) { + try { + fs.mkdirSync(path.dirname(DB_PATH), { recursive: true }); + fs.writeFileSync(DB_PATH, JSON.stringify(state, null, 2), "utf8"); + } catch (e) { + console.error("Failed to persist database:", e); + } +} -db.prepare( - ` - CREATE TABLE IF NOT EXISTS ticket_panel ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - message_id TEXT, - channel_id TEXT, - created_at INTEGER - ) -`, -).run(); - -db.prepare( - ` - CREATE TABLE IF NOT EXISTS ticket_users ( - ticket_id TEXT, - user_id TEXT, - PRIMARY KEY(ticket_id, user_id) - ) -`, -).run(); - -db.prepare( - ` - CREATE TABLE IF NOT EXISTS settings ( - id INTEGER PRIMARY KEY, - data TEXT - ) -`, -).run(); +const state = load(); function ensureProfile(userId) { - const existing = db - .prepare( - ` - SELECT user_id FROM profiles WHERE user_id = ? - `, - ) - .get(userId); - - if (!existing) { - db.prepare( - ` - INSERT INTO profiles (user_id, bio, timezone, clockedIn) - VALUES (?, '', '', 0) - `, - ).run(userId); + if (!state.profiles[userId]) { + state.profiles[userId] = { user_id: userId, bio: "", timezone: "", clockedIn: 0 }; + save(state); } } function normaliseTicketRow(row) { if (!row) return null; - let questionAnswers = []; - if (row.questionAnswers) { try { questionAnswers = JSON.parse(row.questionAnswers); @@ -126,8 +52,7 @@ function normaliseTicketRow(row) { return { ...row, aiModeEnabled: Boolean(row.aiModeEnabled), - clockedIn: - row.clockedIn !== undefined ? Boolean(row.clockedIn) : row.clockedIn, + clockedIn: row.clockedIn !== undefined ? Boolean(row.clockedIn) : row.clockedIn, questionAnswers, }; } @@ -135,53 +60,16 @@ function normaliseTicketRow(row) { module.exports = { addTicket(ticket) { const now = Date.now(); + const id = ticket.id; + state.tickets[id] = state.tickets[id] || {}; + const existing = state.tickets[id]; - db.prepare( - ` - INSERT INTO tickets ( - ticket_id, - user_id, - subject, - description, - created_at, - updated_at, - department, - priority, - aiModeEnabled, - aiChannelId, - aiType, - aiEnabledBy, - aiEnabledAt, - questionAnswers, - ticketName, - ticketNumber - ) - VALUES ( - @ticket_id, - @user_id, - @subject, - @description, - @created_at, - @updated_at, - @department, - @priority, - @aiModeEnabled, - @aiChannelId, - @aiType, - @aiEnabledBy, - @aiEnabledAt, - @questionAnswers, - @ticketName, - @ticketNumber - ) - ON CONFLICT(ticket_id) DO NOTHING - `, - ).run({ - ticket_id: ticket.id, + state.tickets[id] = { + ticket_id: id, user_id: ticket.user, subject: ticket.reason || "", description: ticket.description || "", - created_at: now, + created_at: existing.created_at || now, updated_at: now, department: ticket.department || "general", priority: ticket.priority || "medium", @@ -193,61 +81,46 @@ module.exports = { questionAnswers: JSON.stringify(ticket.questionAnswers || []), ticketName: ticket.name || null, ticketNumber: ticket.number || null, - }); + status: existing.status || "open", + }; + + save(state); if (global.apiServer) { global.apiServer.broadcast("ticket_created", { - ticket_id: ticket.id, + ticket_id: id, user_id: ticket.user, subject: ticket.reason || "", description: ticket.description || "", department: ticket.department || "general", priority: ticket.priority || "medium", - created_at: now + created_at: now, }); } }, updateTicketQuestionAnswers(ticketId, questionAnswers = []) { - db.prepare( - ` - UPDATE tickets - SET questionAnswers=@questionAnswers, updated_at=@updatedAt - WHERE ticket_id=@ticketId - `, - ).run({ - ticketId, - questionAnswers: JSON.stringify(questionAnswers), - updatedAt: Date.now(), - }); + const t = state.tickets[ticketId]; + if (!t) return; + t.questionAnswers = JSON.stringify(questionAnswers); + t.updated_at = Date.now(); + save(state); }, updateTicketVoice(ticketId, voiceChannelId) { - db.prepare( - ` - UPDATE tickets - SET voiceChannelId=@voiceChannelId, updated_at=@updatedAt - WHERE ticket_id=@ticketId - `, - ).run({ - ticketId, - voiceChannelId, - updatedAt: Date.now(), - }); + const t = state.tickets[ticketId]; + if (!t) return; + t.voiceChannelId = voiceChannelId; + t.updated_at = Date.now(); + save(state); }, updateTicketStatus(ticketId, status) { - db.prepare( - ` - UPDATE tickets - SET status=@status, updated_at=@updatedAt - WHERE ticket_id=@ticketId - `, - ).run({ - status, - updatedAt: Date.now(), - ticketId, - }); + const t = state.tickets[ticketId]; + if (!t) return; + t.status = status; + t.updated_at = Date.now(); + save(state); if (global.apiServer && status === "closed") { global.apiServer.broadcast("ticket_closed", { ticket_id: ticketId }); @@ -255,236 +128,120 @@ module.exports = { }, updateTicketDepartment(ticketId, department) { - db.prepare( - ` - UPDATE tickets - SET department=@department, updated_at=@updatedAt - WHERE ticket_id=@ticketId - `, - ).run({ - ticketId, - department, - updatedAt: Date.now(), - }); + const t = state.tickets[ticketId]; + if (!t) return; + t.department = department; + t.updated_at = Date.now(); + save(state); }, updateTicketPriority(ticketId, priority) { - db.prepare( - ` - UPDATE tickets - SET priority=@priority, updated_at=@updatedAt - WHERE ticket_id=@ticketId - `, - ).run({ - ticketId, - priority, - updatedAt: Date.now(), - }); + const t = state.tickets[ticketId]; + if (!t) return; + t.priority = priority; + t.updated_at = Date.now(); + save(state); }, setTicketAIState(ticketId, aiState = {}) { - db.prepare( - ` - UPDATE tickets - SET - aiModeEnabled=@aiModeEnabled, - aiChannelId=@aiChannelId, - aiType=@aiType, - aiEnabledBy=@aiEnabledBy, - aiEnabledAt=@aiEnabledAt, - updated_at=@updatedAt - WHERE ticket_id=@ticketId - `, - ).run({ - ticketId, - aiModeEnabled: aiState.enabled ? 1 : 0, - aiChannelId: aiState.aiChannelId || null, - aiType: aiState.aiType || null, - aiEnabledBy: aiState.enabledBy || null, - aiEnabledAt: aiState.enabledAt - ? new Date(aiState.enabledAt).getTime() - : aiState.enabled - ? Date.now() - : null, - updatedAt: Date.now(), - }); + const t = state.tickets[ticketId]; + if (!t) return; + t.aiModeEnabled = aiState.enabled ? 1 : 0; + t.aiChannelId = aiState.aiChannelId || null; + t.aiType = aiState.aiType || null; + t.aiEnabledBy = aiState.enabledBy || null; + t.aiEnabledAt = aiState.enabledAt ? new Date(aiState.enabledAt).getTime() : aiState.enabled ? Date.now() : null; + t.updated_at = Date.now(); + save(state); }, - enableTicketAI( - ticketId, - aiChannelId = null, - aiType = null, - enabledBy = null, - ) { - db.prepare( - ` - UPDATE tickets - SET - aiModeEnabled=1, - aiChannelId=@aiChannelId, - aiType=@aiType, - aiEnabledBy=@aiEnabledBy, - aiEnabledAt=@aiEnabledAt, - updated_at=@updatedAt - WHERE ticket_id=@ticketId - `, - ).run({ - ticketId, - aiChannelId, - aiType, - aiEnabledBy, - aiEnabledAt: Date.now(), - updatedAt: Date.now(), - }); + enableTicketAI(ticketId, aiChannelId = null, aiType = null, enabledBy = null) { + const t = state.tickets[ticketId]; + if (!t) return; + t.aiModeEnabled = 1; + t.aiChannelId = aiChannelId; + t.aiType = aiType; + t.aiEnabledBy = enabledBy; + t.aiEnabledAt = Date.now(); + t.updated_at = Date.now(); + save(state); }, disableTicketAI(ticketId) { - db.prepare( - ` - UPDATE tickets - SET - aiModeEnabled=0, - updated_at=@updatedAt - WHERE ticket_id=@ticketId - `, - ).run({ - ticketId, - updatedAt: Date.now(), - }); + const t = state.tickets[ticketId]; + if (!t) return; + t.aiModeEnabled = 0; + t.updated_at = Date.now(); + save(state); }, getTicket(ticketId) { - const row = db - .prepare( - ` - SELECT * FROM tickets WHERE ticket_id=@ticketId - `, - ) - .get({ ticketId }); - - return normaliseTicketRow(row); + return normaliseTicketRow(state.tickets[ticketId]); }, getTicketByAIChannel(aiChannelId) { - const row = db - .prepare( - ` - SELECT * FROM tickets WHERE aiChannelId=@aiChannelId LIMIT 1 - `, - ) - .get({ aiChannelId }); - - return normaliseTicketRow(row); + const keys = Object.keys(state.tickets); + for (const k of keys) { + const t = state.tickets[k]; + if (t && t.aiChannelId === aiChannelId) return normaliseTicketRow(t); + } + return null; }, getAllTickets() { - return db.prepare(`SELECT * FROM tickets`).all().map(normaliseTicketRow); + return Object.values(state.tickets).map(normaliseTicketRow); }, saveTicketPanel(messageId, channelId) { - db.prepare( - ` - INSERT INTO ticket_panel (message_id, channel_id, created_at) - VALUES (@messageId, @channelId, @createdAt) - `, - ).run({ messageId, channelId, createdAt: Date.now() }); + state.ticket_panel.push({ id: Date.now(), message_id: messageId, channel_id: channelId, created_at: Date.now() }); + save(state); }, getTicketPanel() { - return db - .prepare( - ` - SELECT * FROM ticket_panel ORDER BY id DESC LIMIT 1 - `, - ) - .get(); + const arr = state.ticket_panel; + if (!arr || arr.length === 0) return null; + return arr[arr.length - 1]; }, addUserToTicket(ticketId, userId) { - db.prepare( - ` - INSERT INTO ticket_users (ticket_id, user_id) - VALUES (?, ?) - ON CONFLICT(ticket_id, user_id) DO NOTHING - `, - ).run(ticketId, userId); + state.ticket_users[ticketId] = state.ticket_users[ticketId] || new Set(); + // Sets cannot be serialized directly; store as array + const arr = new Set(state.ticket_users[ticketId]); + arr.add(userId); + state.ticket_users[ticketId] = Array.from(arr); + save(state); }, removeUserFromTicket(ticketId, userId) { - db.prepare( - ` - DELETE FROM ticket_users WHERE ticket_id=? AND user_id=? - `, - ).run(ticketId, userId); + const arr = new Set(state.ticket_users[ticketId] || []); + arr.delete(userId); + state.ticket_users[ticketId] = Array.from(arr); + save(state); }, getTicketUsers(ticketId) { - return db - .prepare( - ` - SELECT user_id FROM ticket_users WHERE ticket_id=? - `, - ) - .all(ticketId) - .map((r) => r.user_id); + return (state.ticket_users[ticketId] || []).slice(); }, getSettings() { - const row = db.prepare(`SELECT data FROM settings WHERE id=1`).get(); - if (!row) return {}; - try { - return JSON.parse(row.data); - } catch { - return {}; - } + return state.settings || {}; }, getProfile(userId) { ensureProfile(userId); - - const row = db - .prepare( - ` - SELECT * FROM profiles WHERE user_id = ? - `, - ) - .get(userId); - - if (!row) { - return { - user_id: userId, - bio: "", - timezone: "", - clockedIn: false, - }; - } - - return { - ...row, - clockedIn: Boolean(row.clockedIn), - }; + const row = state.profiles[userId]; + if (!row) return { user_id: userId, bio: "", timezone: "", clockedIn: false }; + return { ...row, clockedIn: Boolean(row.clockedIn) }; }, setClockedIn(userId, clockedIn) { ensureProfile(userId); - - db.prepare( - ` - UPDATE profiles - SET clockedIn = ? - WHERE user_id = ? - `, - ).run(clockedIn ? 1 : 0, userId); + state.profiles[userId].clockedIn = clockedIn ? 1 : 0; + save(state); }, saveSettings(obj) { - const data = JSON.stringify(obj); - db.prepare( - ` - INSERT INTO settings (id, data) - VALUES (1, @data) - ON CONFLICT(id) DO UPDATE SET data = excluded.data - `, - ).run({ data }); + state.settings = obj || {}; + save(state); }, };