refactor(approvals): persist title+options on channel/sender approval tables

getAskQuestionRender used to hardcode the card title and option labels
for pending_channel_approvals and pending_sender_approvals in the
DB-access layer, duplicating wording that already lived in the approval
modules. That caused a visible drift between the initial card title —
picked per event in channel-approval.ts ("📣 Bot mentioned in new chat"
vs. "💬 New direct message") — and the post-click render, which
always showed the constant "📣 Channel registration".

Mirror the pattern already used by pending_approvals: add title /
options_json columns on both pending_*_approvals tables via migration
013, have the approval modules write them at creation time, and let
getAskQuestionRender just SELECT.

- Migration 013 ALTERs the two tables to add title + options_json.
- PendingChannelApproval / PendingSenderApproval types and their
  create functions grow the two fields.
- channel-approval.ts / sender-approval.ts normalize options once
  and pass both title and options_json into the insert.
- getAskQuestionRender drops the hardcoded render objects and reads
  the stored values.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gavrielc
2026-04-23 22:54:47 +03:00
parent a8eb82d529
commit f351e46008
7 changed files with 61 additions and 28 deletions

View File

@@ -0,0 +1,27 @@
/**
* Persist ask_question render metadata (title + options_json) on
* `pending_channel_approvals` and `pending_sender_approvals`, mirroring the
* columns migration 003 / module-approvals-title-options added to
* `pending_approvals`.
*
* Before this, `getAskQuestionRender` hardcoded the title + option labels
* for these two tables in the DB-access layer — duplicating wording that
* also lived in the approval modules and causing a visible drift between
* the initial card title ("📣 Bot mentioned in new chat" / "💬 New direct
* message", chosen per event) and the post-click render ("📣 Channel
* registration", constant). Storing the render metadata alongside the row
* lets both sides read from the same source.
*/
import type Database from 'better-sqlite3';
import type { Migration } from './index.js';
export const migration013: Migration = {
version: 13,
name: 'approval-render-metadata',
up(db: Database.Database) {
db.exec(`ALTER TABLE pending_channel_approvals ADD COLUMN title TEXT NOT NULL DEFAULT ''`);
db.exec(`ALTER TABLE pending_channel_approvals ADD COLUMN options_json TEXT NOT NULL DEFAULT '[]'`);
db.exec(`ALTER TABLE pending_sender_approvals ADD COLUMN title TEXT NOT NULL DEFAULT ''`);
db.exec(`ALTER TABLE pending_sender_approvals ADD COLUMN options_json TEXT NOT NULL DEFAULT '[]'`);
},
};

View File

@@ -9,6 +9,7 @@ import { migration009 } from './009-drop-pending-credentials.js';
import { migration010 } from './010-engage-modes.js';
import { migration011 } from './011-pending-sender-approvals.js';
import { migration012 } from './012-channel-registration.js';
import { migration013 } from './013-approval-render-metadata.js';
import { moduleApprovalsPendingApprovals } from './module-approvals-pending-approvals.js';
import { moduleApprovalsTitleOptions } from './module-approvals-title-options.js';
@@ -29,6 +30,7 @@ const migrations: Migration[] = [
migration010,
migration011,
migration012,
migration013,
];
export function runMigrations(db: Database.Database): void {

View File

@@ -194,32 +194,20 @@ export function getAskQuestionRender(
| undefined;
if (a?.title) return { title: a.title, options: JSON.parse(a.options_json) };
// Channel-registration approval — options are fixed constants.
// Channel-registration + unknown-sender approvals persist title/options_json
// the same way pending_approvals does — just SELECT and return.
if (hasTable(getDb(), 'pending_channel_approvals')) {
const c = getDb().prepare('SELECT 1 FROM pending_channel_approvals WHERE messaging_group_id = ?').get(id);
if (c) {
return {
title: '📣 Channel registration',
options: [
{ label: 'Approve', selectedLabel: '✅ Wired', value: 'approve' },
{ label: 'Ignore', selectedLabel: '🙅 Ignored', value: 'reject' },
],
};
}
const c = getDb()
.prepare('SELECT title, options_json FROM pending_channel_approvals WHERE messaging_group_id = ?')
.get(id) as { title: string; options_json: string } | undefined;
if (c?.title) return { title: c.title, options: JSON.parse(c.options_json) };
}
// Unknown-sender approval — options are fixed constants.
if (hasTable(getDb(), 'pending_sender_approvals')) {
const s = getDb().prepare('SELECT 1 FROM pending_sender_approvals WHERE id = ?').get(id);
if (s) {
return {
title: '👤 New sender',
options: [
{ label: 'Allow', selectedLabel: '✅ Allowed', value: 'approve' },
{ label: 'Deny', selectedLabel: '❌ Denied', value: 'reject' },
],
};
}
const s = getDb().prepare('SELECT title, options_json FROM pending_sender_approvals WHERE id = ?').get(id) as
| { title: string; options_json: string }
| undefined;
if (s?.title) return { title: s.title, options: JSON.parse(s.options_json) };
}
return undefined;