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