feat: enhance backlog sync logging and implement UI state persistence
This commit is contained in:
@@ -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) {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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")));
|
||||||
|
|||||||
Reference in New Issue
Block a user