627 lines
22 KiB
JavaScript
627 lines
22 KiB
JavaScript
/**
|
||
* Electron main-process SQLite database module.
|
||
*
|
||
* All SQL queries live here – the renderer communicates exclusively via IPC.
|
||
* Uses sql.js (WASM SQLite) loaded in Node.js.
|
||
*/
|
||
|
||
const { ipcMain, app } = require('electron');
|
||
const fs = require('fs');
|
||
const fsp = fs.promises;
|
||
const path = require('path');
|
||
|
||
let db = null;
|
||
let dbPath = '';
|
||
|
||
/* ------------------------------------------------------------------ */
|
||
/* Migrations */
|
||
/* ------------------------------------------------------------------ */
|
||
|
||
const migrations = [
|
||
{
|
||
version: 1,
|
||
description: 'Initial schema – messages, users, rooms, reactions, bans, meta',
|
||
up(database) {
|
||
database.run(`
|
||
CREATE TABLE IF NOT EXISTS messages (
|
||
id TEXT PRIMARY KEY,
|
||
roomId TEXT NOT NULL,
|
||
channelId TEXT,
|
||
senderId TEXT NOT NULL,
|
||
senderName TEXT NOT NULL,
|
||
content TEXT NOT NULL,
|
||
timestamp INTEGER NOT NULL,
|
||
editedAt INTEGER,
|
||
reactions TEXT NOT NULL DEFAULT '[]',
|
||
isDeleted INTEGER NOT NULL DEFAULT 0,
|
||
replyToId TEXT
|
||
);
|
||
`);
|
||
database.run('CREATE INDEX IF NOT EXISTS idx_messages_roomId ON messages(roomId);');
|
||
|
||
database.run(`
|
||
CREATE TABLE IF NOT EXISTS users (
|
||
id TEXT PRIMARY KEY,
|
||
oderId TEXT,
|
||
username TEXT,
|
||
displayName TEXT,
|
||
avatarUrl TEXT,
|
||
status TEXT,
|
||
role TEXT,
|
||
joinedAt INTEGER,
|
||
peerId TEXT,
|
||
isOnline INTEGER,
|
||
isAdmin INTEGER,
|
||
isRoomOwner INTEGER,
|
||
voiceState TEXT,
|
||
screenShareState TEXT
|
||
);
|
||
`);
|
||
|
||
database.run(`
|
||
CREATE TABLE IF NOT EXISTS rooms (
|
||
id TEXT PRIMARY KEY,
|
||
name TEXT NOT NULL,
|
||
description TEXT,
|
||
topic TEXT,
|
||
hostId TEXT NOT NULL,
|
||
password TEXT,
|
||
isPrivate INTEGER NOT NULL DEFAULT 0,
|
||
createdAt INTEGER NOT NULL,
|
||
userCount INTEGER NOT NULL DEFAULT 0,
|
||
maxUsers INTEGER,
|
||
icon TEXT,
|
||
iconUpdatedAt INTEGER,
|
||
permissions TEXT,
|
||
channels TEXT
|
||
);
|
||
`);
|
||
|
||
database.run(`
|
||
CREATE TABLE IF NOT EXISTS reactions (
|
||
id TEXT PRIMARY KEY,
|
||
messageId TEXT NOT NULL,
|
||
oderId TEXT,
|
||
userId TEXT,
|
||
emoji TEXT NOT NULL,
|
||
timestamp INTEGER NOT NULL
|
||
);
|
||
`);
|
||
database.run('CREATE INDEX IF NOT EXISTS idx_reactions_messageId ON reactions(messageId);');
|
||
|
||
database.run(`
|
||
CREATE TABLE IF NOT EXISTS bans (
|
||
oderId TEXT NOT NULL,
|
||
userId TEXT,
|
||
roomId TEXT NOT NULL,
|
||
bannedBy TEXT NOT NULL,
|
||
displayName TEXT,
|
||
reason TEXT,
|
||
expiresAt INTEGER,
|
||
timestamp INTEGER NOT NULL,
|
||
PRIMARY KEY (oderId, roomId)
|
||
);
|
||
`);
|
||
database.run('CREATE INDEX IF NOT EXISTS idx_bans_roomId ON bans(roomId);');
|
||
|
||
database.run(`
|
||
CREATE TABLE IF NOT EXISTS meta (
|
||
key TEXT PRIMARY KEY,
|
||
value TEXT
|
||
);
|
||
`);
|
||
},
|
||
},
|
||
{
|
||
version: 2,
|
||
description: 'Attachments table',
|
||
up(database) {
|
||
database.run(`
|
||
CREATE TABLE IF NOT EXISTS attachments (
|
||
id TEXT PRIMARY KEY,
|
||
messageId TEXT NOT NULL,
|
||
filename TEXT NOT NULL,
|
||
size INTEGER NOT NULL,
|
||
mime TEXT NOT NULL,
|
||
isImage INTEGER NOT NULL DEFAULT 0,
|
||
uploaderPeerId TEXT,
|
||
filePath TEXT,
|
||
savedPath TEXT
|
||
);
|
||
`);
|
||
database.run('CREATE INDEX IF NOT EXISTS idx_attachments_messageId ON attachments(messageId);');
|
||
},
|
||
},
|
||
];
|
||
|
||
function runMigrations() {
|
||
db.run(`
|
||
CREATE TABLE IF NOT EXISTS schema_version (
|
||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||
version INTEGER NOT NULL DEFAULT 0
|
||
);
|
||
`);
|
||
|
||
const row = db.exec('SELECT version FROM schema_version WHERE id = 1');
|
||
let currentVersion = row.length > 0 ? row[0].values[0][0] : 0;
|
||
|
||
if (row.length === 0) {
|
||
db.run('INSERT INTO schema_version (id, version) VALUES (1, 0)');
|
||
}
|
||
|
||
for (const migration of migrations) {
|
||
if (migration.version > currentVersion) {
|
||
console.log(`[ElectronDB] Running migration v${migration.version}: ${migration.description}`);
|
||
migration.up(db);
|
||
currentVersion = migration.version;
|
||
db.run('UPDATE schema_version SET version = ? WHERE id = 1', [currentVersion]);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* ------------------------------------------------------------------ */
|
||
/* Persistence */
|
||
/* ------------------------------------------------------------------ */
|
||
|
||
function persist() {
|
||
if (!db) return;
|
||
const data = db.export();
|
||
const buffer = Buffer.from(data);
|
||
fs.writeFileSync(dbPath, buffer);
|
||
}
|
||
|
||
/* ------------------------------------------------------------------ */
|
||
/* Initialisation */
|
||
/* ------------------------------------------------------------------ */
|
||
|
||
async function initDatabase() {
|
||
const initSqlJs = require('sql.js');
|
||
const SQL = await initSqlJs();
|
||
|
||
const dbDir = path.join(app.getPath('userData'), 'metoyou');
|
||
await fsp.mkdir(dbDir, { recursive: true });
|
||
dbPath = path.join(dbDir, 'metoyou.sqlite');
|
||
|
||
if (fs.existsSync(dbPath)) {
|
||
const fileBuffer = fs.readFileSync(dbPath);
|
||
db = new SQL.Database(fileBuffer);
|
||
} else {
|
||
db = new SQL.Database();
|
||
}
|
||
|
||
db.run('PRAGMA journal_mode = MEMORY;');
|
||
db.run('PRAGMA synchronous = NORMAL;');
|
||
|
||
runMigrations();
|
||
persist();
|
||
}
|
||
|
||
/* ------------------------------------------------------------------ */
|
||
/* Helpers */
|
||
/* ------------------------------------------------------------------ */
|
||
|
||
/** Run a prepared-statement query and return rows as plain objects. */
|
||
function queryAll(sql, params = []) {
|
||
const stmt = db.prepare(sql);
|
||
stmt.bind(params);
|
||
const results = [];
|
||
while (stmt.step()) results.push(stmt.getAsObject());
|
||
stmt.free();
|
||
return results;
|
||
}
|
||
|
||
/** Return a single row as object or null. */
|
||
function queryOne(sql, params = []) {
|
||
const stmt = db.prepare(sql);
|
||
stmt.bind(params);
|
||
let result = null;
|
||
if (stmt.step()) result = stmt.getAsObject();
|
||
stmt.free();
|
||
return result;
|
||
}
|
||
|
||
/* ------------------------------------------------------------------ */
|
||
/* Row → model mappers */
|
||
/* ------------------------------------------------------------------ */
|
||
|
||
function rowToMessage(r) {
|
||
return {
|
||
id: String(r.id),
|
||
roomId: String(r.roomId),
|
||
channelId: r.channelId ? String(r.channelId) : undefined,
|
||
senderId: String(r.senderId),
|
||
senderName: String(r.senderName),
|
||
content: String(r.content),
|
||
timestamp: Number(r.timestamp),
|
||
editedAt: r.editedAt != null ? Number(r.editedAt) : undefined,
|
||
reactions: JSON.parse(String(r.reactions || '[]')),
|
||
isDeleted: !!r.isDeleted,
|
||
replyToId: r.replyToId ? String(r.replyToId) : undefined,
|
||
};
|
||
}
|
||
|
||
function rowToUser(r) {
|
||
return {
|
||
id: String(r.id),
|
||
oderId: String(r.oderId ?? ''),
|
||
username: String(r.username ?? ''),
|
||
displayName: String(r.displayName ?? ''),
|
||
avatarUrl: r.avatarUrl ? String(r.avatarUrl) : undefined,
|
||
status: String(r.status ?? 'offline'),
|
||
role: String(r.role ?? 'member'),
|
||
joinedAt: Number(r.joinedAt ?? 0),
|
||
peerId: r.peerId ? String(r.peerId) : undefined,
|
||
isOnline: !!r.isOnline,
|
||
isAdmin: !!r.isAdmin,
|
||
isRoomOwner: !!r.isRoomOwner,
|
||
voiceState: r.voiceState ? JSON.parse(String(r.voiceState)) : undefined,
|
||
screenShareState: r.screenShareState ? JSON.parse(String(r.screenShareState)) : undefined,
|
||
};
|
||
}
|
||
|
||
function rowToRoom(r) {
|
||
return {
|
||
id: String(r.id),
|
||
name: String(r.name),
|
||
description: r.description ? String(r.description) : undefined,
|
||
topic: r.topic ? String(r.topic) : undefined,
|
||
hostId: String(r.hostId),
|
||
password: r.password ? String(r.password) : undefined,
|
||
isPrivate: !!r.isPrivate,
|
||
createdAt: Number(r.createdAt),
|
||
userCount: Number(r.userCount),
|
||
maxUsers: r.maxUsers != null ? Number(r.maxUsers) : undefined,
|
||
icon: r.icon ? String(r.icon) : undefined,
|
||
iconUpdatedAt: r.iconUpdatedAt != null ? Number(r.iconUpdatedAt) : undefined,
|
||
permissions: r.permissions ? JSON.parse(String(r.permissions)) : undefined,
|
||
channels: r.channels ? JSON.parse(String(r.channels)) : undefined,
|
||
};
|
||
}
|
||
|
||
function rowToReaction(r) {
|
||
return {
|
||
id: String(r.id),
|
||
messageId: String(r.messageId),
|
||
oderId: String(r.oderId ?? ''),
|
||
userId: String(r.userId ?? ''),
|
||
emoji: String(r.emoji),
|
||
timestamp: Number(r.timestamp),
|
||
};
|
||
}
|
||
|
||
function rowToAttachment(r) {
|
||
return {
|
||
id: String(r.id),
|
||
messageId: String(r.messageId),
|
||
filename: String(r.filename),
|
||
size: Number(r.size),
|
||
mime: String(r.mime),
|
||
isImage: !!r.isImage,
|
||
uploaderPeerId: r.uploaderPeerId ? String(r.uploaderPeerId) : undefined,
|
||
filePath: r.filePath ? String(r.filePath) : undefined,
|
||
savedPath: r.savedPath ? String(r.savedPath) : undefined,
|
||
};
|
||
}
|
||
|
||
function rowToBan(r) {
|
||
return {
|
||
oderId: String(r.oderId),
|
||
userId: String(r.userId ?? ''),
|
||
roomId: String(r.roomId),
|
||
bannedBy: String(r.bannedBy),
|
||
displayName: r.displayName ? String(r.displayName) : undefined,
|
||
reason: r.reason ? String(r.reason) : undefined,
|
||
expiresAt: r.expiresAt != null ? Number(r.expiresAt) : undefined,
|
||
timestamp: Number(r.timestamp),
|
||
};
|
||
}
|
||
|
||
/* ------------------------------------------------------------------ */
|
||
/* IPC handler registration */
|
||
/* ------------------------------------------------------------------ */
|
||
|
||
function registerDatabaseIpc() {
|
||
// ── Lifecycle ──────────────────────────────────────────────────────
|
||
ipcMain.handle('db:initialize', async () => {
|
||
await initDatabase();
|
||
return true;
|
||
});
|
||
|
||
// ── Messages ───────────────────────────────────────────────────────
|
||
ipcMain.handle('db:saveMessage', (_e, message) => {
|
||
db.run(
|
||
`INSERT OR REPLACE INTO messages
|
||
(id, roomId, channelId, senderId, senderName, content, timestamp, editedAt, reactions, isDeleted, replyToId)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||
[
|
||
message.id,
|
||
message.roomId,
|
||
message.channelId ?? null,
|
||
message.senderId,
|
||
message.senderName,
|
||
message.content,
|
||
message.timestamp,
|
||
message.editedAt ?? null,
|
||
JSON.stringify(message.reactions ?? []),
|
||
message.isDeleted ? 1 : 0,
|
||
message.replyToId ?? null,
|
||
],
|
||
);
|
||
persist();
|
||
});
|
||
|
||
ipcMain.handle('db:getMessages', (_e, roomId, limit = 100, offset = 0) => {
|
||
const rows = queryAll(
|
||
'SELECT * FROM messages WHERE roomId = ? ORDER BY timestamp ASC LIMIT ? OFFSET ?',
|
||
[roomId, limit, offset],
|
||
);
|
||
return rows.map(rowToMessage);
|
||
});
|
||
|
||
ipcMain.handle('db:deleteMessage', (_e, messageId) => {
|
||
db.run('DELETE FROM messages WHERE id = ?', [messageId]);
|
||
persist();
|
||
});
|
||
|
||
ipcMain.handle('db:updateMessage', (_e, messageId, updates) => {
|
||
const row = queryOne('SELECT * FROM messages WHERE id = ? LIMIT 1', [messageId]);
|
||
if (!row) return;
|
||
const msg = { ...rowToMessage(row), ...updates };
|
||
db.run(
|
||
`INSERT OR REPLACE INTO messages
|
||
(id, roomId, channelId, senderId, senderName, content, timestamp, editedAt, reactions, isDeleted, replyToId)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||
[
|
||
msg.id, msg.roomId, msg.channelId ?? null, msg.senderId, msg.senderName,
|
||
msg.content, msg.timestamp, msg.editedAt ?? null,
|
||
JSON.stringify(msg.reactions ?? []), msg.isDeleted ? 1 : 0, msg.replyToId ?? null,
|
||
],
|
||
);
|
||
persist();
|
||
});
|
||
|
||
ipcMain.handle('db:getMessageById', (_e, messageId) => {
|
||
const row = queryOne('SELECT * FROM messages WHERE id = ? LIMIT 1', [messageId]);
|
||
return row ? rowToMessage(row) : null;
|
||
});
|
||
|
||
ipcMain.handle('db:clearRoomMessages', (_e, roomId) => {
|
||
db.run('DELETE FROM messages WHERE roomId = ?', [roomId]);
|
||
persist();
|
||
});
|
||
|
||
// ── Reactions ──────────────────────────────────────────────────────
|
||
ipcMain.handle('db:saveReaction', (_e, reaction) => {
|
||
const check = db.exec(
|
||
'SELECT 1 FROM reactions WHERE messageId = ? AND userId = ? AND emoji = ?',
|
||
[reaction.messageId, reaction.userId, reaction.emoji],
|
||
);
|
||
if (check.length > 0 && check[0].values.length > 0) return;
|
||
|
||
db.run(
|
||
`INSERT OR REPLACE INTO reactions (id, messageId, oderId, userId, emoji, timestamp)
|
||
VALUES (?, ?, ?, ?, ?, ?)`,
|
||
[reaction.id, reaction.messageId, reaction.oderId, reaction.userId, reaction.emoji, reaction.timestamp],
|
||
);
|
||
persist();
|
||
});
|
||
|
||
ipcMain.handle('db:removeReaction', (_e, messageId, userId, emoji) => {
|
||
db.run('DELETE FROM reactions WHERE messageId = ? AND userId = ? AND emoji = ?', [messageId, userId, emoji]);
|
||
persist();
|
||
});
|
||
|
||
ipcMain.handle('db:getReactionsForMessage', (_e, messageId) => {
|
||
const rows = queryAll('SELECT * FROM reactions WHERE messageId = ?', [messageId]);
|
||
return rows.map(rowToReaction);
|
||
});
|
||
|
||
// ── Users ──────────────────────────────────────────────────────────
|
||
ipcMain.handle('db:saveUser', (_e, user) => {
|
||
db.run(
|
||
`INSERT OR REPLACE INTO users
|
||
(id, oderId, username, displayName, avatarUrl, status, role, joinedAt,
|
||
peerId, isOnline, isAdmin, isRoomOwner, voiceState, screenShareState)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||
[
|
||
user.id,
|
||
user.oderId ?? null,
|
||
user.username ?? null,
|
||
user.displayName ?? null,
|
||
user.avatarUrl ?? null,
|
||
user.status ?? null,
|
||
user.role ?? null,
|
||
user.joinedAt ?? null,
|
||
user.peerId ?? null,
|
||
user.isOnline ? 1 : 0,
|
||
user.isAdmin ? 1 : 0,
|
||
user.isRoomOwner ? 1 : 0,
|
||
user.voiceState ? JSON.stringify(user.voiceState) : null,
|
||
user.screenShareState ? JSON.stringify(user.screenShareState) : null,
|
||
],
|
||
);
|
||
persist();
|
||
});
|
||
|
||
ipcMain.handle('db:getUser', (_e, userId) => {
|
||
const row = queryOne('SELECT * FROM users WHERE id = ? LIMIT 1', [userId]);
|
||
return row ? rowToUser(row) : null;
|
||
});
|
||
|
||
ipcMain.handle('db:getCurrentUser', () => {
|
||
const rows = db.exec("SELECT value FROM meta WHERE key = 'currentUserId'");
|
||
if (rows.length === 0 || rows[0].values.length === 0) return null;
|
||
const userId = String(rows[0].values[0][0]);
|
||
const row = queryOne('SELECT * FROM users WHERE id = ? LIMIT 1', [userId]);
|
||
return row ? rowToUser(row) : null;
|
||
});
|
||
|
||
ipcMain.handle('db:setCurrentUserId', (_e, userId) => {
|
||
db.run("INSERT OR REPLACE INTO meta (key, value) VALUES ('currentUserId', ?)", [userId]);
|
||
persist();
|
||
});
|
||
|
||
ipcMain.handle('db:getUsersByRoom', (_e, _roomId) => {
|
||
const rows = queryAll('SELECT * FROM users');
|
||
return rows.map(rowToUser);
|
||
});
|
||
|
||
ipcMain.handle('db:updateUser', (_e, userId, updates) => {
|
||
const row = queryOne('SELECT * FROM users WHERE id = ? LIMIT 1', [userId]);
|
||
if (!row) return;
|
||
const user = { ...rowToUser(row), ...updates };
|
||
db.run(
|
||
`INSERT OR REPLACE INTO users
|
||
(id, oderId, username, displayName, avatarUrl, status, role, joinedAt,
|
||
peerId, isOnline, isAdmin, isRoomOwner, voiceState, screenShareState)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||
[
|
||
user.id, user.oderId ?? null, user.username ?? null, user.displayName ?? null,
|
||
user.avatarUrl ?? null, user.status ?? null, user.role ?? null, user.joinedAt ?? null,
|
||
user.peerId ?? null, user.isOnline ? 1 : 0, user.isAdmin ? 1 : 0, user.isRoomOwner ? 1 : 0,
|
||
user.voiceState ? JSON.stringify(user.voiceState) : null,
|
||
user.screenShareState ? JSON.stringify(user.screenShareState) : null,
|
||
],
|
||
);
|
||
persist();
|
||
});
|
||
|
||
// ── Rooms ──────────────────────────────────────────────────────────
|
||
ipcMain.handle('db:saveRoom', (_e, room) => {
|
||
db.run(
|
||
`INSERT OR REPLACE INTO rooms
|
||
(id, name, description, topic, hostId, password, isPrivate, createdAt,
|
||
userCount, maxUsers, icon, iconUpdatedAt, permissions, channels)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||
[
|
||
room.id, room.name, room.description ?? null, room.topic ?? null,
|
||
room.hostId, room.password ?? null, room.isPrivate ? 1 : 0, room.createdAt,
|
||
room.userCount, room.maxUsers ?? null, room.icon ?? null,
|
||
room.iconUpdatedAt ?? null,
|
||
room.permissions ? JSON.stringify(room.permissions) : null,
|
||
room.channels ? JSON.stringify(room.channels) : null,
|
||
],
|
||
);
|
||
persist();
|
||
});
|
||
|
||
ipcMain.handle('db:getRoom', (_e, roomId) => {
|
||
const row = queryOne('SELECT * FROM rooms WHERE id = ? LIMIT 1', [roomId]);
|
||
return row ? rowToRoom(row) : null;
|
||
});
|
||
|
||
ipcMain.handle('db:getAllRooms', () => {
|
||
const rows = queryAll('SELECT * FROM rooms');
|
||
return rows.map(rowToRoom);
|
||
});
|
||
|
||
ipcMain.handle('db:deleteRoom', (_e, roomId) => {
|
||
db.run('DELETE FROM rooms WHERE id = ?', [roomId]);
|
||
db.run('DELETE FROM messages WHERE roomId = ?', [roomId]);
|
||
persist();
|
||
});
|
||
|
||
ipcMain.handle('db:updateRoom', (_e, roomId, updates) => {
|
||
const row = queryOne('SELECT * FROM rooms WHERE id = ? LIMIT 1', [roomId]);
|
||
if (!row) return;
|
||
const room = { ...rowToRoom(row), ...updates };
|
||
db.run(
|
||
`INSERT OR REPLACE INTO rooms
|
||
(id, name, description, topic, hostId, password, isPrivate, createdAt,
|
||
userCount, maxUsers, icon, iconUpdatedAt, permissions, channels)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||
[
|
||
room.id, room.name, room.description ?? null, room.topic ?? null,
|
||
room.hostId, room.password ?? null, room.isPrivate ? 1 : 0, room.createdAt,
|
||
room.userCount, room.maxUsers ?? null, room.icon ?? null,
|
||
room.iconUpdatedAt ?? null,
|
||
room.permissions ? JSON.stringify(room.permissions) : null,
|
||
room.channels ? JSON.stringify(room.channels) : null,
|
||
],
|
||
);
|
||
persist();
|
||
});
|
||
|
||
// ── Bans ───────────────────────────────────────────────────────────
|
||
ipcMain.handle('db:saveBan', (_e, ban) => {
|
||
db.run(
|
||
`INSERT OR REPLACE INTO bans
|
||
(oderId, userId, roomId, bannedBy, displayName, reason, expiresAt, timestamp)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||
[
|
||
ban.oderId, ban.userId ?? null, ban.roomId, ban.bannedBy,
|
||
ban.displayName ?? null, ban.reason ?? null, ban.expiresAt ?? null, ban.timestamp,
|
||
],
|
||
);
|
||
persist();
|
||
});
|
||
|
||
ipcMain.handle('db:removeBan', (_e, oderId) => {
|
||
db.run('DELETE FROM bans WHERE oderId = ?', [oderId]);
|
||
persist();
|
||
});
|
||
|
||
ipcMain.handle('db:getBansForRoom', (_e, roomId) => {
|
||
const now = Date.now();
|
||
const rows = queryAll(
|
||
'SELECT * FROM bans WHERE roomId = ? AND (expiresAt IS NULL OR expiresAt > ?)',
|
||
[roomId, now],
|
||
);
|
||
return rows.map(rowToBan);
|
||
});
|
||
|
||
ipcMain.handle('db:isUserBanned', (_e, userId, roomId) => {
|
||
const now = Date.now();
|
||
const rows = queryAll(
|
||
'SELECT * FROM bans WHERE roomId = ? AND (expiresAt IS NULL OR expiresAt > ?)',
|
||
[roomId, now],
|
||
);
|
||
return rows.some((r) => String(r.oderId) === userId);
|
||
});
|
||
|
||
// ── Attachments ─────────────────────────────────────────────────────
|
||
ipcMain.handle('db:saveAttachment', (_e, attachment) => {
|
||
db.run(
|
||
`INSERT OR REPLACE INTO attachments
|
||
(id, messageId, filename, size, mime, isImage, uploaderPeerId, filePath, savedPath)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||
[
|
||
attachment.id, attachment.messageId, attachment.filename,
|
||
attachment.size, attachment.mime, attachment.isImage ? 1 : 0,
|
||
attachment.uploaderPeerId ?? null, attachment.filePath ?? null,
|
||
attachment.savedPath ?? null,
|
||
],
|
||
);
|
||
persist();
|
||
});
|
||
|
||
ipcMain.handle('db:getAttachmentsForMessage', (_e, messageId) => {
|
||
const rows = queryAll('SELECT * FROM attachments WHERE messageId = ?', [messageId]);
|
||
return rows.map(rowToAttachment);
|
||
});
|
||
|
||
ipcMain.handle('db:getAllAttachments', () => {
|
||
const rows = queryAll('SELECT * FROM attachments');
|
||
return rows.map(rowToAttachment);
|
||
});
|
||
|
||
ipcMain.handle('db:deleteAttachmentsForMessage', (_e, messageId) => {
|
||
db.run('DELETE FROM attachments WHERE messageId = ?', [messageId]);
|
||
persist();
|
||
});
|
||
|
||
// ── Utilities ──────────────────────────────────────────────────────
|
||
ipcMain.handle('db:clearAllData', () => {
|
||
db.run('DELETE FROM messages');
|
||
db.run('DELETE FROM users');
|
||
db.run('DELETE FROM rooms');
|
||
db.run('DELETE FROM reactions');
|
||
db.run('DELETE FROM bans');
|
||
db.run('DELETE FROM attachments');
|
||
db.run('DELETE FROM meta');
|
||
persist();
|
||
});
|
||
}
|
||
|
||
module.exports = { registerDatabaseIpc };
|