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

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…