Files
breakpilot-core/pitch-deck/lib/version-helpers.ts
Sharang Parnerkar 1872079504
Some checks failed
CI / go-lint (pull_request) Failing after 13s
CI / python-lint (pull_request) Failing after 13s
CI / nodejs-lint (pull_request) Failing after 8s
CI / test-go-consent (pull_request) Failing after 3s
CI / test-python-voice (pull_request) Failing after 10s
CI / test-bqas (pull_request) Failing after 11s
CI / Deploy (pull_request) Has been skipped
feat(pitch-deck): full pitch versioning with git-style history + bug fixes
Adds a complete version management system where every piece of pitch
data (all 12 tables: company, team, financials, market, competitors,
features, milestones, metrics, funding, products, fm_scenarios,
fm_assumptions) can be versioned, diffed, and assigned per-investor.

Version lifecycle: create draft → edit freely → commit (immutable) →
fork to create new draft. Parent chain gives full git-style history.

Backend:
- Migration 003: pitch_versions, pitch_version_data tables + investor
  assigned_version_id column
- lib/version-helpers.ts: snapshot base tables, copy between versions
- lib/version-diff.ts: per-table row+field diffing engine
- 7 new API routes: versions CRUD, commit, fork, per-table data
  GET/PUT, diff endpoint
- /api/data + /api/financial-model: version-aware loading (check
  investor's assigned_version_id, serve version data or fall back
  to base tables)
- Investor PATCH: accepts assigned_version_id (validates committed)

Frontend:
- /pitch-admin/versions: list with status badges, fork/commit/delete
- /pitch-admin/versions/new: create from base tables or fork existing
- /pitch-admin/versions/[id]: 12-tab JSON editor (one per data table)
  with save-per-table, commit button, fork button
- /pitch-admin/versions/[id]/diff/[otherId]: side-by-side diff view
  with added/removed/changed highlighting per field
- Investors list: version column showing assigned version name
- Investor detail: version selector dropdown (committed versions only)
- AdminShell: Versions nav item added

Bug fixes:
- FM editor: [object Object] for JSONB array values → JSON.stringify
- Admin pages not scrollable → h-screen + overflow-hidden on shell,
  min-h-0 on flex column

Also includes migration 000 for fresh installs (pitch data tables).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 09:34:03 +02:00

77 lines
2.9 KiB
TypeScript

import pool from './db'
/**
* The 12 data tables tracked per version.
* Each maps to a pitch_version_data.table_name value.
*/
export const VERSION_TABLES = [
'company', 'team', 'financials', 'market', 'competitors',
'features', 'milestones', 'metrics', 'funding', 'products',
'fm_scenarios', 'fm_assumptions',
] as const
export type VersionTableName = typeof VERSION_TABLES[number]
/** Maps version table names to the actual DB table + ORDER BY */
const TABLE_QUERIES: Record<VersionTableName, string> = {
company: 'SELECT * FROM pitch_company LIMIT 1',
team: 'SELECT * FROM pitch_team ORDER BY sort_order',
financials: 'SELECT * FROM pitch_financials ORDER BY year',
market: 'SELECT * FROM pitch_market ORDER BY id',
competitors: 'SELECT * FROM pitch_competitors ORDER BY id',
features: 'SELECT * FROM pitch_features ORDER BY sort_order',
milestones: 'SELECT * FROM pitch_milestones ORDER BY sort_order',
metrics: 'SELECT * FROM pitch_metrics ORDER BY id',
funding: 'SELECT * FROM pitch_funding LIMIT 1',
products: 'SELECT * FROM pitch_products ORDER BY sort_order',
fm_scenarios: 'SELECT * FROM pitch_fm_scenarios ORDER BY is_default DESC, name',
fm_assumptions: 'SELECT * FROM pitch_fm_assumptions ORDER BY sort_order',
}
/**
* Snapshot all base tables into pitch_version_data for a given version.
*/
export async function snapshotBaseTables(versionId: string, adminId: string | null): Promise<void> {
const client = await pool.connect()
try {
for (const tableName of VERSION_TABLES) {
const { rows } = await client.query(TABLE_QUERIES[tableName])
await client.query(
`INSERT INTO pitch_version_data (version_id, table_name, data, updated_by)
VALUES ($1, $2, $3, $4)
ON CONFLICT (version_id, table_name) DO UPDATE SET data = $3, updated_at = NOW(), updated_by = $4`,
[versionId, tableName, JSON.stringify(rows), adminId],
)
}
} finally {
client.release()
}
}
/**
* Copy all version data from one version to another.
*/
export async function copyVersionData(fromVersionId: string, toVersionId: string, adminId: string | null): Promise<void> {
await pool.query(
`INSERT INTO pitch_version_data (version_id, table_name, data, updated_by)
SELECT $1, table_name, data, $3
FROM pitch_version_data WHERE version_id = $2`,
[toVersionId, fromVersionId, adminId],
)
}
/**
* Load all version data as a map of table_name → JSONB rows.
*/
export async function loadVersionData(versionId: string): Promise<Record<string, unknown[]>> {
const { rows } = await pool.query(
`SELECT table_name, data FROM pitch_version_data WHERE version_id = $1`,
[versionId],
)
const result: Record<string, unknown[]> = {}
for (const row of rows) {
result[row.table_name] = typeof row.data === 'string' ? JSON.parse(row.data) : row.data
}
return result
}