Commit bb810201f1b91dba13886e1b3577d1a6989982d2

Thomas de Grivel 2021-11-16T17:03:44

ssh keys

diff --git a/assets/css/app.scss b/assets/css/app.scss
index 52c3fc6..94c8c8c 100644
--- a/assets/css/app.scss
+++ b/assets/css/app.scss
@@ -2,7 +2,26 @@
 @import "../node_modules/bootstrap/scss/bootstrap.scss";
 @import "../node_modules/font-awesome/scss/font-awesome.scss";
 
-@import "flash.scss";
+@import "./flash.scss";
+
+/* users */
+textarea#user_description {
+    min-height: 10em;
+}
+textarea#user_ssh_keys {
+    font-family: monospace;
+    min-height: 20em;
+    overflow-x: scroll;
+    overflow-y: scroll;
+}
+pre.ssh_keys {
+    max-width: 20em;
+    white-space: pre-wrap;       /* Since CSS 2.1 */
+    white-space: -moz-pre-wrap;  /* Mozilla, since 1999 */
+    white-space: -pre-wrap;      /* Opera 4-6 */
+    white-space: -o-pre-wrap;    /* Opera 7 */
+    word-wrap: break-word;       /* Internet Explorer 5.5+ */
+}
 
 /* LiveView specific classes for your customization */
 .phx-no-feedback.invalid-feedback,
diff --git a/config/dev.exs b/config/dev.exs
index e5e7fe6..258a646 100644
--- a/config/dev.exs
+++ b/config/dev.exs
@@ -22,6 +22,11 @@ config :kmxgit, KmxgitWeb.Endpoint,
   debug_errors: true,
   secret_key_base: "DnUNl8ID5LfnhB1FJ4lp2iPfytLPC6/CnoKVNE42d/G4VC1MCQZ6kOxRq6T+Pqb0",
   watchers: [
+    sass: {
+      DartSass,
+      :install_and_run,
+      [:default, ~w(--embed-source-map --source-map-urls=absolute --watch)]
+    },
     # Start the esbuild watcher by calling Esbuild.install_and_run(:default, args)
     esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]}
   ]
diff --git a/lib/kmxgit/organisation_manager.ex b/lib/kmxgit/organisation_manager.ex
index 73e0099..db11619 100644
--- a/lib/kmxgit/organisation_manager.ex
+++ b/lib/kmxgit/organisation_manager.ex
@@ -41,13 +41,4 @@ defmodule Kmxgit.OrganisationManager do
   def delete_organisation(%Organisation{} = organisation) do
     Repo.delete(organisation)
   end
-
-  def organisation_present? do
-    if Repo.one(from organisation in Organisation,
-          limit: 1) do
-      true
-    else
-      false
-    end
-  end
 end
diff --git a/lib/kmxgit/organisation_manager/organisation.ex b/lib/kmxgit/organisation_manager/organisation.ex
index 9dde8ba..7a634e6 100644
--- a/lib/kmxgit/organisation_manager/organisation.ex
+++ b/lib/kmxgit/organisation_manager/organisation.ex
@@ -4,20 +4,19 @@ defmodule Kmxgit.OrganisationManager.Organisation do
   import Ecto.Changeset
 
   schema "organisations" do
-    field :slug, :string
-    field :address, :string
     field :description, :string
-    field :name, :string, null: false
+    field :name, :string
+    field :slug, :string, null: false
     timestamps()
   end
 
   @doc false
   def changeset(organisation, attrs \\ %{}) do
     organisation
-    |> cast(attrs, [:slug, :address, :description, :name])
-    |> validate_required([:slug, :name])
-    |> validate_format(:slug, ~r/^[A-Za-z][-_0-9A-Za-z]{1,64}$/)
-    |> unique_constraint(:slug)
+    |> cast(attrs, [:slug, :description, :name])
+    |> validate_required([:slug])
+    |> validate_format(:slug, ~r/^[A-Za-z][-_+0-9A-Za-z]{1,64}$/)
+    |> unique_constraint(:_lower_slug)
     |> Markdown.validate_markdown(:description)
   end
 
diff --git a/lib/kmxgit/user_manager/user.ex b/lib/kmxgit/user_manager/user.ex
index 231cc71..2f618d1 100644
--- a/lib/kmxgit/user_manager/user.ex
+++ b/lib/kmxgit/user_manager/user.ex
@@ -2,7 +2,7 @@ defmodule Kmxgit.UserManager.User do
   use Ecto.Schema
   import Ecto.Changeset
 
