Compare commits

..

1 commit

Author SHA1 Message Date
39325a81a1
feat(ci): Build docker container
Some checks failed
continuous-integration/drone/push Build is failing
2025-05-28 17:14:41 +02:00
66 changed files with 402 additions and 3091 deletions

View file

@ -4,39 +4,69 @@ name: check
services: services:
- name: postgres - name: postgres
image: docker.io/library/postgres:17.5 image: docker.io/library/postgres:17.2
environment: environment:
POSTGRES_USER: postgres POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres POSTGRES_PASSWORD: postgres
- name: docker
image: docker:dind
privileged: true
volumes:
- name: dockersock
path: /var/run
trigger: trigger:
event: event:
- push - push
steps: steps:
- name: build & publish container? - name: lint
image: docker:dind image: docker.io/library/elixir:1.18.3-otp-27
volumes:
- name: dockersock
path: /var/run
commands: commands:
- sleep 5 # give docker time to start # Install hex package manager
- docker ps -a - mix local.hex --force
- docker build --tag mitgliederverwaltung . # Fetch dependencies
- mix deps.get
# Check for compilation errors & warnings
- mix compile --warnings-as-errors
# Check formatting
- mix format --check-formatted
# Security checks
- mix sobelow --config
# Check dependencies for known vulnerabilities
- mix deps.audit
# Check for dependencies that are not maintained anymore
- mix hex.audit
# Provide hints for improving code quality
- mix credo
volumes: - name: wait_for_postgres
- name: cache image: docker.io/library/postgres:17.2
host: commands:
path: /tmp/drone_cache # Wait for postgres to become available
- |
for i in {1..20}; do
if pg_isready -h postgres -U postgres; then
exit 0
else
true
fi
sleep 2
done
echo "Postgres did not become available, aborting."
exit 1
- name: dockersock - name: test
temp: {} image: docker.io/library/elixir:1.18.3-otp-27
environment:
MIX_ENV: test
TEST_POSTGRES_HOST: postgres
commands:
# Install hex package manager
- mix local.hex --force
# Fetch dependencies
- mix deps.get
# Run tests
- mix test
- name: build & publish container?
image: docker.io/library/elixir:1.18.3-otp-27
commands:
- docker build --tag mitgliederverwaltung .
--- ---
kind: pipeline kind: pipeline
@ -56,13 +86,13 @@ environment:
steps: steps:
- name: renovate - name: renovate
image: renovate/renovate:41.37 image: renovate/renovate:40.22
environment: environment:
RENOVATE_CONFIG_FILE: "renovate_backend_config.js" RENOVATE_CONFIG_FILE: "renovate_backend_config.js"
RENOVATE_TOKEN: RENOVATE_TOKEN:
from_secret: RENOVATE_TOKEN from_secret: RENOVATE_TOKEN
GITHUB_COM_TOKEN: #GITHUB_COM_TOKEN:
from_secret: GITHUB_COM_TOKEN # from_secret: GITHUB_COM_TOKEN
commands: commands:
# https://github.com/renovatebot/renovate/discussions/15049 # https://github.com/renovatebot/renovate/discussions/15049
- unset GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL - unset GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL

View file

