Restructure chat service, web routes, and add test suite
Split monolithic chat service into modular structure with Matrix client support, extract web server into per-resource route modules, introduce bootstrap/main-loop separation, and add comprehensive vitest coverage across services, repositories, and utilities. Adds project docs and CLAUDE.md agent instructions.
This commit is contained in:
1 parent
dfa0580859
commit
633bb0ec04
84 files changed
+19278
-3888
No files matched your search
@@ -0,0 +1,88 @@
|
||||
import { Router } from 'express';
|
||||
import type { WebContext } from '../context.js';
|
||||
|
||||
export function createMediaRouter(ctx: WebContext): Router {
|
||||
const router = Router();
|
||||
|
||||
router.get('/media', async (_req, res) => {
|
||||
if (!ctx.services.media) {
|
||||
res.json([]);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const items = await ctx.services.media.getMediaItems();
|
||||
|
||||
// Build a roomId → current chat lookup so we can override stale
|
||||
// stored usernames (e.g. items captured before username enrichment ran)
|
||||
// and surface the recipient even for rooms that were originally
|
||||
// ingested as `unknown`.
|
||||
const liveChats = ctx.services.chat
|
||||
? await ctx.services.chat.getCachedConversations()
|
||||
: [];
|
||||
const byRoom = new Map(liveChats.map((c) => [c.roomId ?? '', c] as const));
|
||||
|
||||
res.json(items.map((item) => {
|
||||
const live = item.chatRoomId ? byRoom.get(item.chatRoomId) : undefined;
|
||||
const liveName = live?.username && live.username !== 'unknown' ? live.username : null;
|
||||
const username = liveName ?? item.chatUsername ?? null;
|
||||
return {
|
||||
...item,
|
||||
chatUsername: username,
|
||||
urlPath: `/media/${encodeURIComponent(item.relativePath)}`,
|
||||
};
|
||||
}));
|
||||
} catch (error) {
|
||||
ctx.logger.error('Failed to fetch media index:', error);
|
||||
res.status(500).json({ error: String(error) });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/media/sync', (_req, res) => {
|
||||
if (!ctx.services.mediaSync) {
|
||||
res.status(503).json({ error: 'Media sync service not initialized' });
|
||||
return;
|
||||
}
|
||||
res.json(ctx.services.mediaSync.getStatus());
|
||||
});
|
||||
|
||||
router.post('/media/sync', async (req, res) => {
|
||||
if (!ctx.services.mediaSync) {
|
||||
res.status(503).json({ error: 'Media sync service not initialized' });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const full = req.body?.full === true;
|
||||
const started = await ctx.services.mediaSync.triggerSync(full);
|
||||
if (!started) {
|
||||
res.json({ success: false, reason: 'Sync already in progress' });
|
||||
return;
|
||||
}
|
||||
ctx.broadcaster.push('info', `Media sync triggered from dashboard (${full ? 'full' : 'incremental'})`);
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
ctx.logger.error('Media sync trigger failed:', error);
|
||||
res.status(500).json({ error: String(error) });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/media/sync/full', async (_req, res) => {
|
||||
if (!ctx.services.mediaSync) {
|
||||
res.status(503).json({ error: 'Media sync service not initialized' });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const started = await ctx.services.mediaSync.triggerSync(true);
|
||||
if (!started) {
|
||||
res.json({ success: false, reason: 'Sync already in progress' });
|
||||
return;
|
||||
}
|
||||
ctx.broadcaster.push('info', 'Full media sync triggered from dashboard (all conversations)');
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
ctx.logger.error('Full media sync trigger failed:', error);
|
||||
res.status(500).json({ error: String(error) });
|
||||
}
|
||||
});
|
||||
|
||||
return router;
|
||||
}
|
||||
Reference in new issue
Block a user