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

View File

@@ -90,14 +90,27 @@ export async function syncBacklogMessages(
} }
const cutoffTime = Date.now() - config.BACKLOG_SYNC_HOURS * 60 * 60 * 1000; 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); const channels = await collectWatchableChannels(guild);
let total = 0; let total = 0;
logger.info( logger.info(
{ guildId: guild.id, channels: channels.length, hours: config.BACKLOG_SYNC_HOURS }, { 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) { for (const channel of channels) {

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_channel ON attachments(channel_id);
CREATE INDEX IF NOT EXISTS idx_attachments_message ON attachments(message_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 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 = [ const migrations = [
@@ -141,6 +147,28 @@ function getDatabase(): SqliteDatabase {
export { getDatabase }; 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> { export async function enqueueMuxerJob(data: MuxerJobData): Promise<string> {
try { try {
const database = getDatabase(); const database = getDatabase();

View File

@@ -3,7 +3,6 @@ import express from "express";
import helmet from "helmet"; import helmet from "helmet";
import http from "http"; import http from "http";
import path from "path"; import path from "path";
import pinoHttp from "pino-http";
import * as prism from "prism-media"; import * as prism from "prism-media";
import { WebSocketServer } from "ws"; import { WebSocketServer } from "ws";
import { AppError } from "./errors"; import { AppError } from "./errors";
@@ -11,7 +10,7 @@ import { createChildLogger, logger } from "./logger";
import { getMetrics, uptimeGauge } from "./metrics"; import { getMetrics, uptimeGauge } from "./metrics";
import { discordPlayer } from "./player"; import { discordPlayer } from "./player";
import type { VoiceController } from "./voiceController"; import type { VoiceController } from "./voiceController";
import { getDatabase } from "./muxer-queue"; import { getDatabase, getPersistedValue, setPersistedValue } from "./muxer-queue";
import { getMessagesByChannel, getAttachmentsByChannel } from "./moderation/messageStore"; import { getMessagesByChannel, getAttachmentsByChannel } from "./moderation/messageStore";
const wsLogger = createChildLogger("webserver"); const wsLogger = createChildLogger("webserver");
@@ -31,7 +30,7 @@ interface SharedUIState {
isStreaming: boolean; isStreaming: boolean;
} }
const sharedUIState: SharedUIState = { const defaultSharedUIState: SharedUIState = {
selectedGuild: "", selectedGuild: "",
selectedVoiceChannel: "", selectedVoiceChannel: "",
selectedTextChannel: "", selectedTextChannel: "",
@@ -40,6 +39,8 @@ const sharedUIState: SharedUIState = {
isStreaming: false, isStreaming: false,
}; };
const sharedUIState: SharedUIState = getPersistedValue("web-ui-state", defaultSharedUIState);
function getSharedUIState(): SharedUIState { function getSharedUIState(): SharedUIState {
return { ...sharedUIState }; return { ...sharedUIState };
} }
@@ -73,6 +74,7 @@ function patchSharedUIState(patch: Partial<SharedUIState>) {
if (typeof patch.isStreaming === "boolean") { if (typeof patch.isStreaming === "boolean") {
sharedUIState.isStreaming = patch.isStreaming; sharedUIState.isStreaming = patch.isStreaming;
} }
setPersistedValue("web-ui-state", sharedUIState);
broadcastUIState(); broadcastUIState();
return getSharedUIState(); return getSharedUIState();
} }
@@ -121,8 +123,21 @@ export function startWebserver(
}), }),
); );
// HTTP request logging app.use((req, res, next) => {
app.use(pinoHttp({ logger })); 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.json());
app.use(express.static(path.join(__dirname, "../public"))); app.use(express.static(path.join(__dirname, "../public")));