Packages
phoenix_kit
1.7.105
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
6
files changed
+182
additions
-4
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.104">>}. |
| 3 | + {<<"version">>,<<"1.7.105">>}. |
| 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, |
| @@ -457,6 +457,7 @@ | |
| 457 457 | <<"lib/phoenix_kit/migrations/postgres/v88.ex">>, |
| 458 458 | <<"lib/phoenix_kit/migrations/postgres/v20.ex">>, |
| 459 459 | <<"lib/phoenix_kit/migrations/postgres/v50.ex">>, |
| 460 | + <<"lib/phoenix_kit/migrations/postgres/v110.ex">>, |
| 460 461 | <<"lib/phoenix_kit/migrations/postgres/v18.ex">>, |
| 461 462 | <<"lib/phoenix_kit/migrations/postgres/v47.ex">>, |
| 462 463 | <<"lib/phoenix_kit/migrations/postgres/v107.ex">>, |
| @@ -185,6 +185,79 @@ defmodule PhoenixKit.Migration do | |
| 185 185 | migrator().migrated_version(opts) |
| 186 186 | end |
| 187 187 | |
| 188 | + @doc """ |
| 189 | + Idempotently brings `repo` up to the latest PhoenixKit migration version. |
| 190 | + |
| 191 | + Designed for test helpers and re-runnable boot paths where the |
| 192 | + database is long-lived but the calling process restarts on every |
| 193 | + invocation. |
| 194 | + |
| 195 | + ## Why this exists |
| 196 | + |
| 197 | + The natural-looking pattern |
| 198 | + |
| 199 | + Ecto.Migrator.run(repo, [{0, PhoenixKit.Migration}], :up, all: true) |
| 200 | + |
| 201 | + is broken for re-runnable contexts: `Ecto.Migrator` records "version |
| 202 | + 0 applied" in `schema_migrations` after the first call and filters |
| 203 | + `{0, PhoenixKit.Migration}` out of pending on every subsequent call. |
| 204 | + `PhoenixKit.Migration.up/1` is never re-invoked, so newly-shipped |
| 205 | + Vxxx migrations don't get applied even though PhoenixKit's own marker |
| 206 | + (the comment on the `phoenix_kit` table) is itself idempotent. |
| 207 | + |
| 208 | + `ensure_current/2` works around that by passing a fresh wall-clock |
| 209 | + version (`:os.system_time(:microsecond)`) to `Ecto.Migrator.up/4` on |
| 210 | + every call. Ecto sees a "new" migration each time and invokes the |
| 211 | + inner runner; PhoenixKit's marker then short-circuits if there's |
| 212 | + nothing new to apply. The `schema_migrations` table accumulates one |
| 213 | + row per call — cosmetic noise acceptable for the test-DB use case. |
| 214 | + Microsecond precision keeps the collision and clock-skew windows |
| 215 | + small enough that an NTP correction would have to rewind the clock |
| 216 | + by µs at exactly the wrong moment to hide a newly-shipped migration. |
| 217 | + |
| 218 | + For one-shot production migrations, prefer the documented |
| 219 | + `mix ecto.migrate` path with a hand-rolled migration that calls |
| 220 | + `PhoenixKit.Migration.up/1` directly. |
| 221 | + |
| 222 | + ## Options |
| 223 | + |
| 224 | + Forwarded verbatim to `Ecto.Migrator.up/4` and through to |
| 225 | + `PhoenixKit.Migration.up/1`. Common values: |
| 226 | + |
| 227 | + * `:log` — Ecto-level migration log (`:info` default; `false` to |
| 228 | + silence) |
| 229 | + * `:prefix` — runs PhoenixKit's tables under a non-default schema |
| 230 | + |
| 231 | + ## Return contract |
| 232 | + |
| 233 | + Returns `:ok` on success. Failures (advisory-lock contention, |
| 234 | + migration crashes, connection errors) propagate as raises from |
| 235 | + `Ecto.Migrator.up/4`; `ensure_current/2` does not wrap them in |
| 236 | + `{:error, _}`. |
| 237 | + |
| 238 | + ## Example |
| 239 | + |
| 240 | + # In test/test_helper.exs |
| 241 | + PhoenixKit.Migration.ensure_current(MyApp.Test.Repo, log: false) |
| 242 | + """ |
| 243 | + @spec ensure_current(Ecto.Repo.t(), keyword()) :: :ok |
| 244 | + def ensure_current(repo, opts \\ []) do |
| 245 | + # Microsecond precision (rather than millisecond) shrinks the |
| 246 | + # collision and clock-skew windows by 1000x. Two concurrent calls |
| 247 | + # within the same microsecond would still collide, but in practice |
| 248 | + # `ensure_current/2` runs once per `mix test` invocation. Bigint- |
| 249 | + # safe (Postgres `bigint` covers ~9 quintillion microseconds, ~292 |
| 250 | + # years). |
| 251 | + Ecto.Migrator.up( |
| 252 | + repo, |
| 253 | + :os.system_time(:microsecond), |
| 254 | + PhoenixKit.Migration.Runner, |
| 255 | + opts |
| 256 | + ) |
| 257 | + |
| 258 | + :ok |
| 259 | + end |
| 260 | + |
| 188 261 | defp migrator do |
| 189 262 | case repo().__adapter__() do |
| 190 263 | Ecto.Adapters.Postgres -> PhoenixKit.Migrations.Postgres |
| @@ -194,3 +267,39 @@ defmodule PhoenixKit.Migration do | |
| 194 267 | end |
| 195 268 | end |
| 196 269 | end |
| 270 | + |
| 271 | + defmodule PhoenixKit.Migration.Runner do |
| 272 | + @moduledoc false |
| 273 | + # Static `Ecto.Migration` wrapper consumed by |
| 274 | + # `PhoenixKit.Migration.ensure_current/2`. Lives at module scope (not |
| 275 | + # an anonymous one defined per call) so `Ecto.Migrator.up/4` can |
| 276 | + # resolve `up/0` and `down/0` against a known module name. |
| 277 | + # |
| 278 | + # `prefix/0` is imported by `use Ecto.Migration` and reads the |
| 279 | + # current migration's prefix from the runner's process state. When |
| 280 | + # `ensure_current/2` is called with `prefix: "auth"`, Ecto.Migrator |
| 281 | + # propagates that into the runner context; without forwarding it |
| 282 | + # back into `PhoenixKit.Migration.up/1` here, the inner migrator |
| 283 | + # would default to `"public"` and silently apply the migrations to |
| 284 | + # the wrong schema. |
| 285 | + |
| 286 | + use Ecto.Migration |
| 287 | + |
| 288 | + def up, do: PhoenixKit.Migration.up(runner_opts(prefix())) |
| 289 | + def down, do: PhoenixKit.Migration.down(runner_opts(prefix())) |
| 290 | + |
| 291 | + # Pure transform of the runner-context prefix into opts threaded to |
| 292 | + # `PhoenixKit.Migration.up/1` / `down/1`. Split out of `up/0` and |
| 293 | + # `down/0` so it can be regression-tested without standing up a real |
| 294 | + # `Ecto.Migration.Runner` process — see |
| 295 | + # `test/phoenix_kit/migration_test.exs`. |
| 296 | + # |
| 297 | + # `prefix/0` returns nil when no `:prefix` opt was passed to |
| 298 | + # `Ecto.Migrator.up/4`. Forwarding `prefix: nil` to PhoenixKit's |
| 299 | + # migrator would override the `"public"` default in `with_defaults/2` |
| 300 | + # and crash inside `String.replace/4`. Only thread `:prefix` when it's |
| 301 | + # actually set. |
| 302 | + @doc false |
| 303 | + def runner_opts(nil), do: [] |
| 304 | + def runner_opts(prefix), do: [prefix: prefix] |
| 305 | + end |
| @@ -529,7 +529,17 @@ 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 | - ### V109 - Rename Customer Service module to Customer Support ⚡ LATEST |
| 532 | + ### V110 - Add `language` to Document Creator templates ⚡ LATEST |
| 533 | + - Adds nullable `language VARCHAR(10)` to `phoenix_kit_doc_templates` so |
| 534 | + each template can be tagged with a single locale (full code, e.g. |
| 535 | + `en-US`, `et-EE`). Parent apps read it back via the public listing |
| 536 | + API to fill template variables in the matching language. |
| 537 | + - Documents intentionally do not get a language column — they inherit |
| 538 | + from `template_uuid → templates.language`. |
| 539 | + - Existing rows survive without a backfill; the form pre-selects the |
| 540 | + project's primary language when creating new templates. |
| 541 | + |
| 542 | + ### V109 - Rename Customer Service module to Customer Support |
| 533 543 | - Renames 7 settings keys from `customer_service_*` → `customer_support_*` |
| 534 544 | - Renames `auto_granted_perm:customer_service` → `auto_granted_perm:customer_support` |
| 535 545 | - Updates `phoenix_kit_role_permissions.module_key` from `customer_service` → `customer_support` |
| @@ -813,7 +823,7 @@ defmodule PhoenixKit.Migrations.Postgres do | |
| 813 823 | use Ecto.Migration |
| 814 824 | |
| 815 825 | @initial_version 1 |
| 816 | - @current_version 109 |
| 826 | + @current_version 110 |
| 817 827 | @default_prefix "public" |
| 818 828 | |
| 819 829 | @doc false |
| @@ -0,0 +1,58 @@ | |
| 1 | + defmodule PhoenixKit.Migrations.Postgres.V110 do |
| 2 | + @moduledoc """ |
| 3 | + V110: Add `language` column to `phoenix_kit_doc_templates`. |
| 4 | + |
| 5 | + Each Document Creator template represents a single Google Doc, which is |
| 6 | + inherently single-language (translations live in separate templates). |
| 7 | + Storing the locale on the template lets parent apps fill template variables |
| 8 | + in the matching language regardless of the admin's UI locale. |
| 9 | + |
| 10 | + - `language` (VARCHAR(10), nullable) — full locale code (`"en-US"`, |
| 11 | + `"et-EE"`, `"ja"`, etc.) sourced from `PhoenixKit.Modules.Languages`. |
| 12 | + Nullable so existing rows survive without a backfill; the form |
| 13 | + pre-selects the project's primary language for new templates and |
| 14 | + callers can update the value at any time. |
| 15 | + |
| 16 | + Documents intentionally do not get a language column — they inherit |
| 17 | + language from `template_uuid → templates.language`. |
| 18 | + |
| 19 | + All operations are idempotent. |
| 20 | + """ |
| 21 | + |
| 22 | + use Ecto.Migration |
| 23 | + |
| 24 | + def up(opts) do |
| 25 | + prefix = Map.get(opts, :prefix, "public") |
| 26 | + p = prefix_str(prefix) |
| 27 | + schema = if prefix == "public", do: "public", else: prefix |
| 28 | + |
| 29 | + execute(""" |
| 30 | + DO $$ |
| 31 | + BEGIN |
| 32 | + IF NOT EXISTS ( |
| 33 | + SELECT FROM information_schema.columns |
| 34 | + WHERE table_schema = '#{schema}' |
| 35 | + AND table_name = 'phoenix_kit_doc_templates' |
| 36 | + AND column_name = 'language' |
| 37 | + ) THEN |
| 38 | + ALTER TABLE #{p}phoenix_kit_doc_templates |
| 39 | + ADD COLUMN language VARCHAR(10); |
| 40 | + END IF; |
| 41 | + END $$; |
| 42 | + """) |
| 43 | + |
| 44 | + execute("COMMENT ON TABLE #{p}phoenix_kit IS '110'") |
| 45 | + end |
| 46 | + |
| 47 | + def down(opts) do |
| 48 | + prefix = Map.get(opts, :prefix, "public") |
| 49 | + p = prefix_str(prefix) |
| 50 | + |
| 51 | + execute("ALTER TABLE #{p}phoenix_kit_doc_templates DROP COLUMN IF EXISTS language") |
| 52 | + |
| 53 | + execute("COMMENT ON TABLE #{p}phoenix_kit IS '109'") |
| 54 | + end |
| 55 | + |
| 56 | + defp prefix_str("public"), do: "public." |
| 57 | + defp prefix_str(prefix), do: "#{prefix}." |
| 58 | + end |
Loading more files…