-  alias Kmxgit.UserManager.UserOrganisation
+  alias Kmxgit.OrganisationManager.Organisation
   alias KmxgitWeb.Router.Helpers, as: Routes
   alias BCrypt
 
@@ -13,10 +13,11 @@ defmodule Kmxgit.UserManager.User do
     field :is_admin, :boolean, null: false
     field :login, :string, unique: true
     field :name, :string
-    timestamps()
     field :password, :string, virtual: true
     field :password_confirmation, :string, virtual: true
-    has_many :organisations, UserOrganisation
+    field :ssh_keys, :string
+    many_to_many :organisations, Organisation, join_through: "users_organisations"
+    timestamps()
   end
 
   defp common_changeset(user) do
@@ -24,7 +25,7 @@ defmodule Kmxgit.UserManager.User do
     |> check_password_confirmation()
     |> put_password_hash()
     |> validate_required([:email, :login, :encrypted_password])
-    |> validate_format(:email, ~r/^[-_.0-9A-Za-z]+@([-_0-9A-Za-z]+[.])+[A-Za-z]+$/)
+    |> validate_format(:email, ~r/^[-_+.0-9A-Za-z]+@([-_0-9A-Za-z]+[.])+[A-Za-z]+$/)
     |> validate_format(:login, ~r/^[A-Za-z][-_0-9A-Za-z]{1,64}$/)
     |> unique_constraint(:_lower_email)
     |> unique_constraint(:_lower_login)
@@ -34,34 +35,38 @@ defmodule Kmxgit.UserManager.User do
   @doc false
   def changeset(user, attrs \\ %{}) do
     user
-    |> cast(attrs, [:description, :email, :login, :name, :password, :password_confirmation])
+    |> cast(attrs, [:description, :email, :login, :name, :password, :password_confirmation, :ssh_keys])
     |> common_changeset()
   end
 
   @doc false
   def admin_changeset(user, attrs \\ %{}) do
     user
-    |> cast(attrs, [:description, :email, :is_admin, :login, :name, :password, :password_confirmation])
+    |> cast(attrs, [:description, :email, :is_admin, :login, :name, :password, :password_confirmation, :ssh_keys])
     |> common_changeset()
   end
 
   defp check_password_confirmation(%Ecto.Changeset{changes: %{password: password,
                                                               password_confirmation: password_confirmation}} = changeset) do
     if password != password_confirmation do
-      Ecto.Changeset.add_error(changeset,
-        :password_confirmation,
-        "Passwords do not match.")
+      passwords_do_not_match(changeset)
     else
       changeset
     end
   end
 
   defp check_password_confirmation(%Ecto.Changeset{changes: %{password: _}} = changeset) do
-    changeset
+    passwords_do_not_match(changeset)
   end
 
   defp check_password_confirmation(%Ecto.Changeset{changes: %{password_confirmation: _}} = changeset) do
-    changeset
+    passwords_do_not_match(changeset)
+  end
+
+  defp passwords_do_not_match(changeset) do
+    Ecto.Changeset.add_error(changeset,
+      :password_confirmation,
+      "Passwords do not match.")
   end
 
   defp check_password_confirmation(changeset) do
diff --git a/lib/kmxgit_web/controllers/user_controller.ex b/lib/kmxgit_web/controllers/user_controller.ex
index 2e9844c..1f58b6f 100644
--- a/lib/kmxgit_web/controllers/user_controller.ex
+++ b/lib/kmxgit_web/controllers/user_controller.ex
@@ -50,6 +50,7 @@ defmodule KmxgitWeb.UserController do
           |> redirect(to: Routes.user_path(conn, :show, user.login))
         {:error, changeset} ->
           conn
+          |> assign(:page_title, gettext("Edit user %{login}", login: current_user.login))
           |> render("edit.html", changeset: changeset)
       end
     else
diff --git a/lib/kmxgit_web/templates/organisation/show.html.heex b/lib/kmxgit_web/templates/organisation/show.html.heex
new file mode 100644
index 0000000..75adbea
--- /dev/null
+++ b/lib/kmxgit_web/templates/organisation/show.html.heex
@@ -0,0 +1,9 @@
+<div class="container-fluid">
+  <h1><%= @organisation.name %></h1>
+
+  <%= @organisation.address %>
+
+  <%= if @organisation.description do %>
+    <%= raw Earmark.as_html!(@organisation.description) %>
+  <% end %>
+</div>
diff --git a/lib/kmxgit_web/templates/user/edit.html.heex b/lib/kmxgit_web/templates/user/edit.html.heex
index 1a8ec81..daf1403 100644
--- a/lib/kmxgit_web/templates/user/edit.html.heex
+++ b/lib/kmxgit_web/templates/user/edit.html.heex
@@ -1,41 +1,40 @@
 <div class="container-fluid center">
