Compare commits
2 commits
84abe0a4b5
...
cf364fc30e
| Author | SHA1 | Date | |
|---|---|---|---|
| cf364fc30e | |||
| a5e2f46659 |
4 changed files with 314 additions and 2 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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,5 +90,93 @@ 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
|
||||||
|
additional_users = [
|
||||||
|
%{email: "hans.mueller@example.de"},
|
||||||
|
%{email: "greta.schmidt@example.de"},
|
||||||
|
%{email: "maria.weber@example.de"},
|
||||||
|
%{email: "thomas.klein@example.de"}
|
||||||
|
]
|
||||||
|
|
||||||
|
created_users =
|
||||||
|
Enum.map(additional_users, fn user_attrs ->
|
||||||
|
Accounts.create_user!(user_attrs, upsert?: true, upsert_identity: :unique_email)
|
||||||
|
|> Ash.Changeset.for_update(:admin_set_password, %{password: "testpassword"})
|
||||||
|
|> Ash.update!()
|
||||||
|
end)
|
||||||
|
|
||||||
|
# Create members with linked users to demonstrate the 1:1 relationship
|
||||||
|
# Only create if users don't already have members
|
||||||
|
linked_members = [
|
||||||
|
%{
|
||||||
|
first_name: "Maria",
|
||||||
|
last_name: "Weber",
|
||||||
|
email: "maria.weber@example.de",
|
||||||
|
birth_date: ~D[1992-07-14],
|
||||||
|
join_date: ~D[2023-03-15],
|
||||||
|
paid: true,
|
||||||
|
phone_number: "+49301357924",
|
||||||
|
city: "Frankfurt",
|
||||||
|
street: "Goetheplatz",
|
||||||
|
house_number: "5",
|
||||||
|
postal_code: "60313",
|
||||||
|
notes: "Linked to user account",
|
||||||
|
# Link to the third user (maria.weber@example.de)
|
||||||
|
user: Enum.at(created_users, 2)
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
first_name: "Thomas",
|
||||||
|
last_name: "Klein",
|
||||||
|
email: "thomas.klein@example.de",
|
||||||
|
birth_date: ~D[1988-12-03],
|
||||||
|
join_date: ~D[2023-04-01],
|
||||||
|
paid: false,
|
||||||
|
phone_number: "+49302468135",
|
||||||
|
city: "Köln",
|
||||||
|
street: "Rheinstraße",
|
||||||
|
house_number: "23",
|
||||||
|
postal_code: "50667",
|
||||||
|
notes: "Linked to user account - needs payment follow-up",
|
||||||
|
# Link to the fourth user (thomas.klein@example.de)
|
||||||
|
user: Enum.at(created_users, 3)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
# Create the linked members - use upsert to prevent duplicates
|
||||||
|
Enum.each(linked_members, fn member_attrs ->
|
||||||
|
user = member_attrs.user
|
||||||
|
member_attrs_without_user = Map.delete(member_attrs, :user)
|
||||||
|
|
||||||
|
# Check if user already has a member
|
||||||
|
if user.member_id == nil do
|
||||||
|
# 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}),
|
||||||
|
upsert?: true,
|
||||||
|
upsert_identity: :unique_email
|
||||||
|
)
|
||||||
|
else
|
||||||
|
# User already has a member, just create the member without linking - use upsert to prevent duplicates
|
||||||
|
Membership.create_member!(member_attrs_without_user, upsert?: true, upsert_identity: :unique_email)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
IO.puts("✅ Seeds completed successfully!")
|
||||||
|
IO.puts("📝 Created sample data:")
|
||||||
|
IO.puts(" - Property types: String, Date, Boolean, Email")
|
||||||
|
IO.puts(" - Admin user: admin@mv.local (password: testpassword)")
|
||||||
|
IO.puts(" - Sample members: Hans, Greta, Friedrich")
|
||||||
|
|
||||||
|
IO.puts(
|
||||||
|
" - Additional users: hans.mueller@example.de, greta.schmidt@example.de, maria.weber@example.de, thomas.klein@example.de"
|
||||||
|
)
|
||||||
|
|
||||||
|
IO.puts(
|
||||||
|
" - Linked members: Maria Weber ↔ maria.weber@example.de, Thomas Klein ↔ thomas.klein@example.de"
|
||||||
|
)
|
||||||
|
|
||||||
|
IO.puts("🔗 Visit the application to see user-member relationships in action!")
|
||||||
|
|
|
||||||
202
priv/resource_snapshots/repo/members/20250926180341.json
Normal file
202
priv/resource_snapshots/repo/members/20250926180341.json
Normal 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"
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue