Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
8edf113
chore: merge ninja and seeding modules
GuilhermePSF Aug 19, 2025
fb61fba
fix: ninja context
GuilhermePSF Aug 20, 2025
e1fe312
feat: add guardians context
GuilhermePSF Aug 20, 2025
3cc247f
feat: add guardian schema
GuilhermePSF Aug 20, 2025
c3b16fc
feat: guardian migration
GuilhermePSF Aug 20, 2025
9c6eb3a
test: add guardian tests and fixtures
GuilhermePSF Aug 20, 2025
9f07efd
chore: add faker
GuilhermePSF Aug 21, 2025
09dac51
chore: format
GuilhermePSF Aug 21, 2025
fbb6be1
feat: highly improve seeding to better represent users
GuilhermePSF Aug 21, 2025
8fccd78
feat: guardian seeding
GuilhermePSF Aug 21, 2025
f6316ad
fix: typo
GuilhermePSF Aug 21, 2025
f44fef1
feat: guardian ninja relation schema
GuilhermePSF Aug 21, 2025
a90916f
feat: guardian ninja relation migration
GuilhermePSF Aug 21, 2025
3eba2ca
feat: guardian ninja relation context
GuilhermePSF Aug 21, 2025
cad5cff
feat: guardian ninja relation test and fixtures
GuilhermePSF Aug 21, 2025
3f9da8d
feat: create index and define references
GuilhermePSF Aug 21, 2025
8a82228
feat: guardian ninja seeding
GuilhermePSF Aug 21, 2025
09dfbdb
chore: format
GuilhermePSF Aug 21, 2025
a815a22
refactor: improve odds
GuilhermePSF Aug 21, 2025
8c025b0
feat: get guardians by ninja id and ninjas by guardian id.
GuilhermePSF Aug 21, 2025
e992cb8
feat: add guardian_id field to users and fix migration order
GuilhermePSF Aug 21, 2025
803c1c3
feat: remake account seeding to better suit the desired db structure
GuilhermePSF Aug 21, 2025
a1ecf67
Merge branch 'main' into gui/guardian-schemas
GuilhermePSF Sep 3, 2025
6da2e3c
fix: remove fields from ninja
GuilhermePSF Sep 29, 2025
f02866e
fix: merge guardian and guardian_ninjas contexts
GuilhermePSF Sep 29, 2025
8ff74de
fix: guardian_ninja tests and fixtures
GuilhermePSF Sep 29, 2025
c538736
refactor: make a guardian_ninja table to e created when creating a ni…
GuilhermePSF Sep 29, 2025
f71d846
feat: add one-to-one association between User and Guardian
GuilhermePSF Sep 29, 2025
5e0978a
fix: make guardian id optional in user
GuilhermePSF Oct 4, 2025
8d6a428
chore: merge main
GuilhermePSF Oct 4, 2025
6d8e056
chore: format
GuilhermePSF Oct 4, 2025
4dd57e0
fix: file directory accordingly to its context
GuilhermePSF Oct 9, 2025
b11ab15
Merge branch 'main' into gui/guardian-schemas
GuilhermePSF Oct 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/katana/accounts/user.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ defmodule Katana.Accounts.User do
field :name, :string
field :email, :string

has_one :guardian, Katana.Guardians.Guardian, foreign_key: :user_id

field :password, :string, virtual: true, redact: true
field :hashed_password, :string, redact: true
field :current_password, :string, virtual: true, redact: true
Expand Down
149 changes: 149 additions & 0 deletions lib/katana/guardians.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
defmodule Katana.Guardians do
@moduledoc """
The Guardians context, including links between Guardians and Ninjas.
"""

import Ecto.Query, warn: false
alias Katana.Repo

alias Katana.Guardians.Guardian
alias Katana.GuardiansNinjas.GuardianNinja
alias Katana.Ninjas.Ninja

# --------------------
# Guardians CRUD
# --------------------

@doc """
Returns the list of guardians.
"""
def list_guardians do
Repo.all(Guardian)
end

@doc """
Gets a single guardian.
"""
def get_guardian!(id), do: Repo.get!(Guardian, id)

@doc """
Creates a guardian.
"""
def create_guardian(attrs \\ %{}) do
%Guardian{}
|> Guardian.changeset(attrs)
|> Repo.insert()
end

@doc """
Updates a guardian.
"""
def update_guardian(%Guardian{} = guardian, attrs) do
guardian
|> Guardian.changeset(attrs)
|> Repo.update()
end

@doc """
Deletes a guardian.
"""
def delete_guardian(%Guardian{} = guardian) do
Repo.delete(guardian)
end