@ -1,9 +1,7 @@
[ [
import_deps: [ import_deps: [
:ash_authentication_phoenix,
:ash_admin, :ash_admin,
:ash_postgres, :ash_postgres,
:ash_authentication,
:ash_phoenix, :ash_phoenix,
:ash, :ash,
:reactor, :reactor,

4
.gitignore vendored
View file

@ -35,7 +35,3 @@ mv-*.tar
npm-debug.log npm-debug.log
/assets/node_modules/ /assets/node_modules/
.cursor
# Ignore the .env file with env variables
.env

View file

@ -1,10 +0,0 @@
# This is a configuration file for igniter.
# For option documentation, see https://hexdocs.pm/igniter/Igniter.Project.IgniterConfig.html
# To keep it up to date, use `mix igniter.setup`
[
module_location: :outside_matching_folder,
extensions: [{Igniter.Extensions.Phoenix, []}],
deps_location: :last_list_literal,
source_folders: ["lib", "test/support"],
dont_move_files: [~r"lib/mix"]
]

View file

@ -1,3 +1,3 @@
elixir 1.18.3-otp-27 elixir 1.18.3-otp-27
erlang 27.3.4 erlang 27.3
just 1.42.2 just 1.40.0

View file

@ -1,6 +1,4 @@
set dotenv-load := true run: install-dependencies start-database migrate-database
run: install-dependencies start-database migrate-database seed-database
mix phx.server mix phx.server
install-dependencies: install-dependencies:
@ -11,20 +9,12 @@ migrate-database:
reset-database: reset-database:
mix ash.reset mix ash.reset
MIX_ENV=test mix ash.reset
seed-database:
mix run priv/repo/seeds.exs
start-database: start-database:
docker compose up -d docker compose up -d
ci-dev: lint audit test ci-dev: lint audit test
gettext:
mix gettext.extract
mix gettext.merge priv/gettext
lint: lint:
mix format --check-formatted mix format --check-formatted
mix compile --warnings-as-errors mix compile --warnings-as-errors
@ -35,7 +25,7 @@ audit:
mix deps.audit mix deps.audit
mix hex.audit mix hex.audit
test: install-dependencies start-database test:
mix test mix test
format: format:
@ -47,34 +37,3 @@ build-docker-container:
# This is meant for debugging the container build process only. # This is meant for debugging the container build process only.
run-docker-container: build-docker-container run-docker-container: build-docker-container
docker run -e "SECRET_KEY_BASE=ahK8BeiDaibaige1ahkooS0chie9lo7the7uuzar0eeBeeCh2iereteshee2Oosu" -e='DATABASE_URL=postgres://postgres@localhost:5432/mv_dev' -e='PORT=4040' -e='PHX_HOST=localhost' --network=host mitgliederverwaltung docker run -e "SECRET_KEY_BASE=ahK8BeiDaibaige1ahkooS0chie9lo7the7uuzar0eeBeeCh2iereteshee2Oosu" -e='DATABASE_URL=postgres://postgres@localhost:5432/mv_dev' -e='PORT=4040' -e='PHX_HOST=localhost' --network=host mitgliederverwaltung
# Usage:
# just regen-migrations migration_name [commit_hash]
# If commit_hash is given, rollback & delete the migrations from that commit.
# Otherwise, rollback & delete all untracked migrations.
regen-migrations migration_name commit_hash='':
#!/usr/bin/env bash
set -euo pipefail
# Pick migrations either from the given commit or untracked files
if [ -n "{{commit_hash}}" ]; then
echo "→ Rolling back migrations from commit {{commit_hash}}"
MIG_FILES=$(git show --name-only --pretty=format: "{{commit_hash}}" \
| grep -E "^priv/repo/migrations/|^priv/resource_snapshots")
else
echo "→ Rolling back all untracked migrations"
MIG_FILES=$(git ls-files --others priv/repo/migrations)
fi
# Roll back in Ash
COUNT=$(echo "$MIG_FILES" | wc -l)
mix ash_postgres.rollback -n "$COUNT"
# Remove the migration files
echo removing $MIG_FILES
echo "$MIG_FILES" | xargs rm -f
# Also clean up any untracked resource snapshots
git ls-files --others priv/resource_snapshots | xargs rm -f
# Generate a fresh migration
mix ash.codegen --name "{{migration_name}}"

View file

@ -7,7 +7,6 @@ const path = require("path")
module.exports = { module.exports = {
content: [ content: [
"../deps/ash_authentication_phoenix/**/*.*ex",
"./js/**/*.js", "./js/**/*.js",
"../lib/mv_web.ex", "../lib/mv_web.ex",
"../lib/mv_web/**/*.*ex" "../lib/mv_web/**/*.*ex"

View file

@ -49,7 +49,7 @@ config :spark,
config :mv, config :mv,
ecto_repos: [Mv.Repo], ecto_repos: [Mv.Repo],
generators: [timestamp_type: :utc_datetime], generators: [timestamp_type: :utc_datetime],
ash_domains: [Mv.Membership, Mv.Accounts] ash_domains: [Mv.Membership]
# Configures the endpoint # Configures the endpoint
config :mv, MvWeb.Endpoint, config :mv, MvWeb.Endpoint,

View file

@ -84,19 +84,3 @@ config :phoenix_live_view,
# Disable swoosh api client as it is only required for production adapters. # Disable swoosh api client as it is only required for production adapters.
config :swoosh, :api_client, false config :swoosh, :api_client, false
config :mv, :secret_key_base, "ryn7D6ssmIHQFWIks2sFiTGATgwwAR1+3bN8p7fy6qVtB8qnxOuk1uyAwHz1Q8WB"
# Signing Secret for Authentication
config :mv, :token_signing_secret, "IwUwi65TrEeExwBXXFPGm2I7889NsL"
config :mv, :rauthy,
client_id: "mv",
base_url: "http://localhost:8080/auth/v1",
client_secret: System.get_env("OIDC_CLIENT_SECRET"),
redirect_uri: "http://localhost:4000/auth/user/rauthy/callback"
# AshAuthentication development configuration
config :mv, :session_identifier, :jti
config :mv, :require_token_presence_for_authentication, true

View file

@ -53,13 +53,6 @@ if config_env() == :prod do
config :mv, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY") config :mv, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY")
config :mv, :rauthy, redirect_uri: "http://localhost:4000/auth/user/rauthy/callback"
# AshAuthentication production configuration
config :mv, :session_identifier, :jti
config :mv, :require_token_presence_for_authentication, true
config :mv, MvWeb.Endpoint, config :mv, MvWeb.Endpoint,
url: [host: host, port: 443, scheme: "https"], url: [host: host, port: 443, scheme: "https"],
http: [ http: [

View file

@ -9,7 +9,6 @@ config :mv, Mv.Repo,
username: "postgres", username: "postgres",
password: "postgres", password: "postgres",
hostname: System.get_env("TEST_POSTGRES_HOST", "localhost"), hostname: System.get_env("TEST_POSTGRES_HOST", "localhost"),
port: System.get_env("TEST_POSTGRES_PORT", "5000"),
database: "mv_test#{System.get_env("MIX_TEST_PARTITION")}", database: "mv_test#{System.get_env("MIX_TEST_PARTITION")}",
pool: Ecto.Adapters.SQL.Sandbox, pool: Ecto.Adapters.SQL.Sandbox,
pool_size: System.schedulers_online() * 2 pool_size: System.schedulers_online() * 2
@ -36,12 +35,3 @@ config :phoenix, :plug_init_mode, :runtime
# Enable helpful, but potentially expensive runtime checks # Enable helpful, but potentially expensive runtime checks
config :phoenix_live_view, config :phoenix_live_view,
enable_expensive_runtime_checks: true enable_expensive_runtime_checks: true
# Token signing secret for AshAuthentication tests
config :mv, :token_signing_secret, "test_secret_key_for_ash_authentication_tokens"
# AshAuthentication test-specific configuration
# In Tests we don't need token presence, but in other envs its recommended
config :mv, :session_identifier, :unsafe
config :mv, :require_token_presence_for_authentication, false

View file

@ -1,13 +1,8 @@
version: "3.5" version: "3.5"
networks:
local:
rauthy-dev:
driver: bridge
services: services:
db: db:
image: postgres:17.5-alpine image: postgres:17.2-alpine
environment: environment:
POSTGRES_USER: postgres POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres POSTGRES_PASSWORD: postgres
@ -23,46 +18,8 @@ services:
networks: networks:
- local - local
mailcrab: networks:
image: marlonb/mailcrab:latest local:
ports:
- "1080:1080"
networks:
- rauthy-dev
rauthy:
container_name: rauthy-dev
image: ghcr.io/sebadob/rauthy:0.31.2
environment:
- LOCAL_TEST=true
- SMTP_URL=mailcrab
- SMTP_PORT=1025
- SMTP_DANGER_INSECURE=true
- LISTEN_SCHEME=http
- PUB_URL=localhost:8080
- BOOTSTRAP_ADMIN_PASSWORD_PLAIN=RauthyTest12345
#- HIQLITE=false
#- PG_HOST=db
#- PG_PORT=5432
#- PG_USER=postgres
#- PG_PASSWORD=postgres
#- PG_DB_NAME=mv_dev
ports:
- "8080:8080"
depends_on:
- mailcrab
- db
networks:
- rauthy-dev
- local
volumes:
- type: volume
source: rauthy-data
target: /app/data
volumes: volumes:
postgres-data: postgres-data:
rauthy-data:

View file

@ -1,18 +0,0 @@
defmodule Mv.Accounts do
@moduledoc """
AshAuthentication specific domain to handle Authentication for users.
"""
use Ash.Domain,
extensions: [AshPhoenix]
resources do
resource Mv.Accounts.User do
define :create_user, action: :create
define :list_users, action: :read
define :update_user, action: :update
define :destroy_user, action: :destroy
end
resource Mv.Accounts.Token
end
end

View file

@ -1,14 +0,0 @@
defmodule Mv.Accounts.Token do
@moduledoc """
AshAuthentication specific ressource
"""
use Ash.Resource,
data_layer: AshPostgres.DataLayer,
extensions: [AshAuthentication.TokenResource],
domain: Mv.Accounts
postgres do
table "tokens"
repo Mv.Repo
end
end

View file

@ -1,127 +0,0 @@
defmodule Mv.Accounts.User do
@moduledoc """
The ressource for keeping user-specific data related to the login process. It is used by AshAuthentication to handle the Authentication strategies like SSO.
"""
use Ash.Resource,
domain: Mv.Accounts,
data_layer: AshPostgres.DataLayer,
extensions: [AshAuthentication]
# authorizers: [Ash.Policy.Authorizer]
postgres do
table "users"
repo Mv.Repo
end
@doc """
AshAuthentication specific: Defines the strategies we want to use for authentication.
Currently password and SSO with Rauthy as OIDC provider
"""
authentication do
session_identifier Application.compile_env(:mv, :session_identifier, :jti)
tokens do
enabled? true
token_resource Mv.Accounts.Token
require_token_presence_for_authentication? Application.compile_env(
:mv,
:require_token_presence_for_authentication,
false
)
store_all_tokens? true
# signing_algorithm "EdDSA" -> https://git.local-it.org/local-it/mitgliederverwaltung/issues/87
signing_secret fn _, _ ->
{:ok, Application.get_env(:mv, :token_signing_secret)}
end
end
strategies do
oidc :rauthy do
client_id Mv.Secrets
base_url Mv.Secrets
redirect_uri Mv.Secrets
client_secret Mv.Secrets
auth_method :client_secret_jwt
code_verifier true
# id_token_signed_response_alg "EdDSA" #-> https://git.local-it.org/local-it/mitgliederverwaltung/issues/87
end
password :password do
identity_field :email
hash_provider AshAuthentication.BcryptProvider
confirmation_required? false
end
end
end
actions do
defaults [:read, :create, :destroy, :update]
read :get_by_subject do
description "Get a user by the subject claim in a JWT"
argument :subject, :string, allow_nil?: false
get? true
prepare AshAuthentication.Preparations.FilterBySubject
end
read :sign_in_with_rauthy do
argument :user_info, :map, allow_nil?: false
argument :oauth_tokens, :map, allow_nil?: false
prepare AshAuthentication.Strategy.OAuth2.SignInPreparation
filter expr(email == get_path(^arg(:user_info), [:email]))
end
create :register_with_rauthy do
argument :user_info, :map, allow_nil?: false
argument :oauth_tokens, :map, allow_nil?: false
upsert? true
upsert_identity :unique_email
change AshAuthentication.GenerateTokenChange
change fn changeset, _ctx ->
user_info = Ash.Changeset.get_argument(changeset, :user_info)
changeset
|> Ash.Changeset.change_attribute(:email, user_info["preferred_username"])
|> Ash.Changeset.change_attribute(:oidc_id, user_info["id"])
end
end
end
attributes do
uuid_primary_key :id
attribute :email, :ci_string, allow_nil?: false, public?: true
attribute :hashed_password, :string, sensitive?: true, allow_nil?: true
attribute :oidc_id, :string, allow_nil?: true
end
relationships do
belongs_to :member, Mv.Membership.Member
end
identities do
identity :unique_email, [:email]
identity :unique_oidc_id, [:oidc_id]
end
# You can customize this if you wish, but this is a safe default that
# only allows user data to be interacted with via AshAuthentication.
# policies do
# bypass AshAuthentication.Checks.AshAuthenticationInteraction do
# authorize_if(always())
# end
# policy always() do
# forbid_if(always())
# end
# end
end

View file

@ -1,18 +0,0 @@
defmodule Mv.Accounts.UserIdentity do
@moduledoc """
AshAuthentication specific ressource
"""
use Ash.Resource,
data_layer: AshPostgres.DataLayer,
extensions: [AshAuthentication.UserIdentity],
domain: Mv.Accounts
postgres do
table "user_identities"
repo Mv.Repo
end
user_identity do
user_resource Mv.Accounts.User
end
end

View file

@ -1,37 +0,0 @@
defmodule Mv.Membership.Email do
@match_pattern ~S/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/
@match_regex Regex.compile!(@match_pattern)
@min_length 5
@max_length 254
use Ash.Type.NewType,
subtype_of: :string,
constraints: [
match: @match_pattern,
trim?: true,
min_length: @min_length,
max_length: @max_length
]
@impl true
def cast_input(value, _) when is_binary(value) do
value = String.trim(value)
cond do
String.length(value) < @min_length ->
:error
String.length(value) > @max_length ->
:error
!Regex.match?(@match_regex, value) ->
:error
true ->
{:ok, value}
end
end
@impl true
def cast_input(_, _), do: :error
end

View file

@ -14,23 +14,6 @@ defmodule Mv.Membership.Member do
create :create_member do create :create_member do
primary? true primary? true
argument :properties, {:array, :map} argument :properties, {:array, :map}
accept [
:first_name,
:last_name,
:email,
:birth_date,
:paid,
:phone_number,
:join_date,
:exit_date,
:notes,
:city,
:street,
:house_number,
:postal_code
]
change manage_relationship(:properties, type: :create) change manage_relationship(:properties, type: :create)
end end
@ -38,134 +21,12 @@ defmodule Mv.Membership.Member do
primary? true primary? true
require_atomic? false require_atomic? false
argument :properties, {:array, :map} argument :properties, {:array, :map}
accept [
:first_name,
:last_name,
:email,
:birth_date,
:paid,
:phone_number,
:join_date,
:exit_date,
:notes,
:city,
:street,
:house_number,
:postal_code
]
change manage_relationship(:properties, on_match: :update, on_no_match: :create) change manage_relationship(:properties, on_match: :update, on_no_match: :create)
end end
end end
validations do
# Required fields are covered by allow_nil? false
# First name and last name must not be empty
validate present(:first_name)
validate present(:last_name)
validate present(:email)
# Birth date not in the future
validate compare(:birth_date, less_than_or_equal_to: &Date.utc_today/0),
where: [present(:birth_date)],
message: "cannot be in the future"
# Join date not in the future
validate compare(:join_date, less_than_or_equal_to: &Date.utc_today/0),
where: [present(:join_date)],
message: "cannot be in the future"
# Exit date not before join date
validate compare(:exit_date, greater_than: :join_date),
where: [present([:join_date, :exit_date])],
message: "cannot be before join date"
# Phone number format (only if set)
validate match(:phone_number, ~r/^\+?[0-9\- ]{6,20}$/),
where: [present(:phone_number)],
message: "is not a valid phone number"
# Postal code format (only if set)
validate match(:postal_code, ~r/^\d{5}$/),
where: [present(:postal_code)],
message: "must consist of 5 digits"
# Email validation with EctoCommons.EmailValidator
validate fn changeset, _ ->
email = Ash.Changeset.get_attribute(changeset, :email)
changeset2 =
{%{}, %{email: :string}}
|> Ecto.Changeset.cast(%{email: email}, [:email])
|> EctoCommons.EmailValidator.validate_email(:email, checks: [:html_input, :pow])
if changeset2.valid? do
:ok
else
{:error, field: :email, message: "is not a valid email"}
end
end
end
attributes do attributes do
uuid_v7_primary_key :id uuid_v7_primary_key :id
attribute :first_name, :string do
allow_nil? false
constraints min_length: 1
end
attribute :last_name, :string do
allow_nil? false
constraints min_length: 1
end
attribute :email, :string do
allow_nil? false
constraints min_length: 5, max_length: 254
end
attribute :birth_date, :date do
allow_nil? true
end
attribute :paid, :boolean do
allow_nil? true
end
attribute :phone_number, :string do
allow_nil? true
end
attribute :join_date, :date do
allow_nil? true
end
attribute :exit_date, :date do
allow_nil? true
end
attribute :notes, :string do
allow_nil? true
end
attribute :city, :string do
allow_nil? true
end
attribute :street, :string do
allow_nil? true
end
attribute :house_number, :string do
allow_nil? true
end
attribute :postal_code, :string do
allow_nil? true
end
end end
relationships do relationships do

View file

@ -6,10 +6,6 @@ defmodule Mv.Membership.Property do
postgres do postgres do
table "properties" table "properties"
repo Mv.Repo repo Mv.Repo
references do
reference :member, on_delete: :delete
end
end end
actions do actions do
@ -20,17 +16,8 @@ defmodule Mv.Membership.Property do
attributes do attributes do
uuid_primary_key :id uuid_primary_key :id
attribute :value, :union, attribute :value, :string,
constraints: [ description: "Speichert den Wert, Typ-Interpretation per property_type.typ"
storage: :type_and_value,
types: [
boolean: [type: :boolean],
date: [type: :date],
integer: [type: :integer],
string: [type: :string],
email: [type: Mv.Membership.Email]
]
]
end end
relationships do relationships do
@ -38,8 +25,4 @@ defmodule Mv.Membership.Property do
belongs_to :property_type, Mv.Membership.PropertyType belongs_to :property_type, Mv.Membership.PropertyType
end end
calculations do
calculate :value_to_string, :string, expr(value[:value] <> "")
end
end end

View file

@ -10,7 +10,7 @@ defmodule Mv.Membership.PropertyType do
actions do actions do
defaults [:create, :read, :update, :destroy] defaults [:create, :read, :update, :destroy]
default_accept [:name, :value_type, :description, :immutable, :required] default_accept [:name, :type, :description, :immutable, :required]
end end
attributes do attributes do
@ -18,10 +18,9 @@ defmodule Mv.Membership.PropertyType do
attribute :name, :string, allow_nil?: false, public?: true attribute :name, :string, allow_nil?: false, public?: true
attribute :value_type, :atom, attribute :type, :string,
constraints: [one_of: [:string, :integer, :boolean, :date, :email]],
allow_nil?: false, allow_nil?: false,
description: "Defines the datatype `Property.value` is interpreted as" description: "Definies the datatype `Property.value` is interpreted as"
attribute :description, :string, allow_nil?: true, public?: true attribute :description, :string, allow_nil?: true, public?: true

View file

@ -1,32 +0,0 @@
defmodule Mv.Accounts.User.Senders.SendNewUserConfirmationEmail do
@moduledoc """
Sends an email for a new user to confirm their email address.
"""
use AshAuthentication.Sender
use MvWeb, :verified_routes
import Swoosh.Email
alias Mv.Mailer
@impl true
def send(user, token, _) do
new()
# Replace with email from env
|> from({"noreply", "noreply@example.com"})
|> to(to_string(user.email))
|> subject("Confirm your email address")
|> html_body(body(token: token))
|> Mailer.deliver!()
end
defp body(params) do
url = url(~p"/confirm_new_user/#{params[:token]}")
"""
<p>Click this link to confirm your email:</p>
<p><a href="#{url}">#{url}</a></p>
"""
end
end

View file

@ -1,32 +0,0 @@
defmodule Mv.Accounts.User.Senders.SendPasswordResetEmail do
@moduledoc """
Sends a password reset email
"""
use AshAuthentication.Sender
use MvWeb, :verified_routes
import Swoosh.Email
alias Mv.Mailer
@impl true
def send(user, token, _) do
new()
# Replace with email from env
|> from({"noreply", "noreply@example.com"})
|> to(to_string(user.email))
|> subject("Reset your password")
|> html_body(body(token: token))
|> Mailer.deliver!()
end
defp body(params) do
url = url(~p"/password-reset/#{params[:token]}")
"""
<p>Click this link to reset your password:</p>
<p><a href="#{url}">#{url}</a></p>
"""
end
end

View file

@ -14,7 +14,6 @@ defmodule Mv.Application do
{Phoenix.PubSub, name: Mv.PubSub}, {Phoenix.PubSub, name: Mv.PubSub},
# Start the Finch HTTP client for sending emails # Start the Finch HTTP client for sending emails
{Finch, name: Mv.Finch}, {Finch, name: Mv.Finch},
{AshAuthentication.Supervisor, otp_app: :my},
# Start a worker by calling: Mv.Worker.start_link(arg) # Start a worker by calling: Mv.Worker.start_link(arg)
# {Mv.Worker, arg}, # {Mv.Worker, arg},
# Start to serve requests, typically the last entry # Start to serve requests, typically the last entry

View file

@ -5,7 +5,7 @@ defmodule Mv.Repo do
@impl true @impl true
def installed_extensions do def installed_extensions do
# Add extensions here, and the migration generator will install them. # Add extensions here, and the migration generator will install them.
["ash-functions", "citext"] ["ash-functions"]
end end
# Don't open unnecessary transactions # Don't open unnecessary transactions

View file

@ -1,46 +0,0 @@
defmodule Mv.Secrets do
use AshAuthentication.Secret
def secret_for(
[:authentication, :strategies, :rauthy, :client_id],
Mv.Accounts.User,
_opts,
_meth
) do
get_config(:client_id)
end
def secret_for(
[:authentication, :strategies, :rauthy, :redirect_uri],
Mv.Accounts.User,
_opts,
_meth
) do
get_config(:redirect_uri)
end
def secret_for(
[:authentication, :strategies, :rauthy, :client_secret],
Mv.Accounts.User,
_opts,
_meth
) do
get_config(:client_secret)
end
def secret_for(
[:authentication, :strategies, :rauthy, :base_url],
Mv.Accounts.User,
_opts,
_meth
) do
get_config(:base_url)
end
defp get_config(key) do
:mv
|> Application.fetch_env!(:rauthy)
|> Keyword.fetch!(key)
|> then(&{:ok, &1})
end
end

View file

@ -55,7 +55,6 @@ defmodule MvWeb do
use Phoenix.LiveView, use Phoenix.LiveView,
layout: {MvWeb.Layouts, :app} layout: {MvWeb.Layouts, :app}
on_mount MvWeb.LiveHelpers
unquote(html_helpers()) unquote(html_helpers())
end end
end end

View file

@ -1,20 +0,0 @@
defmodule MvWeb.AuthOverrides do
use AshAuthentication.Phoenix.Overrides
# configure your UI overrides here
# First argument to `override` is the component name you are overriding.
# The body contains any number of configurations you wish to override
# Below are some examples
# For a complete reference, see https://hexdocs.pm/ash_authentication_phoenix/ui-overrides.html
# override AshAuthentication.Phoenix.Components.Banner do
# set :image_url, "https://media.giphy.com/media/g7GKcSzwQfugw/giphy.gif"
# set :text_class, "bg-red-500"
# end
# override AshAuthentication.Phoenix.Components.SignIn do
# set :show_banner, false
# end
end

View file

@ -673,28 +673,4 @@ defmodule MvWeb.CoreComponents do
def translate_errors(errors, field) when is_list(errors) do def translate_errors(errors, field) when is_list(errors) do
for {^field, {msg, opts}} <- errors, do: translate_error({msg, opts}) for {^field, {msg, opts}} <- errors, do: translate_error({msg, opts})
end end
@doc """
Renders a list of items with name and value pairs.
## Examples
<.generic_list items={[
{item.name, item.value},
{other.name, other.value}
]} />
"""
attr :items, :list, required: true, doc: "List of {name, value} tuples"
def generic_list(assigns) do
~H"""
<div class="mt-14">
<dl class="-my-4 divide-y divide-zinc-100">
<div :for={{name, value} <- @items} class="flex gap-4 py-4 text-sm leading-6 sm:gap-8">
<dt class="w-1/4 flex-none text-zinc-500">{name}</dt>
<dd class="text-zinc-700">{value}</dd>
</div>
</dl>
</div>
"""
end
end end

View file

@ -9,13 +9,6 @@
</p> </p>
</div> </div>
<div class="flex items-center gap-4 font-semibold leading-6 text-zinc-900"> <div class="flex items-center gap-4 font-semibold leading-6 text-zinc-900">
<form method="post" action="/set_locale" class="mr-4">
<input type="hidden" name="_csrf_token" value={Plug.CSRFProtection.get_csrf_token()} />
<select name="locale" onchange="this.form.submit()" class="rounded border px-2 py-1">
<option value="de" selected={Gettext.get_locale() == "de"}>Deutsch</option>
<option value="en" selected={Gettext.get_locale() == "en"}>English</option>
</select>
</form>
<a href="https://twitter.com/elixirphoenix" class="hover:text-zinc-700"> <a href="https://twitter.com/elixirphoenix" class="hover:text-zinc-700">
@elixirphoenix @elixirphoenix
</a> </a>

View file

@ -1,59 +0,0 @@
require Logger
defmodule MvWeb.AuthController do
use MvWeb, :controller
use AshAuthentication.Phoenix.Controller
def success(conn, activity, user, _token) do
return_to = get_session(conn, :return_to) || ~p"/"
message =
case activity do
{:confirm_new_user, :confirm} -> gettext("Your email address has now been confirmed")
{:password, :reset} -> gettext("Your password has successfully been reset")
_ -> gettext("You are now signed in")
end
conn
|> delete_session(:return_to)
|> store_in_session(user)
# If your resource has a different name, update the assign name here (i.e :current_admin)
|> assign(:current_user, user)
|> put_flash(:info, message)
|> redirect(to: return_to)
end
def failure(conn, activity, reason) do
Logger.error(%{conn: conn, reason: reason})
message =
case {activity, reason} do
{_,
%AshAuthentication.Errors.AuthenticationFailed{
caused_by: %Ash.Error.Forbidden{
errors: [%AshAuthentication.Errors.CannotConfirmUnconfirmedUser{}]
}
}} ->
gettext("""
You have already signed in another way, but have not confirmed your account.
You can confirm your account using the link we sent to you, or by resetting your password.
""")
_ ->
gettext("Incorrect email or password")
end
conn
|> put_flash(:error, message)
|> redirect(to: ~p"/sign-in")
end
def sign_out(conn, _params) do
return_to = get_session(conn, :return_to) || ~p"/"
conn
|> clear_session(:mv)
|> put_flash(:info, gettext("You are now signed out"))
|> redirect(to: return_to)
end
end

View file

@ -1,55 +1,222 @@
<!-- <.flash_group flash={@flash} />
THIS IS JUST THE ASHAUTHENTICATION EXAMPLE - WE NEED TO CHANGE IT LATER <div class="left-[40rem] fixed inset-y-0 right-0 z-0 hidden lg:block xl:left-[50rem]">
--> <svg
<nav class="bg-gray-800"> viewBox="0 0 1480 957"
<div class="px-2 mx-auto max-w-7xl sm:px-6 lg:px-8"> fill="none"
<div class="relative flex items-center justify-between h-16"> aria-hidden="true"
<div class="flex items-center justify-center flex-1 sm:items-stretch sm:justify-start"> class="absolute inset-0 h-full w-full"
<div class="block ml-6"> preserveAspectRatio="xMinYMid slice"
<div class="flex space-x-4"> >
<div class="px-3 py-2 text-xl font-medium text-white "> <path fill="#EE7868" d="M0 0h1480v957H0z" />
Mitgliederverwaltung <path
</div> d="M137.542 466.27c-582.851-48.41-988.806-82.127-1608.412 658.2l67.39 810 3083.15-256.51L1535.94-49.622l-98.36 8.183C1269.29 281.468 734.115 515.799 146.47 467.012l-8.928-.742Z"
fill="#FF9F92"
/>
<path
d="M371.028 528.664C-169.369 304.988-545.754 149.198-1361.45 665.565l-182.58 792.025 3014.73 694.98 389.42-1689.25-96.18-22.171C1505.28 697.438 924.153 757.586 379.305 532.09l-8.277-3.426Z"
fill="#FA8372"
/>
<path
d="M359.326 571.714C-104.765 215.795-428.003-32.102-1349.55 255.554l-282.3 1224.596 3047.04 722.01 312.24-1354.467C1411.25 1028.3 834.355 935.995 366.435 577.166l-7.109-5.452Z"
fill="#E96856"
fill-opacity=".6"
/>
<path
d="M1593.87 1236.88c-352.15 92.63-885.498-145.85-1244.602-613.557l-5.455-7.105C-12.347 152.31-260.41-170.8-1225-131.458l-368.63 1599.048 3057.19 704.76 130.31-935.47Z"
fill="#C42652"
fill-opacity=".2"
/>
<path
d="M1411.91 1526.93c-363.79 15.71-834.312-330.6-1085.883-863.909l-3.822-8.102C72.704 125.95-101.074-242.476-1052.01-408.907l-699.85 1484.267 2837.75 1338.01 326.02-886.44Z"
fill="#A41C42"
fill-opacity=".2"
/>
<path
d="M1116.26 1863.69c-355.457-78.98-720.318-535.27-825.287-1115.521l-1.594-8.816C185.286 163.833 112.786-237.016-762.678-643.898L-1822.83 608.665 571.922 2635.55l544.338-771.86Z"
fill="#A41C42"
fill-opacity=".2"
/>
</svg>
</div>
<div class="px-4 py-10 sm:px-6 sm:py-28 lg:px-8 xl:px-28 xl:py-32">
<div class="mx-auto max-w-xl lg:mx-0">
<svg viewBox="0 0 71 48" class="h-12" aria-hidden="true">
<path
d="m26.371 33.477-.552-.1c-3.92-.729-6.397-3.1-7.57-6.829-.733-2.324.597-4.035 3.035-4.148 1.995-.092 3.362 1.055 4.57 2.39 1.557 1.72 2.984 3.558 4.514 5.305 2.202 2.515 4.797 4.134 8.347 3.634 3.183-.448 5.958-1.725 8.371-3.828.363-.316.761-.592 1.144-.886l-.241-.284c-2.027.63-4.093.841-6.205.735-3.195-.16-6.24-.828-8.964-2.582-2.486-1.601-4.319-3.746-5.19-6.611-.704-2.315.736-3.934 3.135-3.6.948.133 1.746.56 2.463 1.165.583.493 1.143 1.015 1.738 1.493 2.8 2.25 6.712 2.375 10.265-.068-5.842-.026-9.817-3.24-13.308-7.313-1.366-1.594-2.7-3.216-4.095-4.785-2.698-3.036-5.692-5.71-9.79-6.623C12.8-.623 7.745.14 2.893 2.361 1.926 2.804.997 3.319 0 4.149c.494 0 .763.006 1.032 0 2.446-.064 4.28 1.023 5.602 3.024.962 1.457 1.415 3.104 1.761 4.798.513 2.515.247 5.078.544 7.605.761 6.494 4.08 11.026 10.26 13.346 2.267.852 4.591 1.135 7.172.555ZM10.751 3.852c-.976.246-1.756-.148-2.56-.962 1.377-.343 2.592-.476 3.897-.528-.107.848-.607 1.306-1.336 1.49Zm32.002 37.924c-.085-.626-.62-.901-1.04-1.228-1.857-1.446-4.03-1.958-6.333-2-1.375-.026-2.735-.128-4.031-.61-.595-.22-1.26-.505-1.244-1.272.015-.78.693-1 1.31-1.184.505-.15 1.026-.247 1.6-.382-1.46-.936-2.886-1.065-4.787-.3-2.993 1.202-5.943 1.06-8.926-.017-1.684-.608-3.179-1.563-4.735-2.408l-.043.03a2.96 2.96 0 0 0 .04-.029c-.038-.117-.107-.12-.197-.054l.122.107c1.29 2.115 3.034 3.817 5.004 5.271 3.793 2.8 7.936 4.471 12.784 3.73A66.714 66.714 0 0 1 37 40.877c1.98-.16 3.866.398 5.753.899Zm-9.14-30.345c-.105-.076-.206-.266-.42-.069 1.745 2.36 3.985 4.098 6.683 5.193 4.354 1.767 8.773 2.07 13.293.51 3.51-1.21 6.033-.028 7.343 3.38.19-3.955-2.137-6.837-5.843-7.401-2.084-.318-4.01.373-5.962.94-5.434 1.575-10.485.798-15.094-2.553Zm27.085 15.425c.708.059 1.416.123 2.124.185-1.6-1.405-3.55-1.517-5.523-1.404-3.003.17-5.167 1.903-7.14 3.972-1.739 1.824-3.31 3.87-5.903 4.604.043.078.054.117.066.117.35.005.699.021 1.047.005 3.768-.17 7.317-.965 10.14-3.7.89-.86 1.685-1.817 2.544-2.71.716-.746 1.584-1.159 2.645-1.07Zm-8.753-4.67c-2.812.246-5.254 1.409-7.548 2.943-1.766 1.18-3.654 1.738-5.776 1.37-.374-.066-.75-.114-1.124-.17l-.013.156c.135.07.265.151.405.207.354.14.702.308 1.07.395 4.083.971 7.992.474 11.516-1.803 2.221-1.435 4.521-1.707 7.013-1.336.252.038.503.083.756.107.234.022.479.255.795.003-2.179-1.574-4.526-2.096-7.094-1.872Zm-10.049-9.544c1.475.051 2.943-.142 4.486-1.059-.452.04-.643.04-.827.076-2.126.424-4.033-.04-5.733-1.383-.623-.493-1.257-.974-1.889-1.457-2.503-1.914-5.374-2.555-8.514-2.5.05.154.054.26.108.315 3.417 3.455 7.371 5.836 12.369 6.008Zm24.727 17.731c-2.114-2.097-4.952-2.367-7.578-.537 1.738.078 3.043.632 4.101 1.728.374.388.763.768 1.182 1.106 1.6 1.29 4.311 1.352 5.896.155-1.861-.726-1.861-.726-3.601-2.452Zm-21.058 16.06c-1.858-3.46-4.981-4.24-8.59-4.008a9.667 9.667 0 0 1 2.977 1.39c.84.586 1.547 1.311 2.243 2.055 1.38 1.473 3.534 2.376 4.962 2.07-.656-.412-1.238-.848-1.592-1.507Zm17.29-19.32c0-.023.001-.045.003-.068l-.006.006.006-.006-.036-.004.021.018.012.053Zm-20 14.744a7.61 7.61 0 0 0-.072-.041.127.127 0 0 0 .015.043c.005.008.038 0 .058-.002Zm-.072-.041-.008-.034-.008.01.008-.01-.022-.006.005.026.024.014Z"
fill="#FD4F00"
/>
</svg>
<h1 class="text-brand mt-10 flex items-center text-sm font-semibold leading-6">
Phoenix Framework
<small class="bg-brand/5 text-[0.8125rem] ml-3 rounded-full px-2 font-medium leading-6">
v{Application.spec(:phoenix, :vsn)}
</small>
</h1>
<p class="text-[2rem] mt-4 font-semibold leading-10 tracking-tighter text-zinc-900 text-balance">
Peace of mind from prototype to production.
</p>
<p class="mt-4 text-base leading-7 text-zinc-600">
Build rich, interactive web applications quickly, with less code and fewer moving parts. Join our growing community of developers using Phoenix to craft APIs, HTML5 apps and more, for fun or at scale.
</p>
<div class="flex">
<div class="w-full sm:w-auto">
<div class="mt-10 grid grid-cols-1 gap-x-6 gap-y-4 sm:grid-cols-3">
<a
href="https://hexdocs.pm/phoenix/overview.html"
class="group relative rounded-2xl px-6 py-4 text-sm font-semibold leading-6 text-zinc-900 sm:py-6"
>
<span class="absolute inset-0 rounded-2xl bg-zinc-50 transition group-hover:bg-zinc-100 sm:group-hover:scale-105">
</span>
<span class="relative flex items-center gap-4 sm:flex-col">
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-6 w-6">
<path d="m12 4 10-2v18l-10 2V4Z" fill="#18181B" fill-opacity=".15" />
<path
d="M12 4 2 2v18l10 2m0-18v18m0-18 10-2v18l-10 2"
stroke="#18181B"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
Guides &amp; Docs
</span>
</a>
<a
href="https://github.com/phoenixframework/phoenix"
class="group relative rounded-2xl px-6 py-4 text-sm font-semibold leading-6 text-zinc-900 sm:py-6"
>
<span class="absolute inset-0 rounded-2xl bg-zinc-50 transition group-hover:bg-zinc-100 sm:group-hover:scale-105">
</span>
<span class="relative flex items-center gap-4 sm:flex-col">
<svg viewBox="0 0 24 24" aria-hidden="true" class="h-6 w-6">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M12 0C5.37 0 0 5.506 0 12.303c0 5.445 3.435 10.043 8.205 11.674.6.107.825-.262.825-.585 0-.292-.015-1.261-.015-2.291C6 21.67 5.22 20.346 4.98 19.654c-.135-.354-.72-1.446-1.23-1.738-.42-.23-1.02-.8-.015-.815.945-.015 1.62.892 1.845 1.261 1.08 1.86 2.805 1.338 3.495 1.015.105-.8.42-1.338.765-1.645-2.67-.308-5.46-1.37-5.46-6.075 0-1.338.465-2.446 1.23-3.307-.12-.308-.54-1.569.12-3.26 0 0 1.005-.323 3.3 1.26.96-.276 1.98-.415 3-.415s2.04.139 3 .416c2.295-1.6 3.3-1.261 3.3-1.261.66 1.691.24 2.952.12 3.26.765.861 1.23 1.953 1.23 3.307 0 4.721-2.805 5.767-5.475 6.075.435.384.81 1.122.81 2.276 0 1.645-.015 2.968-.015 3.383 0 .323.225.707.825.585a12.047 12.047 0 0 0 5.919-4.489A12.536 12.536 0 0 0 24 12.304C24 5.505 18.63 0 12 0Z"
fill="#18181B"
/>
</svg>
Source Code
</span>
</a>
<a
href={"https://github.com/phoenixframework/phoenix/blob/v#{Application.spec(:phoenix, :vsn)}/CHANGELOG.md"}
class="group relative rounded-2xl px-6 py-4 text-sm font-semibold leading-6 text-zinc-900 sm:py-6"
>
<span class="absolute inset-0 rounded-2xl bg-zinc-50 transition group-hover:bg-zinc-100 sm:group-hover:scale-105">
</span>
<span class="relative flex items-center gap-4 sm:flex-col">
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-6 w-6">
<path
d="M12 1v6M12 17v6"
stroke="#18181B"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
<circle
cx="12"
cy="12"
r="4"
fill="#18181B"
fill-opacity=".15"
stroke="#18181B"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
Changelog
</span>
</a>
</div>
<div class="mt-10 grid grid-cols-1 gap-y-4 text-sm leading-6 text-zinc-700 sm:grid-cols-2">
<div>
<a
href="https://twitter.com/elixirphoenix"
class="group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-zinc-50 hover:text-zinc-900"
>
<svg
viewBox="0 0 16 16"
aria-hidden="true"
class="h-4 w-4 fill-zinc-400 group-hover:fill-zinc-600"
>
<path d="M5.403 14c5.283 0 8.172-4.617 8.172-8.62 0-.131 0-.262-.008-.391A6.033 6.033 0 0 0 15 3.419a5.503 5.503 0 0 1-1.65.477 3.018 3.018 0 0 0 1.263-1.676 5.579 5.579 0 0 1-1.824.736 2.832 2.832 0 0 0-1.63-.916 2.746 2.746 0 0 0-1.821.319A2.973 2.973 0 0 0 8.076 3.78a3.185 3.185 0 0 0-.182 1.938 7.826 7.826 0 0 1-3.279-.918 8.253 8.253 0 0 1-2.64-2.247 3.176 3.176 0 0 0-.315 2.208 3.037 3.037 0 0 0 1.203 1.836A2.739 2.739 0 0 1 1.56 6.22v.038c0 .7.23 1.377.65 1.919.42.54 1.004.912 1.654 1.05-.423.122-.866.14-1.297.052.184.602.541 1.129 1.022 1.506a2.78 2.78 0 0 0 1.662.598 5.656 5.656 0 0 1-2.007 1.074A5.475 5.475 0 0 1 1 12.64a7.827 7.827 0 0 0 4.403 1.358" />
</svg>
Follow on Twitter
</a>
</div>
<div>
<a
href="https://elixirforum.com"
class="group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-zinc-50 hover:text-zinc-900"
>
<svg
viewBox="0 0 16 16"
aria-hidden="true"
class="h-4 w-4 fill-zinc-400 group-hover:fill-zinc-600"
>
<path d="M8 13.833c3.866 0 7-2.873 7-6.416C15 3.873 11.866 1 8 1S1 3.873 1 7.417c0 1.081.292 2.1.808 2.995.606 1.05.806 2.399.086 3.375l-.208.283c-.285.386-.01.905.465.85.852-.098 2.048-.318 3.137-.81a3.717 3.717 0 0 1 1.91-.318c.263.027.53.041.802.041Z" />
</svg>
Discuss on the Elixir Forum
</a>
</div>
<div>
<a
href="https://web.libera.chat/#elixir"
class="group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-zinc-50 hover:text-zinc-900"
>
<svg
viewBox="0 0 16 16"
aria-hidden="true"
class="h-4 w-4 fill-zinc-400 group-hover:fill-zinc-600"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M6.356 2.007a.75.75 0 0 1 .637.849l-1.5 10.5a.75.75 0 1 1-1.485-.212l1.5-10.5a.75.75 0 0 1 .848-.637ZM11.356 2.008a.75.75 0 0 1 .637.848l-1.5 10.5a.75.75 0 0 1-1.485-.212l1.5-10.5a.75.75 0 0 1 .848-.636Z"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M14 5.25a.75.75 0 0 1-.75.75h-9.5a.75.75 0 0 1 0-1.5h9.5a.75.75 0 0 1 .75.75ZM13 10.75a.75.75 0 0 1-.75.75h-9.5a.75.75 0 0 1 0-1.5h9.5a.75.75 0 0 1 .75.75Z"
/>
</svg>
Chat on Libera IRC
</a>
</div>
<div>
<a
href="https://discord.gg/elixir"
class="group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-zinc-50 hover:text-zinc-900"
>
<svg
viewBox="0 0 16 16"
aria-hidden="true"
class="h-4 w-4 fill-zinc-400 group-hover:fill-zinc-600"
>
<path d="M13.545 2.995c-1.02-.46-2.114-.8-3.257-.994a.05.05 0 0 0-.052.024c-.141.246-.297.567-.406.82a12.377 12.377 0 0 0-3.658 0 8.238 8.238 0 0 0-.412-.82.052.052 0 0 0-.052-.024 13.315 13.315 0 0 0-3.257.994.046.046 0 0 0-.021.018C.356 6.063-.213 9.036.066 11.973c.001.015.01.029.02.038a13.353 13.353 0 0 0 3.996 1.987.052.052 0 0 0 .056-.018c.308-.414.582-.85.818-1.309a.05.05 0 0 0-.028-.069 8.808 8.808 0 0 1-1.248-.585.05.05 0 0 1-.005-.084c.084-.062.168-.126.248-.191a.05.05 0 0 1 .051-.007c2.619 1.176 5.454 1.176 8.041 0a.05.05 0 0 1 .053.006c.08.065.164.13.248.192a.05.05 0 0 1-.004.084c-.399.23-.813.423-1.249.585a.05.05 0 0 0-.027.07c.24.457.514.893.817 1.307a.051.051 0 0 0 .056.019 13.31 13.31 0 0 0 4.001-1.987.05.05 0 0 0 .021-.037c.334-3.396-.559-6.345-2.365-8.96a.04.04 0 0 0-.021-.02Zm-8.198 7.19c-.789 0-1.438-.712-1.438-1.587 0-.874.637-1.586 1.438-1.586.807 0 1.45.718 1.438 1.586 0 .875-.637 1.587-1.438 1.587Zm5.316 0c-.788 0-1.438-.712-1.438-1.587 0-.874.637-1.586 1.438-1.586.807 0 1.45.718 1.438 1.586 0 .875-.63 1.587-1.438 1.587Z" />
</svg>
Join our Discord server
</a>
</div>
<div>
<a
href="https://fly.io/docs/elixir/getting-started/"
class="group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-zinc-50 hover:text-zinc-900"
>
<svg
viewBox="0 0 20 20"
aria-hidden="true"
class="h-4 w-4 fill-zinc-400 group-hover:fill-zinc-600"
>
<path d="M1 12.5A4.5 4.5 0 005.5 17H15a4 4 0 001.866-7.539 3.504 3.504 0 00-4.504-4.272A4.5 4.5 0 004.06 8.235 4.502 4.502 0 001 12.5z" />
</svg>
Deploy your application
</a>
</div> </div>
</div> </div>
</div> </div>
<div class="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0">
<%= if @current_user do %>
<span class="px-3 py-2 text-sm font-medium text-white rounded-md">
{@current_user.email}
</span>
<a
href="/sign-out"
class="rounded-lg bg-zinc-100 px-2 py-1 text-[0.8125rem] font-semibold leading-6 text-zinc-900 hover:bg-zinc-200/80 active:text-zinc-900/70"
>
Sign out
</a>
<% else %>
<a
href="/sign-in"
class="rounded-lg bg-zinc-100 px-2 py-1 text-[0.8125rem] font-semibold leading-6 text-zinc-900 hover:bg-zinc-200/80 active:text-zinc-900/70"
>
Sign In
</a>
<% end %>
</div>
</div> </div>
</div> </div>
</nav>
<div class="py-10">
<header>
<div class="px-4 mx-auto max-w-7xl sm:px-6 lg:px-8">
<h1 class="text-3xl font-bold leading-tight tracking-tight text-gray-900">
Demo
</h1>
</div>
</header>
<main>
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
<div class="px-4 py-8 sm:px-0">
<div class="border-4 border-gray-200 border-dashed rounded-lg h-96"></div>
</div>
</div>
</main>
</div> </div>

View file

@ -25,10 +25,6 @@ defmodule MvWeb.Endpoint do
gzip: false, gzip: false,
only: MvWeb.static_paths() only: MvWeb.static_paths()
if Code.ensure_loaded?(Tidewave) do
plug Tidewave
end
# Code reloading can be explicitly enabled under the # Code reloading can be explicitly enabled under the
# :code_reloader configuration of your endpoint. # :code_reloader configuration of your endpoint.
if code_reloading? do if code_reloading? do

View file

@ -1,7 +0,0 @@
defmodule MvWeb.LiveHelpers do
def on_mount(:default, _params, session, socket) do
locale = session["locale"] || "en"
Gettext.put_locale(locale)
{:cont, socket}
end
end

View file

@ -1,46 +0,0 @@
defmodule MvWeb.LiveUserAuth do
@moduledoc """
Helpers for authenticating users in LiveViews.
"""
import Phoenix.Component
use MvWeb, :verified_routes
# This is used for nested liveviews to fetch the current user.
# To use, place the following at the top of that liveview:
# on_mount {MvWeb.LiveUserAuth, :current_user}
def on_mount(:current_user, _params, session, socket) do
return_to = session[:return_to]
socket =
socket
|> assign(:return_to, return_to)
|> AshAuthentication.Phoenix.LiveSession.assign_new_resources(session)
{:cont, session, socket}
end
def on_mount(:live_user_optional, _params, _session, socket) do
if socket.assigns[:current_user] do
{:cont, socket}
else
{:cont, assign(socket, :current_user, nil)}
end
end
def on_mount(:live_user_required, _params, _session, socket) do
if socket.assigns[:current_user] do
{:cont, socket}
else
{:halt, Phoenix.LiveView.redirect(socket, to: ~p"/sign-in")}
end
end
def on_mount(:live_no_user, _params, _session, socket) do
if socket.assigns[:current_user] do
{:halt, Phoenix.LiveView.redirect(socket, to: ~p"/")}
else
{:cont, assign(socket, :current_user, nil)}
end
end
end

View file

@ -1,18 +0,0 @@
defmodule MvWeb.LocaleController do
use MvWeb, :controller
def set_locale(conn, %{"locale" => locale}) do
conn
|> put_session(:locale, locale)
|> redirect(to: get_referer(conn) || "/")
end
defp get_referer(conn) do
conn.req_headers
|> Enum.find(fn {k, _v} -> k == "referer" end)
|> case do
{_, v} -> URI.parse(v).path
_ -> nil
end
end
end

View file

@ -9,11 +9,7 @@ defmodule MvWeb.MemberLive.FormComponent do
Enum.map(property_types, fn pt -> Enum.map(property_types, fn pt ->
%{ %{
"property_type_id" => pt.id, "property_type_id" => pt.id,
"value" => %{ "value" => nil
"type" => pt.value_type,
"value" => nil,
"_union_type" => Atom.to_string(pt.value_type)
}
} }
end) end)
@ -26,9 +22,7 @@ defmodule MvWeb.MemberLive.FormComponent do
<div> <div>
<.header> <.header>
{@title} {@title}
<:subtitle> <:subtitle>Use this form to manage member records and their properties.</:subtitle>
{gettext("Use this form to manage member records and their properties.")}
</:subtitle>
</.header> </.header>
<.simple_form <.simple_form
@ -38,32 +32,9 @@ defmodule MvWeb.MemberLive.FormComponent do
phx-change="validate" phx-change="validate"
phx-submit="save" phx-submit="save"
> >
<.input field={@form[:first_name]} label={gettext("First Name")} required />
<.input field={@form[:last_name]} label={gettext("Last Name")} required />
<.input field={@form[:email]} label={gettext("Email")} required type="email" />
<.input field={@form[:birth_date]} label={gettext("Birth Date")} type="date" />
<.input field={@form[:paid]} label={gettext("Paid")} type="checkbox" />
<.input field={@form[:phone_number]} label={gettext("Phone Number")} />
<.input field={@form[:join_date]} label={gettext("Join Date")} type="date" />
<.input field={@form[:exit_date]} label={gettext("Exit Date")} type="date" />
<.input field={@form[:notes]} label={gettext("Notes")} />
<.input field={@form[:city]} label={gettext("City")} />
<.input field={@form[:street]} label={gettext("Street")} />
<.input field={@form[:house_number]} label={gettext("House Number")} />
<.input field={@form[:postal_code]} label={gettext("Postal Code")} />
<h3 class="mt-8 mb-2 text-lg font-semibold">{gettext("Custom Properties")}</h3>
<.inputs_for :let={f_property} field={@form[:properties]}> <.inputs_for :let={f_property} field={@form[:properties]}>
<% type = Enum.find(@property_types, &(&1.id == f_property[:property_type_id].value)) %> <% type = Enum.find(@property_types, &(&1.id == f_property[:property_type_id].value)) %>
<.inputs_for :let={value_form} field={f_property[:value]}> <.input field={f_property[:value]} label={type && type.name} />
<% input_type =
cond do
type && type.value_type == :boolean -> "checkbox"
type && type.value_type == :date -> :date
true -> :text
end %>
<.input field={value_form[:value]} label={type && type.name} type={input_type} />
</.inputs_for>
<input <input
type="hidden" type="hidden"
name={f_property[:property_type_id].name} name={f_property[:property_type_id].name}
@ -72,7 +43,7 @@ defmodule MvWeb.MemberLive.FormComponent do
</.inputs_for> </.inputs_for>
<:actions> <:actions>
<.button phx-disable-with={gettext("Saving...")}>{gettext("Save Member")}</.button> <.button phx-disable-with="Saving...">Save Member</.button>
</:actions> </:actions>
</.simple_form> </.simple_form>
</div> </div>
@ -97,16 +68,9 @@ defmodule MvWeb.MemberLive.FormComponent do
{:ok, member} -> {:ok, member} ->
notify_parent({:saved, member}) notify_parent({:saved, member})
action =
case socket.assigns.form.source.type do
:create -> gettext("create")
:update -> gettext("update")
other -> to_string(other)
end
socket = socket =
socket socket
|> put_flash(:info, gettext("Member %{action} successfully", action: action)) |> put_flash(:info, "Member #{socket.assigns.form.source.type}d successfully")
|> push_patch(to: socket.assigns.patch) |> push_patch(to: socket.assigns.patch)
{:noreply, socket} {:noreply, socket}
@ -131,27 +95,12 @@ defmodule MvWeb.MemberLive.FormComponent do
not Enum.member?(existing_properties, Map.get(i, "property_type_id")) not Enum.member?(existing_properties, Map.get(i, "property_type_id"))
end end
params = %{
"properties" =>
Enum.map(member.properties, fn prop ->
%{
"property_type_id" => prop.property_type_id,
"value" => %{
"_union_type" => Atom.to_string(prop.value.type),
"type" => prop.value.type,
"value" => prop.value.value
}
}
end)
}
form = form =
AshPhoenix.Form.for_update( AshPhoenix.Form.for_update(
member, member,
:update_member, :update_member,
api: Mv.Membership, api: Mv.Membership,
as: "member", as: "member",
params: params,
forms: [auto?: true] forms: [auto?: true]
) )

View file

@ -5,10 +5,10 @@ defmodule MvWeb.MemberLive.Index do
def render(assigns) do def render(assigns) do
~H""" ~H"""
<.header> <.header>
{gettext("Listing Members")} Listing Members
<:actions> <:actions>
<.link patch={~p"/members/new"}> <.link patch={~p"/members/new"}>
<.button>{gettext("New Member")}</.button> <.button>New Member</.button>
</.link> </.link>
</:actions> </:actions>
</.header> </.header>
@ -18,27 +18,22 @@ defmodule MvWeb.MemberLive.Index do
rows={@streams.members} rows={@streams.members}
row_click={fn {_id, member} -> JS.navigate(~p"/members/#{member}") end} row_click={fn {_id, member} -> JS.navigate(~p"/members/#{member}") end}
> >
<!-- <:col :let={{_id, member}} label="Id">{member.id}</:col> --> <:col :let={{_id, member}} label="Id">{member.id}</:col>
<:col :let={{_id, member}} label={gettext("First Name")}>{member.first_name}</:col>
<:col :let={{_id, member}} label={gettext("Last Name")}>{member.last_name}</:col>
<:col :let={{_id, member}} label={gettext("Email")}>{member.email}</:col>
<:col :let={{_id, member}} label={gettext("City")}>{member.city}</:col>
<:col :let={{_id, member}} label={gettext("Join Date")}>{member.join_date}</:col>
<:action :let={{_id, member}}> <:action :let={{_id, member}}>
<div class="sr-only"> <div class="sr-only">
<.link navigate={~p"/members/#{member}"}>{gettext("Show")}</.link> <.link navigate={~p"/members/#{member}"}>Show</.link>
</div> </div>
<.link patch={~p"/members/#{member}/edit"}>{gettext("Edit")}</.link> <.link patch={~p"/members/#{member}/edit"}>Edit</.link>
</:action> </:action>
<:action :let={{id, member}}> <:action :let={{id, member}}>
<.link <.link
phx-click={JS.push("delete", value: %{id: member.id}) |> hide("##{id}")} phx-click={JS.push("delete", value: %{id: member.id}) |> hide("##{id}")}
data-confirm={gettext("Are you sure?")} data-confirm="Are you sure?"
> >
{gettext("Delete")} Delete
</.link> </.link>
</:action> </:action>
</.table> </.table>
@ -73,19 +68,19 @@ defmodule MvWeb.MemberLive.Index do
defp apply_action(socket, :edit, %{"id" => id}) do defp apply_action(socket, :edit, %{"id" => id}) do
socket socket
|> assign(:page_title, gettext("Edit Member")) |> assign(:page_title, "Edit Member")
|> assign(:member, Ash.get!(Mv.Membership.Member, id)) |> assign(:member, Ash.get!(Mv.Membership.Member, id))
end end
defp apply_action(socket, :new, _params) do defp apply_action(socket, :new, _params) do
socket socket
|> assign(:page_title, gettext("New Member")) |> assign(:page_title, "New Member")
|> assign(:member, nil) |> assign(:member, nil)
end end
defp apply_action(socket, :index, _params) do defp apply_action(socket, :index, _params) do
socket socket
|> assign(:page_title, gettext("Listing Members")) |> assign(:page_title, "Listing Members")
|> assign(:member, nil) |> assign(:member, nil)
end end

View file

@ -1,55 +1,25 @@
defmodule MvWeb.MemberLive.Show do defmodule MvWeb.MemberLive.Show do
use MvWeb, :live_view use MvWeb, :live_view
import Ash.Query
@impl true @impl true
def render(assigns) do def render(assigns) do
~H""" ~H"""
<.header> <.header>
{@member.first_name} {@member.last_name} Member {@member.id}
<:subtitle>{gettext("This is a member record from your database.")}</:subtitle> <:subtitle>This is a member record from your database.</:subtitle>
<:actions> <:actions>
<.link patch={~p"/members/#{@member}/show/edit"} phx-click={JS.push_focus()}> <.link patch={~p"/members/#{@member}/show/edit"} phx-click={JS.push_focus()}>
<.button>{gettext("Edit member")}</.button> <.button>Edit member</.button>
</.link> </.link>
</:actions> </:actions>
</.header> </.header>
<.list> <.list>
<:item title={gettext("Id")}>{@member.id}</:item> <:item title="Id">{@member.id}</:item>
<:item title={gettext("First Name")}>{@member.first_name}</:item>
<:item title={gettext("Last Name")}>{@member.last_name}</:item>
<:item title={gettext("Email")}>{@member.email}</:item>
<:item title={gettext("Birth Date")}>{@member.birth_date}</:item>
<:item title={gettext("Paid")}>
{if @member.paid, do: gettext("Yes"), else: gettext("No")}
</:item>
<:item title={gettext("Phone Number")}>{@member.phone_number}</:item>
<:item title={gettext("Join Date")}>{@member.join_date}</:item>
<:item title={gettext("Exit Date")}>{@member.exit_date}</:item>
<:item title={gettext("Notes")}>{@member.notes}</:item>
<:item title={gettext("City")}>{@member.city}</:item>
<:item title={gettext("Street")}>{@member.street}</:item>
<:item title={gettext("House Number")}>{@member.house_number}</:item>
<:item title={gettext("Postal Code")}>{@member.postal_code}</:item>
</.list> </.list>
<h3 class="mt-8 mb-2 text-lg font-semibold">{gettext("Custom Properties")}</h3> <.back navigate={~p"/members"}>Back to members</.back>
<.generic_list items={
Enum.map(@member.properties, fn p ->
{
# name
p.property_type && p.property_type.name,
# value
case p.value do
%{value: v} -> v
v -> v
end
}
end)
} />
<.back navigate={~p"/members"}>{gettext("Back to members")}</.back>
<.modal <.modal
:if={@live_action == :edit} :if={@live_action == :edit}
@ -76,19 +46,12 @@ defmodule MvWeb.MemberLive.Show do
@impl true @impl true
def handle_params(%{"id" => id}, _, socket) do def handle_params(%{"id" => id}, _, socket) do
query =
Mv.Membership.Member
|> filter(id == ^id)
|> load(properties: [:property_type])
member = Ash.read_one!(query)
{:noreply, {:noreply,
socket socket
|> assign(:page_title, page_title(socket.assigns.live_action)) |> assign(:page_title, page_title(socket.assigns.live_action))
|> assign(:member, member)} |> assign(:member, Ash.get!(Mv.Membership.Member, id))}
end end
defp page_title(:show), do: gettext("Show Member") defp page_title(:show), do: "Show Member"
defp page_title(:edit), do: gettext("Edit Member") defp page_title(:edit), do: "Edit Member"
end end

View file

@ -1,10 +1,6 @@
defmodule MvWeb.Router do defmodule MvWeb.Router do
use MvWeb, :router use MvWeb, :router
use AshAuthentication.Phoenix.Router
import AshAuthentication.Plug.Helpers
pipeline :browser do pipeline :browser do
plug :accepts, ["html"] plug :accepts, ["html"]
plug :fetch_session plug :fetch_session
@ -12,92 +8,33 @@ defmodule MvWeb.Router do
plug :put_root_layout, html: {MvWeb.Layouts, :root} plug :put_root_layout, html: {MvWeb.Layouts, :root}
plug :protect_from_forgery plug :protect_from_forgery
plug :put_secure_browser_headers plug :put_secure_browser_headers
plug :load_from_session
plug :set_locale
end end
pipeline :api do pipeline :api do
plug :accepts, ["json"] plug :accepts, ["json"]
plug :load_from_bearer
plug :set_actor, :user
end end
scope "/", MvWeb do scope "/", MvWeb do
pipe_through :browser pipe_through :browser
ash_authentication_live_session :authenticated_routes do get "/", PageController, :home
# in each liveview, add one of the following at the top of the module: live "/members", MemberLive.Index, :index
# live "/members/new", MemberLive.Index, :new
# If an authenticated user must be present: live "/members/:id/edit", MemberLive.Index, :edit
# on_mount {MvWeb.LiveUserAuth, :live_user_required} live "/members/:id", MemberLive.Show, :show
# live "/members/:id/show/edit", MemberLive.Show, :edit
# If an authenticated user *may* be present:
# on_mount {MvWeb.LiveUserAuth, :live_user_optional}
#
# If an authenticated user must *not* be present:
# on_mount {MvWeb.LiveUserAuth, :live_no_user}
end
end
scope "/", MvWeb do live "/property_types", PropertyTypeLive.Index, :index
pipe_through :browser live "/property_types/new", PropertyTypeLive.Index, :new
live "/property_types/:id/edit", PropertyTypeLive.Index, :edit
live "/property_types/:id", PropertyTypeLive.Show, :show
live "/property_types/:id/show/edit", PropertyTypeLive.Show, :edit
@doc """ live "/properties", PropertyLive.Index, :index
AshAuthentication-specific: We define that all routes can only be accessed when the user is signed in. live "/properties/new", PropertyLive.Index, :new
""" live "/properties/:id/edit", PropertyLive.Index, :edit
ash_authentication_live_session :authentication_required, live "/properties/:id", PropertyLive.Show, :show
on_mount: {MvWeb.LiveUserAuth, :live_user_required} do live "/properties/:id/show/edit", PropertyLive.Show, :edit
get "/", PageController, :home
live "/members", MemberLive.Index, :index
live "/members/new", MemberLive.Index, :new
live "/members/:id/edit", MemberLive.Index, :edit
live "/members/:id", MemberLive.Show, :show
live "/members/:id/show/edit", MemberLive.Show, :edit
live "/property_types", PropertyTypeLive.Index, :index
live "/property_types/new", PropertyTypeLive.Index, :new
live "/property_types/:id/edit", PropertyTypeLive.Index, :edit
live "/property_types/:id", PropertyTypeLive.Show, :show
live "/property_types/:id/show/edit", PropertyTypeLive.Show, :edit
live "/properties", PropertyLive.Index, :index
live "/properties/new", PropertyLive.Index, :new
live "/properties/:id/edit", PropertyLive.Index, :edit
live "/properties/:id", PropertyLive.Show, :show
live "/properties/:id/show/edit", PropertyLive.Show, :edit
post "/set_locale", LocaleController, :set_locale
end
# ASHAUTHENTICATION GENERATED AUTH ROUTES
auth_routes AuthController, Mv.Accounts.User, path: "/auth"
sign_out_route AuthController
# Remove these if you'd like to use your own authentication views
sign_in_route register_path: "/register",
reset_path: "/reset",
auth_routes_prefix: "/auth",
on_mount: [{MvWeb.LiveUserAuth, :live_no_user}],
overrides: [MvWeb.AuthOverrides, AshAuthentication.Phoenix.Overrides.Default],
gettext_backend: {MvWeb.Gettext, "default"}
# Remove this if you do not want to use the reset password feature
reset_route auth_routes_prefix: "/auth",
overrides: [MvWeb.AuthOverrides, AshAuthentication.Phoenix.Overrides.Default],
gettext_backend: {MvWeb.Gettext, "default"}
# Remove this if you do not use the confirmation strategy
confirm_route Mv.Accounts.User, :confirm_new_user,
auth_routes_prefix: "/auth",
overrides: [MvWeb.AuthOverrides, AshAuthentication.Phoenix.Overrides.Default],
gettext_backend: {MvWeb.Gettext, "default"}
# Remove this if you do not use the magic link strategy.
# magic_sign_in_route(Mv.Accounts.User, :magic_link,
# auth_routes_prefix: "/auth",
# overrides: [MvWeb.AuthOverrides, AshAuthentication.Phoenix.Overrides.Default]
# )
end end
# Other scopes may use custom stacks. # Other scopes may use custom stacks.
@ -131,10 +68,4 @@ defmodule MvWeb.Router do
ash_admin "/" ash_admin "/"
end end
end end
defp set_locale(conn, _opts) do
locale = get_session(conn, :locale) || "en"
Gettext.put_locale(MvWeb.Gettext, locale)
conn
end
end end

24
mix.exs
View file

@ -33,17 +33,13 @@ defmodule Mv.MixProject do
# Type `mix help deps` for examples and options. # Type `mix help deps` for examples and options.
defp deps do defp deps do
[ [
{:tidewave, "~> 0.2", only: [:dev]},
{:sourceror, "~> 1.8", only: [:dev, :test]}, {:sourceror, "~> 1.8", only: [:dev, :test]},
{:live_debugger, "~> 0.3", only: [:dev]}, {:live_debugger, "~> 0.1", only: [:dev]},
{:ash_admin, "~> 0.13"}, {:ash_admin, "~> 0.13"},
{:ash_postgres, "~> 2.0"}, {:ash_postgres, "~> 2.0"},
{:ash_phoenix, "~> 2.0"}, {:ash_phoenix, "~> 2.0"},
{:ash, "~> 3.0"}, {:ash, "~> 3.0"},
{:bcrypt_elixir, "~> 3.0"}, {:igniter, "~> 0.5", only: [:dev, :test]},
{:ash_authentication, "~> 4.9"},
{:ash_authentication_phoenix, "~> 2.10"},
{:igniter, "~> 0.6", only: [:dev, :test]},
{:phoenix, "~> 1.7.20"}, {:phoenix, "~> 1.7.20"},
{:phoenix_ecto, "~> 4.5"}, {:phoenix_ecto, "~> 4.5"},
{:ecto_sql, "~> 3.10"}, {:ecto_sql, "~> 3.10"},
@ -53,27 +49,26 @@ defmodule Mv.MixProject do
{:phoenix_live_view, "~> 1.0.0"}, {:phoenix_live_view, "~> 1.0.0"},
{:floki, ">= 0.30.0", only: :test}, {:floki, ">= 0.30.0", only: :test},
{:phoenix_live_dashboard, "~> 0.8.3"}, {:phoenix_live_dashboard, "~> 0.8.3"},
{:esbuild, "~> 0.10", runtime: Mix.env() == :dev}, {:esbuild, "~> 0.8", runtime: Mix.env() == :dev},
{:tailwind, "~> 0.2", runtime: Mix.env() == :dev}, {:tailwind, "~> 0.2", runtime: Mix.env() == :dev},
{:heroicons, {:heroicons,
github: "tailwindlabs/heroicons", github: "tailwindlabs/heroicons",
tag: "v2.2.0", tag: "v2.1.1",
sparse: "optimized", sparse: "optimized",
app: false, app: false,
compile: false, compile: false,
depth: 1}, depth: 1},
{:swoosh, "~> 1.5"}, {:swoosh, "~> 1.5"},
{:finch, "~> 0.20"}, {:finch, "~> 0.13"},
{:telemetry_metrics, "~> 1.0"}, {:telemetry_metrics, "~> 1.0"},
{:telemetry_poller, "~> 1.0"}, {:telemetry_poller, "~> 1.0"},
{:gettext, "~> 0.26"}, {:gettext, "~> 0.26"},
{:jason, "~> 1.2"}, {:jason, "~> 1.2"},
{:dns_cluster, "~> 0.2.0"}, {:dns_cluster, "~> 0.1.1"},
{:bandit, "~> 1.5"}, {:bandit, "~> 1.5"},
{:mix_audit, "~> 2.1", only: [:dev, :test], runtime: false}, {:mix_audit, "~> 2.1", only: [:dev, :test], runtime: false},
{:sobelow, "~> 0.14", only: [:dev, :test], runtime: false}, {:sobelow, "~> 0.13", only: [:dev, :test], runtime: false},
{:credo, "~> 1.7", only: [:dev, :test], runtime: false}, {:credo, "~> 1.7", only: [:dev, :test], runtime: false}
{:ecto_commons, "~> 0.3"}
] ]
end end
@ -95,8 +90,7 @@ defmodule Mv.MixProject do
"tailwind mv --minify", "tailwind mv --minify",
"esbuild mv --minify", "esbuild mv --minify",
"phx.digest" "phx.digest"
], ]
"phx.routes": ["phx.routes", "ash_authentication.phoenix.routes"]
] ]
end end
end end

View file

@ -1,87 +1,71 @@
%{ %{
"ash": {:hex, :ash, "3.5.26", "f2e884623bfc39e0228a4fee0c41fbdb90195c6b1e1618a0a97f03f2dfbb1c4f", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.4 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.65 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a9f5edbb7d838e2053e84f62c428650dadbff3ea4ec40ef68f167483eb7e9012"}, "ash": {:hex, :ash, "3.5.6", "2f187150110b4c280c8551ad411f56d95862fcb37c067a0b8b94eb682bcc43e8", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.24 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d0d9aeb5aacfdc12253fae1e7e4720991868c5f69632c2766afb03b2b1830f55"},
"ash_admin": {:hex, :ash_admin, "0.13.11", "00bf3228b09ed6137e49a68374262f1de2cd5e1ea43ac2a6e2666cce71b7032e", [:mix], [{:ash, ">= 3.4.63 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_phoenix, ">= 2.1.8 and < 3.0.0-0", [hex: :ash_phoenix, repo: "hexpm", optional: false]}, {:gettext, "~> 0.26", [hex: :gettext, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.7", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}], "hexpm", "ed9e8833affb80454ba04c51a70ad96da95bb9d24429cf4f9d7cd538306c6256"}, "ash_admin": {:hex, :ash_admin, "0.13.4", "101bc40e299441a65d5c9e911f3801b6ab23eca2e53bb778ed0c6586993cc453", [:mix], [{:ash, ">= 3.4.63 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_phoenix, ">= 2.1.8 and < 3.0.0-0", [hex: :ash_phoenix, repo: "hexpm", optional: false]}, {:gettext, "~> 0.26", [hex: :gettext, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.7", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}], "hexpm", "d546e2f0d87a745c2156c65960f7a7a8b89abd238be5bfabac2176e814846415"},
"ash_authentication": {:hex, :ash_authentication, "4.9.6", "c333fa8c2a61a64f70be1c69b8479967b3bce448e6420088821c0634dfdace81", [:mix], [{:argon2_elixir, "~> 4.0", [hex: :argon2_elixir, repo: "hexpm", optional: true]}, {:ash, ">= 3.4.29 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_postgres, ">= 2.6.8 and < 3.0.0-0", [hex: :ash_postgres, repo: "hexpm", optional: true]}, {:assent, "~> 0.2.13", [hex: :assent, repo: "hexpm", optional: false]}, {:bcrypt_elixir, "~> 3.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: false]}, {:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:finch, "~> 0.19", [hex: :finch, repo: "hexpm", optional: false]}, {:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:joken, "~> 2.5", [hex: :joken, repo: "hexpm", optional: false]}, {:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}], "hexpm", "805ca73cc6723c60f8cb988a7c1b883f0d0db317e77007e52b30508e0cc32674"}, "ash_phoenix": {:hex, :ash_phoenix, "2.2.0", "aee367f4b3e4c7cfb6a4f1bc219409e0d40961aa9eee5da2113572b66d9f620d", [:mix], [{:ash, ">= 3.4.31 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:igniter, ">= 0.4.3 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.5.6 or ~> 1.6", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.20.3 or ~> 1.0-rc.1", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}], "hexpm", "ac9d58609b4232094c4789c6bd5f9039d1caa14ca6a893d6ab6ac1aee984e122"},
"ash_authentication_phoenix": {:hex, :ash_authentication_phoenix, "2.10.3", "b3c32e51a77eefc02c155eccdd17f1b697da3314fb40102854dcdd79288325b7", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_authentication, ">= 4.9.1 and < 5.0.0-0", [hex: :ash_authentication, repo: "hexpm", optional: false]}, {:ash_phoenix, "~> 2.0", [hex: :ash_phoenix, repo: "hexpm", optional: false]}, {:bcrypt_elixir, "~> 3.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: false]}, {:gettext, "~> 0.26", [hex: :gettext, repo: "hexpm", optional: true]}, {:igniter, ">= 0.5.25 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_html_helpers, "~> 1.0", [hex: :phoenix_html_helpers, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:slugify, "~> 1.3", [hex: :slugify, repo: "hexpm", optional: false]}], "hexpm", "89be0de638123193933a54ae15b9d1c670bb4010775c38b2b22a99180ecc1ac3"}, "ash_postgres": {:hex, :ash_postgres, "2.5.16", "9fc82621aea3c4777f9a322be8cdce10488f0eed50e7d75465285c131c30ec6b", [:mix], [{:ash, ">= 3.4.69 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_sql, ">= 0.2.68 and < 1.0.0-0", [hex: :ash_sql, repo: "hexpm", optional: false]}, {:ecto, ">= 3.12.1 and < 4.0.0-0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.12", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.16 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm", "d9f328683ea861707f19e89c16c2c4f3527431c50071b2aea4bac6a822f4f448"},
"ash_phoenix": {:hex, :ash_phoenix, "2.3.10", "c038cbcd0550a4a26d7ee2d936d2886415dfa69fc5952f45b0e3737c3293a4d3", [:mix], [{:ash, ">= 3.5.13 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:igniter, "~> 0.6", [hex: :igniter, repo: "hexpm", optional: true]}, {:inertia, "~> 2.3", [hex: :inertia, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.5.6 or ~> 1.6", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.20.3 or ~> 1.0-rc.1", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}], "hexpm", "c4b7d86e1636c82c6f6a89983af17f19e55f25b066120193d9d3524d2013456d"}, "ash_sql": {:hex, :ash_sql, "0.2.71", "40cabdd0c7af2eaa0096b2b0eae886085fed1e3b326e20434274120e11dec2c5", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "6e22da3d020aecaca9858f430828c12988c3418d252fa39be3f43fde9fd4224d"},
"ash_postgres": {:hex, :ash_postgres, "2.6.10", "d39ede943fe26dbd69c7511797adcff7f5e4c85e11990e23ec90b9a52a464bf6", [:mix], [{:ash, ">= 3.5.13 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_sql, ">= 0.2.72 and < 1.0.0-0", [hex: :ash_sql, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.13", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:igniter, "~> 0.6", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm", "bcce5bf8c9913be1eb6c2b0be0709decfc79eac1148c3c37d28eca4fda753ebe"}, "bandit": {:hex, :bandit, "1.6.8", "be6fcbe01a74e6cba42ae35f4085acaeae9b2d8d360c0908d0b9addbc2811e47", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "4fc08c8d4733735d175a007ecb25895e84d09292b0180a2e9f16948182c88b6e"},
"ash_sql": {:hex, :ash_sql, "0.2.85", "96a35d197f5ff846c17aca9225d4baafa0fda6c804f1e46a79345d237b5e5c5f", [:mix], [{:ash, ">= 3.5.25 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "62e7f8b79bcb04d82654ba519008b80bd21bd177ec646e9fffb87ec34285722b"},
"assent": {:hex, :assent, "0.2.13", "11226365d2d8661d23e9a2cf94d3255e81054ff9d88ac877f28bfdf38fa4ef31", [:mix], [{:certifi, ">= 0.0.0", [hex: :certifi, repo: "hexpm", optional: true]}, {:finch, "~> 0.15", [hex: :finch, repo: "hexpm", optional: true]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: true]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:req, "~> 0.4", [hex: :req, repo: "hexpm", optional: true]}, {:ssl_verify_fun, ">= 0.0.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: true]}], "hexpm", "bf9f351b01dd6bceea1d1f157f05438f6765ce606e6eb8d29296003d29bf6eab"},
"bandit": {:hex, :bandit, "1.7.0", "d1564f30553c97d3e25f9623144bb8df11f3787a26733f00b21699a128105c0c", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "3e2f7a98c7a11f48d9d8c037f7177cd39778e74d55c7af06fe6227c742a8168a"},
"bcrypt_elixir": {:hex, :bcrypt_elixir, "3.3.2", "d50091e3c9492d73e17fc1e1619a9b09d6a5ef99160eb4d736926fd475a16ca3", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "471be5151874ae7931911057d1467d908955f93554f7a6cd1b7d804cac8cef53"},
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"castore": {:hex, :castore, "1.0.14", "4582dd7d630b48cf5e1ca8d3d42494db51e406b7ba704e81fbd401866366896a", [:mix], [], "hexpm", "7bc1b65249d31701393edaaac18ec8398d8974d52c647b7904d01b964137b9f4"}, "castore": {:hex, :castore, "1.0.12", "053f0e32700cbec356280c0e835df425a3be4bc1e0627b714330ad9d0f05497f", [:mix], [], "hexpm", "3dca286b2186055ba0c9449b4e95b97bf1b57b47c1f2644555879e659960c224"},
"circular_buffer": {:hex, :circular_buffer, "1.0.0", "25c004da0cba7bd8bc1bdabded4f9a902d095e20600fd15faf1f2ffbaea18a07", [:mix], [], "hexpm", "c829ec31c13c7bafd1f546677263dff5bfb006e929f25635878ac3cfba8749e5"},
"comeonin": {:hex, :comeonin, "5.5.1", "5113e5f3800799787de08a6e0db307133850e635d34e9fab23c70b6501669510", [:mix], [], "hexpm", "65aac8f19938145377cee73973f192c5645873dcf550a8a6b18187d17c13ccdb"},
"credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [: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", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [: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", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"},
"db_connection": {:hex, :db_connection, "2.8.0", "64fd82cfa6d8e25ec6660cea73e92a4cbc6a18b31343910427b702838c4b33b2", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "008399dae5eee1bf5caa6e86d204dcb44242c82b1ed5e22c881f2c34da201b15"}, "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"},
"decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"},
"dns_cluster": {:hex, :dns_cluster, "0.2.0", "aa8eb46e3bd0326bd67b84790c561733b25c5ba2fe3c7e36f28e88f384ebcb33", [:mix], [], "hexpm", "ba6f1893411c69c01b9e8e8f772062535a4cf70f3f35bcc964a324078d8c8240"}, "dns_cluster": {:hex, :dns_cluster, "0.1.3", "0bc20a2c88ed6cc494f2964075c359f8c2d00e1bf25518a6a6c7fd277c9b0c66", [:mix], [], "hexpm", "46cb7c4a1b3e52c7ad4cbe33ca5079fbde4840dedeafca2baf77996c2da1bc33"},
"ecto": {:hex, :ecto, "3.13.2", "7d0c0863f3fc8d71d17fc3ad3b9424beae13f02712ad84191a826c7169484f01", [: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", "669d9291370513ff56e7b7e7081b7af3283d02e046cf3d403053c557894a0b3e"}, "ecto": {:hex, :ecto, "3.12.5", "4a312960ce612e17337e7cefcf9be45b95a3be6b36b6f94dfb3d8c361d631866", [: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", "6eb18e80bef8bb57e17f5a7f068a1719fbda384d40fc37acb8eb8aeca493b6ea"},
"ecto_commons": {:hex, :ecto_commons, "0.3.6", "7b1d9e59396cf8c8cbe5a26d50d03f9b6d0fe6c640210dd503622f276f1e59bb", [:mix], [{:burnex, "~> 3.0", [hex: :burnex, repo: "hexpm", optional: true]}, {:ecto, "~> 3.4", [hex: :ecto, repo: "hexpm", optional: false]}, {:ex_phone_number, "~> 0.2", [hex: :ex_phone_number, repo: "hexpm", optional: false]}, {:luhn, "~> 0.3.0", [hex: :luhn, repo: "hexpm", optional: false]}], "hexpm", "3f12981a1e398f206c5d2014e7b732b7ec91b110b9cb84875cb5b28fc75d7a0a"}, "ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [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", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"},
"ecto_sql": {:hex, :ecto_sql, "3.13.2", "a07d2461d84107b3d037097c822ffdd36ed69d1cf7c0f70e12a3d1decf04e2e1", [: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", "539274ab0ecf1a0078a6a72ef3465629e4d6018a3028095dc90f60a19c371717"}, "esbuild": {:hex, :esbuild, "0.9.0", "f043eeaca4932ca8e16e5429aebd90f7766f31ac160a25cbd9befe84f2bc068f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "b415027f71d5ab57ef2be844b2a10d0c1b5a492d431727f43937adce22ba45ae"},
"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"},
"ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"},
"ex_phone_number": {:hex, :ex_phone_number, "0.4.5", "2065cc48c3e9d1ed9821f50877c32f2f6898362cb990f44147ca217c5d1374ed", [:mix], [{:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: false]}], "hexpm", "67163f8706f8cbfef1b1f4b9230c461f19786d0d79fd0b22cbeeefc6f0b99d4a"},
"expo": {:hex, :expo, "1.1.0", "f7b9ed7fb5745ebe1eeedf3d6f29226c5dd52897ac67c0f8af62a07e661e5c75", [:mix], [], "hexpm", "fbadf93f4700fb44c331362177bdca9eeb8097e8b0ef525c9cc501cb9917c960"}, "expo": {:hex, :expo, "1.1.0", "f7b9ed7fb5745ebe1eeedf3d6f29226c5dd52897ac67c0f8af62a07e661e5c75", [:mix], [], "hexpm", "fbadf93f4700fb44c331362177bdca9eeb8097e8b0ef525c9cc501cb9917c960"},
"file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"}, "file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"},
"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.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [: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", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"},
"floki": {:hex, :floki, "0.38.0", "62b642386fa3f2f90713f6e231da0fa3256e41ef1089f83b6ceac7a3fd3abf33", [:mix], [], "hexpm", "a5943ee91e93fb2d635b612caf5508e36d37548e84928463ef9dd986f0d1abd9"}, "floki": {:hex, :floki, "0.37.0", "b83e0280bbc6372f2a403b2848013650b16640cd2470aea6701f0632223d719e", [:mix], [], "hexpm", "516a0c15a69f78c47dc8e0b9b3724b29608aa6619379f91b1ffa47109b5d0dd3"},
"gettext": {:hex, :gettext, "0.26.2", "5978aa7b21fada6deabf1f6341ddba50bc69c999e812211903b169799208f2a8", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "aa978504bcf76511efdc22d580ba08e2279caab1066b76bb9aa81c4a1e0a32a5"}, "gettext": {:hex, :gettext, "0.26.2", "5978aa7b21fada6deabf1f6341ddba50bc69c999e812211903b169799208f2a8", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "aa978504bcf76511efdc22d580ba08e2279caab1066b76bb9aa81c4a1e0a32a5"},
"glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"},
"heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "0435d4ca364a608cc75e2f8683d374e55abbae26", [tag: "v2.2.0", sparse: "optimized", depth: 1]}, "heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "88ab3a0d790e6a47404cba02800a6b25d2afae50", [tag: "v2.1.1", sparse: "optimized", depth: 1]},
"hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"},
"igniter": {:hex, :igniter, "0.6.19", "d87703b36890bc4278341d966a7ed8e10604a18610a4331ac10c75d1af48fff4", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "c2070b3fdbd238fc0a0bfbc1f125b5c0f79a1fe2f5b3c7b43cd33de696783663"}, "igniter": {:hex, :igniter, "0.5.46", "e3ad5b07a194b6e550ddd303bac45a126a65c6157c8acb664b22011cac8e34fd", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "64c59b696b678b2b83e2ee923f5254ac6479aff6c65dd513383bc0e4cdaeeeb7"},
"inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"},
"iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"},
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
"joken": {:hex, :joken, "2.6.2", "5daaf82259ca603af4f0b065475099ada1b2b849ff140ccd37f4b6828ca6892a", [:mix], [{:jose, "~> 1.11.10", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "5134b5b0a6e37494e46dbf9e4dad53808e5e787904b7c73972651b51cce3d72b"},
"jose": {:hex, :jose, "1.11.10", "a903f5227417bd2a08c8a00a0cbcc458118be84480955e8d251297a425723f83", [:mix, :rebar3], [], "hexpm", "0d6cd36ff8ba174db29148fc112b5842186b68a90ce9fc2b3ec3afe76593e614"},
"libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"},
"live_debugger": {:hex, :live_debugger, "0.3.1", "4b4d36481c3b0a49ec082c8268d37974ece34d2091ac323ccc0c906eb0c0d032", [:mix], [{:igniter, ">= 0.5.40 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:phoenix_live_view, "~> 0.20.4 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "e50836495134b0dde98dc96919340749130ecb83618ea99d63a3a58ed1dcc27d"}, "live_debugger": {:hex, :live_debugger, "0.1.5", "e7324a186071ac19885945e4ca7f3257ee07ed8c4ac5862305cab1fd595073aa", [:mix], [{:igniter, ">= 0.5.40 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:phoenix_live_view, "~> 0.20 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "dc9416960bfe12873bc37707d4669797850f4e8ca4fe192f3195330e1c623634"},
"luhn": {:hex, :luhn, "0.3.3", "5aa0c6a32c2db4b9db9f9b883ba8301c1ae169d57199b9e6cb1ba2707bc51d96", [:mix], [], "hexpm", "3e823a913a25aab51352c727f135278d22954874d5f0835be81ed4fec3daf78d"}, "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"},
"mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"},
"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"}, "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"},
"mix_audit": {:hex, :mix_audit, "2.1.5", "c0f77cee6b4ef9d97e37772359a187a166c7a1e0e08b50edf5bf6959dfe5a016", [:make, :mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "87f9298e21da32f697af535475860dc1d3617a010e0b418d2ec6142bc8b42d69"}, "mix_audit": {:hex, :mix_audit, "2.1.4", "0a23d5b07350cdd69001c13882a4f5fb9f90fbd4cbf2ebc190a2ee0d187ea3e9", [:make, :mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "fd807653cc8c1cada2911129c7eb9e985e3cc76ebf26f4dd628bb25bbcaa7099"},
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
"owl": {:hex, :owl, "0.12.2", "65906b525e5c3ef51bab6cba7687152be017aebe1da077bb719a5ee9f7e60762", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "6398efa9e1fea70a04d24231e10dcd66c1ac1aa2da418d20ef5357ec61de2880"}, "owl": {:hex, :owl, "0.12.2", "65906b525e5c3ef51bab6cba7687152be017aebe1da077bb719a5ee9f7e60762", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "6398efa9e1fea70a04d24231e10dcd66c1ac1aa2da418d20ef5357ec61de2880"},
"phoenix": {:hex, :phoenix, "1.7.21", "14ca4f1071a5f65121217d6b57ac5712d1857e40a0833aff7a691b7870fc9a3b", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {: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", "336dce4f86cba56fed312a7d280bf2282c720abb6074bdb1b61ec8095bdd0bc9"}, "phoenix": {:hex, :phoenix, "1.7.20", "6bababaf27d59f5628f9b608de902a021be2cecefb8231e1dbdc0a2e2e480e9b", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {: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", "6be2ab98302e8784a31829e0d50d8bdfa81a23cd912c395bafd8b8bfb5a086c2"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.6.5", "c4ef322acd15a574a8b1a08eff0ee0a85e73096b53ce1403b6563709f15e1cea", [: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", "26ec3208eef407f31b748cadd044045c6fd485fbff168e35963d2f9dfff28d4b"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.6.3", "f686701b0499a07f2e3b122d84d52ff8a31f5def386e03706c916f6feddf69ef", [: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", "909502956916a657a197f94cc1206d9a65247538de8a5e186f7537c895d95764"},
"phoenix_html": {:hex, :phoenix_html, "4.2.1", "35279e2a39140068fc03f8874408d58eef734e488fc142153f055c5454fd1c08", [:mix], [], "hexpm", "cff108100ae2715dd959ae8f2a8cef8e20b593f8dfd031c9cba92702cf23e053"}, "phoenix_html": {:hex, :phoenix_html, "4.2.1", "35279e2a39140068fc03f8874408d58eef734e488fc142153f055c5454fd1c08", [:mix], [], "hexpm", "cff108100ae2715dd959ae8f2a8cef8e20b593f8dfd031c9cba92702cf23e053"},
"phoenix_html_helpers": {:hex, :phoenix_html_helpers, "1.0.1", "7eed85c52eff80a179391036931791ee5d2f713d76a81d0d2c6ebafe1e11e5ec", [:mix], [{:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "cffd2385d1fa4f78b04432df69ab8da63dc5cf63e07b713a4dcf36a3740e3090"}, "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.6", "7b1f0327f54c9eb69845fd09a77accf922f488c549a7e7b8618775eb603a62c7", [: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]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "1681ab813ec26ca6915beb3414aa138f298e17721dc6a2bde9e6eb8a62360ff6"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.7", "405880012cb4b706f26dd1c6349125bfc903fb9e44d1ea668adaf4e04d4884b7", [: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]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "3a8625cab39ec261d48a13b7468dc619c0ede099601b084e343968309bd4d7d7"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.5.3", "f2161c207fda0e4fb55165f650f7f8db23f02b29e3bff00ff7ef161d6ac1f09d", [:mix], [{:file_system, "~> 0.3 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "b4ec9cd73cb01ff1bd1cac92e045d13e7030330b74164297d1aee3907b54803c"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.6.0", "2791fac0e2776b640192308cc90c0dbcf67843ad51387ed4ecae2038263d708d", [: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", "b3a1fa036d7eb2f956774eda7a7638cf5123f8f2175aca6d6420a7f95e598e1c"}, "phoenix_live_view": {:hex, :phoenix_live_view, "1.0.5", "f072166f87c44ffaf2b47b65c5ced8c375797830e517bfcf0a006fe7eb113911", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [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", "94abbc84df8a93a64514fc41528695d7326b6f3095e906b32f264ec4280811f3"},
"phoenix_live_view": {:hex, :phoenix_live_view, "1.0.17", "beeb16d83a7d3760f7ad463df94e83b087577665d2acc0bf2987cd7d9778068f", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, 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", "a4ca05c1eb6922c4d07a508a75bfa12c45e5f4d8f77ae83283465f02c53741e1"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
"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_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"}, "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.18.1", "5067f26f7745b7e31bc3368bc1a2b818b9779faa959b49c934c17730efc911cf", [: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", "57a57db70df2b422b564437d2d33cf8d33cd16339c1edb190cd11b1a3a546cc2"}, "plug": {:hex, :plug, "1.17.0", "a0832e7af4ae0f4819e0c08dd2e7482364937aea6a8a997a679f2cbb7e026b2e", [: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", "f6692046652a69a00a5a21d0b7e11fcf401064839d59d6b8787f23af55b1e6bc"},
"plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"}, "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
"postgrex": {:hex, :postgrex, "0.20.0", "363ed03ab4757f6bc47942eff7720640795eb557e1935951c1626f0d303a3aed", [: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", "d36ef8b36f323d29505314f704e21a1a038e2dc387c6409ee0cd24144e187c0f"}, "postgrex": {:hex, :postgrex, "0.20.0", "363ed03ab4757f6bc47942eff7720640795eb557e1935951c1626f0d303a3aed", [: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", "d36ef8b36f323d29505314f704e21a1a038e2dc387c6409ee0cd24144e187c0f"},
"reactor": {:hex, :reactor, "0.15.6", "d717f9add549b25a089a94c90197718d2d838e35d81dd776b1d81587d4cf2aaa", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "74db98165e3644d86e0f723672d91ceca4339eaa935bcad7e78bf146a46d77b9"}, "reactor": {:hex, :reactor, "0.15.2", "8c1b3fe0527b7a92b0b22c3f33f2e66858dd069bf1dd51d1031f63cd8cbd1fd5", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "091435a1fa0cab9bc2ed3934b203a0fd190f62e8b6aca63741f9242b8c7631ac"},
"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": {:hex, :req, "0.5.10", "a3a063eab8b7510785a467f03d30a8d95f66f5c3d9495be3474b61459c54376c", [: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", "8a604815743f8a2d3b5de0659fa3137fa4b1cffd636ecb69b30b2b9b2c2559be"},
"rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"}, "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"},
"slugify": {:hex, :slugify, "1.3.1", "0d3b8b7e5c1eeaa960e44dce94382bee34a39b3ea239293e457a9c5b47cc6fd3", [:mix], [], "hexpm", "cb090bbeb056b312da3125e681d98933a360a70d327820e4b7f91645c4d8be76"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"},
"sobelow": {:hex, :sobelow, "0.14.0", "dd82aae8f72503f924fe9dd97ffe4ca694d2f17ec463dcfd365987c9752af6ee", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "7ecf91e298acfd9b24f5d761f19e8f6e6ac585b9387fb6301023f1f2cd5eed5f"}, "sourceror": {:hex, :sourceror, "1.9.0", "3bf5fe2d017aaabe3866d8a6da097dd7c331e0d2d54e59e21c2b066d47f1e08e", [:mix], [], "hexpm", "d20a9dd5efe162f0d75a307146faa2e17b823ea4f134f662358d70f0332fed82"},
"sourceror": {:hex, :sourceror, "1.10.0", "38397dedbbc286966ec48c7af13e228b171332be1ad731974438c77791945ce9", [:mix], [], "hexpm", "29dbdfc92e04569c9d8e6efdc422fc1d815f4bd0055dc7c51b8800fb75c4b3f1"}, "spark": {:hex, :spark, "2.2.52", "50094275c9bbafa8e5e9eed0ab61983ee209a500e7044914ccf88e9921ae5082", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "f8de8c298bbbf7abd2a80d0ecabcefef65941f397cdbe94ce6165a121b09084f"},
"spark": {:hex, :spark, "2.2.67", "67626cb9f59ea4b1c5aa85d4afdd025e0740cbd49ed82665d0a40ff007d7fd4b", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "c8575402e3afc66871362e821bece890536d16319cdb758c5fb2d1250182e46f"}, "spitfire": {:hex, :spitfire, "0.2.0", "0de1f519a23f65bde40d316adad53c07a9563f25cc68915d639d8a509a0aad8a", [:mix], [], "hexpm", "743daaee2d81a0d8095431729f478ce49b47ea8943c7d770de86704975cb7775"},
"spitfire": {:hex, :spitfire, "0.2.1", "29e154873f05444669c7453d3d931820822cbca5170e88f0f8faa1de74a79b47", [:mix], [], "hexpm", "6eeed75054a38341b2e1814d41bb0a250564092358de2669fdb57ff88141d91b"},
"splode": {:hex, :splode, "0.2.9", "3a2776e187c82f42f5226b33b1220ccbff74f4bcc523dd4039c804caaa3ffdc7", [:mix], [], "hexpm", "8002b00c6e24f8bd1bcced3fbaa5c33346048047bb7e13d2f3ad428babbd95c3"}, "splode": {:hex, :splode, "0.2.9", "3a2776e187c82f42f5226b33b1220ccbff74f4bcc523dd4039c804caaa3ffdc7", [:mix], [], "hexpm", "8002b00c6e24f8bd1bcced3fbaa5c33346048047bb7e13d2f3ad428babbd95c3"},
"stream_data": {:hex, :stream_data, "1.2.0", "58dd3f9e88afe27dc38bef26fce0c84a9e7a96772b2925c7b32cd2435697a52b", [:mix], [], "hexpm", "eb5c546ee3466920314643edf68943a5b14b32d1da9fe01698dc92b73f89a9ed"}, "stream_data": {:hex, :stream_data, "1.2.0", "58dd3f9e88afe27dc38bef26fce0c84a9e7a96772b2925c7b32cd2435697a52b", [:mix], [], "hexpm", "eb5c546ee3466920314643edf68943a5b14b32d1da9fe01698dc92b73f89a9ed"},
"sweet_xml": {:hex, :sweet_xml, "0.7.5", "803a563113981aaac202a1dbd39771562d0ad31004ddbfc9b5090bdcd5605277", [:mix], [], "hexpm", "193b28a9b12891cae351d81a0cead165ffe67df1b73fe5866d10629f4faefb12"}, "swoosh": {:hex, :swoosh, "1.18.2", "41279e8449b65d14b571b66afe9ab352c3b0179291af8e5f4ad9207f489ad11a", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "032fcb2179f6d4e3b90030514ddc8d3946d8b046be939d121db480ca78adbc38"},
"swoosh": {:hex, :swoosh, "1.19.3", "02ad4455939f502386e4e1443d4de94c514995fd0e51b3cafffd6bd270ffe81c", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5.10 or ~> 0.6 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "04a10f8496786b744b84130e3510eb53ca51e769c39511b65023bdf4136b732f"},
"tailwind": {:hex, :tailwind, "0.3.1", "a89d2835c580748c7a975ad7dd3f2ea5e63216dc16d44f9df492fbd12c094bed", [:mix], [], "hexpm", "98a45febdf4a87bc26682e1171acdedd6317d0919953c353fcd1b4f9f4b676a2"}, "tailwind": {:hex, :tailwind, "0.3.1", "a89d2835c580748c7a975ad7dd3f2ea5e63216dc16d44f9df492fbd12c094bed", [:mix], [], "hexpm", "98a45febdf4a87bc26682e1171acdedd6317d0919953c353fcd1b4f9f4b676a2"},
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
"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_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"}, "telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"},
"text_diff": {:hex, :text_diff, "0.1.0", "1caf3175e11a53a9a139bc9339bd607c47b9e376b073d4571c031913317fecaa", [:mix], [], "hexpm", "d1ffaaecab338e49357b6daa82e435f877e0649041ace7755583a0ea3362dbd7"}, "text_diff": {:hex, :text_diff, "0.1.0", "1caf3175e11a53a9a139bc9339bd607c47b9e376b073d4571c031913317fecaa", [:mix], [], "hexpm", "d1ffaaecab338e49357b6daa82e435f877e0649041ace7755583a0ea3362dbd7"},
"thousand_island": {:hex, :thousand_island, "1.3.14", "ad45ebed2577b5437582bcc79c5eccd1e2a8c326abf6a3464ab6c06e2055a34a", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d0d24a929d31cdd1d7903a4fe7f2409afeedff092d277be604966cd6aa4307ef"}, "thousand_island": {:hex, :thousand_island, "1.3.11", "b68f3e91f74d564ae20b70d981bbf7097dde084343c14ae8a33e5b5fbb3d6f37", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "555c18c62027f45d9c80df389c3d01d86ba11014652c00be26e33b1b64e98d29"},
"tidewave": {:hex, :tidewave, "0.2.0", "e98378803e535d3035138e4b354dcfca26b7f862fd44cffef5aa697b814c0b0b", [:mix], [{:circular_buffer, "~> 0.4 or ~> 1.0", [hex: :circular_buffer, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.47 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:plug, "~> 1.17", [hex: :plug, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "6ad11829f4600cd69955ffc66935e6456b775fea095172147244ba6f65986735"},
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
"websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [: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", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"}, "websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [: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", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"},
"yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"}, "yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"},
"yaml_elixir": {:hex, :yaml_elixir, "2.11.0", "9e9ccd134e861c66b84825a3542a1c22ba33f338d82c07282f4f1f52d847bd50", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "53cc28357ee7eb952344995787f4bb8cc3cecbf189652236e9b163e8ce1bc242"}, "yaml_elixir": {:hex, :yaml_elixir, "2.11.0", "9e9ccd134e861c66b84825a3542a1c22ba33f338d82c07282f4f1f52d847bd50", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "53cc28357ee7eb952344995787f4bb8cc3cecbf189652236e9b163e8ce1bc242"},
"ymlr": {:hex, :ymlr, "5.1.4", "b924d61e1fc1ec371cde6ab3ccd9311110b1e052fc5c2460fb322e8380e7712a", [:mix], [], "hexpm", "75f16cf0709fbd911b30311a0359a7aa4b5476346c01882addefd5f2b1cfaa51"}, "ymlr": {:hex, :ymlr, "5.1.3", "a8061add5a378e20272a31905be70209a5680fdbe0ad51f40cb1af4bdd0a010b", [:mix], [], "hexpm", "8663444fa85101a117887c170204d4c5a2182567e5f84767f0071cf15f2efb1e"},
} }

View file

@ -1,274 +0,0 @@
## `msgid`s in this file come from POT (.pot) files.
##
## Do not add, change, or remove `msgid`s manually here as
## they're tied to the ones in the corresponding POT file
## (with the same domain).
##
## Use `mix gettext.extract --merge` or `mix gettext.merge`
## to merge POT files into PO files.
msgid ""
msgstr ""
"Language: en\n"
#: lib/mv_web/components/core_components.ex:482
#, elixir-autogen, elixir-format
msgid "Actions"
msgstr ""
#: lib/mv_web/member_live/index.ex:39
#, elixir-autogen, elixir-format
msgid "Are you sure?"
msgstr "Bist du sicher?"
#: lib/mv_web/components/core_components.ex:160
#, elixir-autogen, elixir-format
msgid "Attempting to reconnect"
msgstr "Verbindung wird wiederhergestellt"
#: lib/mv_web/member_live/form_component.ex:50
#: lib/mv_web/member_live/index.ex:25
#: lib/mv_web/member_live/show.ex:32
#, elixir-autogen, elixir-format
msgid "City"
msgstr "Stadt"
#: lib/mv_web/member_live/index.ex:41
#, elixir-autogen, elixir-format
msgid "Delete"
msgstr "Löschen"
#: lib/mv_web/member_live/index.ex:33
#, elixir-autogen, elixir-format
msgid "Edit"
msgstr "Bearbeiten"
#: lib/mv_web/member_live/index.ex:76
#: lib/mv_web/member_live/show.ex:93
#, elixir-autogen, elixir-format
msgid "Edit Member"
msgstr "Mitglied bearbeiten"
#: lib/mv_web/member_live/form_component.ex:43
#: lib/mv_web/member_live/index.ex:24
#: lib/mv_web/member_live/show.ex:23
#, elixir-autogen, elixir-format
msgid "Email"
msgstr "E-Mail"
#: lib/mv_web/components/core_components.ex:151
#, elixir-autogen, elixir-format
msgid "Error!"
msgstr "Fehler!"
#: lib/mv_web/member_live/form_component.ex:41
#: lib/mv_web/member_live/index.ex:22
#: lib/mv_web/member_live/show.ex:21
#, elixir-autogen, elixir-format
msgid "First Name"
msgstr "Vorname"
#: lib/mv_web/components/core_components.ex:172
#, elixir-autogen, elixir-format
msgid "Hang in there while we get back on track"
msgstr "Bitte warten, wir stellen die Verbindung wieder her."
#: lib/mv_web/member_live/form_component.ex:47
#: lib/mv_web/member_live/index.ex:26
#: lib/mv_web/member_live/show.ex:29
#, elixir-autogen, elixir-format
msgid "Join Date"
msgstr "Beitrittsdatum"
#: lib/mv_web/member_live/form_component.ex:42
#: lib/mv_web/member_live/index.ex:23
#: lib/mv_web/member_live/show.ex:22
#, elixir-autogen, elixir-format
msgid "Last Name"
msgstr "Nachname"
#: lib/mv_web/member_live/index.ex:8
#: lib/mv_web/member_live/index.ex:88
#, elixir-autogen, elixir-format
msgid "Listing Members"
msgstr "Mitglieder"
#: lib/mv_web/member_live/index.ex:11
#: lib/mv_web/member_live/index.ex:82
#, elixir-autogen, elixir-format
msgid "New Member"
msgstr "Neues Mitglied"
#: lib/mv_web/member_live/index.ex:30
#, elixir-autogen, elixir-format
msgid "Show"
msgstr "Anzeigen"
#: lib/mv_web/components/core_components.ex:167
#, elixir-autogen, elixir-format
msgid "Something went wrong!"
msgstr "Etwas ist schiefgelaufen!"
#: lib/mv_web/components/core_components.ex:150
#, elixir-autogen, elixir-format
msgid "Success!"
msgstr "Erfolg!"
#: lib/mv_web/components/core_components.ex:155
#, elixir-autogen, elixir-format
msgid "We can't find the internet"
msgstr "Keine Internetverbindung gefunden"
#: lib/mv_web/components/core_components.ex:76
#: lib/mv_web/components/core_components.ex:130
#, elixir-autogen, elixir-format
msgid "close"
msgstr "schließen"
#: lib/mv_web/member_live/form_component.ex:44
#: lib/mv_web/member_live/show.ex:24
#, elixir-autogen, elixir-format
msgid "Birth Date"
msgstr "Geburtsdatum"
#: lib/mv_web/member_live/form_component.ex:55
#: lib/mv_web/member_live/show.ex:38
#, elixir-autogen, elixir-format
msgid "Custom Properties"
msgstr "Eigene Eigenschaften"
#: lib/mv_web/member_live/form_component.ex:48
#: lib/mv_web/member_live/show.ex:30
#, elixir-autogen, elixir-format
msgid "Exit Date"
msgstr "Austrittsdatum"
#: lib/mv_web/member_live/form_component.ex:52
#: lib/mv_web/member_live/show.ex:34
#, elixir-autogen, elixir-format
msgid "House Number"
msgstr "Hausnummer"
#: lib/mv_web/member_live/form_component.ex:49
#: lib/mv_web/member_live/show.ex:31
#, elixir-autogen, elixir-format
msgid "Notes"
msgstr "Notizen"
#: lib/mv_web/member_live/form_component.ex:45
#: lib/mv_web/member_live/show.ex:25
#, elixir-autogen, elixir-format
msgid "Paid"
msgstr "Bezahlt"
#: lib/mv_web/member_live/form_component.ex:46
#: lib/mv_web/member_live/show.ex:28
#, elixir-autogen, elixir-format
msgid "Phone Number"
msgstr "Telefonnummer"
#: lib/mv_web/member_live/form_component.ex:53
#: lib/mv_web/member_live/show.ex:35
#, elixir-autogen, elixir-format
msgid "Postal Code"
msgstr "Postleitzahl"
#: lib/mv_web/member_live/form_component.ex:75
#, elixir-autogen, elixir-format
msgid "Save Member"
msgstr "Mitglied speichern"
#: lib/mv_web/member_live/form_component.ex:75
#, elixir-autogen, elixir-format
msgid "Saving..."
msgstr "Speichern..."
#: lib/mv_web/member_live/form_component.ex:51
#: lib/mv_web/member_live/show.ex:33
#, elixir-autogen, elixir-format
msgid "Street"
msgstr "Straße"
#: lib/mv_web/member_live/form_component.ex:30
#, elixir-autogen, elixir-format
msgid "Use this form to manage member records and their properties."
msgstr "Dieses Formular dient zur Verwaltung von Mitgliedern und deren Eigenschaften."
#: lib/mv_web/member_live/show.ex:52
#, elixir-autogen, elixir-format
msgid "Back to members"
msgstr "Zurück zur Mitgliederliste"
#: lib/mv_web/member_live/show.ex:14
#, elixir-autogen, elixir-format
msgid "Edit member"
msgstr "Mitglied bearbeiten"
#: lib/mv_web/member_live/show.ex:20
#, elixir-autogen, elixir-format
msgid "Id"
msgstr "ID"
#: lib/mv_web/member_live/show.ex:26
#, elixir-autogen, elixir-format
msgid "No"
msgstr "Nein"
#: lib/mv_web/member_live/show.ex:92
#, elixir-autogen, elixir-format, fuzzy
msgid "Show Member"
msgstr "Mitglied anzeigen"
#: lib/mv_web/member_live/show.ex:10
#, elixir-autogen, elixir-format
msgid "This is a member record from your database."
msgstr "Dies ist ein Mitglied aus deiner Datenbank."
#: lib/mv_web/member_live/show.ex:26
#, elixir-autogen, elixir-format
msgid "Yes"
msgstr "Ja"
#: lib/mv_web/member_live/form_component.ex:102
#, elixir-autogen, elixir-format
msgid "create"
msgstr "erstellt"
#: lib/mv_web/member_live/form_component.ex:103
#, elixir-autogen, elixir-format
msgid "update"
msgstr "aktualisiert"
#: lib/mv_web/controllers/auth_controller.ex:43
#, elixir-autogen, elixir-format
msgid "Incorrect email or password"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:109
#, elixir-autogen, elixir-format
msgid "Member %{action} successfully"
msgstr "Mitglied %{action} erfolgreich"
#: lib/mv_web/controllers/auth_controller.ex:14
#, elixir-autogen, elixir-format
msgid "You are now signed in"
msgstr ""
#: lib/mv_web/controllers/auth_controller.ex:56
#, elixir-autogen, elixir-format
msgid "You are now signed out"
msgstr ""
#: lib/mv_web/controllers/auth_controller.ex:36
#, elixir-autogen, elixir-format
msgid "You have already signed in another way, but have not confirmed your account.\nYou can confirm your account using the link we sent to you, or by resetting your password.\n"
msgstr ""
#: lib/mv_web/controllers/auth_controller.ex:12
#, elixir-autogen, elixir-format
msgid "Your email address has now been confirmed"
msgstr ""
#: lib/mv_web/controllers/auth_controller.ex:13
#, elixir-autogen, elixir-format
msgid "Your password has successfully been reset"
msgstr ""

View file

@ -1,112 +0,0 @@
## `msgid`s in this file come from POT (.pot) files.
##
## Do not add, change, or remove `msgid`s manually here as
## they're tied to the ones in the corresponding POT file
## (with the same domain).
##
## Use `mix gettext.extract --merge` or `mix gettext.merge`
## to merge POT files into PO files.
msgid ""
msgstr ""
"Language: en\n"
## From Ecto.Changeset.cast/4
msgid "can't be blank"
msgstr ""
## From Ecto.Changeset.unique_constraint/3
msgid "has already been taken"
msgstr ""
## From Ecto.Changeset.put_change/3
msgid "is invalid"
msgstr ""
## From Ecto.Changeset.validate_acceptance/3
msgid "must be accepted"
msgstr ""
## From Ecto.Changeset.validate_format/3
msgid "has invalid format"
msgstr ""
## From Ecto.Changeset.validate_subset/3
msgid "has an invalid entry"
msgstr ""
## From Ecto.Changeset.validate_exclusion/3
msgid "is reserved"
msgstr ""
## From Ecto.Changeset.validate_confirmation/3
msgid "does not match confirmation"
msgstr ""
## From Ecto.Changeset.no_assoc_constraint/3
msgid "is still associated with this entry"
msgstr ""
msgid "are still associated with this entry"
msgstr ""
## From Ecto.Changeset.validate_length/3
msgid "should have %{count} item(s)"
msgid_plural "should have %{count} item(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should be %{count} character(s)"
msgid_plural "should be %{count} character(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should be %{count} byte(s)"
msgid_plural "should be %{count} byte(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should have at least %{count} item(s)"
msgid_plural "should have at least %{count} item(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should be at least %{count} character(s)"
msgid_plural "should be at least %{count} character(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should be at least %{count} byte(s)"
msgid_plural "should be at least %{count} byte(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should have at most %{count} item(s)"
msgid_plural "should have at most %{count} item(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should be at most %{count} character(s)"
msgid_plural "should be at most %{count} character(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should be at most %{count} byte(s)"
msgid_plural "should be at most %{count} byte(s)"
msgstr[0] ""
msgstr[1] ""
## From Ecto.Changeset.validate_number/3
msgid "must be less than %{number}"
msgstr ""
msgid "must be greater than %{number}"
msgstr ""
msgid "must be less than or equal to %{number}"
msgstr ""
msgid "must be greater than or equal to %{number}"
msgstr ""
msgid "must be equal to %{number}"
msgstr ""

View file

@ -1,275 +0,0 @@
## This file is a PO Template file.
##
## "msgid"s here are often extracted from source code.
## Add new messages manually only if they're dynamic
## messages that can't be statically extracted.
##
## Run "mix gettext.extract" to bring this file up to
## date. Leave "msgstr"s empty as changing them here has no
## effect: edit them in PO (.po) files instead.
#
msgid ""
msgstr ""
#: lib/mv_web/components/core_components.ex:482
#, elixir-autogen, elixir-format
msgid "Actions"
msgstr ""
#: lib/mv_web/member_live/index.ex:39
#, elixir-autogen, elixir-format
msgid "Are you sure?"
msgstr ""
#: lib/mv_web/components/core_components.ex:160
#, elixir-autogen, elixir-format
msgid "Attempting to reconnect"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:50
#: lib/mv_web/member_live/index.ex:25
#: lib/mv_web/member_live/show.ex:32
#, elixir-autogen, elixir-format
msgid "City"
msgstr ""
#: lib/mv_web/member_live/index.ex:41
#, elixir-autogen, elixir-format
msgid "Delete"
msgstr ""
#: lib/mv_web/member_live/index.ex:33
#, elixir-autogen, elixir-format
msgid "Edit"
msgstr ""
#: lib/mv_web/member_live/index.ex:76
#: lib/mv_web/member_live/show.ex:93
#, elixir-autogen, elixir-format
msgid "Edit Member"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:43
#: lib/mv_web/member_live/index.ex:24
#: lib/mv_web/member_live/show.ex:23
#, elixir-autogen, elixir-format
msgid "Email"
msgstr ""
#: lib/mv_web/components/core_components.ex:151
#, elixir-autogen, elixir-format
msgid "Error!"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:41
#: lib/mv_web/member_live/index.ex:22
#: lib/mv_web/member_live/show.ex:21
#, elixir-autogen, elixir-format
msgid "First Name"
msgstr ""
#: lib/mv_web/components/core_components.ex:172
#, elixir-autogen, elixir-format
msgid "Hang in there while we get back on track"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:47
#: lib/mv_web/member_live/index.ex:26
#: lib/mv_web/member_live/show.ex:29
#, elixir-autogen, elixir-format
msgid "Join Date"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:42
#: lib/mv_web/member_live/index.ex:23
#: lib/mv_web/member_live/show.ex:22
#, elixir-autogen, elixir-format
msgid "Last Name"
msgstr ""
#: lib/mv_web/member_live/index.ex:8
#: lib/mv_web/member_live/index.ex:88
#, elixir-autogen, elixir-format
msgid "Listing Members"
msgstr ""
#: lib/mv_web/member_live/index.ex:11
#: lib/mv_web/member_live/index.ex:82
#, elixir-autogen, elixir-format
msgid "New Member"
msgstr ""
#: lib/mv_web/member_live/index.ex:30
#, elixir-autogen, elixir-format
msgid "Show"
msgstr ""
#: lib/mv_web/components/core_components.ex:167
#, elixir-autogen, elixir-format
msgid "Something went wrong!"
msgstr ""
#: lib/mv_web/components/core_components.ex:150
#, elixir-autogen, elixir-format
msgid "Success!"
msgstr ""
#: lib/mv_web/components/core_components.ex:155
#, elixir-autogen, elixir-format
msgid "We can't find the internet"
msgstr ""
#: lib/mv_web/components/core_components.ex:76
#: lib/mv_web/components/core_components.ex:130
#, elixir-autogen, elixir-format
msgid "close"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:44
#: lib/mv_web/member_live/show.ex:24
#, elixir-autogen, elixir-format
msgid "Birth Date"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:55
#: lib/mv_web/member_live/show.ex:38
#, elixir-autogen, elixir-format
msgid "Custom Properties"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:48
#: lib/mv_web/member_live/show.ex:30
#, elixir-autogen, elixir-format
msgid "Exit Date"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:52
#: lib/mv_web/member_live/show.ex:34
#, elixir-autogen, elixir-format
msgid "House Number"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:49
#: lib/mv_web/member_live/show.ex:31
#, elixir-autogen, elixir-format
msgid "Notes"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:45
#: lib/mv_web/member_live/show.ex:25
#, elixir-autogen, elixir-format
msgid "Paid"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:46
#: lib/mv_web/member_live/show.ex:28
#, elixir-autogen, elixir-format
msgid "Phone Number"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:53
#: lib/mv_web/member_live/show.ex:35
#, elixir-autogen, elixir-format
msgid "Postal Code"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:75
#, elixir-autogen, elixir-format
msgid "Save Member"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:75
#, elixir-autogen, elixir-format
msgid "Saving..."
msgstr ""
#: lib/mv_web/member_live/form_component.ex:51
#: lib/mv_web/member_live/show.ex:33
#, elixir-autogen, elixir-format
msgid "Street"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:30
#, elixir-autogen, elixir-format
msgid "Use this form to manage member records and their properties."
msgstr ""
#: lib/mv_web/member_live/show.ex:52
#, elixir-autogen, elixir-format
msgid "Back to members"
msgstr ""
#: lib/mv_web/member_live/show.ex:14
#, elixir-autogen, elixir-format
msgid "Edit member"
msgstr ""
#: lib/mv_web/member_live/show.ex:20
#, elixir-autogen, elixir-format
msgid "Id"
msgstr ""
#: lib/mv_web/member_live/show.ex:26
#, elixir-autogen, elixir-format
msgid "No"
msgstr ""
#: lib/mv_web/member_live/show.ex:92
#, elixir-autogen, elixir-format
msgid "Show Member"
msgstr ""
#: lib/mv_web/member_live/show.ex:10
#, elixir-autogen, elixir-format
msgid "This is a member record from your database."
msgstr ""
#: lib/mv_web/member_live/show.ex:26
#, elixir-autogen, elixir-format
msgid "Yes"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:102
#, elixir-autogen, elixir-format
msgid "create"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:103
#, elixir-autogen, elixir-format
msgid "update"
msgstr ""
#: lib/mv_web/controllers/auth_controller.ex:43
#, elixir-autogen, elixir-format
msgid "Incorrect email or password"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:109
#, elixir-autogen, elixir-format
msgid "Member %{action} successfully"
msgstr ""
#: lib/mv_web/controllers/auth_controller.ex:14
#, elixir-autogen, elixir-format
msgid "You are now signed in"
msgstr ""
#: lib/mv_web/controllers/auth_controller.ex:56
#, elixir-autogen, elixir-format
msgid "You are now signed out"
msgstr ""
#: lib/mv_web/controllers/auth_controller.ex:36
#, elixir-autogen, elixir-format
msgid "You have already signed in another way, but have not confirmed your account.\nYou can confirm your account using the link we sent to you, or by resetting your password.\n"
msgstr ""
#: lib/mv_web/controllers/auth_controller.ex:12
#, elixir-autogen, elixir-format
msgid "Your email address has now been confirmed"
msgstr ""
#: lib/mv_web/controllers/auth_controller.ex:13
#, elixir-autogen, elixir-format
msgid "Your password has successfully been reset"
msgstr ""

View file

@ -1,275 +0,0 @@
## "msgid"s in this file come from POT (.pot) files.
###
### Do not add, change, or remove "msgid"s manually here as
### they're tied to the ones in the corresponding POT file
### (with the same domain).
###
### Use "mix gettext.extract --merge" or "mix gettext.merge"
### to merge POT files into PO files.
msgid ""
msgstr ""
"Language: en\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: lib/mv_web/components/core_components.ex:482
#, elixir-autogen, elixir-format
msgid "Actions"
msgstr ""
#: lib/mv_web/member_live/index.ex:39
#, elixir-autogen, elixir-format
msgid "Are you sure?"
msgstr ""
#: lib/mv_web/components/core_components.ex:160
#, elixir-autogen, elixir-format
msgid "Attempting to reconnect"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:50
#: lib/mv_web/member_live/index.ex:25
#: lib/mv_web/member_live/show.ex:32
#, elixir-autogen, elixir-format
msgid "City"
msgstr ""
#: lib/mv_web/member_live/index.ex:41
#, elixir-autogen, elixir-format
msgid "Delete"
msgstr ""
#: lib/mv_web/member_live/index.ex:33
#, elixir-autogen, elixir-format
msgid "Edit"
msgstr ""
#: lib/mv_web/member_live/index.ex:76
#: lib/mv_web/member_live/show.ex:93
#, elixir-autogen, elixir-format
msgid "Edit Member"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:43
#: lib/mv_web/member_live/index.ex:24
#: lib/mv_web/member_live/show.ex:23
#, elixir-autogen, elixir-format
msgid "Email"
msgstr ""
#: lib/mv_web/components/core_components.ex:151
#, elixir-autogen, elixir-format
msgid "Error!"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:41
#: lib/mv_web/member_live/index.ex:22
#: lib/mv_web/member_live/show.ex:21
#, elixir-autogen, elixir-format
msgid "First Name"
msgstr ""
#: lib/mv_web/components/core_components.ex:172
#, elixir-autogen, elixir-format
msgid "Hang in there while we get back on track"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:47
#: lib/mv_web/member_live/index.ex:26
#: lib/mv_web/member_live/show.ex:29
#, elixir-autogen, elixir-format
msgid "Join Date"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:42
#: lib/mv_web/member_live/index.ex:23
#: lib/mv_web/member_live/show.ex:22
#, elixir-autogen, elixir-format
msgid "Last Name"
msgstr ""
#: lib/mv_web/member_live/index.ex:8
#: lib/mv_web/member_live/index.ex:88
#, elixir-autogen, elixir-format
msgid "Listing Members"
msgstr ""
#: lib/mv_web/member_live/index.ex:11
#: lib/mv_web/member_live/index.ex:82
#, elixir-autogen, elixir-format
msgid "New Member"
msgstr ""
#: lib/mv_web/member_live/index.ex:30
#, elixir-autogen, elixir-format
msgid "Show"
msgstr ""
#: lib/mv_web/components/core_components.ex:167
#, elixir-autogen, elixir-format
msgid "Something went wrong!"
msgstr ""
#: lib/mv_web/components/core_components.ex:150
#, elixir-autogen, elixir-format
msgid "Success!"
msgstr ""
#: lib/mv_web/components/core_components.ex:155
#, elixir-autogen, elixir-format
msgid "We can't find the internet"
msgstr ""
#: lib/mv_web/components/core_components.ex:76
#: lib/mv_web/components/core_components.ex:130
#, elixir-autogen, elixir-format
msgid "close"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:44
#: lib/mv_web/member_live/show.ex:24
#, elixir-autogen, elixir-format
msgid "Birth Date"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:55
#: lib/mv_web/member_live/show.ex:38
#, elixir-autogen, elixir-format
msgid "Custom Properties"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:48
#: lib/mv_web/member_live/show.ex:30
#, elixir-autogen, elixir-format
msgid "Exit Date"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:52
#: lib/mv_web/member_live/show.ex:34
#, elixir-autogen, elixir-format
msgid "House Number"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:49
#: lib/mv_web/member_live/show.ex:31
#, elixir-autogen, elixir-format
msgid "Notes"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:45
#: lib/mv_web/member_live/show.ex:25
#, elixir-autogen, elixir-format
msgid "Paid"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:46
#: lib/mv_web/member_live/show.ex:28
#, elixir-autogen, elixir-format
msgid "Phone Number"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:53
#: lib/mv_web/member_live/show.ex:35
#, elixir-autogen, elixir-format
msgid "Postal Code"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:75
#, elixir-autogen, elixir-format, fuzzy
msgid "Save Member"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:75
#, elixir-autogen, elixir-format
msgid "Saving..."
msgstr ""
#: lib/mv_web/member_live/form_component.ex:51
#: lib/mv_web/member_live/show.ex:33
#, elixir-autogen, elixir-format
msgid "Street"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:30
#, elixir-autogen, elixir-format
msgid "Use this form to manage member records and their properties."
msgstr ""
#: lib/mv_web/member_live/show.ex:52
#, elixir-autogen, elixir-format
msgid "Back to members"
msgstr ""
#: lib/mv_web/member_live/show.ex:14
#, elixir-autogen, elixir-format, fuzzy
msgid "Edit member"
msgstr ""
#: lib/mv_web/member_live/show.ex:20
#, elixir-autogen, elixir-format
msgid "Id"
msgstr ""
#: lib/mv_web/member_live/show.ex:26
#, elixir-autogen, elixir-format
msgid "No"
msgstr ""
#: lib/mv_web/member_live/show.ex:92
#, elixir-autogen, elixir-format, fuzzy
msgid "Show Member"
msgstr ""
#: lib/mv_web/member_live/show.ex:10
#, elixir-autogen, elixir-format
msgid "This is a member record from your database."
msgstr ""
#: lib/mv_web/member_live/show.ex:26
#, elixir-autogen, elixir-format
msgid "Yes"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:102
#, elixir-autogen, elixir-format
msgid "create"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:103
#, elixir-autogen, elixir-format
msgid "update"
msgstr ""
#: lib/mv_web/controllers/auth_controller.ex:43
#, elixir-autogen, elixir-format
msgid "Incorrect email or password"
msgstr ""
#: lib/mv_web/member_live/form_component.ex:109
#, elixir-autogen, elixir-format
msgid "Member %{action} successfully"
msgstr ""
#: lib/mv_web/controllers/auth_controller.ex:14
#, elixir-autogen, elixir-format
msgid "You are now signed in"
msgstr ""
#: lib/mv_web/controllers/auth_controller.ex:56
#, elixir-autogen, elixir-format
msgid "You are now signed out"
msgstr ""
#: lib/mv_web/controllers/auth_controller.ex:36
#, elixir-autogen, elixir-format
msgid "You have already signed in another way, but have not confirmed your account.\nYou can confirm your account using the link we sent to you, or by resetting your password.\n"
msgstr ""
#: lib/mv_web/controllers/auth_controller.ex:12
#, elixir-autogen, elixir-format
msgid "Your email address has now been confirmed"
msgstr ""
#: lib/mv_web/controllers/auth_controller.ex:13
#, elixir-autogen, elixir-format
msgid "Your password has successfully been reset"
msgstr ""

View file

@ -11,7 +11,7 @@ defmodule Mv.Repo.Migrations.InitialMigration do
create table(:property_types, primary_key: false) do create table(:property_types, primary_key: false) do
add :id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true add :id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true
add :name, :text, null: false add :name, :text, null: false
add :value_type, :text, null: false add :type, :text, null: false
add :description, :text add :description, :text
add :immutable, :boolean, null: false, default: false add :immutable, :boolean, null: false, default: false
add :required, :boolean, null: false, default: false add :required, :boolean, null: false, default: false
@ -21,7 +21,7 @@ defmodule Mv.Repo.Migrations.InitialMigration do
create table(:properties, primary_key: false) do create table(:properties, primary_key: false) do
add :id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true add :id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true
add :value, :map add :value, :text
add :member_id, :uuid add :member_id, :uuid
add :property_type_id, :uuid add :property_type_id, :uuid
end end

View file

@ -1,45 +0,0 @@
defmodule Mv.Repo.Migrations.MemberFields do
@moduledoc """
Updates resources based on their most recent snapshots.
This file was autogenerated with `mix ash_postgres.generate_migrations`
"""
use Ecto.Migration
def up do
alter table(:members) do
add :first_name, :text, null: false
add :last_name, :text, null: false
add :email, :text, null: false
add :birth_date, :date
add :paid, :boolean
add :phone_number, :text
add :join_date, :date
add :exit_date, :date
add :notes, :text
add :city, :text
add :street, :text
add :house_number, :text
add :postal_code, :text
end
end
def down do
alter table(:members) do
remove :postal_code
remove :house_number
remove :street
remove :city
remove :notes
remove :exit_date
remove :join_date
remove :phone_number
remove :paid
remove :birth_date
remove :email
remove :last_name
remove :first_name
end
end
end

View file

@ -1,38 +0,0 @@
defmodule Mv.Repo.Migrations.MemberDelete do
@moduledoc """
Updates resources based on their most recent snapshots.
This file was autogenerated with `mix ash_postgres.generate_migrations`
"""
use Ecto.Migration
def up do
drop constraint(:properties, "properties_member_id_fkey")
alter table(:properties) do
modify :member_id,
references(:members,
column: :id,
name: "properties_member_id_fkey",
type: :uuid,
prefix: "public",
on_delete: :delete_all
)
end
end
def down do
drop constraint(:properties, "properties_member_id_fkey")
alter table(:properties) do
modify :member_id,
references(:members,
column: :id,
name: "properties_member_id_fkey",
type: :uuid,
prefix: "public"
)
end
end
end

View file

@ -1,19 +0,0 @@
defmodule Mv.Repo.Migrations.AddAccountsDomainExtensions do
@moduledoc """
Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback
This file was autogenerated with `mix ash_postgres.generate_migrations`
"""
use Ecto.Migration
def up do
execute("CREATE EXTENSION IF NOT EXISTS \"citext\"")
end
def down do
# Uncomment this if you actually want to uninstall the extensions
# when this migration is rolled back:
# execute("DROP EXTENSION IF EXISTS \"citext\"")
end
end

View file

@ -1,58 +0,0 @@
defmodule Mv.Repo.Migrations.AddAccountsDomain do
@moduledoc """
Updates resources based on their most recent snapshots.
This file was autogenerated with `mix ash_postgres.generate_migrations`
"""
use Ecto.Migration
def up do
create table(:users, primary_key: false) do
add :id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true
add :email, :citext, null: false
add :hashed_password, :text
add :oidc_id, :text
add :member_id,
references(:members,
column: :id,
name: "users_member_id_fkey",
type: :uuid,
prefix: "public"
)
end
create unique_index(:users, [:email], name: "users_unique_email_index")
create unique_index(:users, [:oidc_id], name: "users_unique_oidc_id_index")
create table(:tokens, primary_key: false) do
add :updated_at, :utc_datetime_usec,
null: false,
default: fragment("(now() AT TIME ZONE 'utc')")
add :created_at, :utc_datetime_usec,
null: false,
default: fragment("(now() AT TIME ZONE 'utc')")
add :extra_data, :map
add :purpose, :text, null: false
add :expires_at, :utc_datetime, null: false
add :subject, :text, null: false
add :jti, :text, null: false, primary_key: true
end
end
def down do
drop table(:tokens)
drop_if_exists unique_index(:users, [:oidc_id], name: "users_unique_oidc_id_index")
drop_if_exists unique_index(:users, [:email], name: "users_unique_email_index")
drop constraint(:users, "users_member_id_fkey")
drop table(:users)
end
end

View file

@ -2,42 +2,38 @@
# #
# mix run priv/repo/seeds.exs # mix run priv/repo/seeds.exs
# #
# Inside the script, you can read and write to any of your
# repositories directly:
#
# Mv.Repo.insert!(%Mv.SomeSchema{})
#
# We recommend using the bang functions (`insert!`, `update!`
# and so on) as they will fail if something goes wrong.
alias Mv.Membership alias Mv.Membership
for attrs <- [ for attrs <- [
%{ %{
name: "String Field", name: "Vorname",
value_type: :string, type: "string",
description: "Example for a field of type string", description: "Vorname des Mitglieds",
immutable: true, immutable: true,
required: true required: true
}, },
%{ %{
name: "Date Field", name: "Email",
value_type: :date, type: "string",
description: "Example for a field of type date", description: "Email-Adresse des Mitglieds",
immutable: true,
required: true
},
%{
name: "Boolean Field",
value_type: :boolean,
description: "Example for a field of type boolean",
immutable: true,
required: true
},
%{
name: "Email Field",
value_type: :email,
description: "Example for a field of type email",
immutable: true, immutable: true,
required: true required: true
} }
] do ] do
Membership.create_property_type!( # upsert?: true sorgt dafür, dass bei bestehendem Namen kein Fehler,
attrs, # sondern ein Update (hier effektiv No-Op) ausgeführt wird
upsert?: true, {:ok, _} =
upsert_identity: :unique_name Membership.create_property_type(
) attrs,
upsert?: true,
upsert_identity: :unique_name
)
end end

View file

@ -1,7 +1,6 @@
{ {
"ash_functions_version": 5, "ash_functions_version": 5,
"installed": [ "installed": [
"ash-functions", "ash-functions"
"citext"
] ]
} }

View file

@ -16,7 +16,7 @@
"custom_indexes": [], "custom_indexes": [],
"custom_statements": [], "custom_statements": [],
"has_create_action": true, "has_create_action": true,
"hash": "35D45214D6D344B0AF6CFCB69B8682FCB3D382D85883D3D3AAC1AEE7F54FD89A", "hash": "A0402269CB456075B81CA4CB3A2135A2C88D8B7FD51CD7A23084AA5264FEE344",
"identities": [], "identities": [],
"multitenancy": { "multitenancy": {
"attribute": null, "attribute": null,

View file

@ -1,187 +0,0 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"uuid_generate_v7()\")",
"generated?": false,
"precision": null,
"primary_key?": true,
"references": null,
"scale": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "first_name",
"type": "text"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "last_name",
"type": "text"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "email",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "birth_date",
"type": "date"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "paid",
"type": "boolean"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "phone_number",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "join_date",
"type": "date"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "exit_date",
"type": "date"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "notes",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "city",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "street",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "house_number",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "postal_code",
"type": "text"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "CF80317E7EE409618E08458B10EE122FF605640DDA8CD6000B433F1979614F5D",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.Mv.Repo",
"schema": null,
"table": "members"
}

View file

@ -18,7 +18,7 @@
"references": null, "references": null,
"size": null, "size": null,
"source": "value", "source": "value",
"type": "map" "type": "text"
}, },
{ {
"allow_nil?": true, "allow_nil?": true,
@ -84,7 +84,7 @@
"custom_indexes": [], "custom_indexes": [],
"custom_statements": [], "custom_statements": [],
"has_create_action": true, "has_create_action": true,
"hash": "8CF241CB9E8239511914EDEC96186BB7879529372BD8A4162431CCE9961F4F1B", "hash": "F2A42A3427E0428637F465E4F357A3BE21B33231F94CF77B4843084128F6BDA5",
"identities": [], "identities": [],
"multitenancy": { "multitenancy": {
"attribute": null, "attribute": null,

View file

@ -1,105 +0,0 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"precision": null,
"primary_key?": true,
"references": null,
"scale": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "value",
"type": "map"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"index?": false,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "properties_member_id_fkey",
"on_delete": "delete",
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "members"
},
"scale": null,
"size": null,
"source": "member_id",
"type": "uuid"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"index?": false,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "properties_property_type_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "property_types"
},
"scale": null,
"size": null,
"source": "property_type_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "4F17BE0106435A1D75D46A3ABDE6A3DA20FC9B1C43D101B6C310009279DD7CBA",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.Mv.Repo",
"schema": null,
"table": "properties"
}

View file

@ -27,7 +27,7 @@
"primary_key?": false, "primary_key?": false,
"references": null, "references": null,
"size": null, "size": null,
"source": "value_type", "source": "type",
"type": "text" "type": "text"
}, },
{ {
@ -66,7 +66,7 @@
"custom_indexes": [], "custom_indexes": [],
"custom_statements": [], "custom_statements": [],
"has_create_action": true, "has_create_action": true,
"hash": "F98A723AE0D20005FBE4205E46ABEE09A88DFF9334C85BADC1FBEEF100F3E25B", "hash": "47210108DE1E7B2A20A67205E875B3440526941E61AB95B166976E8CD8AA0955",
"identities": [ "identities": [
{ {
"all_tenants?": false, "all_tenants?": false,

View file

@ -1,103 +0,0 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "created_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "extra_data",
"type": "map"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "purpose",
"type": "text"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "expires_at",
"type": "utc_datetime"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "subject",
"type": "text"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": true,
"references": null,
"scale": null,
"size": null,
"source": "jti",
"type": "text"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "EA1475C339B5BE2728560EFB2AF911275B2F65C2CE66CD1C093FAB5D9183BB11",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.Mv.Repo",
"schema": null,
"table": "tokens"
}

View file

@ -1,127 +0,0 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"precision": null,
"primary_key?": true,
"references": null,
"scale": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "email",
"type": "citext"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "hashed_password",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "oidc_id",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"index?": false,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "users_member_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "members"
},
"scale": null,
"size": null,
"source": "member_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "03EBA1A8BCE47C4706E2D718E00364465E08C9A3999988D49FC1B89DEC5D717C",
"identities": [
{
"all_tenants?": false,
"base_filter": null,
"index_name": "users_unique_email_index",
"keys": [
{
"type": "atom",
"value": "email"
}
],
"name": "unique_email",
"nils_distinct?": true,
"where": null
},
{
"all_tenants?": false,
"base_filter": null,
"index_name": "users_unique_oidc_id_index",
"keys": [
{
"type": "atom",
"value": "oidc_id"
}
],
"name": "unique_oidc_id",
"nils_distinct?": true,
"where": null
}
],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.Mv.Repo",
"schema": null,
"table": "users"
}

View file

@ -17,14 +17,9 @@
"matchDatasources": ["docker"] "matchDatasources": ["docker"]
}, },
{ {
"description": "Disable elixir updates, as renovate does not work with their <version>-otp-<version> numbering scheme.", "matchFileNames": [".tool-versions", "Dockerfile"],
"matchCurrentValue": "**-otp-**", "matchCurrentValue": "**-otp-**",
"enabled": false "enabled": false
},
{
"description": "Disable erlang updates as they need to be coordinated with elixir updates.",
"matchDepNames": "erlang",
"enabled": false
} }
] ]
} }

View file

@ -1,110 +0,0 @@
defmodule Mv.Membership.MemberTest do
use Mv.DataCase, async: false
alias Mv.Membership
describe "Fields and Validations" do
@valid_attrs %{
first_name: "John",
last_name: "Doe",
birth_date: ~D[1990-01-01],
paid: true,
email: "john@example.com",
phone_number: "+49123456789",
join_date: ~D[2020-01-01],
exit_date: nil,
notes: "Test note",
city: "Berlin",
street: "Main Street",
house_number: "1A",
postal_code: "12345"
}
test "First name is required and must not be empty" do
attrs = Map.put(@valid_attrs, :first_name, "")
assert {:error, %Ash.Error.Invalid{errors: errors}} = Membership.create_member(attrs)
assert error_message(errors, :first_name) =~ "must be present"
end
test "Last name is required and must not be empty" do
attrs = Map.put(@valid_attrs, :last_name, "")
assert {:error, %Ash.Error.Invalid{errors: errors}} = Membership.create_member(attrs)
assert error_message(errors, :last_name) =~ "must be present"
end
test "Email is required" do
attrs = Map.put(@valid_attrs, :email, "")
assert {:error, %Ash.Error.Invalid{errors: errors}} = Membership.create_member(attrs)
assert error_message(errors, :email) =~ "must be present"
end
test "Email must be valid" do
attrs = Map.put(@valid_attrs, :email, "test@")
assert {:error, %Ash.Error.Invalid{errors: errors}} = Membership.create_member(attrs)
assert error_message(errors, :email) =~ "is not a valid email"
end
test "Birth date is optional but must not be in the future" do
attrs = Map.put(@valid_attrs, :birth_date, Date.utc_today() |> Date.add(1))
assert {:error, %Ash.Error.Invalid{errors: errors}} = Membership.create_member(attrs)
assert error_message(errors, :birth_date) =~ "cannot be in the future"
end
test "Paid is optional but must be boolean if specified" do
attrs = Map.put(@valid_attrs, :paid, nil)
attrs2 = Map.put(@valid_attrs, :paid, "yes")
assert {:ok, _member} = Membership.create_member(Map.delete(attrs, :paid))
assert {:error, %Ash.Error.Invalid{errors: errors}} = Membership.create_member(attrs2)
assert error_message(errors, :paid) =~ "is invalid"
end
test "Phone number is optional but must have a valid format if specified" do
attrs = Map.put(@valid_attrs, :phone_number, "abc")
assert {:error, %Ash.Error.Invalid{errors: errors}} = Membership.create_member(attrs)
assert error_message(errors, :phone_number) =~ "is not a valid phone number"
attrs2 = Map.delete(@valid_attrs, :phone_number)
assert {:ok, _member} = Membership.create_member(attrs2)
end
test "Join date is optional but must not be in the future" do
attrs = Map.put(@valid_attrs, :join_date, Date.utc_today() |> Date.add(1))
assert {:error, %Ash.Error.Invalid{errors: errors}} = Membership.create_member(attrs)
assert error_message(errors, :join_date) =~ "cannot be in the future"
attrs2 = Map.delete(@valid_attrs, :join_date)
assert {:ok, _member} = Membership.create_member(attrs2)
end
test "Exit date is optional but must not be before join date if both are specified" do
attrs = Map.put(@valid_attrs, :exit_date, ~D[2010-01-01])
assert {:error, %Ash.Error.Invalid{errors: errors}} = Membership.create_member(attrs)
assert error_message(errors, :exit_date) =~ "cannot be before join date"
attrs2 = Map.delete(@valid_attrs, :exit_date)
assert {:ok, _member} = Membership.create_member(attrs2)
end
test "Notes is optional" do
attrs = Map.delete(@valid_attrs, :notes)
assert {:ok, _member} = Membership.create_member(attrs)
end
test "City, street, house number are optional" do
attrs = @valid_attrs |> Map.drop([:city, :street, :house_number])
assert {:ok, _member} = Membership.create_member(attrs)
end
test "Postal code is optional but must have 5 digits if specified" do
attrs = Map.put(@valid_attrs, :postal_code, "1234")
assert {:error, %Ash.Error.Invalid{errors: errors}} = Membership.create_member(attrs)
assert error_message(errors, :postal_code) =~ "must consist of 5 digits"
attrs2 = Map.delete(@valid_attrs, :postal_code)
assert {:ok, _member} = Membership.create_member(attrs2)
end
end
# Helper function for error evaluation
defp error_message(errors, field) do
errors
|> Enum.filter(fn err -> Map.get(err, :field) == field end)
|> Enum.map(&Map.get(&1, :message, ""))
|> List.first()
end
end

View file

@ -1,48 +0,0 @@
defmodule MvWeb.AuthControllerTest do
use MvWeb.ConnCase, async: true
test "GET /sign-in shows sign in form", %{conn: conn} do
conn = get(conn, ~p"/sign-in")
assert html_response(conn, 200) =~ "Sign in"
end
test "POST /sign-in with valid credentials redirects to home", %{conn: conn} do
# Create a test user first
conn = conn_with_oidc_user(conn)
conn = get(conn, ~p"/sign-in")
assert redirected_to(conn) == ~p"/"
end
test "POST /sign-in with invalid credentials shows error", %{conn: conn} do
conn =
post(conn, ~p"/auth/sign_in", %{
"user" => %{
"email" => "wrong@example.com",
"password" => "wrongpassword"
}
})
assert conn.status == 404
end
test "GET /sign-out redirects to home", %{conn: conn} do
# First sign in a user
conn = conn_with_oidc_user(conn)
# Then sign out
conn = get(conn, ~p"/sign-out")
assert redirected_to(conn) == ~p"/"
end
test "unauthenticated user accessing protected route gets redirected to sign-in", %{conn: conn} do
conn = get(conn, ~p"/members")
assert redirected_to(conn) == ~p"/sign-in"
end
test "authenticated user can access protected route", %{conn: conn} do
conn = conn_with_oidc_user(conn)
conn = get(conn, ~p"/members")
assert conn.status == 200
end
end

View file

@ -2,9 +2,7 @@ defmodule MvWeb.PageControllerTest do
use MvWeb.ConnCase use MvWeb.ConnCase
test "GET /", %{conn: conn} do test "GET /", %{conn: conn} do
conn = conn_with_oidc_user(conn)
conn = get(conn, ~p"/") conn = get(conn, ~p"/")
assert html_response(conn, 200) =~ "Mitgliederverwaltung" assert html_response(conn, 200) =~ "Peace of mind from prototype to production"
end end
end end

View file

@ -1,14 +0,0 @@
defmodule MvWeb.LocaleTest do
use MvWeb.ConnCase, async: true
import Phoenix.ConnTest
test "language switch via form sets the locale to English in the session" do
conn = post(build_conn(), "/set_locale", %{"locale" => "en"})
assert get_session(conn, :locale) == "en"
end
test "language switch via form sets the locale to German in the session" do
conn = post(build_conn(), "/set_locale", %{"locale" => "de"})
assert get_session(conn, :locale) == "de"
end
end

View file

@ -1,66 +0,0 @@
defmodule MvWeb.MemberLive.IndexTest do
use MvWeb.ConnCase, async: true
import Phoenix.LiveViewTest
test "shows translated title in German", %{conn: conn} do
conn = conn_with_oidc_user(conn)
conn = Plug.Test.init_test_session(conn, locale: "de")
{:ok, _view, html} = live(conn, "/members")
# Expected German title
assert html =~ "Mitglieder"
end
test "shows translated title in English", %{conn: conn} do
conn = conn_with_oidc_user(conn)
Gettext.put_locale(MvWeb.Gettext, "en")
{:ok, _view, html} = live(conn, "/members")
# Expected English title
assert html =~ "Members"
end
test "shows translated button text in German", %{conn: conn} do
conn = conn_with_oidc_user(conn)
conn = Plug.Test.init_test_session(conn, locale: "de")
{:ok, _view, html} = live(conn, "/members/new")
assert html =~ "Speichern"
end
test "shows translated button text in English", %{conn: conn} do
conn = conn_with_oidc_user(conn)
Gettext.put_locale(MvWeb.Gettext, "en")
{:ok, _view, html} = live(conn, "/members/new")
assert html =~ "Save"
end
test "shows translated flash message after creating a member in German", %{conn: conn} do
conn = conn_with_oidc_user(conn)
conn = Plug.Test.init_test_session(conn, locale: "de")
{:ok, view, _html} = live(conn, "/members")
view |> element("a", "Neues Mitglied") |> render_click()
form_data = %{
"member[first_name]" => "Max",
"member[last_name]" => "Mustermann",
"member[email]" => "max@example.com"
}
view |> form("#member-form", form_data) |> render_submit()
assert has_element?(view, "#flash-group", "Mitglied erstellt erfolgreich")
end
test "shows translated flash message after creating a member in English", %{conn: conn} do
conn = conn_with_oidc_user(conn)
conn = Plug.Test.init_test_session(conn, locale: "en")
{:ok, view, _html} = live(conn, "/members")
view |> element("a", "New Member") |> render_click()
form_data = %{
"member[first_name]" => "Max",
"member[last_name]" => "Mustermann",
"member[email]" => "max@example.com"
}
view |> form("#member-form", form_data) |> render_submit()
assert has_element?(view, "#flash-group", "Member create successfully")
end
end

View file

@ -31,38 +31,6 @@ defmodule MvWeb.ConnCase do
end end
end end
@doc """
Creates a test user and returns the user struct.
"""
def create_test_user(attrs \\ %{}) do
email = "user@example.com"
password = "password"
{:ok, hashed_password} = AshAuthentication.BcryptProvider.hash(password)
Ash.Seed.seed!(Mv.Accounts.User, %{
email: email,
hashed_password: hashed_password
})
end
@doc """
Signs in a user via OIDC for testing by creating a session with the user's token.
"""
def sign_in_user_via_oidc(conn, user) do
# Mock OIDC sign-in by creating a token directly
conn
|> Phoenix.ConnTest.init_test_session(%{})
|> AshAuthentication.Plug.Helpers.store_in_session(user)
end
@doc """
Signs in a user via OIDC and returns a connection with the user authenticated.
"""
def conn_with_oidc_user(conn, user_attrs \\ %{}) do
user = create_test_user(user_attrs)
sign_in_user_via_oidc(conn, user)
end
setup tags do setup tags do
Mv.DataCase.setup_sandbox(tags) Mv.DataCase.setup_sandbox(tags)
{:ok, conn: Phoenix.ConnTest.build_conn()} {:ok, conn: Phoenix.ConnTest.build_conn()}