Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions apps/backend/database/migrations/001_create_sync_tables.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
-- Migration: Create sync tables for blockchain synchronization
-- This migration adds tables to track blockchain events and sync state

-- Create sync_state table to track synchronization progress
CREATE TABLE IF NOT EXISTS public.sync_state (
id INTEGER PRIMARY KEY DEFAULT 1,
last_processed_block BIGINT NOT NULL DEFAULT 0,
total_events_processed INTEGER NOT NULL DEFAULT 0,
failed_events INTEGER NOT NULL DEFAULT 0,
last_sync_time TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT timezone('utc'::text, now()),
updated_at TIMESTAMPTZ NOT NULL DEFAULT timezone('utc'::text, now())
);

-- Create sync_events table to track all blockchain events
CREATE TABLE IF NOT EXISTS public.sync_events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
event_id TEXT NOT NULL UNIQUE,
event_type TEXT NOT NULL CHECK (event_type IN ('booking_created', 'booking_updated', 'booking_cancelled', 'payment_confirmed')),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

โš ๏ธ Potential issue

Remove duplicate constraint definition.

The event_type constraint is defined twice - once inline and once as a separate constraint.

     event_type TEXT NOT NULL CHECK (event_type IN ('booking_created', 'booking_updated', 'booking_cancelled', 'payment_confirmed')),
     booking_id TEXT,
     property_id TEXT,
     user_id TEXT,
     event_data JSONB NOT NULL,
     processed BOOLEAN NOT NULL DEFAULT FALSE,
     error TEXT,
     created_at TIMESTAMPTZ NOT NULL DEFAULT timezone('utc'::text, now()),
     processed_at TIMESTAMPTZ,
     
-    CONSTRAINT valid_event_type CHECK (event_type IN ('booking_created', 'booking_updated', 'booking_cancelled', 'payment_confirmed'))

Also applies to: 29-29

๐Ÿค– Prompt for AI Agents
In apps/backend/database/migrations/001_create_sync_tables.sql at lines 19 and
29, the CHECK constraint for the event_type column is defined twice, once inline
and once as a separate constraint. Remove one of these duplicate constraint
definitions to avoid redundancy and potential conflicts, keeping only a single
CHECK constraint for event_type.

booking_id TEXT,
property_id TEXT,
user_id TEXT,
event_data JSONB NOT NULL,
processed BOOLEAN NOT NULL DEFAULT FALSE,
error TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT timezone('utc'::text, now()),
processed_at TIMESTAMPTZ,

CONSTRAINT valid_event_type CHECK (event_type IN ('booking_created', 'booking_updated', 'booking_cancelled', 'payment_confirmed'))
);

-- Create sync_logs table for detailed logging
CREATE TABLE IF NOT EXISTS public.sync_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
operation TEXT NOT NULL,
status TEXT NOT NULL CHECK (status IN ('success', 'error', 'warning')),
message TEXT,
data JSONB,
error_details JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT timezone('utc'::text, now())
);

-- Create indexes for better performance
CREATE INDEX IF NOT EXISTS sync_events_event_type_idx ON public.sync_events(event_type);
CREATE INDEX IF NOT EXISTS sync_events_processed_idx ON public.sync_events(processed);
CREATE INDEX IF NOT EXISTS sync_events_created_at_idx ON public.sync_events(created_at);
CREATE INDEX IF NOT EXISTS sync_events_booking_id_idx ON public.sync_events(booking_id);
CREATE INDEX IF NOT EXISTS sync_events_property_id_idx ON public.sync_events(property_id);
CREATE INDEX IF NOT EXISTS sync_events_user_id_idx ON public.sync_events(user_id);

CREATE INDEX IF NOT EXISTS sync_logs_operation_idx ON public.sync_logs(operation);
CREATE INDEX IF NOT EXISTS sync_logs_status_idx ON public.sync_logs(status);
CREATE INDEX IF NOT EXISTS sync_logs_created_at_idx ON public.sync_logs(created_at);

-- Insert initial sync state
INSERT INTO public.sync_state (id, last_processed_block, total_events_processed, failed_events)
VALUES (1, 0, 0, 0)
ON CONFLICT (id) DO NOTHING;

-- Create function to update updated_at timestamp
CREATE OR REPLACE FUNCTION update_sync_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = timezone('utc'::text, now());
RETURN NEW;
END;
$$ language 'plpgsql';

-- Create triggers for updated_at
CREATE TRIGGER update_sync_state_updated_at
BEFORE UPDATE ON public.sync_state
FOR EACH ROW
EXECUTE FUNCTION update_sync_updated_at_column();

-- Add RLS policies for sync tables (if RLS is enabled)
ALTER TABLE public.sync_state ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.sync_events ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.sync_logs ENABLE ROW LEVEL SECURITY;

-- Create policies for admin access only
CREATE POLICY "Admin access to sync_state" ON public.sync_state
FOR ALL USING (auth.role() = 'admin');

CREATE POLICY "Admin access to sync_events" ON public.sync_events
FOR ALL USING (auth.role() = 'admin');

CREATE POLICY "Admin access to sync_logs" ON public.sync_logs
FOR ALL USING (auth.role() = 'admin');

-- Create view for sync dashboard
CREATE OR REPLACE VIEW public.sync_dashboard AS
SELECT
s.last_processed_block,
s.total_events_processed,
s.failed_events,
s.last_sync_time,
s.updated_at as last_updated,
COUNT(se.id) as total_events,
COUNT(CASE WHEN se.processed = false THEN 1 END) as pending_events,
COUNT(CASE WHEN se.processed = false AND se.error IS NOT NULL THEN 1 END) as failed_events_count,
MAX(se.created_at) as last_event_time
FROM public.sync_state s
LEFT JOIN public.sync_events se ON true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๐Ÿ› ๏ธ Refactor suggestion

