From 0a0846e067cf0d96f0c820d60803d5bd62c0833c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Jan 2026 08:20:39 +0100 Subject: [PATCH 01/13] Build(deps): Bump the all-dependencies group with 3 updates (#667) Bumps the all-dependencies group with 3 updates: [credo](https://github.com/rrrene/credo), [ecto_sql](https://github.com/elixir-ecto/ecto_sql) and [waffle](https://github.com/elixir-waffle/waffle). Updates `credo` from 1.7.14 to 1.7.15 - [Release notes](https://github.com/rrrene/credo/releases) - [Changelog](https://github.com/rrrene/credo/blob/master/CHANGELOG.md) - [Commits](https://github.com/rrrene/credo/compare/v1.7.14...v1.7.15) Updates `ecto_sql` from 3.13.3 to 3.13.4 - [Changelog](https://github.com/elixir-ecto/ecto_sql/blob/master/CHANGELOG.md) - [Commits](https://github.com/elixir-ecto/ecto_sql/compare/v3.13.3...v3.13.4) Updates `waffle` from 1.1.9 to 1.1.10 - [Changelog](https://github.com/elixir-waffle/waffle/blob/master/CHANGELOG.md) - [Commits](https://github.com/elixir-waffle/waffle/compare/v1.1.9...v1.1.10) --- updated-dependencies: - dependency-name: credo dependency-version: 1.7.15 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: all-dependencies - dependency-name: ecto_sql dependency-version: 3.13.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-dependencies - dependency-name: waffle dependency-version: 1.1.10 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.exs | 4 ++-- mix.lock | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mix.exs b/mix.exs index ed94acf1..f5001dd7 100644 --- a/mix.exs +++ b/mix.exs @@ -61,7 +61,7 @@ defmodule Mindwendel.MixProject do {:dart_sass, "0.7.0", runtime: Mix.env() == :dev}, {:bypass, "2.1.0", only: :test}, {:csv, "3.2.2"}, - {:ecto_sql, "3.13.3"}, + {:ecto_sql, "3.13.4"}, {:floki, "0.38.0"}, {:gettext, "0.26.2"}, {:httpoison, "2.3.0"}, @@ -71,7 +71,7 @@ defmodule Mindwendel.MixProject do {:cowboy, "2.14.2"}, {:postgrex, "0.21.1"}, {:sobelow, "0.14.1", only: [:dev, :test], runtime: false}, - {:credo, "1.7.14", only: [:dev, :test], runtime: false}, + {:credo, "1.7.15", only: [:dev, :test], runtime: false}, {:excoveralls, "~> 0.18"}, {:telemetry_metrics, "1.1.0"}, {:telemetry_poller, "1.3.0"}, diff --git a/mix.lock b/mix.lock index 8f6df1c7..c2eda871 100644 --- a/mix.lock +++ b/mix.lock @@ -9,14 +9,14 @@ "cowboy": {:hex, :cowboy, "2.14.2", "4008be1df6ade45e4f2a4e9e2d22b36d0b5aba4e20b0a0d7049e28d124e34847", [:make, :rebar3], [{:cowlib, ">= 2.16.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, ">= 1.8.0 and < 3.0.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "569081da046e7b41b5df36aa359be71a0c8874e5b9cff6f747073fc57baf1ab9"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, "cowlib": {:hex, :cowlib, "2.16.0", "54592074ebbbb92ee4746c8a8846e5605052f29309d3a873468d76cdf932076f", [:make, :rebar3], [], "hexpm", "7f478d80d66b747344f0ea7708c187645cfcc08b11aa424632f78e25bf05db51"}, - "credo": {:hex, :credo, "1.7.14", "c7e75216cea8d978ba8c60ed9dede4cc79a1c99a266c34b3600dd2c33b96bc92", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "12a97d6bb98c277e4fb1dff45aaf5c137287416009d214fb46e68147bd9e0203"}, + "credo": {:hex, :credo, "1.7.15", "283da72eeb2fd3ccf7248f4941a0527efb97afa224bcdef30b4b580bc8258e1c", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "291e8645ea3fea7481829f1e1eb0881b8395db212821338e577a90bf225c5607"}, "csv": {:hex, :csv, "3.2.2", "452f96414b39a176b7c390af6d8b78f15130dc6167fe3b836729131f515d843e", [:mix], [], "hexpm", "cbf256ff74a3fa01d9ec420d07b19c90d410ed9fe5b6d6e1bc7662edf35bc574"}, "dart_sass": {:hex, :dart_sass, "0.7.0", "7979e056cb74fd6843e1c72db763cffc7726a9192a657735b7d24c0d9c26a1ce", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "4a8e70bca41aa00846398abdf5ad8a64d7907a0f7bf40145cd2e40d5971629f2"}, "db_connection": {:hex, :db_connection, "2.8.1", "9abdc1e68c34c6163f6fb96a96532272d13ad7ca45262156ae8b7ec6d9dc4bec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a61a3d489b239d76f326e03b98794fb8e45168396c925ef25feb405ed09da8fd"}, "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, "dotenvy": {:hex, :dotenvy, "1.1.0", "316aee89c11a4ec8be3d74a69d17d17ea2e21e633e0cac9f155cf420e237ccb4", [:mix], [], "hexpm", "0519bda67fdfa1c22279c2654b2f292485f0caae7360fe29205f74f28a93df18"}, "ecto": {:hex, :ecto, "3.13.5", "9d4a69700183f33bf97208294768e561f5c7f1ecf417e0fa1006e4a91713a834", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "df9efebf70cf94142739ba357499661ef5dbb559ef902b68ea1f3c1fabce36de"}, - "ecto_sql": {:hex, :ecto_sql, "3.13.3", "81f7067dd1951081888529002dbc71f54e5e891b69c60195040ea44697e1104a", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5751caea36c8f5dd0d1de6f37eceffea19d10bd53f20e5bbe31c45f2efc8944a"}, + "ecto_sql": {:hex, :ecto_sql, "3.13.4", "b6e9d07557ddba62508a9ce4a484989a5bb5e9a048ae0e695f6d93f095c25d60", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2b38cf0749ca4d1c5a8bcbff79bbe15446861ca12a61f9fba604486cb6b62a14"}, "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"}, "esbuild": {:hex, :esbuild, "0.10.0", "b0aa3388a1c23e727c5a3e7427c932d89ee791746b0081bbe56103e9ef3d291f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "468489cda427b974a7cc9f03ace55368a83e1a7be12fba7e30969af78e5f8c70"}, "ex_aws": {:hex, :ex_aws, "2.6.1", "194582c7b09455de8a5ab18a0182e6dd937d53df82be2e63c619d01bddaccdfa", [:mix], [{:configparser_ex, "~> 5.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:req, "~> 0.5.10 or ~> 0.6 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "67842a08c90a1d9a09dbe4ac05754175c7ca253abe4912987c759395d4bd9d26"}, @@ -78,7 +78,7 @@ "tzdata": {:hex, :tzdata, "1.1.3", "b1cef7bb6de1de90d4ddc25d33892b32830f907e7fc2fccd1e7e22778ab7dfbc", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "d4ca85575a064d29d4e94253ee95912edfb165938743dbf002acdf0dcecb0c28"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.1", "a48703a25c170eedadca83b11e88985af08d35f37c6f664d6dcfb106a97782fc", [:rebar3], [], "hexpm", "b3a917854ce3ae233619744ad1e0102e05673136776fb2fa76234f3e03b23642"}, "uniq": {:hex, :uniq, "0.6.1", "369660ecbc19051be526df3aa85dc393af5f61f45209bce2fa6d7adb051ae03c", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "6426c34d677054b3056947125b22e0daafd10367b85f349e24ac60f44effb916"}, - "waffle": {:hex, :waffle, "1.1.9", "8ce5ca9e59fa5491da67a2df57b8711d93223df3c3e5c21ad2acdedc41a0f51a", [:mix], [{:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:ex_aws_s3, "~> 2.1", [hex: :ex_aws_s3, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "307c63cfdfb4624e7c423868a128ccfcb0e5291ae73a9deecb3a10b7a3eb277c"}, + "waffle": {:hex, :waffle, "1.1.10", "0f847ed6f95349af258a90f0f70ffea02b3d3729c4eb78f6fae7bf776e91779e", [:mix], [{:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:ex_aws_s3, "~> 2.1", [hex: :ex_aws_s3, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "859ba6377b78f0a51bc9596227b194f26241efbbd408bd217450c22b0f359cc4"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, "websock_adapter": {:hex, :websock_adapter, "0.5.9", "43dc3ba6d89ef5dec5b1d0a39698436a1e856d000d84bf31a3149862b01a287f", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "5534d5c9adad3c18a0f58a9371220d75a803bf0b9a3d87e6fe072faaeed76a08"}, } From ac131989c0d40485d0d298025afa58b2f12fff42 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Jan 2026 12:37:59 +0100 Subject: [PATCH 02/13] Build(deps): Bump postgrex in the all-dependencies group (#669) Bumps the all-dependencies group with 1 update: [postgrex](https://github.com/elixir-ecto/postgrex). Updates `postgrex` from 0.21.1 to 0.22.0 - [Release notes](https://github.com/elixir-ecto/postgrex/releases) - [Changelog](https://github.com/elixir-ecto/postgrex/blob/v0.22.0/CHANGELOG.md) - [Commits](https://github.com/elixir-ecto/postgrex/compare/v0.21.1...v0.22.0) --- updated-dependencies: - dependency-name: postgrex dependency-version: 0.22.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.exs | 2 +- mix.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.exs b/mix.exs index f5001dd7..09525ff7 100644 --- a/mix.exs +++ b/mix.exs @@ -69,7 +69,7 @@ defmodule Mindwendel.MixProject do {:oban, "2.20.2"}, {:plug_cowboy, "2.7.5"}, {:cowboy, "2.14.2"}, - {:postgrex, "0.21.1"}, + {:postgrex, "0.22.0"}, {:sobelow, "0.14.1", only: [:dev, :test], runtime: false}, {:credo, "1.7.15", only: [:dev, :test], runtime: false}, {:excoveralls, "~> 0.18"}, diff --git a/mix.lock b/mix.lock index c2eda871..39ecbaf3 100644 --- a/mix.lock +++ b/mix.lock @@ -12,7 +12,7 @@ "credo": {:hex, :credo, "1.7.15", "283da72eeb2fd3ccf7248f4941a0527efb97afa224bcdef30b4b580bc8258e1c", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "291e8645ea3fea7481829f1e1eb0881b8395db212821338e577a90bf225c5607"}, "csv": {:hex, :csv, "3.2.2", "452f96414b39a176b7c390af6d8b78f15130dc6167fe3b836729131f515d843e", [:mix], [], "hexpm", "cbf256ff74a3fa01d9ec420d07b19c90d410ed9fe5b6d6e1bc7662edf35bc574"}, "dart_sass": {:hex, :dart_sass, "0.7.0", "7979e056cb74fd6843e1c72db763cffc7726a9192a657735b7d24c0d9c26a1ce", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "4a8e70bca41aa00846398abdf5ad8a64d7907a0f7bf40145cd2e40d5971629f2"}, - "db_connection": {:hex, :db_connection, "2.8.1", "9abdc1e68c34c6163f6fb96a96532272d13ad7ca45262156ae8b7ec6d9dc4bec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a61a3d489b239d76f326e03b98794fb8e45168396c925ef25feb405ed09da8fd"}, + "db_connection": {:hex, :db_connection, "2.9.0", "a6a97c5c958a2d7091a58a9be40caf41ab496b0701d21e1d1abff3fa27a7f371", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "17d502eacaf61829db98facf6f20808ed33da6ccf495354a41e64fe42f9c509c"}, "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, "dotenvy": {:hex, :dotenvy, "1.1.0", "316aee89c11a4ec8be3d74a69d17d17ea2e21e633e0cac9f155cf420e237ccb4", [:mix], [], "hexpm", "0519bda67fdfa1c22279c2654b2f292485f0caae7360fe29205f74f28a93df18"}, "ecto": {:hex, :ecto, "3.13.5", "9d4a69700183f33bf97208294768e561f5c7f1ecf417e0fa1006e4a91713a834", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "df9efebf70cf94142739ba357499661ef5dbb559ef902b68ea1f3c1fabce36de"}, @@ -61,7 +61,7 @@ "plug": {:hex, :plug, "1.19.1", "09bac17ae7a001a68ae393658aa23c7e38782be5c5c00c80be82901262c394c0", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "560a0017a8f6d5d30146916862aaf9300b7280063651dd7e532b8be168511e62"}, "plug_cowboy": {:hex, :plug_cowboy, "2.7.5", "261f21b67aea8162239b2d6d3b4c31efde4daa22a20d80b19c2c0f21b34b270e", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "20884bf58a90ff5a5663420f5d2c368e9e15ed1ad5e911daf0916ea3c57f77ac"}, "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"}, - "postgrex": {:hex, :postgrex, "0.21.1", "2c5cc830ec11e7a0067dd4d623c049b3ef807e9507a424985b8dcf921224cd88", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "27d8d21c103c3cc68851b533ff99eef353e6a0ff98dc444ea751de43eb48bdac"}, + "postgrex": {:hex, :postgrex, "0.22.0", "fb027b58b6eab1f6de5396a2abcdaaeb168f9ed4eccbb594e6ac393b02078cbd", [:mix], [{:db_connection, "~> 2.9", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "a68c4261e299597909e03e6f8ff5a13876f5caadaddd0d23af0d0a61afcc5d84"}, "ranch": {:hex, :ranch, "1.8.1", "208169e65292ac5d333d6cdbad49388c1ae198136e4697ae2f474697140f201c", [:make, :rebar3], [], "hexpm", "aed58910f4e21deea992a67bf51632b6d60114895eb03bb392bb733064594dd0"}, "req": {:hex, :req, "0.5.15", "662020efb6ea60b9f0e0fac9be88cd7558b53fe51155a2d9899de594f9906ba9", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "a6513a35fad65467893ced9785457e91693352c70b58bbc045b47e5eb2ef0c53"}, "req_llm": {:hex, :req_llm, "1.0.0-rc.4", "557aebf2697703763b1958474e3ee9c824925483442f936d99713d5723eb4441", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:jido_keys, "~> 1.0", [hex: :jido_keys, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.1", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:server_sent_events, "~> 0.2", [hex: :server_sent_events, repo: "hexpm", optional: false]}, {:splode, "~> 0.2.3", [hex: :splode, repo: "hexpm", optional: false]}, {:typed_struct, "~> 0.3.0", [hex: :typed_struct, repo: "hexpm", optional: false]}, {:uniq, "~> 0.6", [hex: :uniq, repo: "hexpm", optional: false]}], "hexpm", "ab506a96acb5ab79a8478d334641f74dc48d24d40c2b6863e4722fcebdb0a81c"}, From 1d5d7ee9cddc612d7ce8e80717af3a18382efecd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Mar 2026 08:41:58 +0100 Subject: [PATCH 03/13] Build(deps): Bump docker/login-action from 3 to 4 (#685) Bumps [docker/login-action](https://github.com/docker/login-action) from 3 to 4. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/v3...v4) --- updated-dependencies: - dependency-name: docker/login-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/on_push_main_publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/on_push_main_publish.yml b/.github/workflows/on_push_main_publish.yml index c8b337ed..a1d28527 100644 --- a/.github/workflows/on_push_main_publish.yml +++ b/.github/workflows/on_push_main_publish.yml @@ -38,7 +38,7 @@ jobs: # Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here. - name: Log in to the Container registry - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} From 4c5286b4e3f92ef9870ace9430da099f68d71e49 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Mar 2026 09:17:50 +0100 Subject: [PATCH 04/13] Build(deps): Bump docker/build-push-action from 6 to 7 (#683) Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6 to 7. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v6...v7) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/on_push_main_publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/on_push_main_publish.yml b/.github/workflows/on_push_main_publish.yml index a1d28527..8d406fe1 100644 --- a/.github/workflows/on_push_main_publish.yml +++ b/.github/workflows/on_push_main_publish.yml @@ -64,7 +64,7 @@ jobs: # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository. # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step. - name: Build and push Docker image - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: push: true provenance: false From b472420b2109fee3af77a6b92de8f5d1521b92a1 Mon Sep 17 00:00:00 2001 From: JannikStreek Date: Thu, 19 Mar 2026 10:56:12 +0100 Subject: [PATCH 05/13] update deps (#686) --- docker-compose.dev.yml | 11 --- .../services/idea_clustering_service.ex | 70 +++++++------- lib/mindwendel/services/idea_service.ex | 96 ++++++++++--------- mix.exs | 16 ++-- mix.lock | 24 ++--- .../show_ai_clustering_test.exs | 2 +- 6 files changed, 108 insertions(+), 111 deletions(-) delete mode 100644 docker-compose.dev.yml diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml deleted file mode 100644 index ce3e9cd0..00000000 --- a/docker-compose.dev.yml +++ /dev/null @@ -1,11 +0,0 @@ -services: - app: - build: - context: ${DEV_BUILD_CONTEXT} - dockerfile: Dockerfile - target: development - tty: true - stdin_open: true - volumes: - - .:/home/node/app - - ${DEV_BUILD_CONTEXT}/.claude:/home/node/app/.claude \ No newline at end of file diff --git a/lib/mindwendel/services/idea_clustering_service.ex b/lib/mindwendel/services/idea_clustering_service.ex index 857d1e0e..d34672e7 100644 --- a/lib/mindwendel/services/idea_clustering_service.ex +++ b/lib/mindwendel/services/idea_clustering_service.ex @@ -55,39 +55,43 @@ defmodule Mindwendel.Services.IdeaClusteringService do {:ok, :skipped} true -> - locale = Gettext.get_locale(MindwendelWeb.Gettext) - label_payload = build_label_payload(brainstorming.labels) - idea_payload = build_idea_payload(ideas) - - with {:ok, raw_assignments} <- - ChatCompletionsService.classify_labels( - brainstorming.name, - label_payload, - idea_payload, - locale - ), - {:ok, assignments} <- normalize_assignments(raw_assignments) do - Logger.debug(fn -> - truncated = - raw_assignments - |> inspect(limit: 15, printable_limit: 300, width: 80) - - "AI clustering raw assignments: #{truncated}" - end) - - handle_assignments(brainstorming, ideas, assignments) - else - {:error, %{} = validation_errors} -> - Logger.error( - "AI clustering returned invalid assignments for #{brainstorming.id}: #{inspect(validation_errors)}" - ) - - {:error, {:invalid_assignments, validation_errors}} - - {:error, reason} -> - Logger.error("AI clustering failed for #{brainstorming.id}: #{inspect(reason)}") - {:error, reason} - end + classify_and_assign(brainstorming, ideas) + end + end + + defp classify_and_assign(brainstorming, ideas) do + locale = Gettext.get_locale(MindwendelWeb.Gettext) + label_payload = build_label_payload(brainstorming.labels) + idea_payload = build_idea_payload(ideas) + + with {:ok, raw_assignments} <- + ChatCompletionsService.classify_labels( + brainstorming.name, + label_payload, + idea_payload, + locale + ), + {:ok, assignments} <- normalize_assignments(raw_assignments) do + Logger.debug(fn -> + truncated = + raw_assignments + |> inspect(limit: 15, printable_limit: 300, width: 80) + + "AI clustering raw assignments: #{truncated}" + end) + + handle_assignments(brainstorming, ideas, assignments) + else + {:error, %{} = validation_errors} -> + Logger.error( + "AI clustering returned invalid assignments for #{brainstorming.id}: #{inspect(validation_errors)}" + ) + + {:error, {:invalid_assignments, validation_errors}} + + {:error, reason} -> + Logger.error("AI clustering failed for #{brainstorming.id}: #{inspect(reason)}") + {:error, reason} end end diff --git a/lib/mindwendel/services/idea_service.ex b/lib/mindwendel/services/idea_service.ex index 449bdc16..9f21615f 100644 --- a/lib/mindwendel/services/idea_service.ex +++ b/lib/mindwendel/services/idea_service.ex @@ -38,57 +38,61 @@ defmodule Mindwendel.Services.IdeaService do {:ok, list(Mindwendel.Brainstormings.Idea.t())} | {:error, atom()} def add_ideas_to_brainstorming(brainstorming) do if idea_generation_enabled?() do - # Get current locale from Gettext for language-specific idea generation - locale = Gettext.get_locale(MindwendelWeb.Gettext) - - # Get existing ideas to avoid duplicates - existing_ideas = Ideas.list_ideas_for_brainstorming(brainstorming.id) - - with {:ok, default_lane_id} <- get_first_lane_id(brainstorming), - {:ok, generated_ideas} <- - ChatCompletionsService.generate_ideas( - brainstorming.name, - brainstorming.lanes, - existing_ideas, - locale - ) do - # Build a map of valid lane IDs for quick lookup - valid_lane_ids = MapSet.new(Enum.map(brainstorming.lanes, & &1.id)) - - results = - Enum.map(generated_ideas, fn generated_idea -> - # Use the lane_id from AI if valid, otherwise fall back to default - lane_id = resolve_lane_id(generated_idea["lane_id"], valid_lane_ids, default_lane_id) - - Ideas.create_idea(%{ - username: @ai_username, - body: generated_idea["idea"], - brainstorming_id: brainstorming.id, - lane_id: lane_id - }) - end) - - # Filter out failed creations and return only successful ones - {successful, failed} = Enum.split_with(results, &match?({:ok, _}, &1)) - - unless Enum.empty?(failed) do - Logger.warning( - "Failed to create #{length(failed)} ideas for brainstorming #{brainstorming.id}: #{inspect(failed)}" - ) - end - - successful_ideas = Enum.map(successful, fn {:ok, idea} -> idea end) - {:ok, successful_ideas} - else - {:error, reason} -> - Logger.warning("Failed to generate ideas: #{inspect(reason)}") - {:error, reason} - end + do_add_ideas_to_brainstorming(brainstorming) else {:ok, []} end end + defp do_add_ideas_to_brainstorming(brainstorming) do + # Get current locale from Gettext for language-specific idea generation + locale = Gettext.get_locale(MindwendelWeb.Gettext) + + # Get existing ideas to avoid duplicates + existing_ideas = Ideas.list_ideas_for_brainstorming(brainstorming.id) + + with {:ok, default_lane_id} <- get_first_lane_id(brainstorming), + {:ok, generated_ideas} <- + ChatCompletionsService.generate_ideas( + brainstorming.name, + brainstorming.lanes, + existing_ideas, + locale + ) do + # Build a map of valid lane IDs for quick lookup + valid_lane_ids = MapSet.new(Enum.map(brainstorming.lanes, & &1.id)) + + results = + Enum.map(generated_ideas, fn generated_idea -> + # Use the lane_id from AI if valid, otherwise fall back to default + lane_id = resolve_lane_id(generated_idea["lane_id"], valid_lane_ids, default_lane_id) + + Ideas.create_idea(%{ + username: @ai_username, + body: generated_idea["idea"], + brainstorming_id: brainstorming.id, + lane_id: lane_id + }) + end) + + # Filter out failed creations and return only successful ones + {successful, failed} = Enum.split_with(results, &match?({:ok, _}, &1)) + + unless Enum.empty?(failed) do + Logger.warning( + "Failed to create #{length(failed)} ideas for brainstorming #{brainstorming.id}: #{inspect(failed)}" + ) + end + + successful_ideas = Enum.map(successful, fn {:ok, idea} -> idea end) + {:ok, successful_ideas} + else + {:error, reason} -> + Logger.warning("Failed to generate ideas: #{inspect(reason)}") + {:error, reason} + end + end + defp resolve_lane_id(nil, _valid_lane_ids, default_lane_id), do: default_lane_id defp resolve_lane_id(lane_id, valid_lane_ids, default_lane_id) do diff --git a/mix.exs b/mix.exs index 09525ff7..3968e7b7 100644 --- a/mix.exs +++ b/mix.exs @@ -52,26 +52,26 @@ defmodule Mindwendel.MixProject do # Type `mix help deps` for examples and options. defp deps do [ - {:phoenix, "1.8.3"}, + {:phoenix, "1.8.5"}, {:phoenix_ecto, "4.7.0"}, {:phoenix_html, "~> 4.1"}, {:phoenix_live_reload, "1.6.2", only: :dev}, - {:phoenix_live_view, "~> 1.1.13"}, + {:phoenix_live_view, "~> 1.1.27"}, {:esbuild, "0.10.0", runtime: Mix.env() == :dev}, {:dart_sass, "0.7.0", runtime: Mix.env() == :dev}, {:bypass, "2.1.0", only: :test}, {:csv, "3.2.2"}, - {:ecto_sql, "3.13.4"}, - {:floki, "0.38.0"}, + {:ecto_sql, "3.13.5"}, + {:floki, "0.38.1"}, {:gettext, "0.26.2"}, {:httpoison, "2.3.0"}, {:jason, "1.4.4"}, - {:oban, "2.20.2"}, - {:plug_cowboy, "2.7.5"}, + {:oban, "2.20.3"}, + {:plug_cowboy, "2.8.0"}, {:cowboy, "2.14.2"}, {:postgrex, "0.22.0"}, {:sobelow, "0.14.1", only: [:dev, :test], runtime: false}, - {:credo, "1.7.15", only: [:dev, :test], runtime: false}, + {:credo, "1.7.17", only: [:dev, :test], runtime: false}, {:excoveralls, "~> 0.18"}, {:telemetry_metrics, "1.1.0"}, {:telemetry_poller, "1.3.0"}, @@ -84,7 +84,7 @@ defmodule Mindwendel.MixProject do {:ex_aws_s3, "2.5.9"}, {:cloak, "1.1.4"}, {:lazy_html, ">= 0.1.0", only: :test}, - {:openai_ex, "0.9.18"}, + {:openai_ex, "0.9.20"}, {:mox, "1.2.0", only: :test} ] end diff --git a/mix.lock b/mix.lock index 39ecbaf3..70d68204 100644 --- a/mix.lock +++ b/mix.lock @@ -9,14 +9,14 @@ "cowboy": {:hex, :cowboy, "2.14.2", "4008be1df6ade45e4f2a4e9e2d22b36d0b5aba4e20b0a0d7049e28d124e34847", [:make, :rebar3], [{:cowlib, ">= 2.16.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, ">= 1.8.0 and < 3.0.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "569081da046e7b41b5df36aa359be71a0c8874e5b9cff6f747073fc57baf1ab9"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, "cowlib": {:hex, :cowlib, "2.16.0", "54592074ebbbb92ee4746c8a8846e5605052f29309d3a873468d76cdf932076f", [:make, :rebar3], [], "hexpm", "7f478d80d66b747344f0ea7708c187645cfcc08b11aa424632f78e25bf05db51"}, - "credo": {:hex, :credo, "1.7.15", "283da72eeb2fd3ccf7248f4941a0527efb97afa224bcdef30b4b580bc8258e1c", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "291e8645ea3fea7481829f1e1eb0881b8395db212821338e577a90bf225c5607"}, + "credo": {:hex, :credo, "1.7.17", "f92b6aa5b26301eaa5a35e4d48ebf5aa1e7094ac00ae38f87086c562caf8a22f", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1eb5645c835f0b6c9b5410f94b5a185057bcf6d62a9c2b476da971cde8749645"}, "csv": {:hex, :csv, "3.2.2", "452f96414b39a176b7c390af6d8b78f15130dc6167fe3b836729131f515d843e", [:mix], [], "hexpm", "cbf256ff74a3fa01d9ec420d07b19c90d410ed9fe5b6d6e1bc7662edf35bc574"}, "dart_sass": {:hex, :dart_sass, "0.7.0", "7979e056cb74fd6843e1c72db763cffc7726a9192a657735b7d24c0d9c26a1ce", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "4a8e70bca41aa00846398abdf5ad8a64d7907a0f7bf40145cd2e40d5971629f2"}, "db_connection": {:hex, :db_connection, "2.9.0", "a6a97c5c958a2d7091a58a9be40caf41ab496b0701d21e1d1abff3fa27a7f371", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "17d502eacaf61829db98facf6f20808ed33da6ccf495354a41e64fe42f9c509c"}, "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, "dotenvy": {:hex, :dotenvy, "1.1.0", "316aee89c11a4ec8be3d74a69d17d17ea2e21e633e0cac9f155cf420e237ccb4", [:mix], [], "hexpm", "0519bda67fdfa1c22279c2654b2f292485f0caae7360fe29205f74f28a93df18"}, "ecto": {:hex, :ecto, "3.13.5", "9d4a69700183f33bf97208294768e561f5c7f1ecf417e0fa1006e4a91713a834", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "df9efebf70cf94142739ba357499661ef5dbb559ef902b68ea1f3c1fabce36de"}, - "ecto_sql": {:hex, :ecto_sql, "3.13.4", "b6e9d07557ddba62508a9ce4a484989a5bb5e9a048ae0e695f6d93f095c25d60", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2b38cf0749ca4d1c5a8bcbff79bbe15446861ca12a61f9fba604486cb6b62a14"}, + "ecto_sql": {:hex, :ecto_sql, "3.13.5", "2f8282b2ad97bf0f0d3217ea0a6fff320ead9e2f8770f810141189d182dc304e", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aa36751f4e6a2b56ae79efb0e088042e010ff4935fc8684e74c23b1f49e25fdc"}, "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"}, "esbuild": {:hex, :esbuild, "0.10.0", "b0aa3388a1c23e727c5a3e7427c932d89ee791746b0081bbe56103e9ef3d291f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "468489cda427b974a7cc9f03ace55368a83e1a7be12fba7e30969af78e5f8c70"}, "ex_aws": {:hex, :ex_aws, "2.6.1", "194582c7b09455de8a5ab18a0182e6dd937d53df82be2e63c619d01bddaccdfa", [:mix], [{:configparser_ex, "~> 5.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:req, "~> 0.5.10 or ~> 0.6 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "67842a08c90a1d9a09dbe4ac05754175c7ca253abe4912987c759395d4bd9d26"}, @@ -24,9 +24,9 @@ "excoveralls": {:hex, :excoveralls, "0.18.5", "e229d0a65982613332ec30f07940038fe451a2e5b29bce2a5022165f0c9b157e", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "523fe8a15603f86d64852aab2abe8ddbd78e68579c8525ae765facc5eae01562"}, "expo": {:hex, :expo, "1.1.0", "f7b9ed7fb5745ebe1eeedf3d6f29226c5dd52897ac67c0f8af62a07e661e5c75", [:mix], [], "hexpm", "fbadf93f4700fb44c331362177bdca9eeb8097e8b0ef525c9cc501cb9917c960"}, "file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"}, - "finch": {:hex, :finch, "0.20.0", "5330aefb6b010f424dcbbc4615d914e9e3deae40095e73ab0c1bb0968933cadf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2658131a74d051aabfcba936093c903b8e89da9a1b63e430bee62045fa9b2ee2"}, + "finch": {:hex, :finch, "0.21.0", "b1c3b2d48af02d0c66d2a9ebfb5622be5c5ecd62937cf79a88a7f98d48a8290c", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "87dc6e169794cb2570f75841a19da99cfde834249568f2a5b121b809588a4377"}, "fine": {:hex, :fine, "0.1.4", "b19a89c1476c7c57afb5f9314aed5960b5bc95d5277de4cb5ee8e1d1616ce379", [:mix], [], "hexpm", "be3324cc454a42d80951cf6023b9954e9ff27c6daa255483b3e8d608670303f5"}, - "floki": {:hex, :floki, "0.38.0", "62b642386fa3f2f90713f6e231da0fa3256e41ef1089f83b6ceac7a3fd3abf33", [:mix], [], "hexpm", "a5943ee91e93fb2d635b612caf5508e36d37548e84928463ef9dd986f0d1abd9"}, + "floki": {:hex, :floki, "0.38.1", "f002ccac94b3bcb21d40d9b34cc2cc9fd88a8311879120330075b5dde657ebee", [:mix], [], "hexpm", "e744bf0db7ee34b2c8b62767f04071107af0516a81144b9a2f73fe0494200e5b"}, "gettext": {:hex, :gettext, "0.26.2", "5978aa7b21fada6deabf1f6341ddba50bc69c999e812211903b169799208f2a8", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "aa978504bcf76511efdc22d580ba08e2279caab1066b76bb9aa81c4a1e0a32a5"}, "hackney": {:hex, :hackney, "1.25.0", "390e9b83f31e5b325b9f43b76e1a785cbdb69b5b6cd4e079aa67835ded046867", [:rebar3], [{:certifi, "~> 2.15.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.4", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "7209bfd75fd1f42467211ff8f59ea74d6f2a9e81cbcee95a56711ee79fd6b1d4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, @@ -34,7 +34,7 @@ "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "jido_keys": {:hex, :jido_keys, "1.0.0", "3a0b79ca00aa3a7ddaeb8012a644d799565d7ea06308b6508c5664558dea87db", [:mix], [{:dotenvy, "~> 1.1", [hex: :dotenvy, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}], "hexpm", "e319197e655ba6d9f18fa1fc8a4d619fa2f0210c9f1aa0d9a0e63dc2dcf5d866"}, - "lazy_html": {:hex, :lazy_html, "0.1.8", "677a8642e644eef8de98f3040e2520d42d0f0f8bd6c5cd49db36504e34dffe91", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.9.0", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:fine, "~> 0.1.0", [hex: :fine, repo: "hexpm", optional: false]}], "hexpm", "0d8167d930b704feb94b41414ca7f5779dff9bca7fcf619fcef18de138f08736"}, + "lazy_html": {:hex, :lazy_html, "0.1.10", "ffe42a0b4e70859cf21a33e12a251e0c76c1dff76391609bd56702a0ef5bc429", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.9.0", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:fine, "~> 0.1.0", [hex: :fine, repo: "hexpm", optional: false]}], "hexpm", "50f67e5faa09d45a99c1ddf3fac004f051997877dc8974c5797bb5ccd8e27058"}, "libcluster": {:hex, :libcluster, "3.5.0", "5ee4cfde4bdf32b2fef271e33ce3241e89509f4344f6c6a8d4069937484866ba", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.3", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ebf6561fcedd765a4cd43b4b8c04b1c87f4177b5fb3cbdfe40a780499d72f743"}, "logger_json": {:hex, :logger_json, "7.0.4", "e315f2b9a755504658a745f3eab90d88d2cd7ac2ecfd08c8da94d8893965ab5c", [:mix], [{:decimal, ">= 0.0.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:ecto, "~> 3.11", [hex: :ecto, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "d1369f8094e372db45d50672c3b91e8888bcd695fdc444a37a0734e96717c45c"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, @@ -42,24 +42,24 @@ "mimerl": {:hex, :mimerl, "1.4.0", "3882a5ca67fbbe7117ba8947f27643557adec38fa2307490c4c4207624cb213b", [:rebar3], [], "hexpm", "13af15f9f68c65884ecca3a3891d50a7b57d82152792f3e19d88650aa126b144"}, "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, "mox": {:hex, :mox, "1.2.0", "a2cd96b4b80a3883e3100a221e8adc1b98e4c3a332a8fc434c39526babafd5b3", [:mix], [{:nimble_ownership, "~> 1.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}], "hexpm", "c7b92b3cc69ee24a7eeeaf944cd7be22013c52fcb580c1f33f50845ec821089a"}, - "multipart": {:hex, :multipart, "0.4.0", "634880a2148d4555d050963373d0e3bbb44a55b2badd87fa8623166172e9cda0", [:mix], [{:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm", "3c5604bc2fb17b3137e5d2abdf5dacc2647e60c5cc6634b102cf1aef75a06f0a"}, + "multipart": {:hex, :multipart, "0.6.0", "73eae40ac88b67a8dd7cf6a2972762c1152470efeb0e18d68c7651f3f70c471b", [:mix], [{:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm", "818366054b1ff7215b7c091b73859968aa16e9620ec4a8a79a3a7362960b72bf"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_ownership": {:hex, :nimble_ownership, "1.0.1", "f69fae0cdd451b1614364013544e66e4f5d25f36a2056a9698b793305c5aa3a6", [:mix], [], "hexpm", "3825e461025464f519f3f3e4a1f9b68c47dc151369611629ad08b636b73bb22d"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, - "oban": {:hex, :oban, "2.20.2", "f23313d83b578305cafa825a036cad84e7e2d61549ecbece3a2e6526d347cc3b", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:igniter, "~> 0.5", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.20", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 1.3", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "523365ef0217781c061d15f496e3200a5f1b43e08b1a27c34799ef8bfe95815f"}, - "openai_ex": {:hex, :openai_ex, "0.9.18", "c05fa95b0cf71ab6ddfa7eeec10751a445fa1a0ed6a0d2399e8258d8fbbf370a", [:mix], [{:finch, "~> 0.20", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: false]}], "hexpm", "19a3c6aec6e0b58778c200d8185ccd47ebd303cd4b5a38442a67838b99b63669"}, + "oban": {:hex, :oban, "2.20.3", "e4d27336941955886cc7113420c32c63b70b64f10b27e08e3cf2b001153953cd", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:igniter, "~> 0.5", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.20", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 1.3", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "075ffbf1279a96bec495bc63d647b08929837d70bcc0427249ffe4d1dddaec33"}, + "openai_ex": {:hex, :openai_ex, "0.9.20", "7748b566881afc137ed33e4b49880138794c43121b0986c63b50357d57ee3331", [:mix], [{:finch, "~> 0.21", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:multipart, "~> 0.6", [hex: :multipart, repo: "hexpm", optional: false]}], "hexpm", "577c79187e053762c7b59490f2a628764be0081d7fc51efda14590f51f40f149"}, "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, - "phoenix": {:hex, :phoenix, "1.8.3", "49ac5e485083cb1495a905e47eb554277bdd9c65ccb4fc5100306b350151aa95", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "36169f95cc2e155b78be93d9590acc3f462f1e5438db06e6248613f27c80caec"}, + "phoenix": {:hex, :phoenix, "1.8.5", "919db335247e6d4891764dc3063415b0d2457641c5f9b3751b5df03d8e20bbcf", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "83b2bb125127e02e9f475c8e3e92736325b5b01b0b9b05407bcb4083b7a32485"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.7.0", "75c4b9dfb3efdc42aec2bd5f8bccd978aca0651dbcbc7a3f362ea5d9d43153c6", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "1d75011e4254cb4ddf823e81823a9629559a1be93b4321a6a5f11a5306fbf4cc"}, "phoenix_html": {:hex, :phoenix_html, "4.3.0", "d3577a5df4b6954cd7890c84d955c470b5310bb49647f0a114a6eeecc850f7ad", [:mix], [], "hexpm", "3eaa290a78bab0f075f791a46a981bbe769d94bc776869f4f3063a14f30497ad"}, "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.7.2", "97cc4ff2dba1ebe504db72cb45098cb8e91f11160528b980bd282cc45c73b29c", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0e5fdf063c7a3b620c566a30fcf68b7ee02e5e46fe48ee46a6ec3ba382dc05b7"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.6.2", "b18b0773a1ba77f28c52decbb0f10fd1ac4d3ae5b8632399bbf6986e3b665f62", [:mix], [{:file_system, "~> 0.2.10 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "d1f89c18114c50d394721365ffb428cce24f1c13de0467ffa773e2ff4a30d5b9"}, - "phoenix_live_view": {:hex, :phoenix_live_view, "1.1.19", "c95e9acbc374fb796ee3e24bfecc8213123c74d9f9e45667ca40bb0a4d242953", [:mix], [{:igniter, ">= 0.6.16 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:lazy_html, "~> 0.1.0", [hex: :lazy_html, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0 or ~> 1.8.0-rc", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d5ad357d6b21562a5b431f0ad09dfe76db9ce5648c6949f1aac334c8c4455d32"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "1.1.27", "9afcab28b0c82afdc51044e661bcd5b8de53d242593d34c964a37710b40a42af", [:mix], [{:igniter, ">= 0.6.16 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:lazy_html, "~> 0.1.0", [hex: :lazy_html, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0 or ~> 1.8.0-rc", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "415735d0b2c612c9104108b35654e977626a0cb346711e1e4f1ed16e3c827ede"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.2.0", "ff3a5616e1bed6804de7773b92cbccfc0b0f473faf1f63d7daf1206c7aeaaa6f", [:mix], [], "hexpm", "adc313a5bf7136039f63cfd9668fde73bba0765e0614cba80c06ac9460ff3e96"}, "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, "phoenix_view": {:hex, :phoenix_view, "2.0.4", "b45c9d9cf15b3a1af5fb555c674b525391b6a1fe975f040fb4d913397b31abf4", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "4e992022ce14f31fe57335db27a28154afcc94e9983266835bb3040243eb620b"}, "plug": {:hex, :plug, "1.19.1", "09bac17ae7a001a68ae393658aa23c7e38782be5c5c00c80be82901262c394c0", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "560a0017a8f6d5d30146916862aaf9300b7280063651dd7e532b8be168511e62"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.7.5", "261f21b67aea8162239b2d6d3b4c31efde4daa22a20d80b19c2c0f21b34b270e", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "20884bf58a90ff5a5663420f5d2c368e9e15ed1ad5e911daf0916ea3c57f77ac"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.8.0", "07789e9c03539ee51bb14a07839cc95aa96999fd8846ebfd28c97f0b50c7b612", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "9cbfaaf17463334ca31aed38ea7e08a68ee37cabc077b1e9be6d2fb68e0171d0"}, "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"}, "postgrex": {:hex, :postgrex, "0.22.0", "fb027b58b6eab1f6de5396a2abcdaaeb168f9ed4eccbb594e6ac393b02078cbd", [:mix], [{:db_connection, "~> 2.9", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "a68c4261e299597909e03e6f8ff5a13876f5caadaddd0d23af0d0a61afcc5d84"}, "ranch": {:hex, :ranch, "1.8.1", "208169e65292ac5d333d6cdbad49388c1ae198136e4697ae2f474697140f201c", [:make, :rebar3], [], "hexpm", "aed58910f4e21deea992a67bf51632b6d60114895eb03bb392bb733064594dd0"}, @@ -70,7 +70,7 @@ "splode": {:hex, :splode, "0.2.9", "3a2776e187c82f42f5226b33b1220ccbff74f4bcc523dd4039c804caaa3ffdc7", [:mix], [], "hexpm", "8002b00c6e24f8bd1bcced3fbaa5c33346048047bb7e13d2f3ad428babbd95c3"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "sweet_xml": {:hex, :sweet_xml, "0.7.4", "a8b7e1ce7ecd775c7e8a65d501bc2cd933bff3a9c41ab763f5105688ef485d08", [:mix], [], "hexpm", "e7c4b0bdbf460c928234951def54fe87edf1a170f6896675443279e2dbeba167"}, - "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, + "telemetry": {:hex, :telemetry, "1.4.1", "ab6de178e2b29b58e8256b92b382ea3f590a47152ca3651ea857a6cae05ac423", [:rebar3], [], "hexpm", "2172e05a27531d3d31dd9782841065c50dd5c3c7699d95266b2edd54c2dafa1c"}, "telemetry_metrics": {:hex, :telemetry_metrics, "1.1.0", "5bd5f3b5637e0abea0426b947e3ce5dd304f8b3bc6617039e2b5a008adc02f8f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7b79e8ddfde70adb6db8a6623d1778ec66401f366e9a8f5dd0955c56bc8ce67"}, "telemetry_poller": {:hex, :telemetry_poller, "1.3.0", "d5c46420126b5ac2d72bc6580fb4f537d35e851cc0f8dbd571acf6d6e10f5ec7", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "51f18bed7128544a50f75897db9974436ea9bfba560420b646af27a9a9b35211"}, "timex": {:hex, :timex, "3.7.13", "0688ce11950f5b65e154e42b47bf67b15d3bc0e0c3def62199991b8a8079a1e2", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.26", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "09588e0522669328e973b8b4fd8741246321b3f0d32735b589f78b136e6d4c54"}, diff --git a/test/mindwendel_web/live/brainstorming_live/show_ai_clustering_test.exs b/test/mindwendel_web/live/brainstorming_live/show_ai_clustering_test.exs index fd6b37ed..c5137731 100644 --- a/test/mindwendel_web/live/brainstorming_live/show_ai_clustering_test.exs +++ b/test/mindwendel_web/live/brainstorming_live/show_ai_clustering_test.exs @@ -10,8 +10,8 @@ defmodule MindwendelWeb.BrainstormingLiveAIClusteringTest do import Mindwendel.BrainstormingsFixtures import Ecto.Query - alias Mindwendel.AI.Schemas.IdeaLabelAssignment alias Mindwendel.Accounts + alias Mindwendel.AI.Schemas.IdeaLabelAssignment alias Mindwendel.Brainstormings alias Mindwendel.Brainstormings.Idea alias Mindwendel.Ideas From 3a3130f9cd4b161468cb7ebf95b9105b6a1315ee Mon Sep 17 00:00:00 2001 From: JannikStreek Date: Thu, 19 Mar 2026 11:48:59 +0100 Subject: [PATCH 06/13] refactor: sync tests and remove global stubs (#687) --- lib/mindwendel/ai/config.ex | 15 ++++ lib/mindwendel/ai/config/default_impl.ex | 8 ++ lib/mindwendel/ai/token_tracking_service.ex | 3 +- .../chat_completions_service_impl.ex | 3 +- lib/mindwendel/services/idea_service.ex | 89 +++++++++---------- .../ai/token_tracking_service_test.exs | 25 ++---- .../chat_completions_service_impl_test.exs | 66 +++++--------- ...brainstorming_live_ai_token_limit_test.exs | 57 +++--------- test/support/chat_completions_case.ex | 11 +++ test/support/conn_case.ex | 11 +++ test/support/data_case.ex | 11 +++ test/test_helper.exs | 8 +- 12 files changed, 150 insertions(+), 157 deletions(-) create mode 100644 lib/mindwendel/ai/config.ex create mode 100644 lib/mindwendel/ai/config/default_impl.ex diff --git a/lib/mindwendel/ai/config.ex b/lib/mindwendel/ai/config.ex new file mode 100644 index 00000000..90766a45 --- /dev/null +++ b/lib/mindwendel/ai/config.ex @@ -0,0 +1,15 @@ +defmodule Mindwendel.AI.Config do + @callback fetch_ai_config!() :: keyword() + + def fetch_ai_config! do + impl().fetch_ai_config!() + end + + defp impl do + Application.get_env( + :mindwendel, + :ai_config_service, + Mindwendel.AI.Config.DefaultImpl + ) + end +end diff --git a/lib/mindwendel/ai/config/default_impl.ex b/lib/mindwendel/ai/config/default_impl.ex new file mode 100644 index 00000000..da9c6292 --- /dev/null +++ b/lib/mindwendel/ai/config/default_impl.ex @@ -0,0 +1,8 @@ +defmodule Mindwendel.AI.Config.DefaultImpl do + @behaviour Mindwendel.AI.Config + + @impl Mindwendel.AI.Config + def fetch_ai_config! do + Application.fetch_env!(:mindwendel, :ai) + end +end diff --git a/lib/mindwendel/ai/token_tracking_service.ex b/lib/mindwendel/ai/token_tracking_service.ex index 3c0334ad..a6d40122 100644 --- a/lib/mindwendel/ai/token_tracking_service.ex +++ b/lib/mindwendel/ai/token_tracking_service.ex @@ -10,6 +10,7 @@ defmodule Mindwendel.AI.TokenTrackingService do """ import Ecto.Query + alias Mindwendel.AI.Config alias Mindwendel.AI.TokenUsage alias Mindwendel.Repo @@ -196,6 +197,6 @@ defmodule Mindwendel.AI.TokenTrackingService do end defp fetch_ai_config! do - Application.fetch_env!(:mindwendel, :ai) + Config.fetch_ai_config!() end end diff --git a/lib/mindwendel/services/chat_completions/chat_completions_service_impl.ex b/lib/mindwendel/services/chat_completions/chat_completions_service_impl.ex index b7ecfe4e..0455fb5b 100644 --- a/lib/mindwendel/services/chat_completions/chat_completions_service_impl.ex +++ b/lib/mindwendel/services/chat_completions/chat_completions_service_impl.ex @@ -1,6 +1,7 @@ defmodule Mindwendel.Services.ChatCompletions.ChatCompletionsServiceImpl do require Logger + alias Mindwendel.AI.Config alias Mindwendel.AI.Schemas.IdeaLabelAssignment alias Mindwendel.AI.Schemas.IdeaResponse alias Mindwendel.AI.TokenTrackingService @@ -543,6 +544,6 @@ defmodule Mindwendel.Services.ChatCompletions.ChatCompletionsServiceImpl do end defp fetch_ai_config! do - Application.fetch_env!(:mindwendel, :ai) + Config.fetch_ai_config!() end end diff --git a/lib/mindwendel/services/idea_service.ex b/lib/mindwendel/services/idea_service.ex index 9f21615f..16bc4b5e 100644 --- a/lib/mindwendel/services/idea_service.ex +++ b/lib/mindwendel/services/idea_service.ex @@ -38,59 +38,56 @@ defmodule Mindwendel.Services.IdeaService do {:ok, list(Mindwendel.Brainstormings.Idea.t())} | {:error, atom()} def add_ideas_to_brainstorming(brainstorming) do if idea_generation_enabled?() do - do_add_ideas_to_brainstorming(brainstorming) + # Get current locale from Gettext for language-specific idea generation + locale = Gettext.get_locale(MindwendelWeb.Gettext) + + # Get existing ideas to avoid duplicates + existing_ideas = Ideas.list_ideas_for_brainstorming(brainstorming.id) + + with {:ok, default_lane_id} <- get_first_lane_id(brainstorming), + {:ok, generated_ideas} <- + ChatCompletionsService.generate_ideas( + brainstorming.name, + brainstorming.lanes, + existing_ideas, + locale + ) do + create_generated_ideas(brainstorming, generated_ideas, default_lane_id) + else + {:error, reason} -> + Logger.warning("Failed to generate ideas: #{inspect(reason)}") + {:error, reason} + end else {:ok, []} end end - defp do_add_ideas_to_brainstorming(brainstorming) do - # Get current locale from Gettext for language-specific idea generation - locale = Gettext.get_locale(MindwendelWeb.Gettext) - - # Get existing ideas to avoid duplicates - existing_ideas = Ideas.list_ideas_for_brainstorming(brainstorming.id) - - with {:ok, default_lane_id} <- get_first_lane_id(brainstorming), - {:ok, generated_ideas} <- - ChatCompletionsService.generate_ideas( - brainstorming.name, - brainstorming.lanes, - existing_ideas, - locale - ) do - # Build a map of valid lane IDs for quick lookup - valid_lane_ids = MapSet.new(Enum.map(brainstorming.lanes, & &1.id)) - - results = - Enum.map(generated_ideas, fn generated_idea -> - # Use the lane_id from AI if valid, otherwise fall back to default - lane_id = resolve_lane_id(generated_idea["lane_id"], valid_lane_ids, default_lane_id) - - Ideas.create_idea(%{ - username: @ai_username, - body: generated_idea["idea"], - brainstorming_id: brainstorming.id, - lane_id: lane_id - }) - end) - - # Filter out failed creations and return only successful ones - {successful, failed} = Enum.split_with(results, &match?({:ok, _}, &1)) - - unless Enum.empty?(failed) do - Logger.warning( - "Failed to create #{length(failed)} ideas for brainstorming #{brainstorming.id}: #{inspect(failed)}" - ) - end + defp create_generated_ideas(brainstorming, generated_ideas, default_lane_id) do + valid_lane_ids = MapSet.new(Enum.map(brainstorming.lanes, & &1.id)) - successful_ideas = Enum.map(successful, fn {:ok, idea} -> idea end) - {:ok, successful_ideas} - else - {:error, reason} -> - Logger.warning("Failed to generate ideas: #{inspect(reason)}") - {:error, reason} + results = + Enum.map(generated_ideas, fn generated_idea -> + lane_id = resolve_lane_id(generated_idea["lane_id"], valid_lane_ids, default_lane_id) + + Ideas.create_idea(%{ + username: @ai_username, + body: generated_idea["idea"], + brainstorming_id: brainstorming.id, + lane_id: lane_id + }) + end) + + {successful, failed} = Enum.split_with(results, &match?({:ok, _}, &1)) + + unless Enum.empty?(failed) do + Logger.warning( + "Failed to create #{length(failed)} ideas for brainstorming #{brainstorming.id}: #{inspect(failed)}" + ) end + + successful_ideas = Enum.map(successful, fn {:ok, idea} -> idea end) + {:ok, successful_ideas} end defp resolve_lane_id(nil, _valid_lane_ids, default_lane_id), do: default_lane_id diff --git a/test/mindwendel/ai/token_tracking_service_test.exs b/test/mindwendel/ai/token_tracking_service_test.exs index fcb5edbc..09567ae5 100644 --- a/test/mindwendel/ai/token_tracking_service_test.exs +++ b/test/mindwendel/ai/token_tracking_service_test.exs @@ -1,5 +1,5 @@ defmodule Mindwendel.AI.TokenTrackingServiceTest do - use Mindwendel.DataCase, async: false + use Mindwendel.DataCase, async: true alias Mindwendel.AI.TokenTrackingService alias Mindwendel.AI.TokenUsage @@ -36,21 +36,14 @@ defmodule Mindwendel.AI.TokenTrackingServiceTest do describe "check_limits/0" do setup do - # Mock config for test - Application.put_env(:mindwendel, :ai, - enabled: true, - token_limit_daily: 1000, - token_limit_hourly: 100, - token_reset_hour: 0 - ) - - on_exit(fn -> - # Restore test config from config/test.exs - Application.put_env(:mindwendel, :ai, - enabled: false, - token_limit_daily: nil, - token_limit_hourly: nil - ) + Mindwendel.AI.Config.Mock + |> stub(:fetch_ai_config!, fn -> + [ + enabled: true, + token_limit_daily: 1000, + token_limit_hourly: 100, + token_reset_hour: 0 + ] end) :ok diff --git a/test/mindwendel/services/chat_completions/chat_completions_service_impl_test.exs b/test/mindwendel/services/chat_completions/chat_completions_service_impl_test.exs index bfe4cbc3..57fbe7a1 100644 --- a/test/mindwendel/services/chat_completions/chat_completions_service_impl_test.exs +++ b/test/mindwendel/services/chat_completions/chat_completions_service_impl_test.exs @@ -6,38 +6,31 @@ defmodule Mindwendel.Services.ChatCompletions.ChatCompletionsServiceImplTest do describe "generate_ideas/1" do setup do - # Configure AI for tests - Application.put_env(:mindwendel, :ai, - enabled: true, - provider: :openai, - model: "gpt-4o-mini", - api_key: "test-key", - token_limit_daily: 1000, - token_limit_hourly: 100, - request_timeout: 60_000 - ) - - on_exit(fn -> - # Restore test config from config/test.exs - Application.put_env(:mindwendel, :ai, - enabled: false, - token_limit_daily: nil, - token_limit_hourly: nil, + Mindwendel.AI.Config.Mock + |> stub(:fetch_ai_config!, fn -> + [ + enabled: true, + provider: :openai, + model: "gpt-4o-mini", + api_key: "test-key", + token_limit_daily: 1000, + token_limit_hourly: 100, + token_reset_hour: 0, request_timeout: 60_000 - ) + ] end) :ok end test "returns error when AI is not enabled" do - Application.put_env(:mindwendel, :ai, enabled: false) + Mindwendel.AI.Config.Mock + |> expect(:fetch_ai_config!, fn -> [enabled: false] end) assert {:error, :ai_not_enabled} = ChatCompletionsServiceImpl.generate_ideas("Test") end test "returns error when daily limit is exceeded" do - # Exceed daily limit {:ok, _} = TokenTrackingService.record_usage(%{total_tokens: 1001}) assert {:error, :daily_limit_exceeded} = @@ -45,7 +38,6 @@ defmodule Mindwendel.Services.ChatCompletions.ChatCompletionsServiceImplTest do end test "returns error when hourly limit is exceeded" do - # Exceed hourly limit {:ok, _} = TokenTrackingService.record_usage(%{total_tokens: 101}) assert {:error, :hourly_limit_exceeded} = @@ -55,38 +47,22 @@ defmodule Mindwendel.Services.ChatCompletions.ChatCompletionsServiceImplTest do describe "enabled?/0" do test "returns true when AI is enabled" do - Application.put_env(:mindwendel, :ai, - enabled: true, - token_limit_daily: nil, - token_limit_hourly: nil - ) + Mindwendel.AI.Config.Mock + |> expect(:fetch_ai_config!, fn -> + [enabled: true, token_limit_daily: nil, token_limit_hourly: nil] + end) assert ChatCompletionsServiceImpl.enabled?() == true end test "returns false when AI is disabled" do - Application.put_env(:mindwendel, :ai, - enabled: false, - token_limit_daily: nil, - token_limit_hourly: nil - ) + Mindwendel.AI.Config.Mock + |> expect(:fetch_ai_config!, fn -> + [enabled: false, token_limit_daily: nil, token_limit_hourly: nil] + end) assert ChatCompletionsServiceImpl.enabled?() == false end - - test "raises ArgumentError when AI config is missing" do - # Temporarily delete config to test error handling - original_config = Application.get_env(:mindwendel, :ai) - Application.delete_env(:mindwendel, :ai) - - # enabled?() should raise because it calls fetch_ai_config!() which uses Application.fetch_env! - assert_raise ArgumentError, fn -> - ChatCompletionsServiceImpl.enabled?() - end - - # Restore config - Application.put_env(:mindwendel, :ai, original_config || [enabled: false]) - end end describe "parse_response/1" do diff --git a/test/mindwendel_web/live/brainstorming_live_ai_token_limit_test.exs b/test/mindwendel_web/live/brainstorming_live_ai_token_limit_test.exs index 32bbe0dd..9c8ffb5a 100644 --- a/test/mindwendel_web/live/brainstorming_live_ai_token_limit_test.exs +++ b/test/mindwendel_web/live/brainstorming_live_ai_token_limit_test.exs @@ -14,7 +14,6 @@ defmodule MindwendelWeb.BrainstormingLiveAITokenLimitTest do import Mindwendel.BrainstormingsFixtures alias Mindwendel.Accounts - alias Mindwendel.AI.TokenTrackingService describe "AI generation with token limits" do setup do @@ -35,10 +34,8 @@ defmodule MindwendelWeb.BrainstormingLiveAITokenLimitTest do brainstorming: brainstorming, moderating_user: moderating_user } do - # Exceed daily token limit - {:ok, _} = TokenTrackingService.record_usage(%{total_tokens: 1_000_001}) + mock_generate_ideas_error(:daily_limit_exceeded) - # Mount the LiveView with moderating user {:ok, view, _html} = conn |> init_test_session(%{current_user_id: moderating_user.id}) @@ -46,15 +43,12 @@ defmodule MindwendelWeb.BrainstormingLiveAITokenLimitTest do allow_chat_completions(view.pid) - # Trigger AI idea generation view |> element("button[phx-click='generate_ai_ideas']") |> render_click() - # Wait for async message to be processed :timer.sleep(200) - # Verify an error message is displayed (generic error due to mock setup) html = render(view) - assert html =~ "Failed to generate ideas" + assert html =~ "Daily AI token limit exceeded" end test "shows appropriate error message when hourly token limit is exceeded", %{ @@ -62,10 +56,8 @@ defmodule MindwendelWeb.BrainstormingLiveAITokenLimitTest do brainstorming: brainstorming, moderating_user: moderating_user } do - # Exceed hourly token limit - {:ok, _} = TokenTrackingService.record_usage(%{total_tokens: 100_001}) + mock_generate_ideas_error(:hourly_limit_exceeded) - # Mount the LiveView with moderating user {:ok, view, _html} = conn |> init_test_session(%{current_user_id: moderating_user.id}) @@ -73,15 +65,12 @@ defmodule MindwendelWeb.BrainstormingLiveAITokenLimitTest do allow_chat_completions(view.pid) - # Trigger AI idea generation view |> element("button[phx-click='generate_ai_ideas']") |> render_click() - # Wait for async message to be processed :timer.sleep(200) - # Verify an error message is displayed (generic error due to mock setup) html = render(view) - assert html =~ "Failed to generate ideas" + assert html =~ "Hourly AI request limit exceeded" end test "replaces loading message with error when limit is hit", %{ @@ -89,10 +78,8 @@ defmodule MindwendelWeb.BrainstormingLiveAITokenLimitTest do brainstorming: brainstorming, moderating_user: moderating_user } do - # Exceed daily token limit - {:ok, _} = TokenTrackingService.record_usage(%{total_tokens: 1_000_001}) + mock_generate_ideas_error(:daily_limit_exceeded) - # Mount the LiveView with moderating user {:ok, view, _html} = conn |> init_test_session(%{current_user_id: moderating_user.id}) @@ -100,7 +87,6 @@ defmodule MindwendelWeb.BrainstormingLiveAITokenLimitTest do allow_chat_completions(view.pid) - # Trigger AI idea generation click_html = view |> element("button[phx-click='generate_ai_ideas']") @@ -112,9 +98,9 @@ defmodule MindwendelWeb.BrainstormingLiveAITokenLimitTest do # Wait for async message to process :timer.sleep(200) - # Error message should appear (generic error due to mock setup) + # Error message should appear html = render(view) - assert html =~ "Failed to generate ideas" + assert html =~ "Daily AI token limit exceeded" end test "successfully generates ideas when within limits", %{ @@ -122,10 +108,8 @@ defmodule MindwendelWeb.BrainstormingLiveAITokenLimitTest do brainstorming: brainstorming, moderating_user: moderating_user } do - # Mock successful AI generation with 3 ideas mock_generate_ideas(3) - # Mount the LiveView with moderating user {:ok, view, _html} = conn |> init_test_session(%{current_user_id: moderating_user.id}) @@ -133,25 +117,19 @@ defmodule MindwendelWeb.BrainstormingLiveAITokenLimitTest do allow_chat_completions(view.pid) - # Trigger AI idea generation view |> element("button[phx-click='generate_ai_ideas']") |> render_click() - # Wait for async processing :timer.sleep(200) - # Verify success message html = render(view) assert html =~ "idea(s) generated" end test "hides AI button when AI is disabled", %{conn: conn, brainstorming: brainstorming} do - # Disable AI disable_ai() - # Mount the LiveView {:ok, view, _html} = live(conn, ~p"/brainstormings/#{brainstorming.id}") - # Verify AI button is not shown refute view |> has_element?("button[phx-click='generate_ai_ideas']") end end @@ -163,24 +141,19 @@ defmodule MindwendelWeb.BrainstormingLiveAITokenLimitTest do Mindwendel.Services.ChatCompletions.ChatCompletionsServiceMock |> stub(:enabled?, fn -> true end) - # Get moderating user from brainstorming moderating_user = List.first(brainstorming.users) Accounts.add_moderating_user(brainstorming, moderating_user) %{brainstorming: brainstorming, moderating_user: moderating_user} end - test "allows generation when at exactly the daily limit minus one", %{ + test "allows generation when within limits", %{ conn: conn, brainstorming: brainstorming, moderating_user: moderating_user } do - # Set usage to just under the limit (999,999 tokens) - {:ok, _} = TokenTrackingService.record_usage(%{total_tokens: 999_999}) - mock_generate_ideas(2) - # Mount the LiveView with moderating user {:ok, view, _html} = conn |> init_test_session(%{current_user_id: moderating_user.id}) @@ -188,27 +161,22 @@ defmodule MindwendelWeb.BrainstormingLiveAITokenLimitTest do allow_chat_completions(view.pid) - # Trigger AI idea generation - should succeed view |> element("button[phx-click='generate_ai_ideas']") |> render_click() - # Wait for async processing :timer.sleep(200) - # Verify success (no error message about limits) html = render(view) refute html =~ "Daily AI token limit exceeded" refute html =~ "Hourly AI request limit exceeded" end - test "blocks generation when at exactly the daily limit", %{ + test "blocks generation when daily limit is reached", %{ conn: conn, brainstorming: brainstorming, moderating_user: moderating_user } do - # Set usage to exactly the limit (1,000,000 tokens) - {:ok, _} = TokenTrackingService.record_usage(%{total_tokens: 1_000_000}) + mock_generate_ideas_error(:daily_limit_exceeded) - # Mount the LiveView with moderating user {:ok, view, _html} = conn |> init_test_session(%{current_user_id: moderating_user.id}) @@ -216,15 +184,12 @@ defmodule MindwendelWeb.BrainstormingLiveAITokenLimitTest do allow_chat_completions(view.pid) - # Trigger AI idea generation - should be blocked view |> element("button[phx-click='generate_ai_ideas']") |> render_click() - # Wait for async processing :timer.sleep(200) - # Verify error message (generic error due to mock setup) html = render(view) - assert html =~ "Failed to generate ideas" + assert html =~ "Daily AI token limit exceeded" end end diff --git a/test/support/chat_completions_case.ex b/test/support/chat_completions_case.ex index c163ed85..1765998b 100644 --- a/test/support/chat_completions_case.ex +++ b/test/support/chat_completions_case.ex @@ -36,6 +36,17 @@ defmodule Mindwendel.ChatCompletionsCase do end defp stub_ai_disabled do + Mindwendel.AI.Config.Mock + |> stub(:fetch_ai_config!, fn -> + [ + enabled: false, + token_limit_daily: nil, + token_limit_hourly: nil, + token_reset_hour: 0, + request_timeout: 60_000 + ] + end) + Mindwendel.Services.ChatCompletions.ChatCompletionsServiceMock |> stub(:enabled?, fn -> false end) |> stub(:generate_ideas, fn _title, _lanes, _existing_ideas, _locale -> diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex index 45c88743..8a0b2e65 100644 --- a/test/support/conn_case.ex +++ b/test/support/conn_case.ex @@ -56,6 +56,17 @@ defmodule MindwendelWeb.ConnCase do # Provide default AI disabled stub for all ConnCase tests # This prevents Mox.UnexpectedCallError when LiveViews render defp setup_ai_disabled_stub(_context) do + Mindwendel.AI.Config.Mock + |> Mox.stub(:fetch_ai_config!, fn -> + [ + enabled: false, + token_limit_daily: nil, + token_limit_hourly: nil, + token_reset_hour: 0, + request_timeout: 60_000 + ] + end) + Mindwendel.Services.ChatCompletions.ChatCompletionsServiceMock |> Mox.stub(:enabled?, fn -> false end) |> Mox.stub(:generate_ideas, fn _title, _lanes, _existing_ideas, _locale -> diff --git a/test/support/data_case.ex b/test/support/data_case.ex index 5fd71dc9..fda7678b 100644 --- a/test/support/data_case.ex +++ b/test/support/data_case.ex @@ -48,6 +48,17 @@ defmodule Mindwendel.DataCase do # Provide default AI disabled stub for all DataCase tests defp setup_ai_disabled_stub(_context) do + Mindwendel.AI.Config.Mock + |> Mox.stub(:fetch_ai_config!, fn -> + [ + enabled: false, + token_limit_daily: nil, + token_limit_hourly: nil, + token_reset_hour: 0, + request_timeout: 60_000 + ] + end) + Mindwendel.Services.ChatCompletions.ChatCompletionsServiceMock |> Mox.stub(:enabled?, fn -> false end) |> Mox.stub(:generate_ideas, fn _title, _lanes, _existing_ideas, _locale -> diff --git a/test/test_helper.exs b/test/test_helper.exs index 851d577d..2ac864a7 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -5,15 +5,19 @@ Ecto.Adapters.SQL.Sandbox.mode(Mindwendel.Repo, :manual) upload_path = "priv/static/uploads/" File.mkdir_p!(Path.dirname(upload_path)) -# Define the mock - it will be configured per-test via test case setups +# Define mocks - they will be configured per-test via test case setups Mox.defmock(Mindwendel.Services.ChatCompletions.ChatCompletionsServiceMock, for: Mindwendel.Services.ChatCompletions.ChatCompletionsService ) -# Configure to use the mock in tests +Mox.defmock(Mindwendel.AI.Config.Mock, for: Mindwendel.AI.Config) + +# Configure to use mocks in tests # Each test case (ChatCompletionsCase, ConnCase, DataCase) will set up appropriate stubs/expectations Application.put_env( :mindwendel, :chat_completions_service, Mindwendel.Services.ChatCompletions.ChatCompletionsServiceMock ) + +Application.put_env(:mindwendel, :ai_config_service, Mindwendel.AI.Config.Mock) From 2b484556abf75a68e78600954707b578ffdd202f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Mar 2026 12:26:05 +0100 Subject: [PATCH 07/13] Build(deps): Bump docker/metadata-action from 5 to 6 (#682) Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5 to 6. - [Release notes](https://github.com/docker/metadata-action/releases) - [Commits](https://github.com/docker/metadata-action/compare/v5...v6) --- updated-dependencies: - dependency-name: docker/metadata-action dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: JannikStreek --- .github/workflows/on_push_main_publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/on_push_main_publish.yml b/.github/workflows/on_push_main_publish.yml index 8d406fe1..a37a9a87 100644 --- a/.github/workflows/on_push_main_publish.yml +++ b/.github/workflows/on_push_main_publish.yml @@ -47,7 +47,7 @@ jobs: # This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels. - name: Extract metadata (tags, labels) for Docker id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v6 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | From 7c6ee23008cb4ac55e102488184e0d3f3926a345 Mon Sep 17 00:00:00 2001 From: JannikStreek Date: Thu, 19 Mar 2026 12:32:05 +0100 Subject: [PATCH 08/13] add oban v13 migration and fix v12 down (#688) --- .../migrations/20240104163948_upgrade_oban_jobs_to_v12.exs | 2 +- .../migrations/20260319111230_upgrade_oban_jobs_to_v13.exs | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 priv/repo/migrations/20260319111230_upgrade_oban_jobs_to_v13.exs diff --git a/priv/repo/migrations/20240104163948_upgrade_oban_jobs_to_v12.exs b/priv/repo/migrations/20240104163948_upgrade_oban_jobs_to_v12.exs index 2627d485..38d5dde0 100644 --- a/priv/repo/migrations/20240104163948_upgrade_oban_jobs_to_v12.exs +++ b/priv/repo/migrations/20240104163948_upgrade_oban_jobs_to_v12.exs @@ -3,5 +3,5 @@ defmodule Mindwendel.Repo.Migrations.UpgradeObanJobsToV12 do def up, do: Oban.Migrations.up(version: 12) - def down, do: Oban.Migrations.down(version: 11) + def down, do: Oban.Migrations.down(version: 12) end diff --git a/priv/repo/migrations/20260319111230_upgrade_oban_jobs_to_v13.exs b/priv/repo/migrations/20260319111230_upgrade_oban_jobs_to_v13.exs new file mode 100644 index 00000000..75be0e61 --- /dev/null +++ b/priv/repo/migrations/20260319111230_upgrade_oban_jobs_to_v13.exs @@ -0,0 +1,7 @@ +defmodule Mindwendel.Repo.Migrations.UpgradeObanJobsToV13 do + use Ecto.Migration + + def up, do: Oban.Migrations.up(version: 13) + + def down, do: Oban.Migrations.down(version: 13) +end From fece1c9c4592653d1248b8a10d6d5ee822361831 Mon Sep 17 00:00:00 2001 From: JannikStreek Date: Thu, 19 Mar 2026 13:15:23 +0100 Subject: [PATCH 09/13] fix: missing line breaks (#689) --- assets/scss/app.scss | 4 +++ lib/mindwendel/brainstormings/idea.ex | 33 ++++++++++--------- .../live/idea_live/card_component.html.heex | 2 +- .../live/idea_live/show_component.html.heex | 2 +- test/mindwendel/idea_test.exs | 29 +++++++++++++++- 5 files changed, 52 insertions(+), 18 deletions(-) diff --git a/assets/scss/app.scss b/assets/scss/app.scss index b814e2a8..87a10740 100644 --- a/assets/scss/app.scss +++ b/assets/scss/app.scss @@ -111,4 +111,8 @@ div[data-phx-session] { margin-left: 5px; padding-top: 16px; height: 100%; +} + +.text-pre-line { + white-space: pre-line; } \ No newline at end of file diff --git a/lib/mindwendel/brainstormings/idea.ex b/lib/mindwendel/brainstormings/idea.ex index 5f1456a0..5901127f 100644 --- a/lib/mindwendel/brainstormings/idea.ex +++ b/lib/mindwendel/brainstormings/idea.ex @@ -71,28 +71,31 @@ defmodule Mindwendel.Brainstormings.Idea do end defp strip_html(text) when is_binary(text) do - # Strip all HTML tags by parsing with Floki and extracting text content - # This removes all tags, event handlers, and JavaScript - case Floki.parse_document(text) do - {:ok, parsed} -> - parsed - |> Floki.text(sep: " ") - |> normalize_whitespace() - - {:error, _} -> - # If parsing fails, fall back to basic regex stripping - text - |> String.replace(~r/<[^>]*>/, "") - |> normalize_whitespace() - end + # Convert literal newlines to
so Floki natively preserves them as \n + text + |> String.replace("\n", "
") + |> then(fn html -> + case Floki.parse_document(html) do + {:ok, parsed} -> + Floki.text(parsed, sep: " ") + + {:error, _} -> + String.replace(html, ~r/<[^>]*>/, "") + end + end) + |> normalize_whitespace() end defp strip_html(text), do: text defp normalize_whitespace(text) do text + # Trim horizontal whitespace around newlines, then collapse remaining + |> String.replace(~r/[^\S\n]*\n[^\S\n]*/, "\n") + |> String.replace(~r/[^\S\n]+/, " ") + # Cap consecutive newlines at 2 (one blank line between paragraphs) + |> String.replace(~r/\n{3,}/, "\n\n") |> String.trim() - |> String.replace(~r/\s+/, " ") end defp maybe_put_idea_labels(changeset, attrs) do diff --git a/lib/mindwendel_web/live/idea_live/card_component.html.heex b/lib/mindwendel_web/live/idea_live/card_component.html.heex index c7a2b4f4..b5e3531c 100644 --- a/lib/mindwendel_web/live/idea_live/card_component.html.heex +++ b/lib/mindwendel_web/live/idea_live/card_component.html.heex @@ -47,7 +47,7 @@ <% end %> <%= unless @idea.link do %> -

{@idea.body}

+

{@idea.body}

<% end %> <%= if @idea.link do %> diff --git a/lib/mindwendel_web/live/idea_live/show_component.html.heex b/lib/mindwendel_web/live/idea_live/show_component.html.heex index 2e84b304..0efc4c79 100644 --- a/lib/mindwendel_web/live/idea_live/show_component.html.heex +++ b/lib/mindwendel_web/live/idea_live/show_component.html.heex @@ -1,6 +1,6 @@
<%= unless @idea.link do %> -

{@idea.body}

+

{@idea.body}

<% end %> <%= if @idea.link do %> diff --git a/test/mindwendel/idea_test.exs b/test/mindwendel/idea_test.exs index 1272eda8..0b8c4718 100644 --- a/test/mindwendel/idea_test.exs +++ b/test/mindwendel/idea_test.exs @@ -242,10 +242,37 @@ defmodule Mindwendel.IdeaTest do body: "

First paragraph

Second paragraph

" }) - # Floki.text with sep: " " should preserve spaces assert changeset.changes.body == "First paragraph Second paragraph" end + test "preserves newlines from plain text input", %{ + brainstorming: brainstorming, + lane: lane + } do + changeset = + Idea.changeset(%Idea{}, %{ + brainstorming_id: brainstorming.id, + lane_id: lane.id, + body: "First paragraph\n\nSecond paragraph" + }) + + assert changeset.changes.body == "First paragraph\n\nSecond paragraph" + end + + test "collapses excessive blank lines", %{ + brainstorming: brainstorming, + lane: lane + } do + changeset = + Idea.changeset(%Idea{}, %{ + brainstorming_id: brainstorming.id, + lane_id: lane.id, + body: "Line 1\n\n\n\n\nLine 2" + }) + + assert changeset.changes.body == "Line 1\n\nLine 2" + end + test "strips HTML when updating an existing idea" do idea = Factory.insert!(:idea, body: "Original text") From 850045d125476506ff1fbf6789713e7e9b8a1a79 Mon Sep 17 00:00:00 2001 From: JannikStreek Date: Thu, 19 Mar 2026 14:23:46 +0100 Subject: [PATCH 10/13] fix: horizontal scrollbar (#690) --- .gitignore | 3 +- assets/js/app.js | 44 ++- assets/scss/app.scss | 66 +++++ .../live/lane_live/index_component.html.heex | 252 +++++++++--------- priv/gettext/de/LC_MESSAGES/default.po | 32 +-- priv/gettext/default.pot | 32 +-- priv/gettext/en/LC_MESSAGES/default.po | 32 +-- priv/gettext/it/LC_MESSAGES/default.po | 32 +-- 8 files changed, 305 insertions(+), 188 deletions(-) diff --git a/.gitignore b/.gitignore index 3a81b9e5..a5832ff5 100644 --- a/.gitignore +++ b/.gitignore @@ -57,4 +57,5 @@ docker-compose*.override.yml ca # devcontainer files: -.devcontainer \ No newline at end of file +.devcontainer +.mcp.json \ No newline at end of file diff --git a/assets/js/app.js b/assets/js/app.js index f5b54c2d..19619875 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -170,8 +170,50 @@ Hooks.RemoveMissingBrainstorming = { } }; +Hooks.LanesScrollIndicator = { + mounted() { + this.scrollContainer = this.el.querySelector('.lanes-container'); + this.leftArrow = this.el.querySelector('#lanes-scroll-left'); + this.rightArrow = this.el.querySelector('#lanes-scroll-right'); + + this.getColumnWidth = () => { + const col = this.scrollContainer.querySelector('[class*="col-"]'); + return col ? col.offsetWidth : 300; + }; + + this.updateIndicators = () => { + const { scrollLeft, scrollWidth, clientWidth } = this.scrollContainer; + const canScrollLeft = scrollLeft > 0; + const canScrollRight = scrollLeft + clientWidth < scrollWidth - 1; + + this.leftArrow.classList.toggle('visible', canScrollLeft); + this.rightArrow.classList.toggle('visible', canScrollRight); + }; + + this.leftArrow.addEventListener('click', () => { + this.scrollContainer.scrollBy({ left: -this.getColumnWidth(), behavior: 'smooth' }); + }); + + this.rightArrow.addEventListener('click', () => { + this.scrollContainer.scrollBy({ left: this.getColumnWidth(), behavior: 'smooth' }); + }); + + this.scrollContainer.addEventListener('scroll', this.updateIndicators); + this.resizeObserver = new ResizeObserver(this.updateIndicators); + this.resizeObserver.observe(this.scrollContainer); + + this.updateIndicators(); + }, + updated() { + this.updateIndicators(); + }, + destroyed() { + this.resizeObserver.disconnect(); + } +}; + // The brainstorming secret from the url ("#123") is added as well to the socket. The secret is not available on the server side by default. -let liveSocket = new LiveSocket("/live", Socket, { +let liveSocket = new LiveSocket("/live", Socket, { hooks: Hooks, params: { _csrf_token: csrfToken, adminSecret: window.location.hash.substring(1) } }) diff --git a/assets/scss/app.scss b/assets/scss/app.scss index 87a10740..32c07adc 100644 --- a/assets/scss/app.scss +++ b/assets/scss/app.scss @@ -113,6 +113,72 @@ div[data-phx-session] { height: 100%; } +.lanes-container-wrapper { + position: relative; + + .lanes-container { + overflow-x: auto; + scrollbar-color: $gray-500 $gray-200; + + &::-webkit-scrollbar { + height: 8px; + } + + &::-webkit-scrollbar-track { + background: $gray-200; + border-radius: 4px; + } + + &::-webkit-scrollbar-thumb { + background-color: $gray-500; + border-radius: 4px; + + &:hover { + background-color: $gray-600; + } + } + + > [class*="col-"] { + flex: 0 0 auto; + min-width: 300px; + } + } +} + +.lanes-scroll-indicator { + position: absolute; + top: 0; + bottom: 0; + width: 40px; + pointer-events: none; + opacity: 0; + transition: opacity 0.3s ease; + z-index: 1; + + &.visible { + opacity: 1; + pointer-events: auto; + cursor: pointer; + } + + &--right { + right: 0; + } + + &--left { + left: 0; + } + + .scroll-arrow { + position: sticky; + top: 50vh; + display: block; + font-size: 1.5rem; + color: $gray-600; + text-align: center; + } +} + .text-pre-line { white-space: pre-line; } \ No newline at end of file diff --git a/lib/mindwendel_web/live/lane_live/index_component.html.heex b/lib/mindwendel_web/live/lane_live/index_component.html.heex index 1b927298..99647fca 100644 --- a/lib/mindwendel_web/live/lane_live/index_component.html.heex +++ b/lib/mindwendel_web/live/lane_live/index_component.html.heex @@ -1,134 +1,142 @@ -
- <%= for lane <- @lanes do %> - <.lane_col lane_count={length(@lanes)} class="mb-2"> -
-
- {lane.name || gettext("Default lane")} +
+
+ +
+
+ +
+
+ <%= for lane <- @lanes do %> + <.lane_col lane_count={length(@lanes)} class="mb-2"> +
+
+ {lane.name || gettext("Default lane")} - <.link - class="btn btn-primary btn-sm d-inline-flex align-items-center" - title={gettext("Add idea")} - patch={~p"/brainstormings/#{@brainstorming.id}/lanes/#{lane.id}/new_idea"} - > - {gettext("Add idea")} - - -
lane.id} - phx-hook="Sortable" - class="d-flex flex-wrap justify-content-start sortable lane-ideas-area" - data-sortable-enabled={to_string(has_move_permission(@brainstorming, @current_user))} - data-lane-id={lane.id} - > - <% width_class = - cond do - length(@lanes) == 1 -> - "" +
lane.id} + phx-hook="Sortable" + class="d-flex flex-wrap justify-content-start sortable lane-ideas-area" + data-sortable-enabled={to_string(has_move_permission(@brainstorming, @current_user))} + data-lane-id={lane.id} + > + <% width_class = + cond do + length(@lanes) == 1 -> + "" - length(@lanes) == 2 -> - "card-mindwendel--half-width" + length(@lanes) == 2 -> + "card-mindwendel--half-width" - true -> - "card-mindwendel--full-width" - end %> - <%= for idea <- lane.ideas do %> - <.live_component - module={MindwendelWeb.IdeaLive.CardComponent} - brainstorming={@brainstorming} - id={idea.id} - current_user={@current_user} - width_class={width_class} - idea={idea} - /> - <% end %> + true -> + "card-mindwendel--full-width" + end %> + <%= for idea <- lane.ideas do %> + <.live_component + module={MindwendelWeb.IdeaLive.CardComponent} + brainstorming={@brainstorming} + id={idea.id} + current_user={@current_user} + width_class={width_class} + idea={idea} + /> + <% end %> - <%= if Enum.empty?(lane.ideas) do %> -
- -

