Packages
phoenix_kit
1.7.159
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
38
files changed
+1222
additions
-503
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.157">>}. |
| 3 | + {<<"version">>,<<"1.7.159">>}. |
| 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, |
| @@ -564,6 +564,7 @@ | |
| 564 564 | <<"lib/phoenix_kit/migrations/postgres/v81.ex">>, |
| 565 565 | <<"lib/phoenix_kit/migrations/postgres/v76.ex">>, |
| 566 566 | <<"lib/phoenix_kit/migrations/postgres/v29.ex">>, |
| 567 | + <<"lib/phoenix_kit/migrations/postgres/v136.ex">>, |
| 567 568 | <<"lib/phoenix_kit/migrations/postgres/v119.ex">>, |
| 568 569 | <<"lib/phoenix_kit/migrations/postgres/v06.ex">>, |
| 569 570 | <<"lib/phoenix_kit/migrations/postgres/v59.ex">>, |
| @@ -766,6 +767,11 @@ | |
| 766 767 | {<<"optional">>,false}, |
| 767 768 | {<<"requirement">>,<<"~> 0.3">>}, |
| 768 769 | {<<"repository">>,<<"hexpm">>}], |
| 770 | + [{<<"name">>,<<"mdex">>}, |
| 771 | + {<<"app">>,<<"mdex">>}, |
| 772 | + {<<"optional">>,false}, |
| 773 | + {<<"requirement">>,<<"~> 0.13">>}, |
| 774 | + {<<"repository">>,<<"hexpm">>}], |
| 769 775 | [{<<"name">>,<<"fresco">>}, |
| 770 776 | {<<"app">>,<<"fresco">>}, |
| 771 777 | {<<"optional">>,false}, |
| @@ -143,6 +143,7 @@ defmodule PhoenixKit.Activity do | |
| 143 143 | - `:action` — filter by action string (exact match or prefix with "post.*") |
| 144 144 | - `:actor_uuid` — filter by who performed the action |
| 145 145 | - `:resource_type` — filter by resource type |
| 146 | + - `:resource_uuid` — filter by the specific resource's UUID (scope to one resource) |
| 146 147 | - `:target_uuid` — filter by who was affected |
| 147 148 | - `:since` — filter activities after this datetime |
| 148 149 | - `:until` — filter activities before this datetime |
| @@ -358,6 +359,7 @@ defmodule PhoenixKit.Activity do | |
| 358 359 | |> maybe_filter_action(Keyword.get(opts, :action)) |
| 359 360 | |> maybe_filter_actor(Keyword.get(opts, :actor_uuid)) |
| 360 361 | |> maybe_filter_resource_type(Keyword.get(opts, :resource_type)) |
| 362 | + |> maybe_filter_resource_uuid(Keyword.get(opts, :resource_uuid)) |
| 361 363 | |> maybe_filter_target(Keyword.get(opts, :target_uuid)) |
| 362 364 | |> maybe_filter_since(Keyword.get(opts, :since)) |
| 363 365 | |> maybe_filter_until(Keyword.get(opts, :until)) |
| @@ -386,6 +388,9 @@ defmodule PhoenixKit.Activity do | |
| 386 388 | defp maybe_filter_resource_type(query, nil), do: query |
| 387 389 | defp maybe_filter_resource_type(query, type), do: where(query, [e], e.resource_type == ^type) |
| 388 390 | |
| 391 | + defp maybe_filter_resource_uuid(query, nil), do: query |
| 392 | + defp maybe_filter_resource_uuid(query, uuid), do: where(query, [e], e.resource_uuid == ^uuid) |
| 393 | + |
| 389 394 | defp maybe_filter_target(query, nil), do: query |
| 390 395 | defp maybe_filter_target(query, uuid), do: where(query, [e], e.target_uuid == ^uuid) |
| @@ -529,7 +529,18 @@ 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 | - ### V135 - Structured staff skills ⚡ LATEST |
| 532 | + ### V136 - Staff employment history ⚡ LATEST |
| 533 | + - Adds `phoenix_kit_staff_employments` — a per-person history of employment |
| 534 | + spans (employment type, translatable `job_title`, org placement via |
| 535 | + `primary_department_uuid` + a `primary_team_uuid` snapshot, date range with |
| 536 | + `employment_end_date IS NULL` = the open/current span, `work_location`, |
| 537 | + `notes`). A partial unique index enforces one open span per person. The |
| 538 | + matching `phoenix_kit_staff_people` columns are kept as a denormalized mirror |
| 539 | + of the current span (written by the app's `sync_current/1`), not dropped. |
| 540 | + Backfills one open span per existing person from those columns (guarded, |
| 541 | + retry-safe; people with no employment data are skipped). |
| 542 | + |
| 543 | + ### V135 - Structured staff skills |
| 533 544 | - Replaces the free-text `phoenix_kit_staff_people.skills` column with a |
| 534 545 | first-class translatable `phoenix_kit_staff_skills` entity + a |
| 535 546 | `phoenix_kit_staff_person_skills` join. Each skill carries its own |
| @@ -1161,7 +1172,7 @@ defmodule PhoenixKit.Migrations.Postgres do | |
| 1161 1172 | use Ecto.Migration |
| 1162 1173 | |
| 1163 1174 | @initial_version 1 |
| 1164 | - @current_version 135 |
| 1175 | + @current_version 136 |
| 1165 1176 | @default_prefix "public" |
| 1166 1177 | |
| 1167 1178 | @doc false |
| @@ -0,0 +1,137 @@ | |
| 1 | + defmodule PhoenixKit.Migrations.Postgres.V136 do |
| 2 | + @moduledoc """ |
| 3 | + V136: Employment history for `phoenix_kit_staff`. |
| 4 | + |
| 5 | + Replaces the single flat employment span on `phoenix_kit_staff_people` |
| 6 | + (`employment_type` / `employment_start_date` / `employment_end_date` / |
| 7 | + `job_title` / `work_location`) with a first-class **history** of employment |
| 8 | + spans, surfaced as a dedicated tab on the person profile. |
| 9 | + |
| 10 | + Creates `phoenix_kit_staff_employments` — one row per span, carrying the |
| 11 | + employment type, a translatable `job_title`, the org placement at the time |
| 12 | + (`primary_department_uuid` + a `primary_team_uuid` snapshot), the date range |
| 13 | + (`employment_end_date IS NULL` = the current/open span), `work_location`, and |
| 14 | + free-text `notes`. |
| 15 | + |
| 16 | + ## Single open span per person |
| 17 | + |
| 18 | + A partial unique index enforces **at most one open span** (`employment_end_date |
| 19 | + IS NULL`) per person — the "current" employment. The app context closes the |
| 20 | + prior open span when a new one starts. |
| 21 | + |
| 22 | + ## Denormalized "current" mirror |
| 23 | + |
| 24 | + The matching columns already on `phoenix_kit_staff_people` are kept as a |
| 25 | + denormalized mirror of the current (open) span — the app's `sync_current/1` |
| 26 | + writes them in the same transaction as any span change, so existing readers |
| 27 | + (overview org tree, people list) need no join. This migration does NOT drop |
| 28 | + those columns. |
| 29 | + |
| 30 | + ## Backfill |
| 31 | + |
| 32 | + One open span per existing person is seeded from their current columns |
| 33 | + (`employment_type` / `job_title` / dates / `primary_department_uuid` / |
| 34 | + `work_location`), copying any per-locale `job_title` overrides out of the |
| 35 | + person's `translations` JSONB into the span's `translations`. Guarded by a |
| 36 | + `NOT EXISTS` check on the span table so a re-run is a safe no-op; people with |
| 37 | + no employment data at all are skipped (they start with an empty history). |
| 38 | + `primary_team_uuid` is left null on backfill — the person's team comes from the |
| 39 | + many-to-many `team_memberships`, which has no single "primary" to copy. |
| 40 | + """ |
| 41 | + |
| 42 | + use Ecto.Migration |
| 43 | + |
| 44 | + def up(opts) do |
| 45 | + prefix = Map.get(opts, :prefix, "public") |
| 46 | + p = prefix_str(prefix) |
| 47 | + |
| 48 | + # 1. Employment spans — a person's employment history. |
| 49 | + execute(""" |
| 50 | + CREATE TABLE IF NOT EXISTS #{p}phoenix_kit_staff_employments ( |
| 51 | + uuid UUID PRIMARY KEY DEFAULT uuid_generate_v7(), |
| 52 | + staff_person_uuid UUID NOT NULL REFERENCES #{p}phoenix_kit_staff_people(uuid) ON DELETE CASCADE, |
| 53 | + employment_type VARCHAR(50), |
| 54 | + job_title VARCHAR(255), |
| 55 | + translations JSONB NOT NULL DEFAULT '{}'::jsonb, |
| 56 | + primary_department_uuid UUID REFERENCES #{p}phoenix_kit_staff_departments(uuid) ON DELETE SET NULL, |
| 57 | + primary_team_uuid UUID REFERENCES #{p}phoenix_kit_staff_teams(uuid) ON DELETE SET NULL, |
| 58 | + employment_start_date DATE, |
| 59 | + employment_end_date DATE, |
| 60 | + work_location VARCHAR(255), |
| 61 | + notes TEXT, |
| 62 | + inserted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), |
| 63 | + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() |
| 64 | + ) |
| 65 | + """) |
| 66 | + |
| 67 | + execute(""" |
| 68 | + CREATE INDEX IF NOT EXISTS phoenix_kit_staff_employments_person_index |
| 69 | + ON #{p}phoenix_kit_staff_employments (staff_person_uuid) |
| 70 | + """) |
| 71 | + |
| 72 | + # At most one OPEN span (current employment) per person. |
| 73 | + execute(""" |
| 74 | + CREATE UNIQUE INDEX IF NOT EXISTS phoenix_kit_staff_employments_one_open_index |
| 75 | + ON #{p}phoenix_kit_staff_employments (staff_person_uuid) |
| 76 | + WHERE employment_end_date IS NULL |
| 77 | + """) |
| 78 | + |
| 79 | + # 2. Backfill one span per existing person from the current columns. Guarded |
| 80 | + # by NOT EXISTS so a partial re-run is a no-op; people with no employment |
| 81 | + # data are skipped. job_title per-locale overrides are lifted out of the |
| 82 | + # person's translations JSONB into the span's translations (job_title only). |
| 83 | + execute(""" |
| 84 | + INSERT INTO #{p}phoenix_kit_staff_employments |
| 85 | + (staff_person_uuid, employment_type, job_title, translations, |
| 86 | + primary_department_uuid, employment_start_date, employment_end_date, work_location) |
| 87 | + SELECT |
| 88 | + pers.uuid, |
| 89 | + pers.employment_type, |
| 90 | + pers.job_title, |
| 91 | + COALESCE( |
| 92 | + (SELECT jsonb_object_agg(lang, jsonb_build_object('job_title', submap->'job_title')) |
| 93 | + FROM jsonb_each(pers.translations) AS t(lang, submap) |
| 94 | + WHERE submap ? 'job_title'), |
| 95 | + '{}'::jsonb |
| 96 | + ), |
| 97 | + pers.primary_department_uuid, |
| 98 | + pers.employment_start_date, |
| 99 | + pers.employment_end_date, |
| 100 | + pers.work_location |
| 101 | + FROM #{p}phoenix_kit_staff_people pers |
| 102 | + WHERE ( |
| 103 | + pers.employment_type IS NOT NULL |
| 104 | + OR pers.job_title IS NOT NULL |
| 105 | + OR pers.employment_start_date IS NOT NULL |
| 106 | + OR pers.employment_end_date IS NOT NULL |
| 107 | + OR pers.primary_department_uuid IS NOT NULL |
| 108 | + OR pers.work_location IS NOT NULL |
| 109 | + ) |
| 110 | + AND NOT EXISTS ( |
| 111 | + SELECT 1 FROM #{p}phoenix_kit_staff_employments e |
| 112 | + WHERE e.staff_person_uuid = pers.uuid |
| 113 | + ) |
| 114 | + """) |
| 115 | + |
| 116 | + execute("COMMENT ON TABLE #{p}phoenix_kit IS '136'") |
| 117 | + end |
| 118 | + |
| 119 | + @doc """ |
| 120 | + Drops the employments table. |
| 121 | + |
| 122 | + The denormalized `employment_*` / `job_title` / `work_location` columns on |
| 123 | + `phoenix_kit_staff_people` are left intact, so a rollback keeps each person's |
| 124 | + current employment data — only the history is lost. |
| 125 | + """ |
| 126 | + def down(opts) do |
| 127 | + prefix = Map.get(opts, :prefix, "public") |
| 128 | + p = prefix_str(prefix) |
| 129 | + |
| 130 | + execute("DROP TABLE IF EXISTS #{p}phoenix_kit_staff_employments") |
| 131 | + |
| 132 | + execute("COMMENT ON TABLE #{p}phoenix_kit IS '135'") |
| 133 | + end |
| 134 | + |
| 135 | + defp prefix_str("public"), do: "public." |
| 136 | + defp prefix_str(prefix), do: "#{prefix}." |
| 137 | + end |
Loading more files…