Packages
phoenix_kit
1.7.113
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/v107.ex
defmodule PhoenixKit.Migrations.Postgres.V107 do
@moduledoc """
V107: Pin AI endpoints to a specific integration row via
`integration_uuid` + add the missing unique index on `name`.
Prior to this migration, `phoenix_kit_ai_endpoints.provider` stored a
provider-type tag (`"openrouter"`) and the integrations system had to
guess which connection row to use at request time via
`find_first_connected/1` — necessary compensation for AI never having
recorded the user's actual choice. This column closes that gap: each
endpoint now references the integration row directly, so renaming or
re-labelling integrations doesn't change which one an endpoint uses,
and the resolver's `default`-based fallback chain becomes dead code.
## Up
- Adds nullable `integration_uuid uuid` column to
`phoenix_kit_ai_endpoints`.
- Adds a btree index on the new column for FK-style lookups.
- Backfills `integration_uuid` from the existing `provider` strings:
- For `provider = "openrouter:my-key"` → match the exact storage
row `integration:openrouter:my-key`.
- For `provider = "openrouter"` (bare) → pick the
most-recently-validated `integration:openrouter:*` row, breaking
ties on `uuid ASC` (UUIDv7 is time-ordered, so smaller uuid ≈
older row → ASC = oldest first when timestamps tie).
- Endpoints with no matching integration row stay NULL; the user
re-picks via the endpoint form.
- Adds a UNIQUE index on `lower(name)` so duplicate endpoint names
are rejected at the DB layer. `Endpoint.changeset/2` has
registered `unique_constraint(:name)` since the schema was first
extracted, but the V34 migration that created
`phoenix_kit_ai_endpoints` never created the index — duplicate
names were silently accepted. The hand-rolled test migration in
`phoenix_kit_ai` masked this for years; surfaced when that test
migration was removed (2026-04-29).
## Down
Drops the unique index, the integration_uuid index, and the
integration_uuid column. Lossy — endpoint→integration choices made
via the new picker are discarded.
"""
use Ecto.Migration
def up(opts) do
prefix = Map.get(opts, :prefix, "public")
p = prefix_str(prefix)
execute("""
ALTER TABLE #{p}phoenix_kit_ai_endpoints
ADD COLUMN IF NOT EXISTS integration_uuid uuid
""")
execute("""
CREATE INDEX IF NOT EXISTS phoenix_kit_ai_endpoints_integration_uuid_index
ON #{p}phoenix_kit_ai_endpoints (integration_uuid)
""")
# Backfill #1 — endpoints with explicit `provider:name` shape map
# one-to-one to a storage key.
execute("""
UPDATE #{p}phoenix_kit_ai_endpoints e
SET integration_uuid = s.uuid
FROM #{p}phoenix_kit_settings s
WHERE s.key = 'integration:' || e.provider
AND e.integration_uuid IS NULL
AND e.provider LIKE '%:%'
""")
# Backfill #2 — endpoints with bare provider strings get matched
# against the most-recently-validated row for that provider.
# NULLS LAST keeps un-validated rows at the bottom; the row uuid
# is the deterministic tiebreaker (UUIDv7 encodes time, so smaller
# uuid ≈ older row → ASC = oldest first when timestamps tie).
execute("""
UPDATE #{p}phoenix_kit_ai_endpoints e
SET integration_uuid = winner.uuid
FROM (
SELECT
split_part(key, ':', 2) AS provider_key,
uuid,
ROW_NUMBER() OVER (
PARTITION BY split_part(key, ':', 2)
ORDER BY
(value_json ->> 'last_validated_at') DESC NULLS LAST,
uuid ASC
) AS rn
FROM #{p}phoenix_kit_settings
WHERE key LIKE 'integration:%:%'
) winner
WHERE winner.rn = 1
AND winner.provider_key = e.provider
AND e.integration_uuid IS NULL
AND e.provider NOT LIKE '%:%'
""")
# Add the missing UNIQUE index on `lower(name)`. Endpoint.changeset/2
# has always registered `unique_constraint(:name)` but the V34
# migration that creates this table never created the index, so
# the constraint declaration was dead code. Case-insensitive so
# "Claude" and "claude" can't coexist either.
execute("""
CREATE UNIQUE INDEX IF NOT EXISTS phoenix_kit_ai_endpoints_name_index
ON #{p}phoenix_kit_ai_endpoints (lower(name))
""")
execute("COMMENT ON TABLE #{p}phoenix_kit IS '107'")
end
def down(opts) do
prefix = Map.get(opts, :prefix, "public")
p = prefix_str(prefix)
execute("""
DROP INDEX IF EXISTS #{p}phoenix_kit_ai_endpoints_name_index
""")
execute("""
DROP INDEX IF EXISTS #{p}phoenix_kit_ai_endpoints_integration_uuid_index
""")
execute("""
ALTER TABLE #{p}phoenix_kit_ai_endpoints
DROP COLUMN IF EXISTS integration_uuid
""")
execute("COMMENT ON TABLE #{p}phoenix_kit IS '106'")
end
defp prefix_str("public"), do: "public."
defp prefix_str(prefix), do: "#{prefix}."
end