Compare commits

..

2 Commits

Author SHA1 Message Date
gpt-engineer-app[bot]
406edc96df Implement 100% atomic transaction rollout
Update actions.ts and ApprovalTransactionToggle.tsx to default to the new atomic transaction RPC flow. The feature flag can now be used to disable the new flow for emergency rollback.
2025-11-06 20:48:18 +00:00
gpt-engineer-app[bot]
3be551dc5a Implement blue-green deployment
Implement blue-green deployment strategy for approval flow. This involves deploying the new RPC function alongside the existing edge function, creating a new edge function that calls the RPC, and adding a feature flag to the frontend to toggle between the old and new flows. The plan includes testing in production, gradual rollout, and eventual deprecation of the old edge function.
2025-11-06 20:36:10 +00:00
3 changed files with 48 additions and 42 deletions

View File

@@ -11,11 +11,12 @@ import { Alert, AlertDescription } from '@/components/ui/alert';
* - NEW: Atomic PostgreSQL transaction (true ACID guarantees)
*/
export function ApprovalTransactionToggle() {
const [useRpcApproval, setUseRpcApproval] = useState(false);
const [useRpcApproval, setUseRpcApproval] = useState(true);
useEffect(() => {
// Read feature flag from localStorage
const enabled = localStorage.getItem('use_rpc_approval') === 'true';
// NEW flow is default (100% rollout)
// Only disabled if explicitly set to 'false'
const enabled = localStorage.getItem('use_rpc_approval') !== 'false';
setUseRpcApproval(enabled);
}, []);
@@ -35,7 +36,7 @@ export function ApprovalTransactionToggle() {
Approval Transaction Mode
</CardTitle>
<CardDescription>
Control which approval flow is used for moderation
Atomic Transaction RPC is now the default. Toggle OFF only for emergency rollback.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
@@ -56,10 +57,10 @@ export function ApprovalTransactionToggle() {
</div>
{useRpcApproval ? (
<Alert>
<CheckCircle2 className="h-4 w-4" />
<Alert className="border-green-500 bg-green-50 dark:bg-green-950">
<CheckCircle2 className="h-4 w-4 text-green-600" />
<AlertDescription>
<strong>Atomic Transaction Mode Enabled</strong>
<strong className="text-green-600">Production Mode (100% Rollout) </strong>
<ul className="mt-2 space-y-1 text-sm">
<li> True ACID transactions</li>
<li> Automatic rollback on errors</li>
@@ -69,17 +70,17 @@ export function ApprovalTransactionToggle() {
</AlertDescription>
</Alert>
) : (
<Alert>
<AlertCircle className="h-4 w-4" />
<Alert className="border-orange-500 bg-orange-50 dark:bg-orange-950">
<AlertCircle className="h-4 w-4 text-orange-600" />
<AlertDescription>
<strong>Legacy Mode Active</strong>
<strong className="text-orange-600">Emergency Rollback Mode Active </strong>
<ul className="mt-2 space-y-1 text-sm">
<li> Manual rollback logic (error-prone)</li>
<li> Using legacy manual rollback logic</li>
<li> Risk of orphaned entities if edge function crashes</li>
<li> No true atomicity guarantee</li>
</ul>
<p className="mt-2 font-medium">
Consider enabling Atomic Transaction Mode for improved reliability.
<p className="mt-2 font-medium text-orange-600">
This mode should only be used temporarily if issues are detected with the atomic transaction flow.
</p>
</AlertDescription>
</Alert>

View File

@@ -178,9 +178,9 @@ export async function approvePhotoSubmission(
* @returns Action result
*/
/**
* Feature flag to enable atomic transaction RPC approval flow.
* Set to true to use the new process-selective-approval-v2 edge function
* that wraps the entire approval in a single PostgreSQL transaction.
* Feature flag: Use new atomic transaction RPC for approvals (v2)
*
* ✅ DEFAULT: NEW atomic transaction flow (100% ROLLOUT)
*
* Benefits of v2:
* - True atomic transactions (all-or-nothing)
@@ -188,11 +188,11 @@ export async function approvePhotoSubmission(
* - Network-resilient (edge function crash = auto rollback)
* - Eliminates orphaned entities
*
* To enable: localStorage.setItem('use_rpc_approval', 'true')
* To disable: localStorage.setItem('use_rpc_approval', 'false')
* To disable NEW flow (emergency rollback): localStorage.setItem('use_rpc_approval', 'false')
* To re-enable NEW flow: localStorage.removeItem('use_rpc_approval')
*/
const USE_RPC_APPROVAL = typeof window !== 'undefined' &&
localStorage.getItem('use_rpc_approval') === 'true';
localStorage.getItem('use_rpc_approval') !== 'false';
export async function approveSubmissionItems(
supabase: SupabaseClient,

View File

@@ -14,6 +14,7 @@ import { useAdminSettings } from '@/hooks/useAdminSettings';
import { NovuMigrationUtility } from '@/components/admin/NovuMigrationUtility';
import { TestDataGenerator } from '@/components/admin/TestDataGenerator';
import { IntegrationTestRunner } from '@/components/admin/IntegrationTestRunner';
import { ApprovalTransactionToggle } from '@/components/admin/ApprovalTransactionToggle';
import { Loader2, Save, Clock, Users, Bell, Shield, Settings, Trash2, Plug, AlertTriangle, Lock, TestTube, RefreshCw, Info, AlertCircle } from 'lucide-react';
import { useDocumentTitle } from '@/hooks/useDocumentTitle';
@@ -915,29 +916,33 @@ export default function AdminSettings() {
</TabsContent>
<TabsContent value="system">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Settings className="w-5 h-5" />
System Configuration
</CardTitle>
<CardDescription>
Configure system-wide settings, maintenance options, and technical parameters
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{getSettingsByCategory('system').filter(s => !s.setting_key.startsWith('retry.') && !s.setting_key.startsWith('circuit_breaker.')).length > 0 ? (
getSettingsByCategory('system').filter(s => !s.setting_key.startsWith('retry.') && !s.setting_key.startsWith('circuit_breaker.')).map((setting) => (
<SettingInput key={setting.id} setting={setting} />
))
) : (
<div className="text-center py-8 text-muted-foreground">
<Settings className="w-12 h-12 mx-auto mb-4 opacity-50" />
<p>No system settings configured yet.</p>
</div>
)}
</CardContent>
</Card>
<div className="space-y-4">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Settings className="w-5 h-5" />
System Configuration
</CardTitle>
<CardDescription>
Configure system-wide settings, maintenance options, and technical parameters
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{getSettingsByCategory('system').filter(s => !s.setting_key.startsWith('retry.') && !s.setting_key.startsWith('circuit_breaker.')).length > 0 ? (
getSettingsByCategory('system').filter(s => !s.setting_key.startsWith('retry.') && !s.setting_key.startsWith('circuit_breaker.')).map((setting) => (
<SettingInput key={setting.id} setting={setting} />
))
) : (
<div className="text-center py-8 text-muted-foreground">
<Settings className="w-12 h-12 mx-auto mb-4 opacity-50" />
<p>No system settings configured yet.</p>
</div>
)}
</CardContent>
</Card>
<ApprovalTransactionToggle />
</div>
</TabsContent>
<TabsContent value="integrations">