{gettext("No ideas brainstormed")}

- <.link - class="btn btn-outline-primary d-inline-flex align-items-center" - patch={~p"/brainstormings/#{@brainstorming.id}/lanes/#{lane.id}/new_idea"} - > - {gettext("Add idea")} - -
- <% end %> + <%= if Enum.empty?(lane.ideas) do %> +
+ +

{gettext("No ideas brainstormed")}

+ <.link + class="btn btn-outline-primary d-inline-flex align-items-center" + patch={~p"/brainstormings/#{@brainstorming.id}/lanes/#{lane.id}/new_idea"} + > + {gettext("Add idea")} + +
+ <% end %> +
+ + <% end %> + <%= if Enum.empty?(@lanes) do %> +
+ +

{gettext("No lanes available")}

+ <.link + class="btn btn-outline-primary d-inline-flex align-items-center" + patch={~p"/brainstormings/#{@brainstorming.id}/new_lane"} + > + {gettext("New lane")} +
- - <% end %> - <%= if Enum.empty?(@lanes) do %> -
- -

{gettext("No lanes available")}

- <.link - class="btn btn-outline-primary d-inline-flex align-items-center" - patch={~p"/brainstormings/#{@brainstorming.id}/new_lane"} - > - {gettext("New lane")} - -
- <% end %> + <% end %> +
diff --git a/priv/gettext/de/LC_MESSAGES/default.po b/priv/gettext/de/LC_MESSAGES/default.po index 02f0d12c..ee3dae62 100644 --- a/priv/gettext/de/LC_MESSAGES/default.po +++ b/priv/gettext/de/LC_MESSAGES/default.po @@ -107,7 +107,7 @@ msgstr "Neues Brainstorming" msgid "New idea" msgstr "Neue Idee" -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:109 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:116 #, elixir-autogen, elixir-format msgid "No ideas brainstormed" msgstr "Bisher keine Ideen vorhanden" @@ -131,14 +131,14 @@ msgstr "Speichern" msgid "Saving..." msgstr "Speichere..." -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:50 -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:54 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:57 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:61 #, elixir-autogen, elixir-format msgid "Sort by label" msgstr "Sortiere nach Label" -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:41 -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:44 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:48 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:51 #, elixir-autogen, elixir-format msgid "Sort by likes" msgstr "Sortiere nach Likes" @@ -398,7 +398,7 @@ msgstr "Name" #: lib/mindwendel_web/live/brainstorming_live/show.html.heex:25 #: lib/mindwendel_web/live/brainstorming_live/show.html.heex:27 #: lib/mindwendel_web/live/brainstorming_live/show.html.heex:138 -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:130 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:137 #, elixir-autogen, elixir-format msgid "New lane" msgstr "Neue Spalte" @@ -408,19 +408,19 @@ msgstr "Neue Spalte" msgid "Update lane" msgstr "Spalte bearbeiten" -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:69 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:76 #, elixir-autogen, elixir-format, fuzzy msgid "Are you sure you want to delete this lane?" msgstr "Möchtest du die Idee löschen?" -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:68 -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:71 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:75 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:78 #, elixir-autogen, elixir-format, fuzzy msgid "Delete lane" msgstr "Löschen" -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:59 -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:61 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:66 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:68 #, elixir-autogen, elixir-format, fuzzy msgid "Edit lane" msgstr "Spalte bearbeiten" @@ -430,19 +430,19 @@ msgstr "Spalte bearbeiten" msgid "Lane updated" msgstr "Spalte aktualisiert" -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:10 -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:13 -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:114 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:17 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:20 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:121 #, elixir-autogen, elixir-format, fuzzy msgid "Add idea" msgstr "Neue Idee" -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:6 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:13 #, elixir-autogen, elixir-format, fuzzy msgid "Default lane" msgstr "Eine Spalte" -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:125 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:132 #, elixir-autogen, elixir-format msgid "No lanes available" msgstr "Keine Spalten vorhanden" diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot index a30d82f9..a8acb943 100644 --- a/priv/gettext/default.pot +++ b/priv/gettext/default.pot @@ -106,7 +106,7 @@ msgstr "" msgid "New idea" msgstr "" -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:109 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:116 #, elixir-autogen, elixir-format msgid "No ideas brainstormed" msgstr "" @@ -130,14 +130,14 @@ msgstr "" msgid "Saving..." msgstr "" -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:50 -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:54 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:57 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:61 #, elixir-autogen, elixir-format msgid "Sort by label" msgstr "" -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:41 -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:44 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:48 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:51 #, elixir-autogen, elixir-format msgid "Sort by likes" msgstr "" @@ -397,7 +397,7 @@ msgstr "" #: lib/mindwendel_web/live/brainstorming_live/show.html.heex:25 #: lib/mindwendel_web/live/brainstorming_live/show.html.heex:27 #: lib/mindwendel_web/live/brainstorming_live/show.html.heex:138 -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:130 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:137 #, elixir-autogen, elixir-format msgid "New lane" msgstr "" @@ -407,19 +407,19 @@ msgstr "" msgid "Update lane" msgstr "" -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:69 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:76 #, elixir-autogen, elixir-format msgid "Are you sure you want to delete this lane?" msgstr "" -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:68 -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:71 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:75 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:78 #, elixir-autogen, elixir-format msgid "Delete lane" msgstr "" -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:59 -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:61 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:66 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:68 #, elixir-autogen, elixir-format msgid "Edit lane" msgstr "" @@ -429,19 +429,19 @@ msgstr "" msgid "Lane updated" msgstr "" -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:10 -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:13 -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:114 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:17 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:20 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:121 #, elixir-autogen, elixir-format msgid "Add idea" msgstr "" -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:6 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:13 #, elixir-autogen, elixir-format msgid "Default lane" msgstr "" -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:125 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:132 #, elixir-autogen, elixir-format msgid "No lanes available" msgstr "" diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po index 29da3988..d8ac70eb 100644 --- a/priv/gettext/en/LC_MESSAGES/default.po +++ b/priv/gettext/en/LC_MESSAGES/default.po @@ -107,7 +107,7 @@ msgstr "" msgid "New idea" msgstr "" -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:109 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:116 #, elixir-autogen, elixir-format msgid "No ideas brainstormed" msgstr "" @@ -131,14 +131,14 @@ msgstr "" msgid "Saving..." msgstr "" -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:50 -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:54 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:57 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:61 #, elixir-autogen, elixir-format msgid "Sort by label" msgstr "" -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:41 -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:44 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:48 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:51 #, elixir-autogen, elixir-format msgid "Sort by likes" msgstr "" @@ -398,7 +398,7 @@ msgstr "" #: lib/mindwendel_web/live/brainstorming_live/show.html.heex:25 #: lib/mindwendel_web/live/brainstorming_live/show.html.heex:27 #: lib/mindwendel_web/live/brainstorming_live/show.html.heex:138 -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:130 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:137 #, elixir-autogen, elixir-format msgid "New lane" msgstr "" @@ -408,19 +408,19 @@ msgstr "" msgid "Update lane" msgstr "" -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:69 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:76 #, elixir-autogen, elixir-format, fuzzy msgid "Are you sure you want to delete this lane?" msgstr "" -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:68 -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:71 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:75 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:78 #, elixir-autogen, elixir-format, fuzzy msgid "Delete lane" msgstr "" -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:59 -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:61 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:66 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:68 #, elixir-autogen, elixir-format, fuzzy msgid "Edit lane" msgstr "" @@ -430,19 +430,19 @@ msgstr "" msgid "Lane updated" msgstr "" -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:10 -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:13 -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:114 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:17 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:20 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:121 #, elixir-autogen, elixir-format, fuzzy msgid "Add idea" msgstr "" -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:6 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:13 #, elixir-autogen, elixir-format, fuzzy msgid "Default lane" msgstr "" -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:125 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:132 #, elixir-autogen, elixir-format msgid "No lanes available" msgstr "" diff --git a/priv/gettext/it/LC_MESSAGES/default.po b/priv/gettext/it/LC_MESSAGES/default.po index 2475ee6d..525d3d39 100644 --- a/priv/gettext/it/LC_MESSAGES/default.po +++ b/priv/gettext/it/LC_MESSAGES/default.po @@ -107,7 +107,7 @@ msgstr "Nuovo brainstorming" msgid "New idea" msgstr "Nuova idea" -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:109 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:116 #, elixir-autogen, elixir-format msgid "No ideas brainstormed" msgstr "Nessuna idea elaborata" @@ -131,14 +131,14 @@ msgstr "Salva" msgid "Saving..." msgstr "Salvataggio..." -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:50 -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:54 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:57 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:61 #, elixir-autogen, elixir-format msgid "Sort by label" msgstr "Ordina per etichetta" -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:41 -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:44 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:48 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:51 #, elixir-autogen, elixir-format msgid "Sort by likes" msgstr "Ordina per 'Mi piace'" @@ -398,7 +398,7 @@ msgstr "Nome" #: lib/mindwendel_web/live/brainstorming_live/show.html.heex:25 #: lib/mindwendel_web/live/brainstorming_live/show.html.heex:27 #: lib/mindwendel_web/live/brainstorming_live/show.html.heex:138 -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:130 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:137 #, elixir-autogen, elixir-format msgid "New lane" msgstr "Nuova categoria" @@ -408,19 +408,19 @@ msgstr "Nuova categoria" msgid "Update lane" msgstr "Aggiorna categoria" -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:69 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:76 #, elixir-autogen, elixir-format, fuzzy msgid "Are you sure you want to delete this lane?" msgstr "Sei sicuro di voler eliminare questa categoria?" -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:68 -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:71 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:75 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:78 #, elixir-autogen, elixir-format, fuzzy msgid "Delete lane" msgstr "Elimina categoria" -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:59 -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:61 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:66 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:68 #, elixir-autogen, elixir-format, fuzzy msgid "Edit lane" msgstr "Modifica categoria" @@ -430,19 +430,19 @@ msgstr "Modifica categoria" msgid "Lane updated" msgstr "Categoria aggiornata" -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:10 -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:13 -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:114 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:17 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:20 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:121 #, elixir-autogen, elixir-format, fuzzy msgid "Add idea" msgstr "Aggiungi idea" -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:6 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:13 #, elixir-autogen, elixir-format, fuzzy msgid "Default lane" msgstr "Categoria predefinita" -#: lib/mindwendel_web/live/lane_live/index_component.html.heex:125 +#: lib/mindwendel_web/live/lane_live/index_component.html.heex:132 #, elixir-autogen, elixir-format msgid "No lanes available" msgstr "Nessuna categoria disponibile" From b8358e0c4a8f521005018f12ea44d0755e420ee4 Mon Sep 17 00:00:00 2001 From: JannikStreek Date: Thu, 19 Mar 2026 14:37:56 +0100 Subject: [PATCH 11/13] add images as card backgrounds/headers (#691) --- assets/scss/_bootstrap_custom.scss | 14 ++++++++++++++ .../live/idea_live/card_component.html.heex | 9 +++++++++ priv/gettext/de/LC_MESSAGES/default.po | 19 ++++++++++++------- priv/gettext/default.pot | 19 ++++++++++++------- priv/gettext/en/LC_MESSAGES/default.po | 19 ++++++++++++------- priv/gettext/it/LC_MESSAGES/default.po | 19 ++++++++++++------- 6 files changed, 71 insertions(+), 28 deletions(-) diff --git a/assets/scss/_bootstrap_custom.scss b/assets/scss/_bootstrap_custom.scss index f1ae4911..8ffb81d0 100644 --- a/assets/scss/_bootstrap_custom.scss +++ b/assets/scss/_bootstrap_custom.scss @@ -1,6 +1,20 @@ // Required @import "bootstrap/scss/bootstrap"; +.card-img-top-mindwendel { + overflow: hidden; + max-height: 150px; + border-bottom: 1px solid rgba(0, 0, 0, 0.125); + + &__img { + width: 100%; + height: 150px; + object-fit: cover; + display: block; + border-radius: calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0; + } +} + .card-body-mindwendel-idea { @extend .card-body; padding: 0.5rem 0.5rem; diff --git a/lib/mindwendel_web/live/idea_live/card_component.html.heex b/lib/mindwendel_web/live/idea_live/card_component.html.heex index b5e3531c..8caa67ee 100644 --- a/lib/mindwendel_web/live/idea_live/card_component.html.heex +++ b/lib/mindwendel_web/live/idea_live/card_component.html.heex @@ -7,6 +7,15 @@ data-lane-id={@idea.lane_id} data-position={@idea.position_order} > + <%= with image when not is_nil(image) <- Enum.find(@idea.files, fn f -> Attachments.simplified_attached_file_type(f.file_type) == "image" end) do %> +
+ {image.name +
+ <% end %>
<%= if has_moderating_or_ownership_permission(@brainstorming.id, @idea, @current_user) do %> <.link diff --git a/priv/gettext/de/LC_MESSAGES/default.po b/priv/gettext/de/LC_MESSAGES/default.po index ee3dae62..4f3efa1b 100644 --- a/priv/gettext/de/LC_MESSAGES/default.po +++ b/priv/gettext/de/LC_MESSAGES/default.po @@ -37,7 +37,7 @@ msgid "%{name} - New Idea" msgstr "%{name} - Neue Idee" #: lib/mindwendel_web/live/comment_live/show_component.html.heex:22 -#: lib/mindwendel_web/live/idea_live/card_component.html.heex:17 +#: lib/mindwendel_web/live/idea_live/card_component.html.heex:26 #, elixir-autogen, elixir-format msgid "Are you sure you want to delete this idea?" msgstr "Möchtest du die Idee löschen?" @@ -191,7 +191,7 @@ msgstr "Nutzername" #: lib/mindwendel_web/controllers/admin/brainstorming_html/export.html.heex:3 #: lib/mindwendel_web/live/comment_live/show_component.html.heex:40 -#: lib/mindwendel_web/live/idea_live/card_component.html.heex:84 +#: lib/mindwendel_web/live/idea_live/card_component.html.heex:93 #, elixir-autogen, elixir-format msgid "By" msgstr "Von" @@ -447,13 +447,13 @@ msgstr "Eine Spalte" msgid "No lanes available" msgstr "Keine Spalten vorhanden" -#: lib/mindwendel_web/live/idea_live/card_component.html.heex:24 +#: lib/mindwendel_web/live/idea_live/card_component.html.heex:33 #, elixir-autogen, elixir-format, fuzzy msgid "Edit idea" msgstr "Idee bearbeiten" #: lib/mindwendel_web/live/comment_live/show_component.html.heex:30 -#: lib/mindwendel_web/live/idea_live/card_component.html.heex:16 +#: lib/mindwendel_web/live/idea_live/card_component.html.heex:25 #, elixir-autogen, elixir-format, fuzzy msgid "Delete idea" msgstr "Löschen" @@ -468,7 +468,7 @@ msgstr "Idee aktualisiert" msgid "Additional Attachment" msgstr "Zusätzlicher Anhang" -#: lib/mindwendel_web/live/idea_live/card_component.html.heex:74 +#: lib/mindwendel_web/live/idea_live/card_component.html.heex:83 #: lib/mindwendel_web/live/idea_live/form_component.html.heex:43 #: lib/mindwendel_web/live/idea_live/show_component.html.heex:27 #, elixir-autogen, elixir-format @@ -525,8 +525,8 @@ msgstr "Löschen" msgid "No comments available" msgstr "Keine Kommentare vorhanden" -#: lib/mindwendel_web/live/idea_live/card_component.html.heex:32 -#: lib/mindwendel_web/live/idea_live/card_component.html.heex:137 +#: lib/mindwendel_web/live/idea_live/card_component.html.heex:41 +#: lib/mindwendel_web/live/idea_live/card_component.html.heex:146 #, elixir-autogen, elixir-format, fuzzy msgid "Show idea" msgstr "Zeige Idee" @@ -649,3 +649,8 @@ msgstr "Keine neuen Clustering-Änderungen" #, elixir-autogen, elixir-format msgid "Nothing to cluster right now" msgstr "Derzeit nichts zum Clustern vorhanden" + +#: lib/mindwendel_web/live/idea_live/card_component.html.heex:14 +#, elixir-autogen, elixir-format +msgid "Attached image" +msgstr "" diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot index a8acb943..b94420bd 100644 --- a/priv/gettext/default.pot +++ b/priv/gettext/default.pot @@ -36,7 +36,7 @@ msgid "%{name} - New Idea" msgstr "" #: lib/mindwendel_web/live/comment_live/show_component.html.heex:22 -#: lib/mindwendel_web/live/idea_live/card_component.html.heex:17 +#: lib/mindwendel_web/live/idea_live/card_component.html.heex:26 #, elixir-autogen, elixir-format msgid "Are you sure you want to delete this idea?" msgstr "" @@ -190,7 +190,7 @@ msgstr "" #: lib/mindwendel_web/controllers/admin/brainstorming_html/export.html.heex:3 #: lib/mindwendel_web/live/comment_live/show_component.html.heex:40 -#: lib/mindwendel_web/live/idea_live/card_component.html.heex:84 +#: lib/mindwendel_web/live/idea_live/card_component.html.heex:93 #, elixir-autogen, elixir-format msgid "By" msgstr "" @@ -446,13 +446,13 @@ msgstr "" msgid "No lanes available" msgstr "" -#: lib/mindwendel_web/live/idea_live/card_component.html.heex:24 +#: lib/mindwendel_web/live/idea_live/card_component.html.heex:33 #, elixir-autogen, elixir-format msgid "Edit idea" msgstr "" #: lib/mindwendel_web/live/comment_live/show_component.html.heex:30 -#: lib/mindwendel_web/live/idea_live/card_component.html.heex:16 +#: lib/mindwendel_web/live/idea_live/card_component.html.heex:25 #, elixir-autogen, elixir-format msgid "Delete idea" msgstr "" @@ -467,7 +467,7 @@ msgstr "" msgid "Additional Attachment" msgstr "" -#: lib/mindwendel_web/live/idea_live/card_component.html.heex:74 +#: lib/mindwendel_web/live/idea_live/card_component.html.heex:83 #: lib/mindwendel_web/live/idea_live/form_component.html.heex:43 #: lib/mindwendel_web/live/idea_live/show_component.html.heex:27 #, elixir-autogen, elixir-format @@ -524,8 +524,8 @@ msgstr "" msgid "No comments available" msgstr "" -#: lib/mindwendel_web/live/idea_live/card_component.html.heex:32 -#: lib/mindwendel_web/live/idea_live/card_component.html.heex:137 +#: lib/mindwendel_web/live/idea_live/card_component.html.heex:41 +#: lib/mindwendel_web/live/idea_live/card_component.html.heex:146 #, elixir-autogen, elixir-format msgid "Show idea" msgstr "" @@ -648,3 +648,8 @@ msgstr "" #, elixir-autogen, elixir-format msgid "Nothing to cluster right now" msgstr "" + +#: lib/mindwendel_web/live/idea_live/card_component.html.heex:14 +#, elixir-autogen, elixir-format +msgid "Attached image" +msgstr "" diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po index d8ac70eb..2f1650ef 100644 --- a/priv/gettext/en/LC_MESSAGES/default.po +++ b/priv/gettext/en/LC_MESSAGES/default.po @@ -37,7 +37,7 @@ msgid "%{name} - New Idea" msgstr "" #: lib/mindwendel_web/live/comment_live/show_component.html.heex:22 -#: lib/mindwendel_web/live/idea_live/card_component.html.heex:17 +#: lib/mindwendel_web/live/idea_live/card_component.html.heex:26 #, elixir-autogen, elixir-format msgid "Are you sure you want to delete this idea?" msgstr "" @@ -191,7 +191,7 @@ msgstr "" #: lib/mindwendel_web/controllers/admin/brainstorming_html/export.html.heex:3 #: lib/mindwendel_web/live/comment_live/show_component.html.heex:40 -#: lib/mindwendel_web/live/idea_live/card_component.html.heex:84 +#: lib/mindwendel_web/live/idea_live/card_component.html.heex:93 #, elixir-autogen, elixir-format msgid "By" msgstr "" @@ -447,13 +447,13 @@ msgstr "" msgid "No lanes available" msgstr "" -#: lib/mindwendel_web/live/idea_live/card_component.html.heex:24 +#: lib/mindwendel_web/live/idea_live/card_component.html.heex:33 #, elixir-autogen, elixir-format, fuzzy msgid "Edit idea" msgstr "" #: lib/mindwendel_web/live/comment_live/show_component.html.heex:30 -#: lib/mindwendel_web/live/idea_live/card_component.html.heex:16 +#: lib/mindwendel_web/live/idea_live/card_component.html.heex:25 #, elixir-autogen, elixir-format, fuzzy msgid "Delete idea" msgstr "" @@ -468,7 +468,7 @@ msgstr "" msgid "Additional Attachment" msgstr "" -#: lib/mindwendel_web/live/idea_live/card_component.html.heex:74 +#: lib/mindwendel_web/live/idea_live/card_component.html.heex:83 #: lib/mindwendel_web/live/idea_live/form_component.html.heex:43 #: lib/mindwendel_web/live/idea_live/show_component.html.heex:27 #, elixir-autogen, elixir-format @@ -525,8 +525,8 @@ msgstr "" msgid "No comments available" msgstr "" -#: lib/mindwendel_web/live/idea_live/card_component.html.heex:32 -#: lib/mindwendel_web/live/idea_live/card_component.html.heex:137 +#: lib/mindwendel_web/live/idea_live/card_component.html.heex:41 +#: lib/mindwendel_web/live/idea_live/card_component.html.heex:146 #, elixir-autogen, elixir-format, fuzzy msgid "Show idea" msgstr "" @@ -649,3 +649,8 @@ msgstr "" #, elixir-autogen, elixir-format msgid "Nothing to cluster right now" msgstr "" + +#: lib/mindwendel_web/live/idea_live/card_component.html.heex:14 +#, elixir-autogen, elixir-format +msgid "Attached image" +msgstr "" diff --git a/priv/gettext/it/LC_MESSAGES/default.po b/priv/gettext/it/LC_MESSAGES/default.po index 525d3d39..1f3641d1 100644 --- a/priv/gettext/it/LC_MESSAGES/default.po +++ b/priv/gettext/it/LC_MESSAGES/default.po @@ -37,7 +37,7 @@ msgid "%{name} - New Idea" msgstr "%{name} - Nuova Idea" #: lib/mindwendel_web/live/comment_live/show_component.html.heex:22 -#: lib/mindwendel_web/live/idea_live/card_component.html.heex:17 +#: lib/mindwendel_web/live/idea_live/card_component.html.heex:26 #, elixir-autogen, elixir-format msgid "Are you sure you want to delete this idea?" msgstr "Sei sicuro di voler eliminare questa idea?" @@ -191,7 +191,7 @@ msgstr "Nome utente" #: lib/mindwendel_web/controllers/admin/brainstorming_html/export.html.heex:3 #: lib/mindwendel_web/live/comment_live/show_component.html.heex:40 -#: lib/mindwendel_web/live/idea_live/card_component.html.heex:84 +#: lib/mindwendel_web/live/idea_live/card_component.html.heex:93 #, elixir-autogen, elixir-format msgid "By" msgstr "Di" @@ -447,13 +447,13 @@ msgstr "Categoria predefinita" msgid "No lanes available" msgstr "Nessuna categoria disponibile" -#: lib/mindwendel_web/live/idea_live/card_component.html.heex:24 +#: lib/mindwendel_web/live/idea_live/card_component.html.heex:33 #, elixir-autogen, elixir-format, fuzzy msgid "Edit idea" msgstr "Modifica idea" #: lib/mindwendel_web/live/comment_live/show_component.html.heex:30 -#: lib/mindwendel_web/live/idea_live/card_component.html.heex:16 +#: lib/mindwendel_web/live/idea_live/card_component.html.heex:25 #, elixir-autogen, elixir-format, fuzzy msgid "Delete idea" msgstr "Elimina idea" @@ -468,7 +468,7 @@ msgstr "Idea aggiornata" msgid "Additional Attachment" msgstr "Allegato aggiuntivo" -#: lib/mindwendel_web/live/idea_live/card_component.html.heex:74 +#: lib/mindwendel_web/live/idea_live/card_component.html.heex:83 #: lib/mindwendel_web/live/idea_live/form_component.html.heex:43 #: lib/mindwendel_web/live/idea_live/show_component.html.heex:27 #, elixir-autogen, elixir-format @@ -525,8 +525,8 @@ msgstr "Elimina commento" msgid "No comments available" msgstr "Nessun commento disponibile" -#: lib/mindwendel_web/live/idea_live/card_component.html.heex:32 -#: lib/mindwendel_web/live/idea_live/card_component.html.heex:137 +#: lib/mindwendel_web/live/idea_live/card_component.html.heex:41 +#: lib/mindwendel_web/live/idea_live/card_component.html.heex:146 #, elixir-autogen, elixir-format, fuzzy msgid "Show idea" msgstr "Mostra idea" @@ -649,3 +649,8 @@ msgstr "Nessuna nuova modifica al raggruppamento" #, elixir-autogen, elixir-format msgid "Nothing to cluster right now" msgstr "Niente da raggruppare al momento" + +#: lib/mindwendel_web/live/idea_live/card_component.html.heex:14 +#, elixir-autogen, elixir-format +msgid "Attached image" +msgstr "" From 8629b058217bdba8beecb85ad620d2dfff5460c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Mar 2026 13:42:02 +0000 Subject: [PATCH 12/13] Build(deps): Bump docker/setup-qemu-action from 3 to 4 (#681) Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 3 to 4. - [Release notes](https://github.com/docker/setup-qemu-action/releases) - [Commits](https://github.com/docker/setup-qemu-action/compare/v3...v4) --- updated-dependencies: - dependency-name: docker/setup-qemu-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: JannikStreek --- .github/workflows/on_push_main_publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/on_push_main_publish.yml b/.github/workflows/on_push_main_publish.yml index a37a9a87..f288ae29 100644 --- a/.github/workflows/on_push_main_publish.yml +++ b/.github/workflows/on_push_main_publish.yml @@ -30,7 +30,7 @@ jobs: steps: - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 with: From 0da5b0cf428c6ccc0c0f69b6feed1632d5353e4a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 07:37:28 +0100 Subject: [PATCH 13/13] Build(deps): Bump docker/setup-buildx-action from 3 to 4 (#680) Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3 to 4. - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/v3...v4) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/on_push_main_publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/on_push_main_publish.yml b/.github/workflows/on_push_main_publish.yml index f288ae29..00fdb0c6 100644 --- a/.github/workflows/on_push_main_publish.yml +++ b/.github/workflows/on_push_main_publish.yml @@ -32,7 +32,7 @@ jobs: - name: Set up QEMU uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 with: version: v0.12.0