feat: enhance backlog sync logging and implement UI state persistence

This commit is contained in:
MythEclipse
2026-05-14 03:17:07 +07:00
parent 6e203604ec
commit d4a4f737a8
3 changed files with 63 additions and 7 deletions
+15 -2
View File
@@ -90,14 +90,27 @@ export async function syncBacklogMessages(
}
const cutoffTime = Date.now() - config.BACKLOG_SYNC_HOURS * 60 * 60 * 1000;
await guild.channels.fetch().catch(() => null);
logger.info(
{ guildId: guild.id, hours: config.BACKLOG_SYNC_HOURS },
"Starting message backlog sync",
);
logger.info({ guildId: guild.id }, "Fetching guild channels for backlog sync");
await guild.channels.fetch().catch((error) => {
logger.warn(
{ guildId: guild.id, error: error instanceof Error ? error.message : String(error) },
"Failed to fetch guild channels before backlog sync",
);
return null;
});
logger.info({ guildId: guild.id }, "Collecting watchable channels for backlog sync");
const channels = await collectWatchableChannels(guild);
let total = 0;
logger.info(
{ guildId: guild.id, channels: channels.length, hours: config.BACKLOG_SYNC_HOURS },
"Starting message backlog sync",
"Watchable channels collected for backlog sync",
);
for (const channel of channels) {
+28
View File
@@ -108,6 +108,12 @@ function initializeDatabase(): SqliteDatabase {
CREATE INDEX IF NOT EXISTS idx_attachments_channel ON attachments(channel_id);
CREATE INDEX IF NOT EXISTS idx_attachments_message ON attachments(message_id);
CREATE INDEX IF NOT EXISTS idx_attachments_status ON attachments(upload_status);
CREATE TABLE IF NOT EXISTS ui_state (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at INTEGER NOT NULL
);
`);
const migrations = [
@@ -141,6 +147,28 @@ function getDatabase(): SqliteDatabase {
export { getDatabase };
export function getPersistedValue<T>(key: string, fallback: T): T {
const row = getDatabase()
.prepare("SELECT value FROM ui_state WHERE key = ?")
.get(key) as { value: string } | undefined;
if (!row) return fallback;
try {
return JSON.parse(row.value) as T;
} catch {
return fallback;
}
}
export function setPersistedValue(key: string, value: unknown): void {
getDatabase()
.prepare(`
INSERT INTO ui_state (key, value, updated_at)
VALUES (?, ?, ?)
ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at
`)
.run(key, JSON.stringify(value), Date.now());
}
export async function enqueueMuxerJob(data: MuxerJobData): Promise<string> {
try {
const database = getDatabase();
+20 -5
View File
@@ -3,7 +3,6 @@ import express from "express";
import helmet from "helmet";
import http from "http";
import path from "path";
import pinoHttp from "pino-http";
import * as prism from "prism-media";
import { WebSocketServer } from "ws";
import { AppError } from "./errors";
@@ -11,7 +10,7 @@ import { createChildLogger, logger } from "./logger";
import { getMetrics, uptimeGauge } from "./metrics";
import { discordPlayer } from "./player";
import type { VoiceController } from "./voiceController";
import { getDatabase } from "./muxer-queue";
import { getDatabase, getPersistedValue, setPersistedValue } from "./muxer-queue";
import { getMessagesByChannel, getAttachmentsByChannel } from "./moderation/messageStore";
const wsLogger = createChildLogger("webserver");
@@ -31,7 +30,7 @@ interface SharedUIState {
isStreaming: boolean;
}
const sharedUIState: SharedUIState = {
const defaultSharedUIState: SharedUIState = {
selectedGuild: "",
selectedVoiceChannel: "",
selectedTextChannel: "",
@@ -40,6 +39,8 @@ const sharedUIState: SharedUIState = {
isStreaming: false,
};
const sharedUIState: SharedUIState = getPersistedValue("web-ui-state", defaultSharedUIState);
function getSharedUIState(): SharedUIState {
return { ...sharedUIState };
}
@@ -73,6 +74,7 @@ function patchSharedUIState(patch: Partial<SharedUIState>) {
if (typeof patch.isStreaming === "boolean") {
sharedUIState.isStreaming = patch.isStreaming;
}
setPersistedValue("web-ui-state", sharedUIState);
broadcastUIState();
return getSharedUIState();
}
@@ -121,8 +123,21 @@ export function startWebserver(
}),
);
// HTTP request logging
app.use(pinoHttp({ logger }));
app.use((req, res, next) => {
if (req.path.startsWith("/api/")) {
res.set("Cache-Control", "no-store");
}
res.on("finish", () => {
if (req.originalUrl.startsWith("/.well-known/appspecific/")) return;
if (res.statusCode >= 400) {
logger.error(
{ method: req.method, url: req.originalUrl, statusCode: res.statusCode },
"HTTP request failed",
);
}
});
next();
});
app.use(express.json());
app.use(express.static(path.join(__dirname, "../public")));