@doc """
Returns an `%Ecto.Changeset{}` for tracking guardian changes.
"""
def change_guardian(%Guardian{} = guardian, attrs \\ %{}) do
Guardian.changeset(guardian, attrs)
end

# --------------------
# GuardianNinja join table CRUD
# --------------------

@doc """
Returns the list of guardian_ninjas.
"""
def list_guardian_ninjas do
Repo.all(GuardianNinja)
end

@doc """
Gets a single guardian_ninja.
"""
def get_guardian_ninja!(id), do: Repo.get!(GuardianNinja, id)

@doc """
Creates a guardian_ninja link.
"""
def create_guardian_ninja(attrs \\ %{}) do
%GuardianNinja{}
|> GuardianNinja.changeset(attrs)
|> Repo.insert()
end

@doc """
Updates a guardian_ninja link.
"""
def update_guardian_ninja(%GuardianNinja{} = guardian_ninja, attrs) do
guardian_ninja
|> GuardianNinja.changeset(attrs)
|> Repo.update()
end

@doc """
Deletes a guardian_ninja link.
"""
def delete_guardian_ninja(%GuardianNinja{} = guardian_ninja) do
Repo.delete(guardian_ninja)
end

@doc """
Returns an `%Ecto.Changeset{}` for tracking guardian_ninja changes.
"""
def change_guardian_ninja(%GuardianNinja{} = guardian_ninja, attrs \\ %{}) do
GuardianNinja.changeset(guardian_ninja, attrs)
end

# --------------------
# Custom Queries
# --------------------

@doc """
Returns all Guardians linked to a given Ninja id.
"""
def get_guardians_for_ninja(ninja_id) do
query =
from gn in GuardianNinja,
where: gn.ninja_id == ^ninja_id,
join: g in Guardian,
on: g.id == gn.guardian_id,
select: g

Repo.all(query)
end

@doc """
Returns all Ninjas linked to a given Guardian id.
"""
def get_ninjas_for_guardian(guardian_id) do
query =
from gn in GuardianNinja,
where: gn.guardian_id == ^guardian_id,
join: n in Ninja,
on: n.id == gn.ninja_id,
select: n

Repo.all(query)
end

@doc """
Creates a link between a Guardian and a Ninja.
"""
def link_guardian_and_ninja(guardian_id, ninja_id) do
%GuardianNinja{}
|> GuardianNinja.changeset(%{guardian_id: guardian_id, ninja_id: ninja_id})
|> Repo.insert()
end
end
20 changes: 20 additions & 0 deletions lib/katana/guardians/guardian.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
defmodule Katana.Guardians.Guardian do
use Ecto.Schema
import Ecto.Changeset

@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "guardians" do
field :phone, :string

belongs_to :user, Katana.Accounts.User, type: :binary_id

timestamps(type: :utc_datetime)
end

def changeset(guardian, attrs) do
guardian
|> cast(attrs, [:phone, :user_id])
|> validate_required([:phone, :user_id])
end
end
20 changes: 20 additions & 0 deletions lib/katana/guardians/guardians_ninjas.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
defmodule Katana.GuardiansNinjas.GuardianNinja do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused. This file seems to be duplicated 🤔

use Ecto.Schema
import Ecto.Changeset

@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "guardian_ninja" do
field :ninja_id, :binary_id
field :guardian_id, :binary_id

timestamps(type: :utc_datetime)
end

@doc false
def changeset(guardian_ninja, attrs) do
guardian_ninja
|> cast(attrs, [:ninja_id, :guardian_id])
|> validate_required([:ninja_id, :guardian_id])
end
end
20 changes: 20 additions & 0 deletions lib/katana/guardians_ninjas/guardian_ninja.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
defmodule Katana.GuardiansNinjas.GuardianNinja do
use Ecto.Schema
import Ecto.Changeset

@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "guardian_ninja" do
field :ninja_id, :binary_id
field :guardian_id, :binary_id

timestamps(type: :utc_datetime)
end

@doc false
def changeset(guardian_ninja, attrs) do
guardian_ninja
|> cast(attrs, [:ninja_id, :guardian_id])
|> validate_required([:ninja_id, :guardian_id])
end
end
42 changes: 37 additions & 5 deletions lib/katana/ninjas.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,34 @@ defmodule Katana.Ninjas do
"""

import Ecto.Query, warn: false
alias Ecto.Multi
alias Katana.Repo

alias Katana.Ninjas.Ninja
alias Katana.GuardiansNinjas.GuardianNinja

@doc """
Creates a ninja.
Creates a ninja **and** links it to a guardian via GuardianNinja.

