Files
SupportBot/Structures/Database.js
T

474 lines
9.7 KiB
JavaScript
Raw Permalink Normal View History

2025-09-20 23:09:22 +01:00
// SupportBot | Emerald Services
const Database = require("better-sqlite3");
const path = require("path");
const db = new Database(path.join(__dirname, "../Data/supportbot.db"));
2026-03-10 20:02:43 +00:00
db.prepare(
`
2025-09-20 23:09:22 +01:00
CREATE TABLE IF NOT EXISTS suggestions (
thread_id TEXT PRIMARY KEY,
author_id TEXT,
2026-03-10 20:02:43 +00:00
text TEXT,
status TEXT DEFAULT 'Created'
2025-09-20 23:09:22 +01:00
)
2026-03-10 20:02:43 +00:00
`,
).run();
2025-09-20 23:09:22 +01:00
2026-03-10 20:02:43 +00:00
db.prepare(
`
2025-09-20 23:09:22 +01:00
CREATE TABLE IF NOT EXISTS profiles (
2026-03-10 20:02:43 +00:00
user_id TEXT PRIMARY KEY,
bio TEXT,
timezone TEXT,
2025-09-20 23:09:22 +01:00
clockedIn INTEGER DEFAULT 0
)
2026-03-10 20:02:43 +00:00
`,
).run();
2025-09-20 23:09:22 +01:00
2026-03-10 20:02:43 +00:00
db.prepare(
`
2025-09-20 23:09:22 +01:00
CREATE TABLE IF NOT EXISTS tickets (
2026-03-10 20:02:43 +00:00
ticket_id TEXT PRIMARY KEY,
user_id TEXT,
status TEXT DEFAULT 'open',
created_at INTEGER,
updated_at INTEGER,
subject TEXT,
description TEXT
2025-09-20 23:09:22 +01:00
)
2026-03-10 20:02:43 +00:00
`,
).run();
2025-09-20 23:09:22 +01:00
2026-03-10 20:02:43 +00:00
// Safe column upgrades
const safeAlter = (sql) => {
try {
db.prepare(sql).run();
} catch (e) {
if (!String(e).includes("duplicate column name")) throw e;
2025-09-20 23:09:22 +01:00
}
2026-03-10 20:02:43 +00:00
};
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'`);
2026-03-11 18:58:15 +00:00
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`);
2025-09-20 23:09:22 +01:00
2026-03-10 20:02:43 +00:00
db.prepare(
`
2025-09-20 23:09:22 +01:00
CREATE TABLE IF NOT EXISTS ticket_panel (
id INTEGER PRIMARY KEY AUTOINCREMENT,
message_id TEXT,
channel_id TEXT,
created_at INTEGER
)
2026-03-10 20:02:43 +00:00
`,
).run();
2025-09-20 23:09:22 +01:00
2026-03-10 20:02:43 +00:00
db.prepare(
`
2025-09-20 23:09:22 +01:00
CREATE TABLE IF NOT EXISTS ticket_users (
ticket_id TEXT,
2026-03-10 20:02:43 +00:00
user_id TEXT,
2025-09-20 23:09:22 +01:00
PRIMARY KEY(ticket_id, user_id)
)
2026-03-10 20:02:43 +00:00
`,
).run();
2025-09-20 23:09:22 +01:00
2026-03-10 20:02:43 +00:00
db.prepare(
`
2025-09-20 23:09:22 +01:00
CREATE TABLE IF NOT EXISTS settings (
2026-03-10 20:02:43 +00:00
id INTEGER PRIMARY KEY,
2025-09-20 23:09:22 +01:00
data TEXT
)
2026-03-10 20:02:43 +00:00
`,
).run();
2025-09-20 23:09:22 +01:00
2026-03-10 20:02:43 +00:00
function ensureProfile(userId) {
2026-03-11 18:58:15 +00:00
const existing = db
.prepare(
`
2026-03-10 20:02:43 +00:00
SELECT user_id FROM profiles WHERE user_id = ?
2026-03-11 18:58:15 +00:00
`,
)
.get(userId);
2025-09-20 23:09:22 +01:00
2026-03-10 20:02:43 +00:00
if (!existing) {
2026-03-11 18:58:15 +00:00
db.prepare(
`
2025-09-20 23:09:22 +01:00
INSERT INTO profiles (user_id, bio, timezone, clockedIn)
2026-03-10 20:02:43 +00:00
VALUES (?, '', '', 0)
2026-03-11 18:58:15 +00:00
`,
).run(userId);
2026-03-10 20:02:43 +00:00
}
}
2025-09-20 23:09:22 +01:00
2026-03-11 18:58:15 +00:00
function normaliseTicketRow(row) {
if (!row) return null;
let questionAnswers = [];
if (row.questionAnswers) {
try {
questionAnswers = JSON.parse(row.questionAnswers);
} catch {
questionAnswers = [];
}
}
return {
...row,
aiModeEnabled: Boolean(row.aiModeEnabled),
clockedIn:
row.clockedIn !== undefined ? Boolean(row.clockedIn) : row.clockedIn,
questionAnswers,
};
}
2026-03-10 20:02:43 +00:00
module.exports = {
2025-09-20 23:09:22 +01:00
addTicket(ticket) {
const now = Date.now();
2026-03-10 20:02:43 +00:00
db.prepare(
`
INSERT INTO tickets (
ticket_id,
user_id,
subject,
description,
created_at,
updated_at,
department,
2026-03-11 18:58:15 +00:00
priority,
aiModeEnabled,
aiChannelId,
aiType,
aiEnabledBy,
aiEnabledAt,
questionAnswers,
ticketName,
ticketNumber
2026-03-10 20:02:43 +00:00
)
VALUES (
@ticket_id,
@user_id,
@subject,
@description,
@created_at,
@updated_at,
@department,
2026-03-11 18:58:15 +00:00
@priority,
@aiModeEnabled,
@aiChannelId,
@aiType,
@aiEnabledBy,
@aiEnabledAt,
@questionAnswers,
@ticketName,
@ticketNumber
2026-03-10 20:02:43 +00:00
)
2025-09-20 23:09:22 +01:00
ON CONFLICT(ticket_id) DO NOTHING
2026-03-10 20:02:43 +00:00
`,
).run({
2025-09-20 23:09:22 +01:00
ticket_id: ticket.id,
user_id: ticket.user,
subject: ticket.reason || "",
description: ticket.description || "",
created_at: now,
2026-03-10 20:02:43 +00:00
updated_at: now,
department: ticket.department || "general",
priority: ticket.priority || "medium",
2026-03-11 18:58:15 +00:00
aiModeEnabled: ticket.aiModeEnabled ? 1 : 0,
aiChannelId: ticket.aiChannelId || null,
aiType: ticket.aiType || null,
aiEnabledBy: ticket.aiEnabledBy || null,
aiEnabledAt: ticket.aiEnabledAt || null,
questionAnswers: JSON.stringify(ticket.questionAnswers || []),
ticketName: ticket.name || null,
ticketNumber: ticket.number || null,
});
},
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(),
2025-09-20 23:09:22 +01:00
});
},
updateTicketVoice(ticketId, voiceChannelId) {
2026-03-10 20:02:43 +00:00
db.prepare(
`
UPDATE tickets
SET voiceChannelId=@voiceChannelId, updated_at=@updatedAt
WHERE ticket_id=@ticketId
`,
).run({
ticketId,
voiceChannelId,
updatedAt: Date.now(),
});
2025-09-20 23:09:22 +01:00
},
2026-03-10 20:02:43 +00:00
updateTicketStatus(ticketId, status) {
db.prepare(
`
UPDATE tickets
SET status=@status, updated_at=@updatedAt
WHERE ticket_id=@ticketId
`,
).run({
status,
updatedAt: Date.now(),
ticketId,
});
2025-09-20 23:09:22 +01:00
},
2026-03-10 20:02:43 +00:00
updateTicketDepartment(ticketId, department) {
db.prepare(
`
UPDATE tickets
SET department=@department, updated_at=@updatedAt
WHERE ticket_id=@ticketId
`,
).run({
ticketId,
department,
updatedAt: Date.now(),
});
2025-09-20 23:09:22 +01:00
},
2026-03-10 20:02:43 +00:00
updateTicketPriority(ticketId, priority) {
db.prepare(
`
UPDATE tickets
SET priority=@priority, updated_at=@updatedAt
WHERE ticket_id=@ticketId
`,
).run({
ticketId,
priority,
updatedAt: Date.now(),
});
2025-09-20 23:09:22 +01:00
},
2026-03-11 18:58:15 +00:00
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(),
});
},
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(),
});
},
disableTicketAI(ticketId) {
db.prepare(
`
UPDATE tickets
SET
aiModeEnabled=0,
updated_at=@updatedAt
WHERE ticket_id=@ticketId
`,
).run({
ticketId,
updatedAt: Date.now(),
});
},
2025-09-20 23:09:22 +01:00
getTicket(ticketId) {
2026-03-11 18:58:15 +00:00
const row = db
2026-03-10 20:02:43 +00:00
.prepare(
`
2025-09-20 23:09:22 +01:00
SELECT * FROM tickets WHERE ticket_id=@ticketId
2026-03-10 20:02:43 +00:00
`,
)
.get({ ticketId });
2026-03-11 18:58:15 +00:00
return normaliseTicketRow(row);
},
getTicketByAIChannel(aiChannelId) {
const row = db
.prepare(
`
SELECT * FROM tickets WHERE aiChannelId=@aiChannelId LIMIT 1
`,
)
.get({ aiChannelId });
return normaliseTicketRow(row);
2025-09-20 23:09:22 +01:00
},
getAllTickets() {
2026-03-11 18:58:15 +00:00
return db.prepare(`SELECT * FROM tickets`).all().map(normaliseTicketRow);
2025-09-20 23:09:22 +01:00
},
saveTicketPanel(messageId, channelId) {
2026-03-10 20:02:43 +00:00
db.prepare(
`
2025-09-20 23:09:22 +01:00
INSERT INTO ticket_panel (message_id, channel_id, created_at)
VALUES (@messageId, @channelId, @createdAt)
2026-03-10 20:02:43 +00:00
`,
).run({ messageId, channelId, createdAt: Date.now() });
2025-09-20 23:09:22 +01:00
},
getTicketPanel() {
2026-03-10 20:02:43 +00:00
return db
.prepare(
`
2025-09-20 23:09:22 +01:00
SELECT * FROM ticket_panel ORDER BY id DESC LIMIT 1
2026-03-10 20:02:43 +00:00
`,
)
.get();
2025-09-20 23:09:22 +01:00
},
addUserToTicket(ticketId, userId) {
2026-03-10 20:02:43 +00:00
db.prepare(
`
2025-09-20 23:09:22 +01:00
INSERT INTO ticket_users (ticket_id, user_id)
VALUES (?, ?)
ON CONFLICT(ticket_id, user_id) DO NOTHING
2026-03-10 20:02:43 +00:00
`,
).run(ticketId, userId);
2025-09-20 23:09:22 +01:00
},
removeUserFromTicket(ticketId, userId) {
2026-03-10 20:02:43 +00:00
db.prepare(
`
2025-09-20 23:09:22 +01:00
DELETE FROM ticket_users WHERE ticket_id=? AND user_id=?
2026-03-10 20:02:43 +00:00
`,
).run(ticketId, userId);
2025-09-20 23:09:22 +01:00
},
getTicketUsers(ticketId) {
2026-03-10 20:02:43 +00:00
return db
.prepare(
`
2025-09-20 23:09:22 +01:00
SELECT user_id FROM ticket_users WHERE ticket_id=?
2026-03-10 20:02:43 +00:00
`,
)
.all(ticketId)
.map((r) => r.user_id);
2025-09-20 23:09:22 +01:00
},
getSettings() {
const row = db.prepare(`SELECT data FROM settings WHERE id=1`).get();
if (!row) return {};
try {
return JSON.parse(row.data);
} catch {
return {};
}
},
2026-03-10 20:02:43 +00:00
getProfile(userId) {
ensureProfile(userId);
2026-03-11 18:58:15 +00:00
const row = db
.prepare(
`
2026-03-10 20:02:43 +00:00
SELECT * FROM profiles WHERE user_id = ?
2026-03-11 18:58:15 +00:00
`,
)
.get(userId);
2026-03-10 20:02:43 +00:00
if (!row) {
return {
user_id: userId,
bio: "",
timezone: "",
clockedIn: false,
};
}
return {
...row,
clockedIn: Boolean(row.clockedIn),
};
},
setClockedIn(userId, clockedIn) {
ensureProfile(userId);
2026-03-11 18:58:15 +00:00
db.prepare(
`
2026-03-10 20:02:43 +00:00
UPDATE profiles
SET clockedIn = ?
WHERE user_id = ?
2026-03-11 18:58:15 +00:00
`,
).run(clockedIn ? 1 : 0, userId);
2026-03-10 20:02:43 +00:00
},
2025-09-20 23:09:22 +01:00
saveSettings(obj) {
const data = JSON.stringify(obj);
2026-03-10 20:02:43 +00:00
db.prepare(
`
2025-09-20 23:09:22 +01:00
INSERT INTO settings (id, data)
VALUES (1, @data)
ON CONFLICT(id) DO UPDATE SET data = excluded.data
2026-03-10 20:02:43 +00:00
`,
).run({ data });
},
2025-09-20 23:09:22 +01:00
};