Packages
phoenix_kit
1.7.128
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
221 Versions
Jump to
Current section
221 Versions
Compare versions
59
files changed
+1957
additions
-42
deletions
unknownCHANGELOG.md
File is too large to be displayed (100 KB limit).
| @@ -1,6 +1,6 @@ | |
| 1 1 | {<<"links">>,[{<<"GitHub">>,<<"https://github.com/BeamLabEU/phoenix_kit">>}]}. |
| 2 2 | {<<"name">>,<<"phoenix_kit">>}. |
| 3 | - {<<"version">>,<<"1.7.126">>}. |
| 3 | + {<<"version">>,<<"1.7.128">>}. |
| 4 4 | {<<"description">>, |
| 5 5 | <<65,32,102,111,117,110,100,97,116,105,111,110,32,102,111,114,32,98,117,105, |
| 6 6 | 108,100,105,110,103,32,69,108,105,120,105,114,32,80,104,111,101,110,105, |
| @@ -133,6 +133,7 @@ | |
| 133 133 | <<"lib/phoenix_kit_web/users/oauth.ex">>, |
| 134 134 | <<"lib/phoenix_kit_web/users/forgot_password.ex">>, |
| 135 135 | <<"lib/phoenix_kit_web/users/auth.ex">>, |
| 136 | + <<"lib/phoenix_kit_web/users/multi_session.ex">>, |
| 136 137 | <<"lib/phoenix_kit_web/users/confirmation.html.heex">>, |
| 137 138 | <<"lib/phoenix_kit_web/users/confirmation_instructions.html.heex">>, |
| 138 139 | <<"lib/phoenix_kit_web/users/magic_link.html.heex">>, |
| @@ -286,6 +287,9 @@ | |
| 286 287 | <<"lib/phoenix_kit_web/live/modules/jobs/index.html.heex">>, |
| 287 288 | <<"lib/phoenix_kit_web/live/modules/jobs/index.ex">>, |
| 288 289 | <<"lib/phoenix_kit_web/live/modules/languages.ex">>, |
| 290 | + <<"lib/phoenix_kit_web/live/modules/notifications">>, |
| 291 | + <<"lib/phoenix_kit_web/live/modules/notifications/index.html.heex">>, |
| 292 | + <<"lib/phoenix_kit_web/live/modules/notifications/index.ex">>, |
| 289 293 | <<"lib/phoenix_kit_web/live/modules/languages.html.heex">>, |
| 290 294 | <<"lib/phoenix_kit_web/live/settings.ex">>, |
| 291 295 | <<"lib/phoenix_kit_web/live/dashboard.html.heex">>, |
| @@ -314,6 +318,7 @@ | |
| 314 318 | <<"lib/phoenix_kit_web/projects_gettext_manifest.ex">>, |
| 315 319 | <<"lib/phoenix_kit_web/controllers">>, |
| 316 320 | <<"lib/phoenix_kit_web/controllers/error_json.ex">>, |
| 321 | + <<"lib/phoenix_kit_web/controllers/pdf_viewer_controller.ex">>, |
| 317 322 | <<"lib/phoenix_kit_web/controllers/error_html.ex">>, |
| 318 323 | <<"lib/phoenix_kit_web/controllers/file_controller.ex">>, |
| 319 324 | <<"lib/phoenix_kit_web/controllers/upload_controller.ex">>, |
| @@ -486,6 +491,7 @@ | |
| 486 491 | <<"lib/phoenix_kit/migrations/postgres/v18.ex">>, |
| 487 492 | <<"lib/phoenix_kit/migrations/postgres/v47.ex">>, |
| 488 493 | <<"lib/phoenix_kit/migrations/postgres/v107.ex">>, |
| 494 | + <<"lib/phoenix_kit/migrations/postgres/v128.ex">>, |
| 489 495 | <<"lib/phoenix_kit/migrations/postgres/v68.ex">>, |
| 490 496 | <<"lib/phoenix_kit/migrations/postgres/v37.ex">>, |
| 491 497 | <<"lib/phoenix_kit/migrations/postgres/v114.ex">>, |
| @@ -521,6 +527,7 @@ | |
| 521 527 | <<"lib/phoenix_kit/migrations/postgres/v38.ex">>, |
| 522 528 | <<"lib/phoenix_kit/migrations/postgres/v67.ex">>, |
| 523 529 | <<"lib/phoenix_kit/migrations/postgres/v90.ex">>, |
| 530 | + <<"lib/phoenix_kit/migrations/postgres/v127.ex">>, |
| 524 531 | <<"lib/phoenix_kit/migrations/postgres/v108.ex">>, |
| 525 532 | <<"lib/phoenix_kit/migrations/postgres/v48.ex">>, |
| 526 533 | <<"lib/phoenix_kit/migrations/postgres/v17.ex">>, |
| @@ -535,6 +542,7 @@ | |
| 535 542 | <<"lib/phoenix_kit/migrations/postgres/v91.ex">>, |
| 536 543 | <<"lib/phoenix_kit/migrations/postgres/v66.ex">>, |
| 537 544 | <<"lib/phoenix_kit/migrations/postgres/v39.ex">>, |
| 545 | + <<"lib/phoenix_kit/migrations/postgres/v126.ex">>, |
| 538 546 | <<"lib/phoenix_kit/migrations/postgres/v14.ex">>, |
| 539 547 | <<"lib/phoenix_kit/migrations/postgres/v124.ex">>, |
| 540 548 | <<"lib/phoenix_kit/migrations/postgres/v64.ex">>, |
| @@ -529,7 +529,35 @@ defmodule PhoenixKit.Migrations.Postgres do | |
| 529 529 | - Replaces unique index with partial index (slug-mode only, WHERE slug IS NOT NULL) |
| 530 530 | - Adds unique index on `(group_uuid, post_date, post_time)` for timestamp-mode posts |
| 531 531 | |
| 532 | - ### V125 - Project workflow statuses (entities-backed, cement-at-start) ⚡ LATEST |
| 532 | + ### V128 - Assignee on projects (and sub-projects) ⚡ LATEST |
| 533 | + - Adds `assigned_team_uuid` / `assigned_department_uuid` / `assigned_person_uuid` |
| 534 | + (FKs to the staff tables, `ON DELETE SET NULL`) to `phoenix_kit_projects`, |
| 535 | + with a `num_nonnulls(...) <= 1` single-assignee CHECK + a partial index per |
| 536 | + FK. Lets a whole project — or, since a sub-project is a project (V127), a |
| 537 | + sub-project — be assigned to a Department/Team/Person like a task. |
| 538 | + |
| 539 | + ### V127 - Sub-projects as tasks |
| 540 | + - Adds `child_project_uuid` (FK `phoenix_kit_projects(uuid) ON DELETE RESTRICT`) |
| 541 | + to `phoenix_kit_project_assignments` — a sub-project is an assignment that |
| 542 | + points at a child project instead of a task template, so it lives in the |
| 543 | + parent's task timeline with dependencies + drag-reorder for free. |
| 544 | + - Drops `NOT NULL` on `task_uuid`; adds a `CHECK` that exactly one of |
| 545 | + `task_uuid` / `child_project_uuid` is set (XOR). |
| 546 | + - Partial UNIQUE index on `(child_project_uuid) WHERE NOT NULL` (a project is |
| 547 | + a child of at most one parent); it also serves child-link lookups, since an |
| 548 | + equality predicate implies `IS NOT NULL`. |
| 549 | + |
| 550 | + ### V126 - Standalone notifications |
| 551 | + - Drops NOT NULL on `phoenix_kit_notifications.activity_uuid` so a |
| 552 | + notification can exist without an originating activity (the unique |
| 553 | + `(activity_uuid, recipient_uuid)` index still holds — NULLs are |
| 554 | + distinct in Postgres). |
| 555 | + - Adds `metadata JSONB NOT NULL DEFAULT '{}'` so a standalone |
| 556 | + notification carries its own display content (`Render` reads the same |
| 557 | + `notification_text` / `notification_icon` / `notification_link` keys |
| 558 | + it honors on activity metadata). |
| 559 | + |
| 560 | + ### V125 - Project workflow statuses (entities-backed, cement-at-start) |
| 533 561 | - Adds `status_entity_uuid` (FK `phoenix_kit_entities(uuid) ON DELETE SET NULL`), |
| 534 562 | `current_status_slug`, and a generic `settings` JSONB (first key: |
| 535 563 | `use_status_translations`) to `phoenix_kit_projects` — the catalog list a |
| @@ -1079,7 +1107,7 @@ defmodule PhoenixKit.Migrations.Postgres do | |
| 1079 1107 | use Ecto.Migration |
| 1080 1108 | |
| 1081 1109 | @initial_version 1 |
| 1082 | - @current_version 125 |
| 1110 | + @current_version 128 |
| 1083 1111 | @default_prefix "public" |
| 1084 1112 | |
| 1085 1113 | @doc false |
| @@ -0,0 +1,55 @@ | |
| 1 | + defmodule PhoenixKit.Migrations.Postgres.V126 do |
| 2 | + @moduledoc """ |
| 3 | + V126: Allow standalone notifications. |
| 4 | + |
| 5 | + Until now every `phoenix_kit_notifications` row had to point at an |
| 6 | + `activity_uuid` (NOT NULL) — a notification was strictly a per-user |
| 7 | + projection of an activity. This relaxes that so a notification can |
| 8 | + exist on its own: |
| 9 | + |
| 10 | + 1. Drops NOT NULL on `activity_uuid`. The unique |
| 11 | + `(activity_uuid, recipient_uuid)` index keeps working — Postgres |
| 12 | + treats NULLs as distinct by default, so a recipient can hold many |
| 13 | + standalone notifications. |
| 14 | + 2. Adds `metadata JSONB NOT NULL DEFAULT '{}'` so a standalone |
| 15 | + notification carries its own display content. `Render` reads the |
| 16 | + same `notification_text` / `notification_icon` / `notification_link` |
| 17 | + keys it already honors on activity metadata. |
| 18 | + |
| 19 | + Idempotent: `DROP NOT NULL` is a no-op when already nullable and the |
| 20 | + column add is `IF NOT EXISTS`. |
| 21 | + """ |
| 22 | + |
| 23 | + use Ecto.Migration |
| 24 | + |
| 25 | + def up(opts) do |
| 26 | + prefix = Map.get(opts, :prefix, "public") |
| 27 | + p = prefix_str(prefix) |
| 28 | + |
| 29 | + execute("ALTER TABLE #{p}phoenix_kit_notifications ALTER COLUMN activity_uuid DROP NOT NULL") |
| 30 | + |
| 31 | + execute( |
| 32 | + "ALTER TABLE #{p}phoenix_kit_notifications ADD COLUMN IF NOT EXISTS metadata JSONB NOT NULL DEFAULT '{}'::jsonb" |
| 33 | + ) |
| 34 | + |
| 35 | + execute("COMMENT ON TABLE #{p}phoenix_kit IS '126'") |
| 36 | + end |
| 37 | + |
| 38 | + def down(opts) do |
| 39 | + prefix = Map.get(opts, :prefix, "public") |
| 40 | + p = prefix_str(prefix) |
| 41 | + |
| 42 | + execute("ALTER TABLE #{p}phoenix_kit_notifications DROP COLUMN IF EXISTS metadata") |
| 43 | + |
| 44 | + # Reverting the feature means activity-less notifications can no longer |
| 45 | + # exist — drop them before restoring NOT NULL so the constraint holds. |
| 46 | + execute("DELETE FROM #{p}phoenix_kit_notifications WHERE activity_uuid IS NULL") |
| 47 | + |
| 48 | + execute("ALTER TABLE #{p}phoenix_kit_notifications ALTER COLUMN activity_uuid SET NOT NULL") |
| 49 | + |
| 50 | + execute("COMMENT ON TABLE #{p}phoenix_kit IS '125'") |
| 51 | + end |
| 52 | + |
| 53 | + defp prefix_str("public"), do: "public." |
| 54 | + defp prefix_str(prefix), do: "#{prefix}." |
| 55 | + end |
| @@ -0,0 +1,191 @@ | |
| 1 | + defmodule PhoenixKit.Migrations.Postgres.V127 do |
| 2 | + @moduledoc """ |
| 3 | + V127: Sub-projects as tasks (`phoenix_kit_project_assignments.child_project_uuid`). |
| 4 | + |
| 5 | + Lets a project be embedded inside another project as one of its task rows. |
| 6 | + A sub-project is an `Assignment` that points at a child `Project` instead of |
| 7 | + a reusable `Task` template — so it lives in the parent's task timeline and |
| 8 | + gets dependencies + drag-reorder for free (both are already assignment-level |
| 9 | + and project-scoped). The child project is the single source of truth; the |
| 10 | + parent's linking assignment carries denormalized rollup fields (status / |
| 11 | + progress_pct / estimated_duration / completed_at) synced by the context layer |
| 12 | + whenever the child changes, so every existing read site (schedule math, |
| 13 | + `recompute_project_completion`, dashboards, sorting) keeps working unchanged. |
| 14 | + |
| 15 | + Changes to `phoenix_kit_project_assignments`: |
| 16 | + |
| 17 | + * `child_project_uuid UUID` → FK `phoenix_kit_projects(uuid) ON DELETE RESTRICT`. |
| 18 | + `RESTRICT` (not `CASCADE`) so a stray child-project delete fails loudly |
| 19 | + instead of silently mutating the parent's task list — recursive teardown |
| 20 | + is orchestrated explicitly in `PhoenixKitProjects.Projects` inside a |
| 21 | + transaction so it can log activity and tear the subtree down in order. |
| 22 | + * `task_uuid` loses its `NOT NULL` — a sub-project assignment has no template. |
| 23 | + * `CHECK ((task_uuid IS NOT NULL) <> (child_project_uuid IS NOT NULL))` — |
| 24 | + exactly one of the two is set (XOR). Existing rows (task set, child NULL) |
| 25 | + satisfy it, so the constraint validates against current data without a |
| 26 | + backfill. |
| 27 | + * Partial UNIQUE index on `(child_project_uuid) WHERE child_project_uuid IS |
| 28 | + NOT NULL` — a project is a child of at most one parent assignment. This is |
| 29 | + also what forces template cloning to *deep-clone* child subtrees rather |
| 30 | + than point two parents at the same child. It also serves the "find the |
| 31 | + linking row for this child" lookups (parent breadcrumb, rollup sync): an |
| 32 | + equality predicate `child_project_uuid = $1` implies `IS NOT NULL`, so |
| 33 | + Postgres uses the partial index for it — no separate plain index needed. |
| 34 | + |
| 35 | + Idempotent: re-running is a no-op once the column/constraints/indexes exist. |
| 36 | + """ |
| 37 | + |
| 38 | + use Ecto.Migration |
| 39 | + |
| 40 | + def up(opts) do |
| 41 | + prefix = Map.get(opts, :prefix, "public") |
| 42 | + p = prefix_str(prefix) |
| 43 | + schema = schema_for(prefix) |
| 44 | + |
| 45 | + add_child_project_uuid_column(p, schema) |
| 46 | + add_child_project_uuid_fk(p, schema) |
| 47 | + drop_task_uuid_not_null(p) |
| 48 | + add_task_xor_child_check(p, schema) |
| 49 | + create_child_project_unique_index(p, schema) |
| 50 | + |
| 51 | + execute("COMMENT ON TABLE #{p}phoenix_kit IS '127'") |
| 52 | + end |
| 53 | + |
| 54 | + def down(opts) do |
| 55 | + prefix = Map.get(opts, :prefix, "public") |
| 56 | + p = prefix_str(prefix) |
| 57 | + |
| 58 | + execute("DROP INDEX IF EXISTS #{p}phoenix_kit_project_assignments_child_project_unique") |
| 59 | + |
| 60 | + execute(""" |
| 61 | + ALTER TABLE #{p}phoenix_kit_project_assignments |
| 62 | + DROP CONSTRAINT IF EXISTS phoenix_kit_project_assignments_task_xor_child |
| 63 | + """) |
| 64 | + |
| 65 | + execute(""" |
| 66 | + ALTER TABLE #{p}phoenix_kit_project_assignments |
| 67 | + DROP CONSTRAINT IF EXISTS phoenix_kit_project_assignments_child_project_uuid_fkey |
| 68 | + """) |
| 69 | + |
| 70 | + # Drop the sub-project linking rows first — they're exactly the rows that |
| 71 | + # would violate the NOT NULL we restore below. Target them directly by |
| 72 | + # `child_project_uuid IS NOT NULL` (the column still exists here, dropped |
| 73 | + # just after): that's precisely the V127-created set, without leaning on |
| 74 | + # the XOR check we dropped two statements ago. The child projects survive |
| 75 | + # as standalone projects, but note this also cascade-deletes any |
| 76 | + # `phoenix_kit_project_dependencies` edges touching these assignments |
| 77 | + # (FK `ON DELETE CASCADE`), so a sub-project's dependency wiring is lost — |
| 78 | + # rollback is feature-removal, not a reversible round-trip. |
| 79 | + execute( |
| 80 | + "DELETE FROM #{p}phoenix_kit_project_assignments WHERE child_project_uuid IS NOT NULL" |
| 81 | + ) |
| 82 | + |
| 83 | + execute( |
| 84 | + "ALTER TABLE #{p}phoenix_kit_project_assignments DROP COLUMN IF EXISTS child_project_uuid" |
| 85 | + ) |
| 86 | + |
| 87 | + # Restore the pre-V127 NOT NULL on task_uuid. Safe now: the XOR check is |
| 88 | + # gone and the only rows that could have had a NULL task_uuid (the child |
| 89 | + # links) were just deleted, so every remaining row is a plain |
| 90 | + # template-backed assignment with task_uuid set. |
| 91 | + execute("ALTER TABLE #{p}phoenix_kit_project_assignments ALTER COLUMN task_uuid SET NOT NULL") |
| 92 | + |
| 93 | + execute("COMMENT ON TABLE #{p}phoenix_kit IS '126'") |
| 94 | + end |
| 95 | + |
| 96 | + defp add_child_project_uuid_column(p, schema) do |
| 97 | + execute(""" |
| 98 | + DO $$ |
| 99 | + BEGIN |
| 100 | + IF NOT EXISTS ( |
| 101 | + SELECT FROM information_schema.columns |
| 102 | + WHERE table_schema = '#{schema}' |
| 103 | + AND table_name = 'phoenix_kit_project_assignments' |
| 104 | + AND column_name = 'child_project_uuid' |
| 105 | + ) THEN |
| 106 | + ALTER TABLE #{p}phoenix_kit_project_assignments |
| 107 | + ADD COLUMN child_project_uuid UUID; |
| 108 | + END IF; |
| 109 | + END $$; |
| 110 | + """) |
| 111 | + end |
| 112 | + |
| 113 | + # FK to the embedded child project. `ON DELETE RESTRICT` keeps the parent's |
| 114 | + # task list honest: you can't delete a project that is still embedded as a |
| 115 | + # sub-project — the context tears the subtree down explicitly instead. |
| 116 | + # Postgres has no `ADD CONSTRAINT IF NOT EXISTS`, so guard on the name. |
| 117 | + defp add_child_project_uuid_fk(p, schema) do |
| 118 | + execute(""" |
| 119 | + DO $$ |
| 120 | + BEGIN |
| 121 | + IF NOT EXISTS ( |
| 122 | + SELECT FROM information_schema.table_constraints |
| 123 | + WHERE table_schema = '#{schema}' |
| 124 | + AND table_name = 'phoenix_kit_project_assignments' |
| 125 | + AND constraint_name = 'phoenix_kit_project_assignments_child_project_uuid_fkey' |
| 126 | + ) THEN |
| 127 | + ALTER TABLE #{p}phoenix_kit_project_assignments |
| 128 | + ADD CONSTRAINT phoenix_kit_project_assignments_child_project_uuid_fkey |
| 129 | + FOREIGN KEY (child_project_uuid) |
| 130 | + REFERENCES #{p}phoenix_kit_projects(uuid) |
| 131 | + ON DELETE RESTRICT; |
| 132 | + END IF; |
| 133 | + END $$; |
| 134 | + """) |
| 135 | + end |
| 136 | + |
| 137 | + # A sub-project assignment has no task template, so task_uuid must be |
| 138 | + # nullable. `DROP NOT NULL` is itself idempotent (a no-op when already |
| 139 | + # nullable), so no existence guard is needed. |
| 140 | + defp drop_task_uuid_not_null(p) do |
| 141 | + execute( |
| 142 | + "ALTER TABLE #{p}phoenix_kit_project_assignments ALTER COLUMN task_uuid DROP NOT NULL" |
| 143 | + ) |
| 144 | + end |
| 145 | + |
| 146 | + # Exactly one of task_uuid / child_project_uuid is set. `<>` is boolean XOR |
| 147 | + # in Postgres. Existing rows (task set, child NULL) already satisfy it. |
| 148 | + defp add_task_xor_child_check(p, schema) do |
| 149 | + execute(""" |
| 150 | + DO $$ |
| 151 | + BEGIN |
| 152 | + IF NOT EXISTS ( |
| 153 | + SELECT FROM information_schema.table_constraints |
| 154 | + WHERE table_schema = '#{schema}' |
| 155 | + AND table_name = 'phoenix_kit_project_assignments' |
| 156 | + AND constraint_name = 'phoenix_kit_project_assignments_task_xor_child' |
| 157 | + ) THEN |
| 158 | + ALTER TABLE #{p}phoenix_kit_project_assignments |
| 159 | + ADD CONSTRAINT phoenix_kit_project_assignments_task_xor_child |
| 160 | + CHECK ((task_uuid IS NOT NULL) <> (child_project_uuid IS NOT NULL)); |
| 161 | + END IF; |
| 162 | + END $$; |
| 163 | + """) |
| 164 | + end |
| 165 | + |
| 166 | + # A project can be embedded as a sub-project in at most one parent. Partial |
| 167 | + # so the common task-backed rows (child NULL) don't collide. |
| 168 | + defp create_child_project_unique_index(p, schema) do |
| 169 | + execute(""" |
| 170 | + DO $$ |
| 171 | + BEGIN |
| 172 | + IF NOT EXISTS ( |
| 173 | + SELECT FROM pg_indexes |
| 174 | + WHERE schemaname = '#{schema}' |
| 175 | + AND tablename = 'phoenix_kit_project_assignments' |
| 176 | + AND indexname = 'phoenix_kit_project_assignments_child_project_unique' |
| 177 | + ) THEN |
| 178 | + CREATE UNIQUE INDEX phoenix_kit_project_assignments_child_project_unique |
| 179 | + ON #{p}phoenix_kit_project_assignments (child_project_uuid) |
| 180 | + WHERE child_project_uuid IS NOT NULL; |
| 181 | + END IF; |
| 182 | + END $$; |
| 183 | + """) |
| 184 | + end |
| 185 | + |
| 186 | + defp schema_for("public"), do: "public" |
| 187 | + defp schema_for(prefix), do: prefix |
| 188 | + |
| 189 | + defp prefix_str("public"), do: "public." |
| 190 | + defp prefix_str(prefix), do: "#{prefix}." |
| 191 | + end |
Loading more files…