Commit 766e5b5031a1a1a82e79991dc0401aff57053910

Thomas de Grivel 2021-11-30T08:23:05

create repositories with unique slug

diff --git a/lib/kmxgit/repository_manager.ex b/lib/kmxgit/repository_manager.ex
index e7d5888..856d778 100644
--- a/lib/kmxgit/repository_manager.ex
+++ b/lib/kmxgit/repository_manager.ex
@@ -26,23 +26,25 @@ defmodule Kmxgit.RepositoryManager do
     end)
   end
 
-  def change_repository(repository \\ %Repository{}) do
+  def change_repository() do
+    change_repository(%Repository{})
+  end
+  def change_repository(repository = %Repository{}) do
     Repository.changeset(repository, %{})
   end
-
-  defp put_owner(changeset, owner) do
-    case owner do
-      %User{} ->
-        Ecto.Changeset.put_assoc(changeset, :user, owner)
-      %Organisation{} ->
-        Ecto.Changeset.put_assoc(changeset, :organisation, owner)
-    end
+  def change_repository(owner) do
+    change_repository(%Repository{}, owner)
+  end
+  def change_repository(repository = %Repository{}, owner = %Organisation{}) do
+    Repository.owner_changeset(repository, %{}, owner)
+  end
+  def change_repository(repository = %Repository{}, owner = %User{}) do
+    Repository.owner_changeset(repository, %{}, owner)
   end
 
-  def create_repository(owner, attrs \\ %{}) do
+  def create_repository(attrs \\ %{}, owner) do
     %Repository{}
-    |> Repository.changeset(attrs)
-    |> put_owner(owner)
+    |> Repository.owner_changeset(attrs, owner)
     |> Repo.insert()
   end
 
@@ -65,6 +67,17 @@ defmodule Kmxgit.RepositoryManager do
     get_repository(id) || raise Ecto.NoResultsError
   end
 
+  def get_repository_by_owner_id_and_slug(org_id, nil, slug) do
+    Repo.one from r in Repository,
+      where: r.organisation_id == ^org_id and is_nil(r.user_id) and fragment("lower(?)", r.slug) == ^String.downcase(slug),
+      limit: 1
+  end
+  def get_repository_by_owner_id_and_slug(nil, user_id, slug) do
+    Repo.one from r in Repository,
+      where: is_nil(r.organisation_id) and r.user_id == ^user_id and fragment("lower(?)", r.slug) == ^String.downcase(slug),
+      limit: 1
+  end
+
   def get_repository_by_owner_and_slug(owner, slug) do
     downcase_owner = String.downcase(owner)
     downcase_slug = String.downcase(slug)
@@ -80,7 +93,8 @@ defmodule Kmxgit.RepositoryManager do
       where: (fragment("lower(?)", os.slug) == ^downcase_owner or fragment("lower(?)", us.slug) == ^downcase_owner) and fragment("lower(?)", r.slug) == ^downcase_slug,
       preload: [members: :slug,
                 organisation: [:slug, [users: :slug]],
-                user: :slug]
+                user: :slug],
+      limit: 1
   end
 
   def add_member(%Repository{} = repo, login) do
diff --git a/lib/kmxgit/repository_manager/repository.ex b/lib/kmxgit/repository_manager/repository.ex
index 4920f5d..38f3340 100644
--- a/lib/kmxgit/repository_manager/repository.ex
+++ b/lib/kmxgit/repository_manager/repository.ex
@@ -3,7 +3,10 @@ defmodule Kmxgit.RepositoryManager.Repository do
   use Ecto.Schema
   import Ecto.Changeset
 
+  alias Kmxgit.RepositoryManager
+  alias Kmxgit.OrganisationManager
   alias Kmxgit.OrganisationManager.Organisation
+  alias Kmxgit.UserManager
   alias Kmxgit.UserManager.User
 
   schema "repositories" do
@@ -15,22 +18,66 @@ defmodule Kmxgit.RepositoryManager.Repository do
     timestamps()
   end
 
-  def changeset(repository, attrs \\ %{}) do
+  def changeset(repository, attrs) do
     repository
     |> cast(attrs, [:description, :slug])
+    |> common_changeset()
+  end
+
+  def owner_changeset(repository, attrs, owner = %Organisation{}) do
+    repository
+    |> cast(attrs, [:description, :slug])
+    |> put_assoc(:organisation, owner)
+    |> put_assoc(:user, nil)
+    |> common_changeset()
+  end
+  def owner_changeset(repository, attrs, owner = %User{}) do
+    repository
+    |> cast(attrs, [:description, :slug])
+    |> put_assoc(:organisation, nil)
+    |> put_assoc(:user, owner)
+    |> common_changeset()
+  end
+
+  defp common_changeset(changeset) do
+    changeset
     |> validate_required([:slug])
     |> validate_format(:slug, ~r|^[A-Za-z][-_+.@0-9A-Za-z]{0,64}(/[A-Za-z][-_+.@0-9A-Za-z]{0,64})*$|)
