mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 13:31:12 -05:00
Implement Phase 2, Part 2
This commit is contained in:
100
src/components/versioning/VersionIndicator.tsx
Normal file
100
src/components/versioning/VersionIndicator.tsx
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||||
|
import { History, Clock } from 'lucide-react';
|
||||||
|
import { formatDistanceToNow } from 'date-fns';
|
||||||
|
import { EntityVersionHistory } from './EntityVersionHistory';
|
||||||
|
import { useEntityVersions } from '@/hooks/useEntityVersions';
|
||||||
|
|
||||||
|
interface VersionIndicatorProps {
|
||||||
|
entityType: string;
|
||||||
|
entityId: string;
|
||||||
|
entityName: string;
|
||||||
|
compact?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function VersionIndicator({
|
||||||
|
entityType,
|
||||||
|
entityId,
|
||||||
|
entityName,
|
||||||
|
compact = false,
|
||||||
|
}: VersionIndicatorProps) {
|
||||||
|
const [showHistory, setShowHistory] = useState(false);
|
||||||
|
const { currentVersion, loading } = useEntityVersions(entityType, entityId);
|
||||||
|
|
||||||
|
if (loading || !currentVersion) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeAgo = currentVersion.changed_at
|
||||||
|
? formatDistanceToNow(new Date(currentVersion.changed_at), { addSuffix: true })
|
||||||
|
: 'Unknown';
|
||||||
|
|
||||||
|
if (compact) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setShowHistory(true)}
|
||||||
|
className="gap-2"
|
||||||
|
>
|
||||||
|
<History className="h-4 w-4" />
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
v{currentVersion.version_number}
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Dialog open={showHistory} onOpenChange={setShowHistory}>
|
||||||
|
<DialogContent className="max-w-4xl max-h-[80vh]">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Version History: {entityName}</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<EntityVersionHistory
|
||||||
|
entityType={entityType}
|
||||||
|
entityId={entityId}
|
||||||
|
entityName={entityName}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Badge variant="outline" className="gap-1.5">
|
||||||
|
<Clock className="h-3 w-3" />
|
||||||
|
Version {currentVersion.version_number}
|
||||||
|
</Badge>
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
Last edited {timeAgo}
|
||||||
|
</span>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setShowHistory(true)}
|
||||||
|
className="gap-2"
|
||||||
|
>
|
||||||
|
<History className="h-4 w-4" />
|
||||||
|
View History
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Dialog open={showHistory} onOpenChange={setShowHistory}>
|
||||||
|
<DialogContent className="max-w-4xl max-h-[80vh]">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Version History: {entityName}</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<EntityVersionHistory
|
||||||
|
entityType={entityType}
|
||||||
|
entityId={entityId}
|
||||||
|
entityName={entityName}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -166,8 +166,17 @@ export async function approveSubmissionItems(
|
|||||||
|
|
||||||
for (const item of sortedItems) {
|
for (const item of sortedItems) {
|
||||||
let entityId: string | null = null;
|
let entityId: string | null = null;
|
||||||
|
let isEdit = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Determine if this is an edit by checking for entity_id in item_data
|
||||||
|
isEdit = !!(
|
||||||
|
item.item_data.park_id ||
|
||||||
|
item.item_data.ride_id ||
|
||||||
|
item.item_data.company_id ||
|
||||||
|
item.item_data.ride_model_id
|
||||||
|
);
|
||||||
|
|
||||||
// Create the entity based on type with dependency resolution
|
// Create the entity based on type with dependency resolution
|
||||||
switch (item.item_type) {
|
switch (item.item_type) {
|
||||||
case 'park':
|
case 'park':
|
||||||
@@ -200,6 +209,17 @@ export async function approveSubmissionItems(
|
|||||||
approved_entity_id: entityId,
|
approved_entity_id: entityId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Create version history (skip for photo type)
|
||||||
|
if (item.item_type !== 'photo') {
|
||||||
|
await createVersionForApprovedItem(
|
||||||
|
item.item_type,
|
||||||
|
entityId,
|
||||||
|
userId,
|
||||||
|
item.submission_id,
|
||||||
|
isEdit
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Add to dependency map for child items
|
// Add to dependency map for child items
|
||||||
dependencyMap.set(item.id, entityId);
|
dependencyMap.set(item.id, entityId);
|
||||||
|
|
||||||
@@ -217,6 +237,62 @@ export async function approveSubmissionItems(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create version history for approved submission item
|
||||||
|
*/
|
||||||
|
async function createVersionForApprovedItem(
|
||||||
|
itemType: string,
|
||||||
|
entityId: string,
|
||||||
|
userId: string,
|
||||||
|
submissionId: string,
|
||||||
|
isEdit: boolean
|
||||||
|
): Promise<void> {
|
||||||
|
const { captureCurrentState, createEntityVersion } = await import('./versioningHelpers');
|
||||||
|
|
||||||
|
// Map item_type to entity_type
|
||||||
|
let entityType: 'park' | 'ride' | 'company' | 'ride_model';
|
||||||
|
switch (itemType) {
|
||||||
|
case 'park':
|
||||||
|
entityType = 'park';
|
||||||
|
break;
|
||||||
|
case 'ride':
|
||||||
|
entityType = 'ride';
|
||||||
|
break;
|
||||||
|
case 'manufacturer':
|
||||||
|
case 'operator':
|
||||||
|
case 'property_owner':
|
||||||
|
case 'designer':
|
||||||
|
entityType = 'company';
|
||||||
|
break;
|
||||||
|
case 'ride_model':
|
||||||
|
entityType = 'ride_model';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.warn(`Unknown entity type for versioning: ${itemType}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capture current state
|
||||||
|
const currentState = await captureCurrentState(entityType, entityId);
|
||||||
|
if (!currentState) {
|
||||||
|
console.warn(`Failed to capture state for ${entityType} ${entityId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create version
|
||||||
|
await createEntityVersion({
|
||||||
|
entityType,
|
||||||
|
entityId,
|
||||||
|
versionData: currentState,
|
||||||
|
changedBy: userId,
|
||||||
|
changeReason: isEdit
|
||||||
|
? `Approved edit from submission #${submissionId.slice(0, 8)}`
|
||||||
|
: `Created via submission #${submissionId.slice(0, 8)}`,
|
||||||
|
submissionId,
|
||||||
|
changeType: isEdit ? 'updated' : 'created',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Topological sort for dependency-ordered processing
|
* Topological sort for dependency-ordered processing
|
||||||
*/
|
*/
|
||||||
|
|||||||
207
src/lib/versioningHelpers.ts
Normal file
207
src/lib/versioningHelpers.ts
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
import { supabase } from '@/integrations/supabase/client';
|
||||||
|
import { toast } from '@/hooks/use-toast';
|
||||||
|
|
||||||
|
export type EntityType = 'park' | 'ride' | 'company' | 'ride_model';
|
||||||
|
export type ChangeType = 'created' | 'updated' | 'deleted' | 'restored' | 'archived';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the table name for a given entity type
|
||||||
|
*/
|
||||||
|
export function getEntityTableName(entityType: EntityType): string {
|
||||||
|
const tableMap: Record<EntityType, string> = {
|
||||||
|
park: 'parks',
|
||||||
|
ride: 'rides',
|
||||||
|
company: 'companies',
|
||||||
|
ride_model: 'ride_models',
|
||||||
|
};
|
||||||
|
return tableMap[entityType];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Capture the current state of an entity
|
||||||
|
*/
|
||||||
|
export async function captureCurrentState(
|
||||||
|
entityType: EntityType,
|
||||||
|
entityId: string
|
||||||
|
): Promise<Record<string, any> | null> {
|
||||||
|
const tableName = getEntityTableName(entityType);
|
||||||
|
|
||||||
|
const { data, error } = await supabase
|
||||||
|
.from(tableName as any)
|
||||||
|
.select('*')
|
||||||
|
.eq('id', entityId)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error('Error capturing entity state:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data as Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new entity version with proper error handling
|
||||||
|
*/
|
||||||
|
export async function createEntityVersion(params: {
|
||||||
|
entityType: EntityType;
|
||||||
|
entityId: string;
|
||||||
|
versionData: Record<string, any>;
|
||||||
|
changedBy: string;
|
||||||
|
changeReason?: string;
|
||||||
|
submissionId?: string;
|
||||||
|
changeType?: ChangeType;
|
||||||
|
}): Promise<string | null> {
|
||||||
|
const {
|
||||||
|
entityType,
|
||||||
|
entityId,
|
||||||
|
versionData,
|
||||||
|
changedBy,
|
||||||
|
changeReason,
|
||||||
|
submissionId,
|
||||||
|
changeType = 'updated',
|
||||||
|
} = params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data, error } = await supabase.rpc('create_entity_version', {
|
||||||
|
p_entity_type: entityType,
|
||||||
|
p_entity_id: entityId,
|
||||||
|
p_version_data: versionData,
|
||||||
|
p_changed_by: changedBy,
|
||||||
|
p_change_reason: changeReason || null,
|
||||||
|
p_submission_id: submissionId || null,
|
||||||
|
p_change_type: changeType,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error('Error creating entity version:', error);
|
||||||
|
toast({
|
||||||
|
title: 'Version Creation Failed',
|
||||||
|
description: error.message,
|
||||||
|
variant: 'destructive',
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Unexpected error creating entity version:', err);
|
||||||
|
toast({
|
||||||
|
title: 'Version Creation Failed',
|
||||||
|
description: 'An unexpected error occurred',
|
||||||
|
variant: 'destructive',
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create entity version with audit log entry
|
||||||
|
*/
|
||||||
|
export async function createEntityVersionWithAudit(
|
||||||
|
params: {
|
||||||
|
entityType: EntityType;
|
||||||
|
entityId: string;
|
||||||
|
versionData: Record<string, any>;
|
||||||
|
changedBy: string;
|
||||||
|
changeReason?: string;
|
||||||
|
submissionId?: string;
|
||||||
|
changeType?: ChangeType;
|
||||||
|
},
|
||||||
|
auditDetails?: Record<string, any>
|
||||||
|
): Promise<string | null> {
|
||||||
|
const versionId = await createEntityVersion(params);
|
||||||
|
|
||||||
|
if (versionId && auditDetails) {
|
||||||
|
// Log to admin audit log
|
||||||
|
const { error: auditError } = await supabase.rpc('log_admin_action', {
|
||||||
|
_admin_user_id: params.changedBy,
|
||||||
|
_target_user_id: params.changedBy, // Or entity owner if available
|
||||||
|
_action: `version_${params.changeType || 'updated'}`,
|
||||||
|
_details: {
|
||||||
|
version_id: versionId,
|
||||||
|
entity_type: params.entityType,
|
||||||
|
entity_id: params.entityId,
|
||||||
|
submission_id: params.submissionId,
|
||||||
|
...auditDetails,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (auditError) {
|
||||||
|
console.warn('Failed to create audit log entry:', auditError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return versionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rollback an entity to a previous version
|
||||||
|
*/
|
||||||
|
export async function rollbackToVersion(
|
||||||
|
entityType: EntityType,
|
||||||
|
entityId: string,
|
||||||
|
targetVersionId: string,
|
||||||
|
userId: string,
|
||||||
|
reason: string
|
||||||
|
): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const { data, error } = await supabase.rpc('rollback_to_version', {
|
||||||
|
p_entity_type: entityType,
|
||||||
|
p_entity_id: entityId,
|
||||||
|
p_target_version_id: targetVersionId,
|
||||||
|
p_changed_by: userId,
|
||||||
|
p_reason: reason,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error('Error rolling back to version:', error);
|
||||||
|
toast({
|
||||||
|
title: 'Rollback Failed',
|
||||||
|
description: error.message,
|
||||||
|
variant: 'destructive',
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast({
|
||||||
|
title: 'Rollback Successful',
|
||||||
|
description: 'Entity has been restored to the selected version',
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Unexpected error during rollback:', err);
|
||||||
|
toast({
|
||||||
|
title: 'Rollback Failed',
|
||||||
|
description: 'An unexpected error occurred',
|
||||||
|
variant: 'destructive',
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare two versions and get the diff
|
||||||
|
*/
|
||||||
|
export async function compareVersions(
|
||||||
|
fromVersionId: string,
|
||||||
|
toVersionId: string
|
||||||
|
): Promise<Record<string, any> | null> {
|
||||||
|
try {
|
||||||
|
const { data, error } = await supabase.rpc('compare_versions', {
|
||||||
|
p_from_version_id: fromVersionId,
|
||||||
|
p_to_version_id: toVersionId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error('Error comparing versions:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data as Record<string, any>;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Unexpected error comparing versions:', err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
-- Phase 2: Fix rollback function and add auto-versioning triggers
|
||||||
|
|
||||||
|
-- Drop the placeholder rollback function
|
||||||
|
DROP FUNCTION IF EXISTS public.rollback_to_version(text, uuid, uuid, uuid, text);
|
||||||
|
|
||||||
|
-- Enhanced rollback function with dynamic SQL
|
||||||
|
CREATE OR REPLACE FUNCTION public.rollback_to_version(
|
||||||
|
p_entity_type TEXT,
|
||||||
|
p_entity_id UUID,
|
||||||
|
p_target_version_id UUID,
|
||||||
|
p_changed_by UUID,
|
||||||
|
p_reason TEXT
|
||||||
|
)
|
||||||
|
RETURNS UUID
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
SECURITY DEFINER
|
||||||
|
SET search_path = public
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
v_target_data JSONB;
|
||||||
|
v_table_name TEXT;
|
||||||
|
v_new_version_id UUID;
|
||||||
|
v_key TEXT;
|
||||||
|
v_value TEXT;
|
||||||
|
v_update_parts TEXT[];
|
||||||
|
v_sql TEXT;
|
||||||
|
BEGIN
|
||||||
|
-- Get target version data
|
||||||
|
SELECT version_data INTO v_target_data
|
||||||
|
FROM public.entity_versions
|
||||||
|
WHERE id = p_target_version_id
|
||||||
|
AND entity_type = p_entity_type
|
||||||
|
AND entity_id = p_entity_id;
|
||||||
|
|
||||||
|
IF v_target_data IS NULL THEN
|
||||||
|
RAISE EXCEPTION 'Target version not found';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Determine table name from entity type
|
||||||
|
v_table_name := CASE p_entity_type
|
||||||
|
WHEN 'park' THEN 'parks'
|
||||||
|
WHEN 'ride' THEN 'rides'
|
||||||
|
WHEN 'company' THEN 'companies'
|
||||||
|
WHEN 'ride_model' THEN 'ride_models'
|
||||||
|
ELSE p_entity_type || 's'
|
||||||
|
END;
|
||||||
|
|
||||||
|
-- Build UPDATE statement dynamically
|
||||||
|
v_update_parts := ARRAY[]::TEXT[];
|
||||||
|
|
||||||
|
FOR v_key, v_value IN SELECT * FROM jsonb_each_text(v_target_data)
|
||||||
|
LOOP
|
||||||
|
-- Skip metadata fields
|
||||||
|
IF v_key NOT IN ('id', 'created_at', 'updated_at') THEN
|
||||||
|
v_update_parts := array_append(v_update_parts,
|
||||||
|
format('%I = %L', v_key, v_value)
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
END LOOP;
|
||||||
|
|
||||||
|
-- Execute the UPDATE
|
||||||
|
IF array_length(v_update_parts, 1) > 0 THEN
|
||||||
|
v_sql := format(
|
||||||
|
'UPDATE %I SET %s, updated_at = now() WHERE id = %L',
|
||||||
|
v_table_name,
|
||||||
|
array_to_string(v_update_parts, ', '),
|
||||||
|
p_entity_id
|
||||||
|
);
|
||||||
|
|
||||||
|
EXECUTE v_sql;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Create new version with restored change type
|
||||||
|
v_new_version_id := public.create_entity_version(
|
||||||
|
p_entity_type,
|
||||||
|
p_entity_id,
|
||||||
|
v_target_data,
|
||||||
|
p_changed_by,
|
||||||
|
'Rolled back: ' || p_reason,
|
||||||
|
NULL,
|
||||||
|
'restored'
|
||||||
|
);
|
||||||
|
|
||||||
|
RETURN v_new_version_id;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
-- Auto-version creation trigger function
|
||||||
|
CREATE OR REPLACE FUNCTION public.auto_create_entity_version()
|
||||||
|
RETURNS TRIGGER
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
SECURITY DEFINER
|
||||||
|
SET search_path = public
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
v_entity_type TEXT;
|
||||||
|
v_change_type version_change_type;
|
||||||
|
v_user_id UUID;
|
||||||
|
v_version_data JSONB;
|
||||||
|
BEGIN
|
||||||
|
-- Determine entity type from table name
|
||||||
|
v_entity_type := CASE TG_TABLE_NAME
|
||||||
|
WHEN 'parks' THEN 'park'
|
||||||
|
WHEN 'rides' THEN 'ride'
|
||||||
|
WHEN 'companies' THEN 'company'
|
||||||
|
WHEN 'ride_models' THEN 'ride_model'
|
||||||
|
ELSE substring(TG_TABLE_NAME from 1 for length(TG_TABLE_NAME) - 1)
|
||||||
|
END;
|
||||||
|
|
||||||
|
-- Determine change type
|
||||||
|
v_change_type := CASE TG_OP
|
||||||
|
WHEN 'INSERT' THEN 'created'::version_change_type
|
||||||
|
WHEN 'UPDATE' THEN 'updated'::version_change_type
|
||||||
|
ELSE 'updated'::version_change_type
|
||||||
|
END;
|
||||||
|
|
||||||
|
-- Get user from session or auth context
|
||||||
|
BEGIN
|
||||||
|
v_user_id := current_setting('app.current_user_id', true)::UUID;
|
||||||
|
EXCEPTION WHEN OTHERS THEN
|
||||||
|
v_user_id := auth.uid();
|
||||||
|
END;
|
||||||
|
|
||||||
|
-- Convert NEW record to JSONB
|
||||||
|
v_version_data := to_jsonb(NEW);
|
||||||
|
|
||||||
|
-- Create version (only if we have a user context)
|
||||||
|
IF v_user_id IS NOT NULL THEN
|
||||||
|
PERFORM public.create_entity_version(
|
||||||
|
v_entity_type,
|
||||||
|
NEW.id,
|
||||||
|
v_version_data,
|
||||||
|
v_user_id,
|
||||||
|
CASE TG_OP
|
||||||
|
WHEN 'INSERT' THEN 'Entity created'
|
||||||
|
WHEN 'UPDATE' THEN 'Entity updated'
|
||||||
|
ELSE 'Entity modified'
|
||||||
|
END,
|
||||||
|
NULL,
|
||||||
|
v_change_type
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
-- Add triggers to entity tables
|
||||||
|
DROP TRIGGER IF EXISTS auto_version_parks ON public.parks;
|
||||||
|
CREATE TRIGGER auto_version_parks
|
||||||
|
AFTER INSERT OR UPDATE ON public.parks
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION public.auto_create_entity_version();
|
||||||
|
|
||||||
|
DROP TRIGGER IF EXISTS auto_version_rides ON public.rides;
|
||||||
|
CREATE TRIGGER auto_version_rides
|
||||||
|
AFTER INSERT OR UPDATE ON public.rides
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION public.auto_create_entity_version();
|
||||||
|
|
||||||
|
DROP TRIGGER IF EXISTS auto_version_companies ON public.companies;
|
||||||
|
CREATE TRIGGER auto_version_companies
|
||||||
|
AFTER INSERT OR UPDATE ON public.companies
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION public.auto_create_entity_version();
|
||||||
|
|
||||||
|
DROP TRIGGER IF EXISTS auto_version_ride_models ON public.ride_models;
|
||||||
|
CREATE TRIGGER auto_version_ride_models
|
||||||
|
AFTER INSERT OR UPDATE ON public.ride_models
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION public.auto_create_entity_version();
|
||||||
|
|
||||||
|
COMMENT ON FUNCTION public.rollback_to_version IS 'Restores an entity to a previous version using dynamic SQL';
|
||||||
|
COMMENT ON FUNCTION public.auto_create_entity_version IS 'Automatically creates version records when entities are modified';
|
||||||
Reference in New Issue
Block a user