Packages

phoenix_kit

1.7.169
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 v122.ex
Raw

lib/phoenix_kit/migrations/postgres/v122.ex

defmodule PhoenixKit.Migrations.Postgres.V122 do
@moduledoc """
V122: Three unrelated additions bundled together because they shipped
in the same release cycle.
## 1. `phoenix_kit_location_spaces` — nested floors / rooms / zones
under a `phoenix_kit_locations` row
Each row belongs to exactly one Location (required FK, cascade) and
may optionally belong to a parent Space within the same Location
(self-ref FK, cascade) forming a filesystem-like tree of arbitrary
depth.
The cross-row "child belongs to same location as parent" guarantee
is enforced at application layer in `PhoenixKitLocations.Spaces`
enforcing it at the DB requires a composite FK + redundant column
pair which is heavier than the consumer surface justifies.
`data JSONB` mirrors the parent Location's column: top-level keys
carry attachment pointers (`files_folder_uuid`, `featured_image_uuid`)
and the multilang translation tree (`%{ "es-ES" => %{ "name" =>
"..."} }`). Primary-language values stay denormalized in the
dedicated `name` / `description` columns for cheap querying.
## 2. `translations JSONB` on the three staff tables
Adds `translations JSONB NOT NULL DEFAULT '{}'` to
`phoenix_kit_staff_departments`, `phoenix_kit_staff_teams`, and
`phoenix_kit_staff_people`. Mirrors the settings-translations shape
already used by `phoenix_kit_projects` V112 (project / project_task /
project_assignment): primary stays in dedicated columns, the JSONB
holds non-primary overrides only.
%{"es-ES" => %{"name" => "...", "description" => "..."}}
Translatable fields by schema:
* Department: `name`, `description`
* Team: `name`, `description`
* Person: `job_title`, `bio`, `skills`, `notes`
Read paths use `<Schema>.localized_<field>/2` helpers with primary-
fallback semantics.
## 3. `name` column on `phoenix_kit_staff_people`
Adds a single nullable `name VARCHAR` for the staff person's full
display name — consistent with Department, Team, Space, and Location
which all use a single `name` field. Owned by the staff profile
rather than `phoenix_kit_users` because placeholder users created via
`Staff.find_or_create_user_by_email/1` are anonymous until claimed.
"""
use Ecto.Migration
@kinds ~w(floor room hall suite section zone aisle shelf corner)
def up(opts) do
prefix = Map.get(opts, :prefix, "public")
p = prefix_str(prefix)
# ── Backfill `phoenix_kit_locations` parent (issue #598) ────────
# `phoenix_kit_location_spaces` below carries a required FK to
# `phoenix_kit_locations`, which is created only in V91. Because the
# locations tables were added to an already-released `v91.ex`, any
# database that passed V91 *before* that addition never received the
# parent table — so V122 would abort with `42P01 undefined_table`
# while building the FK. Recreate it here (mirroring V91's final
# shape) so the FK target always exists. Idempotent no-op on installs
# where V91 already created it.
create_if_not_exists table(:phoenix_kit_locations,
primary_key: false,
prefix: prefix
) do
add(:uuid, :uuid, primary_key: true, default: fragment("uuid_generate_v7()"))
add(:name, :string, null: false, size: 255)
add(:description, :text)
add(:public_notes, :text)
add(:address_line_1, :string, size: 500)
add(:address_line_2, :string, size: 500)
add(:city, :string, size: 255)
add(:state, :string, size: 255)
add(:postal_code, :string, size: 20)
add(:country, :string, size: 255)
add(:phone, :string, size: 50)
add(:email, :string, size: 255)
add(:website, :string, size: 500)
add(:notes, :text)
add(:status, :string, default: "active", size: 20)
add(:features, :map, default: %{})
add(:data, :map, default: %{})
timestamps(type: :utc_datetime)
end
create_if_not_exists(index(:phoenix_kit_locations, [:status], prefix: prefix))
create_if_not_exists table(:phoenix_kit_location_spaces,
primary_key: false,
prefix: prefix
) do
add(:uuid, :uuid, primary_key: true, default: fragment("uuid_generate_v7()"))
add(
:location_uuid,
references(:phoenix_kit_locations,
column: :uuid,
type: :uuid,
on_delete: :delete_all,
prefix: prefix
),
null: false
)
add(
:parent_uuid,
references(:phoenix_kit_location_spaces,
column: :uuid,
type: :uuid,
on_delete: :delete_all,
prefix: prefix
)
)
add(:kind, :string, null: false, size: 32)
add(:name, :string, null: false, size: 255)
add(:description, :text)
add(:notes, :text)
add(:status, :string, null: false, default: "active", size: 20)
add(:position, :integer, null: false, default: 0)
add(:data, :map, null: false, default: %{})
timestamps(type: :utc_datetime)
end
create_if_not_exists(index(:phoenix_kit_location_spaces, [:location_uuid], prefix: prefix))
create_if_not_exists(index(:phoenix_kit_location_spaces, [:parent_uuid], prefix: prefix))
# Sibling-ordering query: list spaces under (location, parent) by position.
create_if_not_exists(
index(:phoenix_kit_location_spaces, [:location_uuid, :parent_uuid, :position],
prefix: prefix
)
)
# Kind whitelist mirrors the schema's @kinds module attribute. Keep in
# sync if the consumer adds a new label. DROP-then-ADD pattern (same
# shape as V121) so the migration stays idempotent on re-run.
execute(
"ALTER TABLE #{p}phoenix_kit_location_spaces DROP CONSTRAINT IF EXISTS phoenix_kit_location_spaces_kind_check"
)
execute("""
ALTER TABLE #{p}phoenix_kit_location_spaces
ADD CONSTRAINT phoenix_kit_location_spaces_kind_check
CHECK (kind IN (#{Enum.map_join(@kinds, ", ", &"'#{&1}'")}))
""")
execute(
"ALTER TABLE #{p}phoenix_kit_location_spaces DROP CONSTRAINT IF EXISTS phoenix_kit_location_spaces_status_check"
)
execute("""
ALTER TABLE #{p}phoenix_kit_location_spaces
ADD CONSTRAINT phoenix_kit_location_spaces_status_check
CHECK (status IN ('active', 'inactive'))
""")
# ── Staff translations columns ──────────────────────────────────
# ALTER TABLE ... ADD COLUMN IF NOT EXISTS — safe to re-run on a
# database that's already at V122 but missing the staff translation
# bits (the case for installs that picked up the early V122 before
# this bundling).
execute(
"ALTER TABLE #{p}phoenix_kit_staff_departments ADD COLUMN IF NOT EXISTS translations JSONB NOT NULL DEFAULT '{}'::jsonb"
)
execute(
"ALTER TABLE #{p}phoenix_kit_staff_teams ADD COLUMN IF NOT EXISTS translations JSONB NOT NULL DEFAULT '{}'::jsonb"
)
execute(
"ALTER TABLE #{p}phoenix_kit_staff_people ADD COLUMN IF NOT EXISTS translations JSONB NOT NULL DEFAULT '{}'::jsonb"
)
# ── Person.name ─────────────────────────────────────────────────
execute("ALTER TABLE #{p}phoenix_kit_staff_people ADD COLUMN IF NOT EXISTS name VARCHAR")
execute("COMMENT ON TABLE #{p}phoenix_kit IS '122'")
end
def down(opts) do
prefix = Map.get(opts, :prefix, "public")
p = prefix_str(prefix)
execute("ALTER TABLE #{p}phoenix_kit_staff_people DROP COLUMN IF EXISTS name")
# Earlier V123 sketch added first / middle / last as three separate
# columns before we simplified to a single `name`. Drop them too so
# the rollback fully reverses any V122 shape that might already have
# been applied to this host.
execute("ALTER TABLE #{p}phoenix_kit_staff_people DROP COLUMN IF EXISTS first_name")
execute("ALTER TABLE #{p}phoenix_kit_staff_people DROP COLUMN IF EXISTS middle_name")
execute("ALTER TABLE #{p}phoenix_kit_staff_people DROP COLUMN IF EXISTS last_name")
execute("ALTER TABLE #{p}phoenix_kit_staff_departments DROP COLUMN IF EXISTS translations")
execute("ALTER TABLE #{p}phoenix_kit_staff_teams DROP COLUMN IF EXISTS translations")
execute("ALTER TABLE #{p}phoenix_kit_staff_people DROP COLUMN IF EXISTS translations")
drop_if_exists(table(:phoenix_kit_location_spaces, prefix: prefix))
execute("COMMENT ON TABLE #{p}phoenix_kit IS '121'")
end
defp prefix_str("public"), do: "public."
defp prefix_str(prefix), do: "#{prefix}."
end