-    |> validate_slug_uniqueness()
+    |> validate_required_owner()
+    |> validate_unique_slug()
     |> Markdown.validate_markdown(:description)
   end
 
-  def validate_slug_uniqueness(changeset = %Ecto.Changeset{changes: %{slug: slug}}) do
-    IO.inspect [changeset, slug]
-    changeset
+  defp validate_required_owner(changeset) do
+    org = get_field(changeset, :organisation)
+    user = get_field(changeset, :user)
+    owner = org || user
+    if owner do
+      changeset
+    else
+      changeset
+      |> add_error(:organisation, "can't be blank")
+      |> add_error(:user, "can't be blank")
+    end
   end
 
-  def validate_slug_uniqueness(changeset) do
-    IO.inspect changeset
+  defp validate_unique_slug(changeset = %Ecto.Changeset{valid?: true}) do
+    org = get_field(changeset, :organisation)
+    user = get_field(changeset, :user)
+    owner = org || user
+    slug = get_field(changeset, :slug)
+    if repo = RepositoryManager.get_repository_by_owner_and_slug(owner.slug.slug, slug) do
+      if repo.id != changeset.data.id do
+        changeset
+        |> add_error(:slug, "is already taken")
+      else
+        changeset
+      end
+    else
+      changeset
+    end
+  end
+  defp validate_unique_slug(changeset) do
     changeset
   end
 
diff --git a/lib/kmxgit_web/controllers/repository_controller.ex b/lib/kmxgit_web/controllers/repository_controller.ex
index e7236a1..9386022 100644
--- a/lib/kmxgit_web/controllers/repository_controller.ex
+++ b/lib/kmxgit_web/controllers/repository_controller.ex
@@ -9,13 +9,13 @@ defmodule KmxgitWeb.RepositoryController do
 
   def new(conn, params) do
     action = Routes.repository_path(conn, :create, params["owner"])
-    changeset = RepositoryManager.change_repository
     current_user = conn.assigns.current_user
     slug = SlugManager.get_slug(params["owner"])
     if !slug do
       not_found(conn)
     else
       if slug.user && slug.user.id == current_user.id do
+        changeset = RepositoryManager.change_repository(slug.user)
         conn
         |> assign(:action, action)
         |> assign(:changeset, changeset)
@@ -23,7 +23,8 @@ defmodule KmxgitWeb.RepositoryController do
         |> render("new.html")
       else
         org = slug.organisation
-        if org && org.users |> Enum.find(& &1.id == current_user.id) do
+        if org && Organisation.owner?(org, current_user) do
+          changeset = RepositoryManager.change_repository(org)
           conn
           |> assign(:action, action)
           |> assign(:changeset, changeset)
@@ -45,11 +46,11 @@ defmodule KmxgitWeb.RepositoryController do
     else
       user = slug.user
       if user && user.id == current_user.id do
-        create_repo(conn, user, params["repository"])
+        create_repo(conn, params["repository"], user)
       else
         org = slug.organisation
         if org && org.users |> Enum.find(& &1.id == current_user.id) do
-          create_repo(conn, org, params["repository"])
+          create_repo(conn, params["repository"], org)
         else
           not_found(conn)
         end
@@ -57,9 +58,9 @@ defmodule KmxgitWeb.RepositoryController do
     end
   end
 
-  defp create_repo(conn, owner, params) do
+  defp create_repo(conn, params, owner) do
     case Repo.transaction(fn ->
-          case RepositoryManager.create_repository(owner, params) do
+          case RepositoryManager.create_repository(params, owner) do
             {:ok, repo} ->
               case GitManager.create(Repository.full_slug(repo)) do
                 {:ok, _} -> repo
diff --git a/priv/repo/migrations/20211118161213_create_repositories.exs b/priv/repo/migrations/20211118161213_create_repositories.exs
index 1243c05..31ca6ca 100644
--- a/priv/repo/migrations/20211118161213_create_repositories.exs
+++ b/priv/repo/migrations/20211118161213_create_repositories.exs
@@ -3,12 +3,11 @@ defmodule Kmxgit.Repo.Migrations.CreateRepositories do
 
   def change do
     create table(:repositories) do
-      add :slug, :string, unique: true
+      add :slug, :string
       add :description, :string
       add :organisation_id, references(:organisations), null: true
       add :user_id, references(:users), null: true
       timestamps()
     end
-    create index(:repositories, ["(lower(slug))"], unique: true)
   end
 end