feat: Complete versioning system transformation

This commit is contained in:
gpt-engineer-app[bot]
2025-10-15 18:12:52 +00:00
parent ea78aff4a7
commit 8cd38234fa
4 changed files with 352 additions and 1 deletions

View File

@@ -1,3 +1,23 @@
/**
* @deprecated This file uses the old JSONB-based versioning system.
*
* **Migration Notice:**
* The versioning system has been migrated to a pure relational structure.
* Triggers now automatically handle version creation - no manual calls needed.
*
* **New System:**
* - Versioning is automatic via database triggers
* - Use `useEntityVersions` hook for frontend access
* - Use `get_version_diff()` RPC for comparisons
* - Version tables: `park_versions`, `ride_versions`, etc.
*
* **Scheduled for Removal:** 2025-12-01
*
* @see docs/versioning/MIGRATION.md for migration guide
* @see docs/versioning/API.md for new API reference
* @see src/lib/versioningUtils.ts for modern utilities
*/
import { supabase } from '@/integrations/supabase/client';
import { toast } from '@/hooks/use-toast';
@@ -42,6 +62,12 @@ export async function captureCurrentState(
/**
* Create a new entity version with proper error handling
*
* @deprecated Use automatic trigger-based versioning instead.
* This function calls the old JSONB-based `create_entity_version` RPC.
* Versions are now created automatically when entities are updated via triggers.
*
* @see docs/versioning/ARCHITECTURE.md for how triggers work
*/
export async function createEntityVersion(params: {
entityType: EntityType;
@@ -62,6 +88,11 @@ export async function createEntityVersion(params: {
changeType = 'updated',
} = params;
console.warn(
'⚠️ createEntityVersion is deprecated. Versioning is now automatic via triggers. ' +
'See docs/versioning/MIGRATION.md for migration guide.'
);
try {
const { data, error } = await supabase.rpc('create_entity_version', {
p_entity_type: entityType,
@@ -97,6 +128,9 @@ export async function createEntityVersion(params: {
/**
* Create entity version with audit log entry
*
* @deprecated Use automatic trigger-based versioning instead.
* Versions are now created automatically when entities are updated.
*/
export async function createEntityVersionWithAudit(
params: {
@@ -137,6 +171,9 @@ export async function createEntityVersionWithAudit(
/**
* Rollback an entity to a previous version
*
* @deprecated This uses the old JSONB-based RPC.
* Use the relational `rollback_to_version()` RPC or `useEntityVersions` hook instead.
*/
export async function rollbackToVersion(
entityType: EntityType,
@@ -183,6 +220,9 @@ export async function rollbackToVersion(
/**
* Compare two versions and get the diff
*
* @deprecated This uses the old JSONB-based RPC.
* Use `get_version_diff()` RPC or `useVersionComparison` hook instead.
*/
export async function compareVersions(
fromVersionId: string,

170
src/lib/versioningUtils.ts Normal file
View File

@@ -0,0 +1,170 @@
/**
* Modern versioning utilities for relational version tables
*
* These functions work with the new trigger-based versioning system.
* All version creation is automatic via database triggers - no manual calls needed.
*
* @see docs/versioning/ARCHITECTURE.md for system design
* @see docs/versioning/API.md for complete API reference
*/
import { supabase } from '@/integrations/supabase/client';
import type { EntityType } from '@/types/versioning';
/**
* Manually trigger cleanup of old versions for a specific entity type
*
* Note: This should normally run automatically via pg_cron, but can be called manually.
*
* @param entityType - The entity type to clean up ('park', 'ride', 'company', 'ride_model')
* @param keepCount - Number of most recent versions to keep per entity (default: 50)
* @returns Number of versions deleted
*
* @example
* ```typescript
* const deleted = await cleanupVersions('park', 50);
* console.log(`Deleted ${deleted} old park versions`);
* ```
*/
export async function cleanupVersions(
entityType: EntityType,
keepCount: number = 50
): Promise<number> {
const { data, error } = await supabase.rpc('cleanup_old_versions', {
entity_type: entityType,
keep_versions: keepCount
});
if (error) {
console.error('Version cleanup failed:', error);
return 0;
}
return data as number;
}
/**
* Get statistics about versions for a specific entity
*
* @param entityType - The entity type ('park', 'ride', 'company', 'ride_model')
* @param entityId - The UUID of the entity
* @returns Version statistics including total count, date range, and change type distribution
*
* @example
* ```typescript
* const stats = await getVersionStats('park', 'uuid-here');
* console.log(`Total versions: ${stats.totalVersions}`);
* console.log(`Change types:`, stats.changeTypes);
* ```
*/
export async function getVersionStats(
entityType: EntityType,
entityId: string
) {
const versionTable = `${entityType}_versions`;
const entityIdCol = `${entityType}_id`;
const { data, error } = await supabase
.from(versionTable as any)
.select('version_number, created_at, change_type', { count: 'exact' })
.eq(entityIdCol, entityId)
.order('version_number', { ascending: true });
if (error || !data) {
console.error('Failed to fetch version stats:', error);
return null;
}
if (data.length === 0) {
return {
totalVersions: 0,
oldestVersion: null,
newestVersion: null,
changeTypes: {}
};
}
// Type-safe access to version data
const versions = data as unknown as Array<{
version_number: number;
created_at: string;
change_type: string;
}>;
return {
totalVersions: versions.length,
oldestVersion: versions[0]?.created_at || null,
newestVersion: versions[versions.length - 1]?.created_at || null,
changeTypes: versions.reduce((acc, v) => {
acc[v.change_type] = (acc[v.change_type] || 0) + 1;
return acc;
}, {} as Record<string, number>)
};
}
/**
* Get total version counts across all entity types
*
* Useful for monitoring storage usage and cleanup effectiveness.
*
* @returns Total version counts for each entity type
*
* @example
* ```typescript
* const counts = await getAllVersionCounts();
* console.log('Park versions:', counts.park);
* console.log('Ride versions:', counts.ride);
* ```
*/
export async function getAllVersionCounts() {
const counts = {
park: 0,
ride: 0,
company: 0,
ride_model: 0,
};
const parkCount = await supabase
.from('park_versions')
.select('*', { count: 'exact', head: true });
counts.park = parkCount.count || 0;
const rideCount = await supabase
.from('ride_versions')
.select('*', { count: 'exact', head: true });
counts.ride = rideCount.count || 0;
const companyCount = await supabase
.from('company_versions')
.select('*', { count: 'exact', head: true });
counts.company = companyCount.count || 0;
const modelCount = await supabase
.from('ride_model_versions')
.select('*', { count: 'exact', head: true });
counts.ride_model = modelCount.count || 0;
return counts;
}
/**
* Check if an entity has any versions
*
* @param entityType - The entity type
* @param entityId - The UUID of the entity
* @returns True if entity has at least one version
*/
export async function hasVersions(
entityType: EntityType,
entityId: string
): Promise<boolean> {
const versionTable = `${entityType}_versions`;
const entityIdCol = `${entityType}_id`;
const { count } = await supabase
.from(versionTable as any)
.select('*', { count: 'exact', head: true })
.eq(entityIdCol, entityId);
return (count || 0) > 0;
}