Compare commits

..

2 commits

Author SHA1 Message Date
cf364fc30e
feat: make member emails unique 2025-09-26 20:07:47 +02:00
a5e2f46659
feat: seed member user relations 2025-09-26 20:07:23 +02:00
4 changed files with 236 additions and 7 deletions

View file

@ -242,4 +242,9 @@ defmodule Mv.Membership.Member do
# The relationship is optional (allow_nil? true by default) # The relationship is optional (allow_nil? true by default)
has_one :user, Mv.Accounts.User has_one :user, Mv.Accounts.User
end end
# Define identities for upsert operations
identities do
identity :unique_email, [:email]
end
end end

View file

@ -0,0 +1,17 @@
defmodule Mv.Repo.Migrations.AddUniqueEmailToMembers 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 unique_index(:members, [:email], name: "members_unique_email_index")
end
def down do
drop_if_exists unique_index(:members, [:email], name: "members_unique_email_index")
end
end

View file

@ -48,7 +48,7 @@ Accounts.create_user!(%{email: "admin@mv.local"}, upsert?: true, upsert_identity
|> Ash.Changeset.for_update(:admin_set_password, %{password: "testpassword"}) |> Ash.Changeset.for_update(:admin_set_password, %{password: "testpassword"})
|> Ash.update!() |> Ash.update!()
# Create sample members for testing # Create sample members for testing - use upsert to prevent duplicates
for member_attrs <- [ for member_attrs <- [
%{ %{
first_name: "Hans", first_name: "Hans",
@ -90,7 +90,8 @@ for member_attrs <- [
house_number: "8" house_number: "8"
} }
] do ] do
Membership.create_member!(member_attrs) # Use upsert to prevent duplicates based on email
Membership.create_member!(member_attrs, upsert?: true, upsert_identity: :unique_email)
end end
# Create additional users for user-member linking examples # Create additional users for user-member linking examples
@ -145,18 +146,22 @@ linked_members = [
} }
] ]
# Create the linked members only if the users don't already have members # Create the linked members - use upsert to prevent duplicates
Enum.each(linked_members, fn member_attrs -> Enum.each(linked_members, fn member_attrs ->
user = member_attrs.user user = member_attrs.user
member_attrs_without_user = Map.delete(member_attrs, :user) member_attrs_without_user = Map.delete(member_attrs, :user)
# Check if user already has a member # Check if user already has a member
if user.member_id == nil do if user.member_id == nil do
# User is free, create member and link # User is free, create member and link - use upsert to prevent duplicates
Membership.create_member!(Map.put(member_attrs_without_user, :user, %{id: user.id})) Membership.create_member!(
Map.put(member_attrs_without_user, :user, %{id: user.id}),
upsert?: true,
upsert_identity: :unique_email
)
else else
# User already has a member, just create the member without linking # User already has a member, just create the member without linking - use upsert to prevent duplicates
Membership.create_member!(member_attrs_without_user) Membership.create_member!(member_attrs_without_user, upsert?: true, upsert_identity: :unique_email)
end end
end) end)

View file

@ -0,0 +1,202 @@
{
"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": "5F070A1E5BEE9883AE864FB5A4A5E81F487A1C57D41576C23BAC8D933005D565",
"identities": [
{
"all_tenants?": false,
"base_filter": null,
"index_name": "members_unique_email_index",
"keys": [
{
"type": "atom",
"value": "email"
}
],
"name": "unique_email",
"nils_distinct?": true,
"where": null
}
],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.Mv.Repo",
"schema": null,
"table": "members"
}