-  <h1>Edit user</h1>
+  <h1>Edit user <%= @current_user.login %></h1>
   <%= form_for @changeset, Routes.user_path(@conn, :update, @current_user.login), fn f -> %>
 
     <div class="mb-3">
       <%= label f, :name, class: "form-label" %>
       <%= text_input f, :name, class: "form-control" %>
-      <%= error_tag f, :name %>
     </div>
 
     <div class="mb-3">
       <%= label f, :email, class: "form-label" %>
       <%= text_input f, :email, class: "form-control" %>
-      <%= error_tag f, :email %>
     </div>
 
     <div class="mb-3">
       <%= label f, :login, class: "form-label" %>
       <%= text_input f, :login, class: "form-control" %>
-      <%= error_tag f, :login %>
+    </div>
+
+    <div class="mb-3">
+      <%= label f, :description, class: "form-label" %>
+      <%= textarea f, :description, class: "form-control" %>
+    </div>
+
+    <div class="mb-3">
+      <%= label f, :ssh_keys, gettext("SSH keys"), class: "form-label" %>
+      <%= textarea f, :ssh_keys, class: "form-control" %>
     </div>
 
     <div class="mb-3">
       <%= label f, :password, class: "form-label" %>
       <%= password_input f, :password, class: "form-control" %>
-      <%= error_tag f, :password %>
     </div>
 
     <div class="mb-3">
       <%= label f, :password_confirmation, class: "form-label" %>
       <%= password_input f, :password_confirmation, class: "form-control" %>
-      <%= error_tag f, :password_confirmation %>
-    </div>
-
-    <div class="mb-3">
-      <%= label f, :description, class: "form-label" %>
-      <%= textarea f, :description, class: "form-control" %>
-      <%= error_tag f, :description %>
     </div>
 
     <div class="mb-3">
diff --git a/lib/kmxgit_web/templates/user/show.html.heex b/lib/kmxgit_web/templates/user/show.html.heex
index 115376e..5cb4ac8 100644
--- a/lib/kmxgit_web/templates/user/show.html.heex
+++ b/lib/kmxgit_web/templates/user/show.html.heex
@@ -47,7 +47,10 @@
         </tr>
         <tr>
           <th><%= gettext "SSH keys" %></th>
-          <td>
+          <td class="scroll-x">
+            <pre class="ssh_keys">
+              <%= @user.ssh_keys %>
+            </pre>
           </td>
         </tr>
       </table>
diff --git a/lib/kmxgit_web/views/organisation_view.ex b/lib/kmxgit_web/views/organisation_view.ex
new file mode 100644
index 0000000..12fbdd5
--- /dev/null
+++ b/lib/kmxgit_web/views/organisation_view.ex
@@ -0,0 +1,3 @@
+defmodule KmxgitWeb.OrganisationView do
+  use KmxgitWeb, :view
+end
diff --git a/priv/repo/migrations/20211116104115_add_ssh_keys_to_users.exs b/priv/repo/migrations/20211116104115_add_ssh_keys_to_users.exs
new file mode 100644
index 0000000..5ef98a6
--- /dev/null
+++ b/priv/repo/migrations/20211116104115_add_ssh_keys_to_users.exs
@@ -0,0 +1,9 @@
+defmodule Kmxgit.Repo.Migrations.AddSshKeysToUsers do
+  use Ecto.Migration
+
+  def change do
+    alter table(:users) do
+      add :ssh_keys, :text
+    end
+  end
+end
diff --git a/priv/repo/migrations/20211116154852_create_organisations.exs b/priv/repo/migrations/20211116154852_create_organisations.exs
new file mode 100644
index 0000000..5e3cbb2
--- /dev/null
+++ b/priv/repo/migrations/20211116154852_create_organisations.exs
@@ -0,0 +1,13 @@
+defmodule Kmxgit.Repo.Migrations.CreateOrganisations do
+  use Ecto.Migration
+
+  def change do
+    create table(:organisations) do
+      add :description, :string
+      add :name, :string
+      add :slug, :string, null: false
+      timestamps()
+    end
+    create index(:organisations, ["(lower(slug))"], unique: true)
+  end
+end