Packages
phoenix_kit
1.7.178
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/v96.ex
defmodule PhoenixKit.Migrations.Postgres.V96 do
@moduledoc """
V96: Attach catalogue items directly to a catalogue.
Adds a nullable `catalogue_uuid` FK on `phoenix_kit_cat_items` so items
can belong to a catalogue independently of having a category. This lets
"uncategorized" items (items with no category) still be scoped to a
catalogue instead of floating in a global pool.
- Adds `catalogue_uuid` column with a FK to `phoenix_kit_cat_catalogues`
(`on_delete: :nilify_all`) — in-app cascades handle soft-delete lifecycle
- Backfills existing items from their category's catalogue_uuid
- Pins any remaining orphans (items with no category at all) to the
oldest non-deleted catalogue so they stay visible in the UI
- Adds indexes on `catalogue_uuid` and `(catalogue_uuid, status)`
All operations are idempotent.
"""
use Ecto.Migration
def up(opts) do
prefix = Map.get(opts, :prefix, "public")
p = prefix_str(prefix)
schema = if prefix == "public", do: "public", else: prefix
# 1. Add catalogue_uuid column (nullable FK, nilify on catalogue hard-delete)
execute("""
DO $$
BEGIN
IF NOT EXISTS (
SELECT FROM information_schema.columns
WHERE table_schema = '#{schema}'
AND table_name = 'phoenix_kit_cat_items'
AND column_name = 'catalogue_uuid'
) THEN
ALTER TABLE #{p}phoenix_kit_cat_items
ADD COLUMN catalogue_uuid uuid
REFERENCES #{p}phoenix_kit_cat_catalogues(uuid) ON DELETE SET NULL;
END IF;
END $$;
""")
# 2. Backfill catalogue_uuid from the item's category
execute("""
UPDATE #{p}phoenix_kit_cat_items AS i
SET catalogue_uuid = c.catalogue_uuid
FROM #{p}phoenix_kit_cat_categories AS c
WHERE i.category_uuid = c.uuid
AND i.catalogue_uuid IS NULL
""")
# 3. Backfill any remaining orphans (items with no category at all) into
# the oldest non-deleted catalogue. In the pre-v95 world these items
# showed up on every catalogue detail page as "global uncategorized";
# pinning them to the first active catalogue keeps them visible and
# behaves the same way in the common single-catalogue case.
#
# We filter out `status = 'deleted'` so we never land orphans inside a
# trashed catalogue (which would immediately soft-delete them via the
# normal cascade semantics). If no non-deleted catalogues exist, the
# subquery returns NULL and nothing is updated — the items stay orphaned
# until one does.
execute("""
UPDATE #{p}phoenix_kit_cat_items
SET catalogue_uuid = (
SELECT uuid FROM #{p}phoenix_kit_cat_catalogues
WHERE status <> 'deleted'
ORDER BY inserted_at ASC
LIMIT 1
)
WHERE catalogue_uuid IS NULL
""")
# 4. Index on catalogue_uuid for per-catalogue queries, plus a composite
# index on (catalogue_uuid, status) because every per-catalogue query
# (`item_count_for_catalogue`, `list_items_for_catalogue`,
# `item_counts_by_catalogue`, `search_items_in_catalogue`) filters on
# both columns. The composite lets the planner satisfy the filter
# without a separate status check.
create_if_not_exists(index(:phoenix_kit_cat_items, [:catalogue_uuid], prefix: prefix))
create_if_not_exists(
index(:phoenix_kit_cat_items, [:catalogue_uuid, :status], prefix: prefix)
)
execute("COMMENT ON TABLE #{p}phoenix_kit IS '96'")
end
@doc """
Rolls V96 back by dropping the `catalogue_uuid` column (and its indexes).
**Lossy rollback:** items that were created *after* V96 as uncategorized
(no category) have their catalogue linkage stored solely in
`catalogue_uuid`. Dropping the column means those items can no longer
be attributed to a catalogue — they'll become global orphans again
(their pre-V96 shape). Back up before rolling back in production.
"""
def down(opts) do
prefix = Map.get(opts, :prefix, "public")
p = prefix_str(prefix)
drop_if_exists(index(:phoenix_kit_cat_items, [:catalogue_uuid, :status], prefix: prefix))
drop_if_exists(index(:phoenix_kit_cat_items, [:catalogue_uuid], prefix: prefix))
execute("ALTER TABLE #{p}phoenix_kit_cat_items DROP COLUMN IF EXISTS catalogue_uuid")
execute("COMMENT ON TABLE #{p}phoenix_kit IS '95'")
end
defp prefix_str("public"), do: "public."
defp prefix_str(prefix), do: "#{prefix}."
end