Optimize dashboard view join condition.

The LEFT JOIN with ON true creates a Cartesian product, which could be inefficient for large datasets.

 FROM public.sync_state s
-LEFT JOIN public.sync_events se ON true
+LEFT JOIN public.sync_events se ON s.id IS NOT NULL

Or consider restructuring the view to use subqueries for better performance:

CREATE OR REPLACE VIEW public.sync_dashboard AS
SELECT 
    s.last_processed_block,
    s.total_events_processed,
    s.failed_events,
    s.last_sync_time,
    s.updated_at as last_updated,
    (SELECT COUNT(*) FROM public.sync_events) as total_events,
    (SELECT COUNT(*) FROM public.sync_events WHERE processed = false) as pending_events,
    (SELECT COUNT(*) FROM public.sync_events WHERE processed = false AND error IS NOT NULL) as failed_events_count,
    (SELECT MAX(created_at) FROM public.sync_events) as last_event_time
FROM public.sync_state s;
๐Ÿค– Prompt for AI Agents
In apps/backend/database/migrations/001_create_sync_tables.sql at line 103, the
LEFT JOIN with ON true causes a Cartesian product, leading to inefficiency.
Replace the join with subqueries in the SELECT clause to aggregate the needed
sync_events data, as shown in the suggested view definition, to optimize
performance and avoid unnecessary row multiplication.

GROUP BY s.id, s.last_processed_block, s.total_events_processed, s.failed_events, s.last_sync_time, s.updated_at;

-- Grant permissions
GRANT SELECT ON public.sync_dashboard TO authenticated;
GRANT ALL ON public.sync_state TO authenticated;
GRANT ALL ON public.sync_events TO authenticated;
GRANT ALL ON public.sync_logs TO authenticated;
Comment on lines +107 to +110
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๐Ÿ› ๏ธ Refactor suggestion

Reconsider broad permissions for authenticated users.

Granting ALL permissions to all authenticated users on sync tables might be too permissive for a production environment.

 -- Grant permissions
 GRANT SELECT ON public.sync_dashboard TO authenticated;
-GRANT ALL ON public.sync_state TO authenticated;
-GRANT ALL ON public.sync_events TO authenticated;
-GRANT ALL ON public.sync_logs TO authenticated;
+-- Grant specific permissions based on actual service needs
+GRANT SELECT, INSERT, UPDATE ON public.sync_state TO service_role;
+GRANT SELECT, INSERT, UPDATE ON public.sync_events TO service_role;
+GRANT SELECT, INSERT ON public.sync_logs TO service_role;
๐Ÿค– Prompt for AI Agents
In apps/backend/database/migrations/001_create_sync_tables.sql around lines 107
to 110, the GRANT statements give ALL permissions on sync_state, sync_events,
and sync_logs tables to all authenticated users, which is too broad. Modify
these statements to grant only the necessary minimal permissions (e.g., SELECT,
INSERT, UPDATE) instead of ALL, based on the actual access requirements for
authenticated users in production.

35 changes: 35 additions & 0 deletions apps/backend/env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Database Configuration
SUPABASE_URL=your_supabase_url
SUPABASE_ANON_KEY=your_supabase_anon_key
SUPABASE_SERVICE_ROLE_KEY=your_supabase_service_role_key

# JWT Configuration
JWT_SECRET=your_jwt_secret_key

# Stellar Network Configuration
SOROBAN_RPC_URL=https://soroban-testnet.stellar.org
SOROBAN_CONTRACT_ID=your_contract_id_here
SOROBAN_NETWORK_PASSPHRASE=Test SDF Network ; September 2015
STELLAR_SECRET_KEY=your_stellar_secret_key
Comment on lines +12 to +13
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

โš ๏ธ Potential issue

Fix duplicate network passphrase configuration.

The STELLAR_NETWORK_PASSPHRASE appears in both Soroban and legacy configurations with the same value, which could lead to confusion and maintenance issues.

 SOROBAN_NETWORK_PASSPHRASE=Test SDF Network ; September 2015
 STELLAR_SECRET_KEY=your_stellar_secret_key

 # Legacy Stellar Configuration (for backward compatibility)
 STELLAR_RPC_URL=https://horizon-testnet.stellar.org
 BOOKING_CONTRACT_ADDRESS=your_booking_contract_address
 STELLAR_SOURCE_ACCOUNT=your_stellar_source_account
-STELLAR_NETWORK_PASSPHRASE=Test SDF Network ; September 2015
+# STELLAR_NETWORK_PASSPHRASE uses SOROBAN_NETWORK_PASSPHRASE for consistency

Also applies to: 19-19

๐Ÿค– Prompt for AI Agents
In apps/backend/env.example around lines 12 to 13 and line 19, there is a
duplicate configuration for the network passphrase under different names
(SOROBAN_NETWORK_PASSPHRASE and STELLAR_NETWORK_PASSPHRASE) with the same value.
Remove one of these duplicate entries to avoid confusion and maintain a single
source of truth for the network passphrase configuration.


# Legacy Stellar Configuration (for backward compatibility)
STELLAR_RPC_URL=https://horizon-testnet.stellar.org
BOOKING_CONTRACT_ADDRESS=your_booking_contract_address
STELLAR_SOURCE_ACCOUNT=your_stellar_source_account
STELLAR_NETWORK_PASSPHRASE=Test SDF Network ; September 2015

# Sync Service Configuration
SYNC_POLL_INTERVAL=5000
SYNC_MAX_RETRIES=3
SYNC_RETRY_DELAY=1000

# Server Configuration
PORT=3001
NODE_ENV=development

# Mock Configuration (for testing)
USE_MOCK=false

# Rate Limiting
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX_REQUESTS=100
Loading
Loading