Expects attrs to contain `:guardian_id`.
"""
def create_ninja(attrs \\ %{}) do
%Ninja{}
|> Ninja.changeset(attrs)
|> Repo.insert()
def create_ninja(%{guardian_id: guardian_id} = attrs) do
Multi.new()
|> Multi.insert(:ninja, Ninja.changeset(%Ninja{}, attrs))
|> Multi.run(:guardian_ninja, fn _repo, %{ninja: ninja} ->
%GuardianNinja{}
|> GuardianNinja.changeset(%{guardian_id: guardian_id, ninja_id: ninja.id})
|> Repo.insert()
end)
|> Repo.transaction()
|> case do
{:ok, %{ninja: ninja}} -> {:ok, ninja}
{:error, _step, changeset, _changes} -> {:error, changeset}
end
end

def create_ninja(_attrs) do
{:error, "guardian_id is required when creating a ninja"}
end

@doc """
Expand Down Expand Up @@ -45,6 +63,20 @@ defmodule Katana.Ninjas do
Repo.delete(ninja)
end

@doc """
Returns all ninjas linked to a given guardian.
"""
def get_ninjas_for_guardian(guardian_id) do
query =
from gn in GuardianNinja,
where: gn.guardian_id == ^guardian_id,
join: n in Ninja,
on: n.id == gn.ninja_id,
select: n

Repo.all(query)
end

@doc """
Returns an `%Ecto.Changeset{}` for tracking ninja changes.
"""
Expand Down
1 change: 1 addition & 0 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ defmodule Katana.MixProject do

# test
{:floki, ">= 0.30.0", only: :test},
{:faker, "~> 0.18", only: [:dev, :test]},

# i18n
{:gettext, "~> 0.26"},
Expand Down
1 change: 1 addition & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"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"},
"elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"},
"expo": {:hex, :expo, "1.1.0", "f7b9ed7fb5745ebe1eeedf3d6f29226c5dd52897ac67c0f8af62a07e661e5c75", [:mix], [], "hexpm", "fbadf93f4700fb44c331362177bdca9eeb8097e8b0ef525c9cc501cb9917c960"},
"faker": {:hex, :faker, "0.18.0", "943e479319a22ea4e8e39e8e076b81c02827d9302f3d32726c5bf82f430e6e14", [:mix], [], "hexpm", "bfbdd83958d78e2788e99ec9317c4816e651ad05e24cfd1196ce5db5b3e81797"},
"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"},
"floki": {:hex, :floki, "0.38.0", "62b642386fa3f2f90713f6e231da0fa3256e41ef1089f83b6ceac7a3fd3abf33", [:mix], [], "hexpm", "a5943ee91e93fb2d635b612caf5508e36d37548e84928463ef9dd986f0d1abd9"},
Expand Down
15 changes: 15 additions & 0 deletions priv/repo/migrations/20250820140556_create_guardians.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
defmodule Katana.Repo.Migrations.CreateGuardians do
use Ecto.Migration

def change do
create table(:guardians, primary_key: false) do
add :id, :binary_id, primary_key: true
add :phone, :string
add :user_id, references(:users, type: :binary_id, on_delete: :delete_all), null: false

timestamps(type: :utc_datetime)
end

create unique_index(:guardians, [:user_id])
end
end
19 changes: 19 additions & 0 deletions priv/repo/migrations/20250821015859_create_guardian_ninja.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
defmodule Katana.Repo.Migrations.CreateGuardianNinja do
use Ecto.Migration

def change do
create table(:guardian_ninja, primary_key: false) do
add :id, :binary_id, primary_key: true
add :ninja_id, references(:ninjas, type: :binary_id, on_delete: :delete_all), null: false

add :guardian_id, references(:guardians, type: :binary_id, on_delete: :delete_all),
null: false

timestamps(type: :utc_datetime)
end

create index(:guardian_ninja, [:ninja_id])
create index(:guardian_ninja, [:guardian_id])
create unique_index(:guardian_ninja, [:ninja_id, :guardian_id])
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ defmodule Katana.Repo.Migrations.CreateUsersAuthTables do
add :name, :string, null: false
add :email, :citext, null: false

add :guardian_id, references(:guardians, type: :binary_id, on_delete: :delete_all),
null: false

add :hashed_password, :string, null: false

add :confirmed_at, :utc_datetime
Expand Down
5 changes: 3 additions & 2 deletions priv/repo/seeds.exs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ defmodule Katana.Repo.Seeds do

def run do
[
"accounts.exs",
"locations.exs",
"ninjas.exs"
"guardians.exs",
"ninjas.exs",
"accounts.exs"
]
|> Enum.each(fn file ->
Code.require_file("#{@seeds_dir}/#{file}")
Expand Down
Loading