Fix BETTERSQLLITE ISSUE
This commit is contained in:
1 parent
762fcf45c3
commit
a7598d26aa
2 files changed
+201
-715
No files matched your search
+80
-351
@@ -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);
|
||||
},
|
||||
};
|
||||
+121
-364
@@ -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);
|
||||
},
|
||||
};
|
||||
Reference in new issue
Block a user