Packages
phoenix_kit
1.7.76
1.7.208
1.7.207
1.7.206
1.7.205
1.7.204
1.7.203
1.7.202
1.7.201
1.7.200
1.7.199
1.7.198
1.7.197
1.7.196
1.7.194
1.7.193
1.7.192
1.7.191
1.7.190
1.7.189
1.7.187
1.7.186
1.7.185
1.7.184
1.7.183
1.7.182
1.7.181
1.7.180
1.7.179
1.7.178
1.7.177
1.7.176
1.7.175
1.7.174
1.7.173
1.7.172
1.7.171
1.7.170
1.7.169
1.7.168
1.7.167
1.7.166
1.7.165
1.7.164
1.7.162
1.7.161
1.7.160
1.7.159
1.7.157
1.7.156
1.7.155
1.7.154
1.7.153
1.7.152
1.7.151
1.7.150
1.7.149
1.7.146
1.7.145
1.7.144
1.7.143
1.7.138
1.7.133
1.7.132
1.7.131
1.7.130
1.7.128
1.7.126
1.7.125
1.7.121
1.7.120
1.7.119
1.7.118
1.7.117
1.7.116
1.7.115
1.7.114
1.7.113
1.7.112
1.7.111
1.7.110
1.7.109
1.7.108
1.7.107
1.7.106
1.7.105
1.7.104
1.7.103
1.7.102
1.7.101
1.7.100
1.7.99
1.7.98
1.7.97
1.7.96
1.7.95
1.7.94
1.7.93
1.7.92
1.7.91
1.7.90
1.7.89
1.7.88
1.7.87
1.7.86
1.7.85
1.7.84
1.7.83
1.7.82
1.7.81
1.7.80
1.7.79
1.7.78
1.7.77
1.7.76
1.7.75
1.7.74
1.7.71
1.7.70
1.7.69
1.7.66
1.7.65
1.7.64
1.7.63
1.7.62
1.7.61
1.7.59
1.7.58
1.7.57
1.7.56
1.7.55
1.7.54
1.7.53
1.7.52
1.7.51
1.7.49
1.7.44
1.7.43
1.7.42
1.7.41
1.7.39
1.7.38
1.7.37
1.7.36
1.7.34
1.7.33
1.7.31
1.7.30
1.7.29
1.7.28
1.7.27
1.7.26
1.7.25
1.7.24
1.7.23
1.7.22
1.7.21
1.7.20
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.20
1.6.19
1.6.18
1.6.17
1.6.16
1.6.15
1.6.14
1.6.13
1.6.12
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.10
1.2.9
1.2.8
1.2.7
1.2.5
1.2.4
1.2.2
1.2.1
1.2.0
1.1.0
1.0.0
A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more
Current section
Files
Jump to
Current section
Files
lib/phoenix_kit/migrations/postgres/v37.ex
defmodule PhoenixKit.Migrations.Postgres.V37 do
@moduledoc """
PhoenixKit V37 Migration: DB Sync - Connections & Transfer Tracking
This migration adds persistent connections and transfer tracking to the DB Sync module.
## Changes
### Connections Table (phoenix_kit_db_sync_connections)
- Permanent connections between PhoenixKit instances
- Sender-side access controls (approval mode, allowed/excluded tables)
- Expiration and download limits
- Additional security (password, IP whitelist, time restrictions)
- Receiver-side settings (conflict strategy, auto-sync)
- Full audit trail
### Transfers Table (phoenix_kit_db_sync_transfers)
- Track all data transfers (uploads and downloads)
- Record counts and bytes transferred
- Approval workflow support
- Request context for security
## Key Features
- **Approval Modes**: auto_approve, require_approval, per_table
- **Expiration**: Time-based and usage-based limits
- **Security**: Password protection, IP whitelist, time-of-day restrictions
- **Audit Trail**: Full tracking of who approved/suspended/revoked connections
"""
use Ecto.Migration
@doc """
Run the V37 migration to add DB Sync connections and transfers.
"""
def up(%{prefix: prefix} = _opts) do
# ===========================================
# 1. CONNECTIONS TABLE
# ===========================================
create_if_not_exists table(:phoenix_kit_db_sync_connections, prefix: prefix) do
add :name, :string, null: false
add :direction, :string, size: 10, null: false
add :site_url, :string, null: false
add :auth_token, :string
add :auth_token_hash, :string
add :status, :string, size: 20, null: false, default: "pending"
# Sender-side settings
add :approval_mode, :string, size: 20, default: "require_approval"
add :allowed_tables, {:array, :string}, null: false, default: []
add :excluded_tables, {:array, :string}, null: false, default: []
add :auto_approve_tables, {:array, :string}, null: false, default: []
# Expiration & limits
add :expires_at, :utc_datetime_usec
add :max_downloads, :integer
add :downloads_used, :integer, null: false, default: 0
add :max_records_total, :bigint
add :records_downloaded, :bigint, null: false, default: 0
# Per-request limits
add :max_records_per_request, :integer, null: false, default: 10_000
add :rate_limit_requests_per_minute, :integer, null: false, default: 60
# Additional security
add :download_password_hash, :string
add :ip_whitelist, {:array, :string}, null: false, default: []
add :allowed_hours_start, :integer
add :allowed_hours_end, :integer
# Receiver-side settings
add :default_conflict_strategy, :string, size: 20, default: "skip"
add :auto_sync_enabled, :boolean, null: false, default: false
add :auto_sync_tables, {:array, :string}, null: false, default: []
add :auto_sync_interval_minutes, :integer, null: false, default: 60
# Approval & status tracking
add :approved_at, :utc_datetime_usec
add :approved_by, :integer
add :suspended_at, :utc_datetime_usec
add :suspended_by, :integer
add :suspended_reason, :string
add :revoked_at, :utc_datetime_usec
add :revoked_by, :integer
add :revoked_reason, :string
# Audit & statistics
add :created_by, :integer
add :last_connected_at, :utc_datetime_usec
add :last_transfer_at, :utc_datetime_usec
add :total_transfers, :integer, null: false, default: 0
add :total_records_transferred, :bigint, null: false, default: 0
add :total_bytes_transferred, :bigint, null: false, default: 0
add :metadata, :map, null: false, default: %{}
timestamps(type: :utc_datetime_usec)
end
create_if_not_exists unique_index(
:phoenix_kit_db_sync_connections,
[:site_url, :direction],
name: :phoenix_kit_db_sync_connections_site_direction_uidx,
prefix: prefix
)
create_if_not_exists index(:phoenix_kit_db_sync_connections, [:status],
name: :phoenix_kit_db_sync_connections_status_idx,
prefix: prefix
)
create_if_not_exists index(:phoenix_kit_db_sync_connections, [:direction],
name: :phoenix_kit_db_sync_connections_direction_idx,
prefix: prefix
)
create_if_not_exists index(:phoenix_kit_db_sync_connections, [:expires_at],
name: :phoenix_kit_db_sync_connections_expires_at_idx,
prefix: prefix
)
# Add foreign keys for connections (optional - user references)
add_user_foreign_key("phoenix_kit_db_sync_connections", "approved_by", prefix)
add_user_foreign_key("phoenix_kit_db_sync_connections", "suspended_by", prefix)
add_user_foreign_key("phoenix_kit_db_sync_connections", "revoked_by", prefix)
add_user_foreign_key("phoenix_kit_db_sync_connections", "created_by", prefix)
# ===========================================
# 2. TRANSFERS TABLE
# ===========================================
create_if_not_exists table(:phoenix_kit_db_sync_transfers, prefix: prefix) do
add :direction, :string, size: 10, null: false
add :connection_id, :integer
add :session_code, :string, size: 20
add :remote_site_url, :string
add :table_name, :string, null: false
add :records_requested, :integer, null: false, default: 0
add :records_transferred, :integer, null: false, default: 0
add :records_created, :integer, null: false, default: 0
add :records_updated, :integer, null: false, default: 0
add :records_skipped, :integer, null: false, default: 0
add :records_failed, :integer, null: false, default: 0
add :bytes_transferred, :bigint, null: false, default: 0
add :conflict_strategy, :string, size: 20
# Status and approval
add :status, :string, size: 20, null: false, default: "pending"
add :requires_approval, :boolean, null: false, default: false
add :approved_at, :utc_datetime_usec
add :approved_by, :integer
add :denied_at, :utc_datetime_usec
add :denied_by, :integer
add :denial_reason, :string
add :approval_expires_at, :utc_datetime_usec
# Request context
add :requester_ip, :string
add :requester_user_agent, :string
add :error_message, :text
add :initiated_by, :integer
add :started_at, :utc_datetime_usec
add :completed_at, :utc_datetime_usec
add :metadata, :map, null: false, default: %{}
timestamps(type: :utc_datetime_usec, updated_at: false)
end
create_if_not_exists index(:phoenix_kit_db_sync_transfers, [:direction],
name: :phoenix_kit_db_sync_transfers_direction_idx,
prefix: prefix
)
create_if_not_exists index(:phoenix_kit_db_sync_transfers, [:connection_id],
name: :phoenix_kit_db_sync_transfers_connection_id_idx,
prefix: prefix
)
create_if_not_exists index(:phoenix_kit_db_sync_transfers, [:status],
name: :phoenix_kit_db_sync_transfers_status_idx,
prefix: prefix
)
create_if_not_exists index(:phoenix_kit_db_sync_transfers, [:initiated_by],
name: :phoenix_kit_db_sync_transfers_initiated_by_idx,
prefix: prefix
)
create_if_not_exists index(:phoenix_kit_db_sync_transfers, [:inserted_at],
name: :phoenix_kit_db_sync_transfers_inserted_at_idx,
prefix: prefix
)
create_if_not_exists index(
:phoenix_kit_db_sync_transfers,
[:requires_approval, :status],
name: :phoenix_kit_db_sync_transfers_approval_idx,
prefix: prefix
)
# Add foreign keys for transfers
add_connection_foreign_key("phoenix_kit_db_sync_transfers", "connection_id", prefix)
add_user_foreign_key("phoenix_kit_db_sync_transfers", "approved_by", prefix)
add_user_foreign_key("phoenix_kit_db_sync_transfers", "denied_by", prefix)
add_user_foreign_key("phoenix_kit_db_sync_transfers", "initiated_by", prefix)
# ===========================================
# 3. TABLE COMMENTS
# ===========================================
execute """
COMMENT ON TABLE #{prefix_table_name("phoenix_kit_db_sync_connections", prefix)} IS
'Permanent connections between PhoenixKit instances for data sync'
"""
execute """
COMMENT ON COLUMN #{prefix_table_name("phoenix_kit_db_sync_connections", prefix)}.approval_mode IS
'Access control mode: auto_approve, require_approval, or per_table'
"""
execute """
COMMENT ON TABLE #{prefix_table_name("phoenix_kit_db_sync_transfers", prefix)} IS
'Track all data transfers (uploads and downloads) with approval workflow'
"""
execute """
COMMENT ON COLUMN #{prefix_table_name("phoenix_kit_db_sync_transfers", prefix)}.status IS
'Transfer status: pending_approval, approved, denied, in_progress, completed, failed, cancelled, expired'
"""
# Update version
execute "COMMENT ON TABLE #{prefix_table_name("phoenix_kit", prefix)} IS '37'"
end
@doc """
Rollback the V37 migration.
"""
def down(%{prefix: prefix} = _opts) do
# Drop transfers foreign keys
drop_foreign_key_if_exists("phoenix_kit_db_sync_transfers", "initiated_by", prefix)
drop_foreign_key_if_exists("phoenix_kit_db_sync_transfers", "denied_by", prefix)
drop_foreign_key_if_exists("phoenix_kit_db_sync_transfers", "approved_by", prefix)
drop_foreign_key_if_exists("phoenix_kit_db_sync_transfers", "connection_id", prefix)
# Drop transfers indexes
drop_if_exists index(:phoenix_kit_db_sync_transfers, [:requires_approval, :status],
name: :phoenix_kit_db_sync_transfers_approval_idx,
prefix: prefix
)
drop_if_exists index(:phoenix_kit_db_sync_transfers, [:inserted_at],
name: :phoenix_kit_db_sync_transfers_inserted_at_idx,
prefix: prefix
)
drop_if_exists index(:phoenix_kit_db_sync_transfers, [:initiated_by],
name: :phoenix_kit_db_sync_transfers_initiated_by_idx,
prefix: prefix
)
drop_if_exists index(:phoenix_kit_db_sync_transfers, [:status],
name: :phoenix_kit_db_sync_transfers_status_idx,
prefix: prefix
)
drop_if_exists index(:phoenix_kit_db_sync_transfers, [:connection_id],
name: :phoenix_kit_db_sync_transfers_connection_id_idx,
prefix: prefix
)
drop_if_exists index(:phoenix_kit_db_sync_transfers, [:direction],
name: :phoenix_kit_db_sync_transfers_direction_idx,
prefix: prefix
)
# Drop transfers table
drop_if_exists table(:phoenix_kit_db_sync_transfers, prefix: prefix)
# Drop connections foreign keys
drop_foreign_key_if_exists("phoenix_kit_db_sync_connections", "created_by", prefix)
drop_foreign_key_if_exists("phoenix_kit_db_sync_connections", "revoked_by", prefix)
drop_foreign_key_if_exists("phoenix_kit_db_sync_connections", "suspended_by", prefix)
drop_foreign_key_if_exists("phoenix_kit_db_sync_connections", "approved_by", prefix)
# Drop connections indexes
drop_if_exists index(:phoenix_kit_db_sync_connections, [:expires_at],
name: :phoenix_kit_db_sync_connections_expires_at_idx,
prefix: prefix
)
drop_if_exists index(:phoenix_kit_db_sync_connections, [:direction],
name: :phoenix_kit_db_sync_connections_direction_idx,
prefix: prefix
)
drop_if_exists index(:phoenix_kit_db_sync_connections, [:status],
name: :phoenix_kit_db_sync_connections_status_idx,
prefix: prefix
)
drop_if_exists index(:phoenix_kit_db_sync_connections, [:site_url, :direction],
name: :phoenix_kit_db_sync_connections_site_direction_uidx,
prefix: prefix
)
# Drop connections table
drop_if_exists table(:phoenix_kit_db_sync_connections, prefix: prefix)
# Update version
execute "COMMENT ON TABLE #{prefix_table_name("phoenix_kit", prefix)} IS '36'"
end
# Helper functions
defp prefix_table_name(table_name, nil), do: table_name
defp prefix_table_name(table_name, prefix), do: "#{prefix}.#{table_name}"
defp add_user_foreign_key(table, column, prefix) do
constraint_name = "#{table}_#{column}_fkey"
execute """
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = '#{constraint_name}'
AND conrelid = '#{prefix_table_name(table, prefix)}'::regclass
) THEN
ALTER TABLE #{prefix_table_name(table, prefix)}
ADD CONSTRAINT #{constraint_name}
FOREIGN KEY (#{column})
REFERENCES #{prefix_table_name("phoenix_kit_users", prefix)}(id)
ON DELETE SET NULL;
END IF;
END $$;
"""
end
defp add_connection_foreign_key(table, column, prefix) do
constraint_name = "#{table}_#{column}_fkey"
execute """
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = '#{constraint_name}'
AND conrelid = '#{prefix_table_name(table, prefix)}'::regclass
) THEN
ALTER TABLE #{prefix_table_name(table, prefix)}
ADD CONSTRAINT #{constraint_name}
FOREIGN KEY (#{column})
REFERENCES #{prefix_table_name("phoenix_kit_db_sync_connections", prefix)}(id)
ON DELETE SET NULL;
END IF;
END $$;
"""
end
defp drop_foreign_key_if_exists(table, column, prefix) do
constraint_name = "#{table}_#{column}_fkey"
execute """
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = '#{constraint_name}'
AND conrelid = '#{prefix_table_name(table, prefix)}'::regclass
) THEN
ALTER TABLE #{prefix_table_name(table, prefix)}
DROP CONSTRAINT #{constraint_name};
END IF;
END $$;
"""
end
end