Make subreddit list editable from the dashboard
Adds an inline subreddit editor on the dashboard that lets you add,
remove, and edit (tone, type, weight) the subreddit list, with changes
persisted to config.yaml on disk so they survive restarts.
Backend
- src/config/persistence.ts: round-trips config.yaml via
YAML.parseDocument, mutating existing row nodes in place so their
trailing inline `# ...` comments survive an edit; new rows are
appended as flow-style maps to match the on-disk idiom; removed rows
are dropped. Default values ('default' tone, 'image' type) are
omitted on write so the file stays clean. Atomic via tmp+rename.
- src/web/routes/config.ts: GET /api/config now also returns the
current subreddit list and the available tone keys. PUT /api/config
accepts a `subreddits` field, validates with the existing
SubredditConfigSchema, rejects duplicates, persists, then mutates
ctx.config.reddit.subreddits in place so the processor picks up the
new tone immediately without a restart.
Frontend
- public/index.html: replaces the read-only subreddit chip list in the
Configuration snapshot section with a grid of editable rows (name,
type, tone dropdown, weight, remove). + Add / Save buttons with
inline status. Tones are sourced from /api/config so the catalog
drives the dropdown.
Tests
- tests/config-persistence.test.ts: covers in-place mutation comment
preservation, removal, appends, reorder, default omission, and
atomic write cleanup.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
1 parent
46a213e3e0
commit
d9b9a79bdd
4 files changed
+476
-5
No files matched your search
@@ -1,5 +1,11 @@
|
||||
import { Router } from 'express';
|
||||
import type { WebContext } from '../context.js';
|
||||
import { SubredditConfigSchema, type SubredditConfig } from '../../config/index.js';
|
||||
import { TONE_KEYS } from '../../config/tone-profiles.js';
|
||||
import { persistSubreddits } from '../../config/persistence.js';
|
||||
import { z } from 'zod';
|
||||
|
||||
const SubredditsUpdateSchema = z.array(SubredditConfigSchema).min(1);
|
||||
|
||||
export function createConfigRouter(ctx: WebContext): Router {
|
||||
const router = Router();
|
||||
@@ -9,6 +15,8 @@ export function createConfigRouter(ctx: WebContext): Router {
|
||||
rateLimit: ctx.runtimeConfig.rateLimit,
|
||||
browser: { dryRun: ctx.runtimeConfig.browser.dryRun },
|
||||
reddit: ctx.runtimeConfig.reddit,
|
||||
subreddits: ctx.config.reddit.subreddits,
|
||||
tones: TONE_KEYS,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -57,12 +65,36 @@ export function createConfigRouter(ctx: WebContext): Router {
|
||||
ctx.config.reddit.prioritizeRecentPosts = ctx.runtimeConfig.reddit.prioritizeRecentPosts;
|
||||
ctx.config.reddit.recentWindowMinutes = ctx.runtimeConfig.reddit.recentWindowMinutes;
|
||||
|
||||
if (updates.subreddits !== undefined) {
|
||||
const parsed = SubredditsUpdateSchema.safeParse(updates.subreddits);
|
||||
if (!parsed.success) {
|
||||
res.status(400).json({ error: 'Invalid subreddits payload', detail: parsed.error.format() });
|
||||
return;
|
||||
}
|
||||
const next: SubredditConfig[] = parsed.data;
|
||||
const seen = new Set<string>();
|
||||
for (const s of next) {
|
||||
const key = s.name.toLowerCase();
|
||||
if (seen.has(key)) {
|
||||
res.status(400).json({ error: `Duplicate subreddit: ${s.name}` });
|
||||
return;
|
||||
}
|
||||
seen.add(key);
|
||||
}
|
||||
persistSubreddits(next);
|
||||
ctx.config.reddit.subreddits = next;
|
||||
}
|
||||
|
||||
ctx.getOnConfigChange()?.(ctx.runtimeConfig);
|
||||
|
||||
ctx.activity.push('info', 'Config updated via dashboard');
|
||||
ctx.broadcaster.push('info', 'Configuration updated via dashboard', updates);
|
||||
|
||||
res.json({ success: true, config: ctx.runtimeConfig });
|
||||
res.json({
|
||||
success: true,
|
||||
config: ctx.runtimeConfig,
|
||||
subreddits: ctx.config.reddit.subreddits,
|
||||
});
|
||||
} catch (error) {
|
||||
ctx.logger.error('Failed to update config:', error);
|
||||
res.status(400).json({ error: String(error) });
|
||||
|
||||
Reference in new issue
Block a user