Packages

phoenix_kit

1.7.186
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
phoenix_kit lib phoenix_kit migrations postgres v120.ex
Raw

lib/phoenix_kit/migrations/postgres/v120.ex

defmodule PhoenixKit.Migrations.Postgres.V120 do
@moduledoc """
V120: Document Creator Category → Type taxonomy.
Creates two tables — `phoenix_kit_doc_categories` and
`phoenix_kit_doc_types` — and adds nullable `category_uuid` /
`type_uuid` FK columns to `phoenix_kit_doc_templates` and
`phoenix_kit_doc_documents`.
Data migration: each distinct non-empty legacy `category` string on
templates becomes a Category row (matched case-insensitively, so
`"Financial"` and `"financial"` collapse into one); templates are
repointed via `category_uuid`; documents inherit their template's
category. The legacy `category` string columns on templates and
presets are then dropped. `type_uuid` stays NULL everywhere (no
Types yet).
Note: `phoenix_kit_doc_template_presets` does not get a
`category_uuid` column — presets do not join the new taxonomy, and
their legacy `category` strings are discarded (not migrated).
Dropping the preset `category` column also drops the V117
`phoenix_kit_doc_template_presets_scope_index`, which this migration
recreates on `(scope_type, scope_id)`.
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
create_if_not_exists table(:phoenix_kit_doc_categories,
primary_key: false,
prefix: prefix
) do
add(:uuid, :uuid, primary_key: true, default: fragment("uuid_generate_v7()"))
add(:name, :string, null: false)
add(:description, :text)
add(:position, :integer, null: false, default: 0)
add(:status, :string, null: false, default: "active")
add(:data, :map, null: false, default: %{})
timestamps(type: :utc_datetime)
end
create_if_not_exists(index(:phoenix_kit_doc_categories, [:status], prefix: prefix))
create_if_not_exists(index(:phoenix_kit_doc_categories, [:position], prefix: prefix))
create_if_not_exists table(:phoenix_kit_doc_types,
primary_key: false,
prefix: prefix
) do
add(:uuid, :uuid, primary_key: true, default: fragment("uuid_generate_v7()"))
add(:name, :string, null: false)
add(:description, :text)
add(:position, :integer, null: false, default: 0)
add(:status, :string, null: false, default: "active")
add(:data, :map, null: false, default: %{})
add(
:category_uuid,
references(:phoenix_kit_doc_categories,
column: :uuid,
type: :uuid,
on_delete: :delete_all,
prefix: prefix
),
null: false
)
timestamps(type: :utc_datetime)
end
# Composite index also serves category_uuid-only lookups (leftmost
# prefix), including the FK — no standalone [:category_uuid] index.
create_if_not_exists(
index(:phoenix_kit_doc_types, [:category_uuid, :position], prefix: prefix)
)
create_if_not_exists(index(:phoenix_kit_doc_types, [:status], prefix: prefix))
# FK columns on templates and documents.
for table <- ["phoenix_kit_doc_templates", "phoenix_kit_doc_documents"] do
execute("""
DO $$ BEGIN
IF NOT EXISTS (
SELECT FROM information_schema.columns
WHERE table_schema = '#{schema}'
AND table_name = '#{table}' AND column_name = 'category_uuid'
) THEN
ALTER TABLE #{p}#{table}
ADD COLUMN category_uuid uuid
REFERENCES #{p}phoenix_kit_doc_categories(uuid) ON DELETE SET NULL;
END IF;
IF NOT EXISTS (
SELECT FROM information_schema.columns
WHERE table_schema = '#{schema}'
AND table_name = '#{table}' AND column_name = 'type_uuid'
) THEN
ALTER TABLE #{p}#{table}
ADD COLUMN type_uuid uuid
REFERENCES #{p}phoenix_kit_doc_types(uuid) ON DELETE SET NULL;
END IF;
END $$
""")
end
create_if_not_exists(index(:phoenix_kit_doc_templates, [:category_uuid], prefix: prefix))
create_if_not_exists(index(:phoenix_kit_doc_templates, [:type_uuid], prefix: prefix))
create_if_not_exists(index(:phoenix_kit_doc_documents, [:category_uuid], prefix: prefix))
create_if_not_exists(index(:phoenix_kit_doc_documents, [:type_uuid], prefix: prefix))
# Data migration: legacy category strings -> Category rows.
# Only runs if the legacy column still exists.
execute("""
DO $$
DECLARE
rec record;
new_uuid uuid;
pos int := 0;
display_name text;
BEGIN
IF EXISTS (
SELECT FROM information_schema.columns
WHERE table_schema = '#{schema}'
AND table_name = 'phoenix_kit_doc_templates'
AND column_name = 'category'
) THEN
-- Group case-insensitively so 'Financial'/'financial' collapse
-- into a single Category row instead of duplicating.
FOR rec IN
SELECT lower(category) AS norm, min(category) AS sample
FROM #{p}phoenix_kit_doc_templates
WHERE category IS NOT NULL AND category <> ''
GROUP BY lower(category)
ORDER BY lower(category)
LOOP
-- Map known values explicitly; capitalize first letter for anything else.
display_name := CASE rec.norm
WHEN 'financial' THEN 'Financial'
WHEN 'technical' THEN 'Technical'
ELSE upper(substr(rec.sample, 1, 1)) || substr(rec.sample, 2)
END;
new_uuid := uuid_generate_v7();
INSERT INTO #{p}phoenix_kit_doc_categories
(uuid, name, position, status, data, inserted_at, updated_at)
VALUES
(new_uuid, display_name, pos, 'active', '{}'::jsonb, now(), now());
UPDATE #{p}phoenix_kit_doc_templates
SET category_uuid = new_uuid WHERE lower(category) = rec.norm;
pos := pos + 1;
END LOOP;
-- Documents inherit their template's category.
UPDATE #{p}phoenix_kit_doc_documents d
SET category_uuid = t.category_uuid
FROM #{p}phoenix_kit_doc_templates t
WHERE d.template_uuid = t.uuid
AND t.category_uuid IS NOT NULL;
END IF;
END $$
""")
# Drop legacy string columns.
execute("ALTER TABLE #{p}phoenix_kit_doc_templates DROP COLUMN IF EXISTS category")
execute("""
DO $$ BEGIN
IF EXISTS (
SELECT FROM information_schema.columns
WHERE table_schema = '#{schema}'
AND table_name = 'phoenix_kit_doc_template_presets'
AND column_name = 'category'
) THEN
ALTER TABLE #{p}phoenix_kit_doc_template_presets DROP COLUMN category;
END IF;
END $$
""")
# Dropping presets.category also dropped the V117 composite index
# `(scope_type, scope_id, category)`. Recreate it without `category`
# so scope-filtered preset lookups keep an index. Guarded on table
# existence — the Document Creator module is optional and parent
# apps that never installed it don't have `phoenix_kit_doc_template_presets`.
execute("""
DO $$ BEGIN
IF EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = '#{schema}'
AND table_name = 'phoenix_kit_doc_template_presets'
) THEN
CREATE INDEX IF NOT EXISTS phoenix_kit_doc_template_presets_scope_index
ON #{p}phoenix_kit_doc_template_presets (scope_type, scope_id);
END IF;
END $$
""")
execute("COMMENT ON TABLE #{p}phoenix_kit IS '120'")
end
def down(opts) do
prefix = Map.get(opts, :prefix, "public")
p = prefix_str(prefix)
schema = if prefix == "public", do: "public", else: prefix
execute("ALTER TABLE #{p}phoenix_kit_doc_templates ADD COLUMN IF NOT EXISTS category varchar")
execute(
"ALTER TABLE #{p}phoenix_kit_doc_template_presets ADD COLUMN IF NOT EXISTS category varchar"
)
# Restore the V117 3-column scope index now that `category` exists again
# (up/0 left it as a 2-column index).
execute("DROP INDEX IF EXISTS #{p}phoenix_kit_doc_template_presets_scope_index")
execute("""
CREATE INDEX IF NOT EXISTS phoenix_kit_doc_template_presets_scope_index
ON #{p}phoenix_kit_doc_template_presets (scope_type, scope_id, category)
""")
# Best-effort restore of the legacy string from the category name.
execute("""
DO $$ BEGIN
IF EXISTS (
SELECT FROM information_schema.columns
WHERE table_schema = '#{schema}'
AND table_name = 'phoenix_kit_doc_categories'
) THEN
UPDATE #{p}phoenix_kit_doc_templates t
SET category = lower(c.name)
FROM #{p}phoenix_kit_doc_categories c
WHERE t.category_uuid = c.uuid;
END IF;
END $$
""")
execute("ALTER TABLE #{p}phoenix_kit_doc_templates DROP COLUMN IF EXISTS type_uuid")
execute("ALTER TABLE #{p}phoenix_kit_doc_templates DROP COLUMN IF EXISTS category_uuid")
execute("ALTER TABLE #{p}phoenix_kit_doc_documents DROP COLUMN IF EXISTS type_uuid")
execute("ALTER TABLE #{p}phoenix_kit_doc_documents DROP COLUMN IF EXISTS category_uuid")
drop_if_exists(table(:phoenix_kit_doc_types, prefix: prefix))
drop_if_exists(table(:phoenix_kit_doc_categories, prefix: prefix))
execute("COMMENT ON TABLE #{p}phoenix_kit IS '119'")
end
defp prefix_str("public"), do: "public."
defp prefix_str(prefix), do: "#{prefix}."
end