Packages
phoenix_kit
1.7.104
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/v32.ex
defmodule PhoenixKit.Migrations.Postgres.V32 do
@moduledoc """
PhoenixKit V32 Migration: AI System
This migration introduces the AI provider account management and usage tracking system.
## Changes
### AI Accounts Table (phoenix_kit_ai_accounts)
- Store AI provider credentials (OpenRouter, etc.)
- Support multiple accounts per provider
- Optional settings for provider-specific configuration
- API key validation tracking
### AI Requests Table (phoenix_kit_ai_requests)
- Track every AI API request for history and statistics
- Token usage tracking (input, output, total)
- Cost tracking when available
- Latency and status monitoring
- Per-user and per-account tracking
### Settings Seeds
- AI module enable/disable
- Text processing slots configuration (JSON)
## PostgreSQL Support
- Leverages PostgreSQL's native JSONB for flexible metadata
- Supports prefix for schema isolation
- Optimized indexes for common queries
"""
use Ecto.Migration
@doc """
Run the V32 migration to add the AI system.
"""
def up(%{prefix: prefix} = _opts) do
# ===========================================
# 1. AI ACCOUNTS TABLE
# ===========================================
create_if_not_exists table(:phoenix_kit_ai_accounts, prefix: prefix) do
add :name, :string, size: 100, null: false
add :provider, :string, size: 50, null: false, default: "openrouter"
add :api_key, :text, null: false
add :base_url, :string, size: 255
add :settings, :map, null: true, default: %{}
add :enabled, :boolean, null: false, default: true
add :last_validated_at, :utc_datetime_usec
timestamps(type: :utc_datetime_usec)
end
create_if_not_exists index(:phoenix_kit_ai_accounts, [:provider],
name: :phoenix_kit_ai_accounts_provider_idx,
prefix: prefix
)
create_if_not_exists index(:phoenix_kit_ai_accounts, [:enabled],
name: :phoenix_kit_ai_accounts_enabled_idx,
prefix: prefix
)
execute """
COMMENT ON TABLE #{prefix_table_name("phoenix_kit_ai_accounts", prefix)} IS
'AI provider accounts for text processing (OpenRouter, etc.)'
"""
execute """
COMMENT ON COLUMN #{prefix_table_name("phoenix_kit_ai_accounts", prefix)}.api_key IS
'Provider API key (stored in plain text like OAuth credentials)'
"""
execute """
COMMENT ON COLUMN #{prefix_table_name("phoenix_kit_ai_accounts", prefix)}.settings IS
'Provider-specific settings (HTTP-Referer, X-Title headers for OpenRouter, etc.)'
"""
# ===========================================
# 2. AI REQUESTS TABLE (Usage History)
# ===========================================
create_if_not_exists table(:phoenix_kit_ai_requests, prefix: prefix) do
add :account_id, :integer
add :user_id, :integer
add :slot_index, :integer
add :model, :string, size: 100
add :request_type, :string, size: 50, null: false, default: "text_completion"
add :input_tokens, :integer, null: false, default: 0
add :output_tokens, :integer, null: false, default: 0
add :total_tokens, :integer, null: false, default: 0
add :cost_cents, :integer
add :latency_ms, :integer
add :status, :string, size: 20, null: false, default: "success"
add :error_message, :text
add :metadata, :map, null: true, default: %{}
timestamps(type: :utc_datetime_usec)
end
create_if_not_exists index(:phoenix_kit_ai_requests, [:account_id],
name: :phoenix_kit_ai_requests_account_id_idx,
prefix: prefix
)
create_if_not_exists index(:phoenix_kit_ai_requests, [:user_id],
name: :phoenix_kit_ai_requests_user_id_idx,
prefix: prefix
)
create_if_not_exists index(:phoenix_kit_ai_requests, [:status],
name: :phoenix_kit_ai_requests_status_idx,
prefix: prefix
)
create_if_not_exists index(:phoenix_kit_ai_requests, [:inserted_at],
name: :phoenix_kit_ai_requests_inserted_at_idx,
prefix: prefix
)
create_if_not_exists index(:phoenix_kit_ai_requests, [:model],
name: :phoenix_kit_ai_requests_model_idx,
prefix: prefix
)
# Add foreign key to ai_accounts (optional - account can be deleted)
execute """
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'phoenix_kit_ai_requests_account_id_fkey'
AND conrelid = '#{prefix_table_name("phoenix_kit_ai_requests", prefix)}'::regclass
) THEN
ALTER TABLE #{prefix_table_name("phoenix_kit_ai_requests", prefix)}
ADD CONSTRAINT phoenix_kit_ai_requests_account_id_fkey
FOREIGN KEY (account_id)
REFERENCES #{prefix_table_name("phoenix_kit_ai_accounts", prefix)}(id)
ON DELETE SET NULL;
END IF;
END $$;
"""
# Add foreign key to users (optional - user can be deleted)
execute """
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'phoenix_kit_ai_requests_user_id_fkey'
AND conrelid = '#{prefix_table_name("phoenix_kit_ai_requests", prefix)}'::regclass
) THEN
ALTER TABLE #{prefix_table_name("phoenix_kit_ai_requests", prefix)}
ADD CONSTRAINT phoenix_kit_ai_requests_user_id_fkey
FOREIGN KEY (user_id)
REFERENCES #{prefix_table_name("phoenix_kit_users", prefix)}(id)
ON DELETE SET NULL;
END IF;
END $$;
"""
execute """
COMMENT ON TABLE #{prefix_table_name("phoenix_kit_ai_requests", prefix)} IS
'AI API request history for usage tracking and statistics'
"""
execute """
COMMENT ON COLUMN #{prefix_table_name("phoenix_kit_ai_requests", prefix)}.slot_index IS
'Which text processing slot was used (0, 1, or 2)'
"""
execute """
COMMENT ON COLUMN #{prefix_table_name("phoenix_kit_ai_requests", prefix)}.cost_cents IS
'Estimated cost in cents (when available from API response)'
"""
execute """
COMMENT ON COLUMN #{prefix_table_name("phoenix_kit_ai_requests", prefix)}.status IS
'Request status: success, error, or timeout'
"""
# ===========================================
# 3. AI SETTINGS
# ===========================================
execute """
INSERT INTO #{prefix_table_name("phoenix_kit_settings", prefix)} (key, value, module, date_added, date_updated)
VALUES
('ai_enabled', 'false', 'ai', NOW(), NOW())
ON CONFLICT (key) DO NOTHING
"""
# Default text processing slots configuration (JSON)
default_slots =
Jason.encode!(%{
"slots" => [
%{
"name" => "Slot 1",
"description" => "",
"account_id" => nil,
"model" => "",
"temperature" => 0.7,
"max_tokens" => 1000,
"enabled" => false
},
%{
"name" => "Slot 2",
"description" => "",
"account_id" => nil,
"model" => "",
"temperature" => 0.7,
"max_tokens" => 2000,
"enabled" => false
},
%{
"name" => "Slot 3",
"description" => "",
"account_id" => nil,
"model" => "",
"temperature" => 0.7,
"max_tokens" => 4000,
"enabled" => false
}
]
})
execute """
INSERT INTO #{prefix_table_name("phoenix_kit_settings", prefix)} (key, value_json, module, date_added, date_updated)
VALUES
('ai_text_processing_slots', '#{default_slots}'::jsonb, 'ai', NOW(), NOW())
ON CONFLICT (key) DO NOTHING
"""
# Update version
execute "COMMENT ON TABLE #{prefix_table_name("phoenix_kit", prefix)} IS '32'"
end
@doc """
Rollback the V32 migration.
"""
def down(%{prefix: prefix} = _opts) do
# Drop requests foreign keys first
execute """
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'phoenix_kit_ai_requests_user_id_fkey'
AND conrelid = '#{prefix_table_name("phoenix_kit_ai_requests", prefix)}'::regclass
) THEN
ALTER TABLE #{prefix_table_name("phoenix_kit_ai_requests", prefix)}
DROP CONSTRAINT phoenix_kit_ai_requests_user_id_fkey;
END IF;
END $$;
"""
execute """
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'phoenix_kit_ai_requests_account_id_fkey'
AND conrelid = '#{prefix_table_name("phoenix_kit_ai_requests", prefix)}'::regclass
) THEN
ALTER TABLE #{prefix_table_name("phoenix_kit_ai_requests", prefix)}
DROP CONSTRAINT phoenix_kit_ai_requests_account_id_fkey;
END IF;
END $$;
"""
# Drop requests indexes
drop_if_exists index(:phoenix_kit_ai_requests, [:model],
name: :phoenix_kit_ai_requests_model_idx,
prefix: prefix
)
drop_if_exists index(:phoenix_kit_ai_requests, [:inserted_at],
name: :phoenix_kit_ai_requests_inserted_at_idx,
prefix: prefix
)
drop_if_exists index(:phoenix_kit_ai_requests, [:status],
name: :phoenix_kit_ai_requests_status_idx,
prefix: prefix
)
drop_if_exists index(:phoenix_kit_ai_requests, [:user_id],
name: :phoenix_kit_ai_requests_user_id_idx,
prefix: prefix
)
drop_if_exists index(:phoenix_kit_ai_requests, [:account_id],
name: :phoenix_kit_ai_requests_account_id_idx,
prefix: prefix
)
# Drop requests table
drop_if_exists table(:phoenix_kit_ai_requests, prefix: prefix)
# Drop accounts indexes
drop_if_exists index(:phoenix_kit_ai_accounts, [:enabled],
name: :phoenix_kit_ai_accounts_enabled_idx,
prefix: prefix
)
drop_if_exists index(:phoenix_kit_ai_accounts, [:provider],
name: :phoenix_kit_ai_accounts_provider_idx,
prefix: prefix
)
# Drop accounts table
drop_if_exists table(:phoenix_kit_ai_accounts, prefix: prefix)
# Remove AI settings
execute """
DELETE FROM #{prefix_table_name("phoenix_kit_settings", prefix)}
WHERE key IN (
'ai_enabled',
'ai_text_processing_slots'
)
"""
# Update version
execute "COMMENT ON TABLE #{prefix_table_name("phoenix_kit", prefix)} IS '31'"
end
defp prefix_table_name(table_name, nil), do: table_name
defp prefix_table_name(table_name, prefix), do: "#{prefix}.#{table_name}"
end