feat(pitch-deck): full pitch versioning with git-style history (#4)
Some checks failed
Build pitch-deck / build-and-push (push) Failing after 1m8s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 32s
CI / Deploy (push) Failing after 4s
Some checks failed
Build pitch-deck / build-and-push (push) Failing after 1m8s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 32s
CI / Deploy (push) Failing after 4s
Full pitch versioning: 12 data tables versioned as JSONB snapshots, git-style parent chain (draft→commit→fork), per-investor assignment, side-by-side diff engine, version-aware /api/data + /api/financial-model. Bug fixes: FM editor [object Object] for JSONB arrays, admin scroll. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit was merged in pull request #4.
This commit is contained in:
76
pitch-deck/lib/version-helpers.ts
Normal file
76
pitch-deck/lib/version-helpers.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user