From db2c033391e4c0948c0868b8fd287118653e7baa Mon Sep 17 00:00:00 2001 From: E-Liang Tan Date: Tue, 14 Aug 2018 19:10:58 +0800 Subject: [PATCH] Add Coherence --- api/sync/config/config.exs | 26 ++ api/sync/lib/sync/coherence/schemas.ex | 138 +++++++++++ api/sync/lib/sync/coherence/user.ex | 31 +++ api/sync/lib/sync_web/coherence_messages.ex | 124 ++++++++++ api/sync/lib/sync_web/coherence_web.ex | 47 ++++ .../controllers/coherence/redirects.ex | 53 ++++ .../emails/coherence/coherence_mailer.ex | 6 + .../sync_web/emails/coherence/user_email.ex | 102 ++++++++ api/sync/lib/sync_web/router.ex | 21 ++ .../coherence/confirmation/new.html.eex | 17 ++ .../coherence/email/confirmation.html.eex | 11 + .../coherence/email/invitation.html.eex | 11 + .../coherence/email/password.html.eex | 16 ++ .../templates/coherence/email/unlock.html.eex | 11 + .../templates/coherence/layout/email.html.eex | 8 + .../coherence/password/edit.html.eex | 25 ++ .../templates/coherence/password/new.html.eex | 17 ++ .../coherence/registration/edit.html.eex | 5 + .../coherence/registration/form.html.eex | 53 ++++ .../coherence/registration/new.html.eex | 5 + .../coherence/registration/show.html.eex | 25 ++ .../templates/coherence/session/new.html.eex | 35 +++ .../templates/coherence/unlock/new.html.eex | 22 ++ .../views/coherence/coherence_view.ex | 3 + .../views/coherence/coherence_view_helpers.ex | 232 ++++++++++++++++++ .../views/coherence/confirmation_view.ex | 3 + .../sync_web/views/coherence/email_view.ex | 3 + .../sync_web/views/coherence/layout_view.ex | 3 + .../sync_web/views/coherence/password_view.ex | 3 + .../views/coherence/registration_view.ex | 3 + .../sync_web/views/coherence/session_view.ex | 3 + .../sync_web/views/coherence/unlock_view.ex | 3 + api/sync/mix.exs | 5 +- api/sync/mix.lock | 19 ++ .../20180814110855_create_coherence_user.exs | 31 +++ api/sync/priv/repo/seeds.exs | 11 + 36 files changed, 1129 insertions(+), 2 deletions(-) create mode 100644 api/sync/lib/sync/coherence/schemas.ex create mode 100644 api/sync/lib/sync/coherence/user.ex create mode 100644 api/sync/lib/sync_web/coherence_messages.ex create mode 100644 api/sync/lib/sync_web/coherence_web.ex create mode 100644 api/sync/lib/sync_web/controllers/coherence/redirects.ex create mode 100644 api/sync/lib/sync_web/emails/coherence/coherence_mailer.ex create mode 100644 api/sync/lib/sync_web/emails/coherence/user_email.ex create mode 100644 api/sync/lib/sync_web/templates/coherence/confirmation/new.html.eex create mode 100644 api/sync/lib/sync_web/templates/coherence/email/confirmation.html.eex create mode 100644 api/sync/lib/sync_web/templates/coherence/email/invitation.html.eex create mode 100644 api/sync/lib/sync_web/templates/coherence/email/password.html.eex create mode 100644 api/sync/lib/sync_web/templates/coherence/email/unlock.html.eex create mode 100644 api/sync/lib/sync_web/templates/coherence/layout/email.html.eex create mode 100644 api/sync/lib/sync_web/templates/coherence/password/edit.html.eex create mode 100644 api/sync/lib/sync_web/templates/coherence/password/new.html.eex create mode 100644 api/sync/lib/sync_web/templates/coherence/registration/edit.html.eex create mode 100644 api/sync/lib/sync_web/templates/coherence/registration/form.html.eex create mode 100644 api/sync/lib/sync_web/templates/coherence/registration/new.html.eex create mode 100644 api/sync/lib/sync_web/templates/coherence/registration/show.html.eex create mode 100644 api/sync/lib/sync_web/templates/coherence/session/new.html.eex create mode 100644 api/sync/lib/sync_web/templates/coherence/unlock/new.html.eex create mode 100644 api/sync/lib/sync_web/views/coherence/coherence_view.ex create mode 100644 api/sync/lib/sync_web/views/coherence/coherence_view_helpers.ex create mode 100644 api/sync/lib/sync_web/views/coherence/confirmation_view.ex create mode 100644 api/sync/lib/sync_web/views/coherence/email_view.ex create mode 100644 api/sync/lib/sync_web/views/coherence/layout_view.ex create mode 100644 api/sync/lib/sync_web/views/coherence/password_view.ex create mode 100644 api/sync/lib/sync_web/views/coherence/registration_view.ex create mode 100644 api/sync/lib/sync_web/views/coherence/session_view.ex create mode 100644 api/sync/lib/sync_web/views/coherence/unlock_view.ex create mode 100644 api/sync/priv/repo/migrations/20180814110855_create_coherence_user.exs diff --git a/api/sync/config/config.exs b/api/sync/config/config.exs index 8965c43903b..0f641cb1e50 100644 --- a/api/sync/config/config.exs +++ b/api/sync/config/config.exs @@ -24,3 +24,29 @@ config :logger, :console, # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. import_config "#{Mix.env()}.exs" + +# %% Coherence Configuration %% Don't remove this line +config :coherence, + user_schema: Sync.Coherence.User, + repo: Sync.Repo, + module: Sync, + web_module: SyncWeb, + router: SyncWeb.Router, + messages_backend: SyncWeb.Coherence.Messages, + logged_out_url: "/", + email_from_name: "Your Name", + email_from_email: "yourname@example.com", + opts: [ + :authenticatable, + :recoverable, + :trackable, + :unlockable_with_token, + :confirmable, + :registerable + ] + +config :coherence, SyncWeb.Coherence.Mailer, + adapter: Swoosh.Adapters.Sendgrid, + api_key: "your api key here" + +# %% End Coherence Configuration %% diff --git a/api/sync/lib/sync/coherence/schemas.ex b/api/sync/lib/sync/coherence/schemas.ex new file mode 100644 index 00000000000..ced523078e1 --- /dev/null +++ b/api/sync/lib/sync/coherence/schemas.ex @@ -0,0 +1,138 @@ +defmodule Sync.Coherence.Schemas do + use Coherence.Config + + import Ecto.Query + + @user_schema Config.user_schema() + @repo Config.repo() + + def list_user do + @repo.all(@user_schema) + end + + def get_by_user(opts) do + @repo.get_by(@user_schema, opts) + end + + def get_user(id) do + @repo.get(@user_schema, id) + end + + def get_user!(id) do + @repo.get!(@user_schema, id) + end + + def get_user_by_email(email) do + @repo.get_by(@user_schema, email: email) + end + + def change_user(struct, params) do + @user_schema.changeset(struct, params) + end + + def change_user(params) do + @user_schema.changeset(@user_schema.__struct__, params) + end + + def change_user do + @user_schema.changeset(@user_schema.__struct__, %{}) + end + + def update_user(user, params) do + @repo.update(change_user(user, params)) + end + + def create_user(params) do + @repo.insert(change_user(params)) + end + + Enum.each([], fn module -> + name = + module + |> Module.split() + |> List.last() + |> String.downcase() + + def unquote(String.to_atom("list_#{name}"))() do + @repo.all(unquote(module)) + end + + def unquote(String.to_atom("list_#{name}"))(%Ecto.Query{} = query) do + @repo.all(query) + end + + def unquote(String.to_atom("get_#{name}"))(id) do + @repo.get(unquote(module), id) + end + + def unquote(String.to_atom("get_#{name}!"))(id) do + @repo.get!(unquote(module), id) + end + + def unquote(String.to_atom("get_by_#{name}"))(opts) do + @repo.get_by(unquote(module), opts) + end + + def unquote(String.to_atom("change_#{name}"))(struct, params) do + unquote(module).changeset(struct, params) + end + + def unquote(String.to_atom("change_#{name}"))(params) do + unquote(module).new_changeset(params) + end + + def unquote(String.to_atom("change_#{name}"))() do + unquote(module).new_changeset(%{}) + end + + def unquote(String.to_atom("create_#{name}"))(params) do + @repo.insert(unquote(module).new_changeset(params)) + end + + def unquote(String.to_atom("update_#{name}"))(struct, params) do + @repo.update(unquote(module).changeset(struct, params)) + end + + def unquote(String.to_atom("delete_#{name}"))(struct) do + @repo.delete(struct) + end + end) + + def query_by(schema, opts) do + Enum.reduce(opts, schema, fn {k, v}, query -> + where(query, [b], field(b, ^k) == ^v) + end) + end + + def delete_all(%Ecto.Query{} = query) do + @repo.delete_all(query) + end + + def delete_all(module) when is_atom(module) do + @repo.delete_all(module) + end + + def create(%Ecto.Changeset{} = changeset) do + @repo.insert(changeset) + end + + def create!(%Ecto.Changeset{} = changeset) do + @repo.insert!(changeset) + end + + def update(%Ecto.Changeset{} = changeset) do + @repo.update(changeset) + end + + def update!(%Ecto.Changeset{} = changeset) do + @repo.update!(changeset) + end + + def delete(schema) do + @repo.delete(schema) + end + + def delete!(schema) do + @repo.delete!(schema) + end +end diff --git a/api/sync/lib/sync/coherence/user.ex b/api/sync/lib/sync/coherence/user.ex new file mode 100644 index 00000000000..6bf319cf71d --- /dev/null +++ b/api/sync/lib/sync/coherence/user.ex @@ -0,0 +1,31 @@ +defmodule Sync.Coherence.User do + @moduledoc false + use Ecto.Schema + use Coherence.Schema + + schema "users" do + field(:name, :string) + field(:email, :string) + coherence_schema() + + timestamps() + end + + def changeset(model, params \\ %{}) do + model + |> cast(params, [:name, :email] ++ coherence_fields()) + |> validate_required([:name, :email]) + |> validate_format(:email, ~r/@/) + |> unique_constraint(:email) + |> validate_coherence(params) + end + + def changeset(model, params, :password) do + model + |> cast( + params, + ~w(password password_confirmation reset_password_token reset_password_sent_at) + ) + |> validate_coherence_password_reset(params) + end +end diff --git a/api/sync/lib/sync_web/coherence_messages.ex b/api/sync/lib/sync_web/coherence_messages.ex new file mode 100644 index 00000000000..ca3e4b65a33 --- /dev/null +++ b/api/sync/lib/sync_web/coherence_messages.ex @@ -0,0 +1,124 @@ +defmodule SyncWeb.Coherence.Messages do + @moduledoc """ + Application facing messages generated by the Coherence application. + + This module was created by the coh.install mix task. It contains all the + messages used in the coherence application except those in other generated + files like the view and templates. + + To assist in upgrading Coherence, the `Coherence.Messages behaviour will + alway contain every message for the current version. This will help in upgrades + to ensure the user had added new the new messages from the current version. + """ + @behaviour Coherence.Messages + + import SyncWeb.Gettext + + # Change this to override the "coherence" gettext domain. If you would like + # the coherence message to be part of your projects domain change it to "default" + @domain "coherence" + + ################## + # Messages + + def account_already_confirmed, do: Gettext.dgettext(@domain, "Account already confirmed.") + def account_is_not_locked, do: Gettext.dgettext(@domain, "Account is not locked.") + def account_updated_successfully, do: Gettext.dgettext(@domain, "Account updated successfully.") + def already_confirmed, do: Gettext.dgettext(@domain, "already confirmed") + def already_locked, do: Gettext.dgettext(@domain, "already locked") + def already_logged_in, do: Gettext.dgettext(@domain, "Already logged in.") + def cant_be_blank, do: Gettext.dgettext(@domain, "can't be blank") + def cant_find_that_token, do: Gettext.dgettext(@domain, "Can't find that token") + def confirmation_email_sent, do: Gettext.dgettext(@domain, "Confirmation email sent.") + def confirmation_token_expired, do: Gettext.dgettext(@domain, "Confirmation token expired.") + + def could_not_find_that_email_address, + do: Gettext.dgettext(@domain, "Could not find that email address") + + def forgot_your_password, do: Gettext.dgettext(@domain, "Forgot your password?") + def http_authentication_required, do: Gettext.dgettext(@domain, "HTTP Authentication Required") + + def incorrect_login_or_password(opts), + do: Gettext.dgettext(@domain, "Incorrect %{login_field} or password.", opts) + + def invalid_current_password, do: Gettext.dgettext(@domain, "invalid current password") + + def invalid_invitation, + do: Gettext.dgettext(@domain, "Invalid Invitation. Please contact the site administrator.") + + def invalid_request, do: Gettext.dgettext(@domain, "Invalid Request.") + def invalid_confirmation_token, do: Gettext.dgettext(@domain, "Invalid confirmation token.") + def invalid_email_or_password, do: Gettext.dgettext(@domain, "Invalid email or password.") + def invalid_invitation_token, do: Gettext.dgettext(@domain, "Invalid invitation token.") + def invalid_reset_token, do: Gettext.dgettext(@domain, "Invalid reset token.") + def invalid_unlock_token, do: Gettext.dgettext(@domain, "Invalid unlock token.") + def invitation_already_sent, do: Gettext.dgettext(@domain, "Invitation already sent.") + def invitation_sent, do: Gettext.dgettext(@domain, "Invitation sent.") + def invite_someone, do: Gettext.dgettext(@domain, "Invite Someone") + + def maximum_login_attempts_exceeded, + do: + Gettext.dgettext(@domain, "Maximum Login attempts exceeded. Your account has been locked.") + + def need_an_account, do: Gettext.dgettext(@domain, "Need An Account?") + def not_locked, do: Gettext.dgettext(@domain, "not locked") + def password_reset_token_expired, do: Gettext.dgettext(@domain, "Password reset token expired.") + + def password_updated_successfully, + do: Gettext.dgettext(@domain, "Password updated successfully.") + + def problem_confirming_user_account, + do: + Gettext.dgettext( + @domain, + "Problem confirming user account. Please contact the system administrator." + ) + + def registration_created_successfully, + do: Gettext.dgettext(@domain, "Registration created successfully.") + + def required, do: Gettext.dgettext(@domain, "required") + def resend_confirmation_email, do: Gettext.dgettext(@domain, "Resend confirmation email") + + def reset_email_sent, + do: Gettext.dgettext(@domain, "Reset email sent. Check your email for a reset link.") + + def restricted_area, do: Gettext.dgettext(@domain, "Restricted Area") + def send_an_unlock_email, do: Gettext.dgettext(@domain, "Send an unlock email") + def sign_in, do: Gettext.dgettext(@domain, "Sign In") + def sign_out, do: Gettext.dgettext(@domain, "Sign Out") + def signed_in_successfully, do: Gettext.dgettext(@domain, "Signed in successfully.") + + def too_many_failed_login_attempts, + do: Gettext.dgettext(@domain, "Too many failed login attempts. Account has been locked.") + + def unauthorized_ip_address, do: Gettext.dgettext(@domain, "Unauthorized IP Address") + def unlock_instructions_sent, do: Gettext.dgettext(@domain, "Unlock Instructions sent.") + + def user_account_confirmed_successfully, + do: Gettext.dgettext(@domain, "User account confirmed successfully.") + + def user_already_has_an_account, do: Gettext.dgettext(@domain, "User already has an account!") + + def you_must_confirm_your_account, + do: Gettext.dgettext(@domain, "You must confirm your account before you can login.") + + def your_account_has_been_unlocked, + do: Gettext.dgettext(@domain, "Your account has been unlocked") + + def your_account_is_not_locked, do: Gettext.dgettext(@domain, "Your account is not locked.") + + def verify_user_token(opts), + do: Gettext.dgettext(@domain, "Invalid %{user_token} error: %{error}", opts) + + def you_are_using_an_invalid_security_token, + do: + Gettext.dgettext( + @domain, + "You are using an invalid security token for this site! This security\n" <> + "violation has been logged.\n" + ) + + def mailer_required, do: Gettext.dgettext(@domain, "Mailer configuration required!") + def account_is_inactive(), do: Gettext.dgettext(@domain, "Account is inactive!") +end diff --git a/api/sync/lib/sync_web/coherence_web.ex b/api/sync/lib/sync_web/coherence_web.ex new file mode 100644 index 00000000000..db3394f86ce --- /dev/null +++ b/api/sync/lib/sync_web/coherence_web.ex @@ -0,0 +1,47 @@ +defmodule SyncWeb.Coherence do + @moduledoc false + + def view do + quote do + use Phoenix.View, root: "lib/sync_web/templates" + # Import convenience functions from controllers + + import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1] + + # Use all HTML functionality (forms, tags, etc) + use Phoenix.HTML + + import SyncWeb.Router.Helpers + import SyncWeb.ErrorHelpers + import SyncWeb.Gettext + import SyncWeb.Coherence.ViewHelpers + end + end + + def controller do + quote do + use Phoenix.Controller, except: [layout_view: 2] + use Coherence.Config + use Timex + + import Ecto + import Ecto.Query + import Plug.Conn + import SyncWeb.Router.Helpers + import SyncWeb.Gettext + import Coherence.ControllerHelpers + + alias Coherence.Config + alias Coherence.ControllerHelpers, as: Helpers + + require Redirects + end + end + + @doc """ + When used, dispatch to the appropriate controller/view/etc. + """ + defmacro __using__(which) when is_atom(which) do + apply(__MODULE__, which, []) + end +end diff --git a/api/sync/lib/sync_web/controllers/coherence/redirects.ex b/api/sync/lib/sync_web/controllers/coherence/redirects.ex new file mode 100644 index 00000000000..638cdbd0780 --- /dev/null +++ b/api/sync/lib/sync_web/controllers/coherence/redirects.ex @@ -0,0 +1,53 @@ +defmodule Coherence.Redirects do + @moduledoc """ + Define controller action redirection functions. + + This module contains default redirect functions for each of the controller + actions that perform redirects. By using this Module you get the following + functions: + + * session_create/2 + * session_delete/2 + * password_create/2 + * password_update/2, + * unlock_create_not_locked/2 + * unlock_create_invalid/2 + * unlock_create/2 + * unlock_edit_not_locked/2 + * unlock_edit/2 + * unlock_edit_invalid/2 + * registration_create/2 + * invitation_create/2 + * confirmation_create/2 + * confirmation_edit_invalid/2 + * confirmation_edit_expired/2 + * confirmation_edit/2 + * confirmation_edit_error/2 + + You can override any of the functions to customize the redirect path. Each + function is passed the `conn` and `params` arguments from the controller. + + ## Examples + + import SyncWeb.Router.Helpers + + # override the log out action back to the log in page + def session_delete(conn, _), do: redirect(conn, to: session_path(conn, :new)) + + # redirect the user to the login page after registering + def registration_create(conn, _), do: redirect(conn, to: session_path(conn, :new)) + + # disable the user_return_to feature on login + def session_create(conn, _), do: redirect(conn, to: landing_path(conn, :index)) + + """ + use Redirects + # Uncomment the import below if adding overrides + # import SyncWeb.Router.Helpers + + # Add function overrides below + + # Example usage + # Uncomment the following line to return the user to the login form after logging out + # def session_delete(conn, _), do: redirect(conn, to: session_path(conn, :new)) +end diff --git a/api/sync/lib/sync_web/emails/coherence/coherence_mailer.ex b/api/sync/lib/sync_web/emails/coherence/coherence_mailer.ex new file mode 100644 index 00000000000..3e9844b09dd --- /dev/null +++ b/api/sync/lib/sync_web/emails/coherence/coherence_mailer.ex @@ -0,0 +1,6 @@ +defmodule SyncWeb.Coherence.Mailer do + @moduledoc false + if Coherence.Config.mailer?() do + use Swoosh.Mailer, otp_app: :coherence + end +end diff --git a/api/sync/lib/sync_web/emails/coherence/user_email.ex b/api/sync/lib/sync_web/emails/coherence/user_email.ex new file mode 100644 index 00000000000..bbbdf56499b --- /dev/null +++ b/api/sync/lib/sync_web/emails/coherence/user_email.ex @@ -0,0 +1,102 @@ +Code.ensure_loaded(Phoenix.Swoosh) + +defmodule SyncWeb.Coherence.UserEmail do + @moduledoc false + use Phoenix.Swoosh, + view: SyncWeb.Coherence.EmailView, + layout: {SyncWeb.Coherence.LayoutView, :email} + + alias Swoosh.Email + require Logger + alias Coherence.Config + import SyncWeb.Gettext + + defp site_name, do: Config.site_name(inspect(Config.module())) + + def password(user, url) do + %Email{} + |> from(from_email()) + |> to(user_email(user)) + |> add_reply_to() + |> subject( + dgettext("coherence", "%{site_name} - Reset password instructions", site_name: site_name()) + ) + |> render_body("password.html", %{url: url, name: first_name(user.name)}) + end + + def confirmation(user, url) do + %Email{} + |> from(from_email()) + |> to(user_email(user)) + |> add_reply_to() + |> subject( + dgettext("coherence", "%{site_name} - Confirm your new account", site_name: site_name()) + ) + |> render_body("confirmation.html", %{url: url, name: first_name(user.name)}) + end + + def invitation(invitation, url) do + %Email{} + |> from(from_email()) + |> to(user_email(invitation)) + |> add_reply_to() + |> subject( + dgettext("coherence", "%{site_name} - Invitation to create a new account", + site_name: site_name() + ) + ) + |> render_body("invitation.html", %{url: url, name: first_name(invitation.name)}) + end + + def unlock(user, url) do + %Email{} + |> from(from_email()) + |> to(user_email(user)) + |> add_reply_to() + |> subject( + dgettext("coherence", "%{site_name} - Unlock Instructions", site_name: site_name()) + ) + |> render_body("unlock.html", %{url: url, name: first_name(user.name)}) + end + + defp add_reply_to(mail) do + case Coherence.Config.email_reply_to() do + nil -> mail + true -> reply_to(mail, from_email()) + address -> reply_to(mail, address) + end + end + + defp first_name(name) do + case String.split(name, " ") do + [first_name | _] -> first_name + _ -> name + end + end + + defp user_email(user) do + {user.name, user.email} + end + + defp from_email do + case Coherence.Config.email_from() do + nil -> + Logger.error( + ~s|Need to configure :coherence, :email_from_name, "Name", and :email_from_email, "me@example.com"| + ) + + nil + + {name, email} = email_tuple -> + if is_nil(name) or is_nil(email) do + Logger.error( + ~s|Need to configure :coherence, :email_from_name, "Name", and :email_from_email, "me@example.com"| + ) + + nil + else + email_tuple + end + end + end +end diff --git a/api/sync/lib/sync_web/router.ex b/api/sync/lib/sync_web/router.ex index 07ea6cdf74d..3a91f53d737 100644 --- a/api/sync/lib/sync_web/router.ex +++ b/api/sync/lib/sync_web/router.ex @@ -1,5 +1,6 @@ defmodule SyncWeb.Router do use SyncWeb, :router + use Coherence.Router pipeline :browser do plug(:accepts, ["html"]) @@ -7,12 +8,32 @@ defmodule SyncWeb.Router do plug(:fetch_flash) plug(:protect_from_forgery) plug(:put_secure_browser_headers) + plug(Coherence.Authentication.Session) + end + + pipeline :protected do + plug(:accepts, ["html"]) + plug(:fetch_session) + plug(:fetch_flash) + plug(:protect_from_forgery) + plug(:put_secure_browser_headers) + plug(Coherence.Authentication.Session, protected: true) end pipeline :api do plug(:accepts, ["json"]) end + scope "/" do + pipe_through(:browser) + coherence_routes() + end + + scope "/" do + pipe_through(:protected) + coherence_routes(:protected) + end + scope "/", SyncWeb do # Use the default browser stack pipe_through(:browser) diff --git a/api/sync/lib/sync_web/templates/coherence/confirmation/new.html.eex b/api/sync/lib/sync_web/templates/coherence/confirmation/new.html.eex new file mode 100644 index 00000000000..e8a160109b8 --- /dev/null +++ b/api/sync/lib/sync_web/templates/coherence/confirmation/new.html.eex @@ -0,0 +1,17 @@ +
+ +

<%= dgettext "coherence", "Resend Confirmation Instructions" %>

+ +<%= form_for @changeset, confirmation_path(@conn, :create), [as: :confirmation], fn f -> %> + +
+ <%= required_label f, dgettext("coherence", "Email"), class: "control-label" %> + <%= text_input f, :email, class: "form-control", required: "" %> + <%= error_tag f, :email %> +
+ +
+ <%= submit dgettext("coherence", "Resend Email"), class: "btn btn-primary" %> + <%= link dgettext("coherence", "Cancel"), to: Coherence.Config.logged_out_url("/"), class: "btn" %> +
+<% end %> diff --git a/api/sync/lib/sync_web/templates/coherence/email/confirmation.html.eex b/api/sync/lib/sync_web/templates/coherence/email/confirmation.html.eex new file mode 100644 index 00000000000..c22cef75dc0 --- /dev/null +++ b/api/sync/lib/sync_web/templates/coherence/email/confirmation.html.eex @@ -0,0 +1,11 @@ +
+

<%= dgettext "coherence", "Hello %{name}!", name: @name %>

+

+ <%= dgettext "coherence", "Your new account is almost ready. Click the link below to confirm you new account." %> +

+

+ <%= dgettext "coherence", "Confirm my Account" %> +

+

<%= dgettext "coherence", "Thank you!" %>

+
+ diff --git a/api/sync/lib/sync_web/templates/coherence/email/invitation.html.eex b/api/sync/lib/sync_web/templates/coherence/email/invitation.html.eex new file mode 100644 index 00000000000..3cdc89d329b --- /dev/null +++ b/api/sync/lib/sync_web/templates/coherence/email/invitation.html.eex @@ -0,0 +1,11 @@ +
+

<%= dgettext "coherence", "Hello %{name}!", name: @name %>

+

+ <%= dgettext "coherence", "You have been invited to create an Account. Use the link below to create an account." %> +

+

+ <%= dgettext "coherence", "Create my Account" %> +

+

<%= dgettext "coherence", "Thank you!" %>

+
+ diff --git a/api/sync/lib/sync_web/templates/coherence/email/password.html.eex b/api/sync/lib/sync_web/templates/coherence/email/password.html.eex new file mode 100644 index 00000000000..84df757429a --- /dev/null +++ b/api/sync/lib/sync_web/templates/coherence/email/password.html.eex @@ -0,0 +1,16 @@ +
+

<%= dgettext "coherence", "Hello %{name}!", name: @name %>

+

+ <%= dgettext "coherence", "Someone has requested a link to change your password, and you can do this through the link below." %> +

+

+ <%= dgettext "coherence", "Change my password" %> +

+

+ <%= dgettext "coherence", "If you didn't request this, please ignore this email." %> +

+

+ <%= dgettext "coherence", "Your password won't change until you access the link above and create a new one." %> +

+
+ diff --git a/api/sync/lib/sync_web/templates/coherence/email/unlock.html.eex b/api/sync/lib/sync_web/templates/coherence/email/unlock.html.eex new file mode 100644 index 00000000000..df15573245f --- /dev/null +++ b/api/sync/lib/sync_web/templates/coherence/email/unlock.html.eex @@ -0,0 +1,11 @@ +
+

<%= dgettext "coherence", "Hello %{name}!", name: @name %>

+

+ <%= dgettext "coherence", "You requested unlock instructions for your locked account. Please click the link below to unlock your account." %> +

+

+ <%= dgettext "coherence", "Unlock my Account" %> +

+

<%= dgettext "coherence", "Thank you!" %>

+
+ diff --git a/api/sync/lib/sync_web/templates/coherence/layout/email.html.eex b/api/sync/lib/sync_web/templates/coherence/layout/email.html.eex new file mode 100644 index 00000000000..4bb6a205864 --- /dev/null +++ b/api/sync/lib/sync_web/templates/coherence/layout/email.html.eex @@ -0,0 +1,8 @@ + + + <%= @email.subject %> + + + <%= render @view_module, @view_template, assigns %> + + diff --git a/api/sync/lib/sync_web/templates/coherence/password/edit.html.eex b/api/sync/lib/sync_web/templates/coherence/password/edit.html.eex new file mode 100644 index 00000000000..f7e3261daaf --- /dev/null +++ b/api/sync/lib/sync_web/templates/coherence/password/edit.html.eex @@ -0,0 +1,25 @@ +
+ +

<%= dgettext "coherence", "Create a New Password" %>

+ +<%= form_for @changeset, password_path(@conn, :update, @changeset.data), [as: :password], fn f -> %> + + <%= hidden_input f, :reset_password_token %> + +
+ <%= required_label f, dgettext("coherence", "Password"), class: "control-label" %> + <%= password_input f, :password, class: "form-control", required: "" %> + <%= error_tag f, :password %> +
+ +
+ <%= required_label f, dgettext("coherence", "Password Confirmation"), class: "control-label" %> + <%= password_input f, :password_confirmation, class: "form-control", required: "" %> + <%= error_tag f, :password_confirmation %> +
+ +
+ <%= submit dgettext("coherence", "Update Password"), class: "btn btn-primary" %> + <%= link dgettext("coherence", "Cancel"), to: Coherence.Config.logged_out_url("/"), class: "btn" %> +
+<% end %> diff --git a/api/sync/lib/sync_web/templates/coherence/password/new.html.eex b/api/sync/lib/sync_web/templates/coherence/password/new.html.eex new file mode 100644 index 00000000000..3d2cbdb1f69 --- /dev/null +++ b/api/sync/lib/sync_web/templates/coherence/password/new.html.eex @@ -0,0 +1,17 @@ +
+ +

<%= dgettext "coherence", "Send reset password link" %>

+ +<%= form_for @changeset, password_path(@conn, :create), [as: :password], fn f -> %> + +
+ <%= required_label f, :email, class: "control-label" %> + <%= text_input f, :email, class: "form-control", required: "" %> + <%= error_tag f, :email %> +
+ +
+ <%= submit dgettext("coherence", "Reset Password"), class: "btn btn-primary" %> + <%= link dgettext("coherence", "Cancel"), to: Coherence.Config.logged_out_url("/"), class: "btn" %> +
+<% end %> diff --git a/api/sync/lib/sync_web/templates/coherence/registration/edit.html.eex b/api/sync/lib/sync_web/templates/coherence/registration/edit.html.eex new file mode 100644 index 00000000000..2052cf63c51 --- /dev/null +++ b/api/sync/lib/sync_web/templates/coherence/registration/edit.html.eex @@ -0,0 +1,5 @@ +

<%= dgettext "coherence", "Edit Account" %>

+ +<%= render "form.html", changeset: @changeset, + label: dgettext("coherence", "Update"), required: [], + action: registration_path(@conn, :update) %> diff --git a/api/sync/lib/sync_web/templates/coherence/registration/form.html.eex b/api/sync/lib/sync_web/templates/coherence/registration/form.html.eex new file mode 100644 index 00000000000..40fa8820b22 --- /dev/null +++ b/api/sync/lib/sync_web/templates/coherence/registration/form.html.eex @@ -0,0 +1,53 @@ +<%= form_for @changeset, @action, [as: :registration], fn f -> %> + + <%= if @changeset.action do %> +
+

<%= dgettext "coherence", "Oops, something went wrong! Please check the errors below." %>

+
+ <% end %> + +
+ <%= required_label f, dgettext("coherence", "Name"), class: "control-label" %> + <%= text_input f, :name, class: "form-control", required: "" %> + <%= error_tag f, :name %> +
+ + <%= unless (login_field = Coherence.Config.login_field) == :email do %> +
+ <%= required_label f, login_field, class: "control-label" %> + <%= text_input f, login_field, class: "form-control", required: "" %> + <%= error_tag f, login_field %> +
+ <% end %> + +
+ <%= required_label f, dgettext("coherence", "Email"), class: "control-label" %> + <%= text_input f, :email, class: "form-control", required: "" %> + <%= error_tag f, :email %> +
+ + <%= if Coherence.Config.require_current_password and not is_nil(@changeset.data.id) do %> +
+ <%= required_label f, :current_password, class: "control-label" %> + <%= password_input f, :current_password, [class: "form-control"] ++ @required %> + <%= error_tag f, :current_password %> +
+ <% end %> + +
+ <%= required_label f, dgettext("coherence", "Password"), class: "control-label" %> + <%= password_input f, :password, [class: "form-control"] ++ @required %> + <%= error_tag f, :password %> +
+ +
+ <%= required_label f, dgettext("coherence", "Password Confirmation"), class: "control-label" %> + <%= password_input f, :password_confirmation, [class: "form-control"] ++ @required %> + <%= error_tag f, :password_confirmation %> +
+ +
+ <%= submit @label, class: "btn btn-primary" %> + <%= link dgettext("coherence", "Cancel"), to: Coherence.Config.logged_out_url("/"), class: "btn" %> +
+<% end %> diff --git a/api/sync/lib/sync_web/templates/coherence/registration/new.html.eex b/api/sync/lib/sync_web/templates/coherence/registration/new.html.eex new file mode 100644 index 00000000000..cc2d1d2c2c4 --- /dev/null +++ b/api/sync/lib/sync_web/templates/coherence/registration/new.html.eex @@ -0,0 +1,5 @@ +

<%= dgettext "coherence", "Register Account" %>

+ +<%= render "form.html", changeset: @changeset, + label: dgettext("coherence", "Register"), required: [required: ""], + action: registration_path(@conn, :create) %> diff --git a/api/sync/lib/sync_web/templates/coherence/registration/show.html.eex b/api/sync/lib/sync_web/templates/coherence/registration/show.html.eex new file mode 100644 index 00000000000..5881f46846e --- /dev/null +++ b/api/sync/lib/sync_web/templates/coherence/registration/show.html.eex @@ -0,0 +1,25 @@ +

<%= dgettext "coherence", "Show account" %>

+ + +<%= link dgettext("coherence", "Edit"), to: registration_path(@conn, :edit) %> | +<%= link dgettext("coherence", "Delete"), + to: registration_path(@conn, :delete), + method: :delete, + data: [confirm: dgettext("coherence", "Are you sure?")] %> diff --git a/api/sync/lib/sync_web/templates/coherence/session/new.html.eex b/api/sync/lib/sync_web/templates/coherence/session/new.html.eex new file mode 100644 index 00000000000..9fe30d9c387 --- /dev/null +++ b/api/sync/lib/sync_web/templates/coherence/session/new.html.eex @@ -0,0 +1,35 @@ +
+ +<%= form_for @conn, session_path(@conn, :create), [as: :session], fn f -> %> + + <% login_field = Coherence.Config.login_field %> +
+ <%= required_label f, login_field, class: "control-label" %> + <%= text_input f, login_field, class: "form-control", required: "" %> + <%= error_tag f, login_field %> +
+ +
+ <%= required_label f, dgettext("coherence", "Password"), class: "control-label" %> + <%= password_input f, :password, class: "form-control", required: "" %> + <%= error_tag f, :password %> +
+ + <%= if @remember do %> +
+ + +
+
+ <% end %> + +
+ <%= submit dgettext("coherence", "Sign In"), class: "btn btn-primary" %> + <%= link dgettext("coherence", "Cancel"), to: Coherence.Config.logged_out_url("/"), class: "btn" %> +
+ +
+ <%= coherence_links(@conn, :new_session) %> +
+ +<% end %> diff --git a/api/sync/lib/sync_web/templates/coherence/unlock/new.html.eex b/api/sync/lib/sync_web/templates/coherence/unlock/new.html.eex new file mode 100644 index 00000000000..7e6888f1c84 --- /dev/null +++ b/api/sync/lib/sync_web/templates/coherence/unlock/new.html.eex @@ -0,0 +1,22 @@ +
+ +<%= form_for @conn, unlock_path(@conn, :create), [as: :unlock], fn f -> %> + +
+ <%= required_label f, dgettext("coherence", "Email"), class: "control-label" %> + <%= text_input f, :email, class: "form-control", required: "" %> + <%= error_tag f, :email %> +
+ +
+ <%= required_label f, dgettext("coherence", "Password"), class: "control-label" %> + <%= password_input f, :password, class: "form-control", required: "" %> + <%= error_tag f, :password %> +
+ +
+ <%= submit dgettext("coherence", "Send Instructions"), class: "btn btn-primary" %> + <%= link dgettext("coherence", "Cancel"), to: Coherence.Config.logged_out_url("/"), class: "btn" %> +
+ +<% end %> diff --git a/api/sync/lib/sync_web/views/coherence/coherence_view.ex b/api/sync/lib/sync_web/views/coherence/coherence_view.ex new file mode 100644 index 00000000000..6d06f3f2bda --- /dev/null +++ b/api/sync/lib/sync_web/views/coherence/coherence_view.ex @@ -0,0 +1,3 @@ +defmodule Coherence.CoherenceView do + use SyncWeb.Coherence, :view +end diff --git a/api/sync/lib/sync_web/views/coherence/coherence_view_helpers.ex b/api/sync/lib/sync_web/views/coherence/coherence_view_helpers.ex new file mode 100644 index 00000000000..8a5cea3d499 --- /dev/null +++ b/api/sync/lib/sync_web/views/coherence/coherence_view_helpers.ex @@ -0,0 +1,232 @@ +defmodule SyncWeb.Coherence.ViewHelpers do + @moduledoc """ + Helper functions for Coherence Views. + """ + use Phoenix.HTML + alias Coherence.Config + import SyncWeb.Gettext + + @type conn :: Plug.Conn.t() + @type schema :: Ecto.Schema.t() + + @seperator {:safe, "  |  "} + @helpers SyncWeb.Router.Helpers + + @recover_link dgettext("coherence", "Forgot your password?") + @unlock_link dgettext("coherence", "Send an unlock email") + @register_link dgettext("coherence", "Need An Account?") + @invite_link dgettext("coherence", "Invite Someone") + @confirm_link dgettext("coherence", "Resend confirmation email") + @signin_link dgettext("coherence", "Sign In") + @signout_link dgettext("coherence", "Sign Out") + + @doc """ + Create coherence template links. + + Generates links if the appropriate option is installed. This function + can be used to: + + * create links for the new session page `:new_session` + * create links for your layout template `:layout` + + + Defaults are provided based on the options configured for Coherence. + However, the defaults can be overridden by passing the following options. + + ## Customize the links + + ### :new_session Options + + * :recover - customize the recover link (#{@recover_link}) + * :unlock - customize the unlock link (#{@unlock_link}) + * :register - customize the register link (#{@register_link}) + * :confirm - customize the confirm link (#{@confirm_link}) + + ### :layout Options + + * :list_tag - customize the list tag (:li) + * :signout_class - customize the class on the signout link ("navbar-form") + * :signin - customize the signin link text (#{@signin_link}) + * :signout - customize the signout link text (#{@signout_link}) + * :register - customize the register link text (#{@register_link}) + + ### Disable links + + If you set an option to false, the link will not be shown. For example, to + disable the register link on the layout, use the following in your layout template: + + coherence_links(conn, :layout, register: false) + + ## Examples + + coherence_links(conn, :new_session) + Generates: #{@recover_link} #{@unlock_link} #{@register_link} #{@confirm_link} + + coherence_links(conn, :new_session, recover: "Password reset", register: false + Generates: Password reset #{@unlock_link} + + coherence_links(conn, :layout) # when logged in + Generates: User's Name #{@signout_link} + + coherence_links(conn, :layout) # when not logged in + Generates: #{@register_link} #{@signin_link} + """ + @spec coherence_links(conn, atom, Keyword.t()) :: tuple + def coherence_links(conn, which, opts \\ []) + + def coherence_links(conn, :new_session, opts) do + recover_link = Keyword.get(opts, :recover, @recover_link) + unlock_link = Keyword.get(opts, :unlock, @unlock_link) + register_link = Keyword.get(opts, :register, @register_link) + confirm_link = Keyword.get(opts, :confirm, @confirm_link) + + user_schema = Coherence.Config.user_schema() + + [ + recover_link(conn, user_schema, recover_link), + unlock_link(conn, user_schema, unlock_link), + register_link(conn, user_schema, register_link), + confirmation_link(conn, user_schema, confirm_link) + ] + |> List.flatten() + |> concat([]) + end + + def coherence_links(conn, :layout, opts) do + list_tag = Keyword.get(opts, :list_tag, :li) + signout_class = Keyword.get(opts, :signout_class, "navbar-form") + signin = Keyword.get(opts, :signin, @signin_link) + signout = Keyword.get(opts, :signout, @signout_link) + register = Keyword.get(opts, :register, @register_link) + + if Coherence.logged_in?(conn) do + current_user = Coherence.current_user(conn) + + [ + content_tag(list_tag, profile_link(current_user, conn)), + content_tag(list_tag, signout_link(conn, signout, signout_class)) + ] + else + signin_link = + content_tag( + list_tag, + link(signin, to: coherence_path(@helpers, :session_path, conn, :new)) + ) + + if Config.has_option(:registerable) && register do + [ + content_tag( + list_tag, + link(register, to: coherence_path(@helpers, :registration_path, conn, :new)) + ), + signin_link + ] + else + signin_link + end + end + end + + @doc """ + Helper to avoid compile warnings when options are disabled. + """ + @spec coherence_path(module, atom, conn, atom) :: String.t() + def coherence_path(module, route_name, conn, action) do + apply(module, route_name, [conn, action]) + end + + def coherence_path(module, route_name, conn, action, opts) do + apply(module, route_name, [conn, action, opts]) + end + + defp concat([], acc), do: Enum.reverse(acc) + defp concat([h | t], []), do: concat(t, [h]) + defp concat([h | t], acc), do: concat(t, [h, @seperator | acc]) + + @spec recover_link(conn, module, false | String.t()) :: [any] | [] + def recover_link(_conn, _user_schema, false), do: [] + + def recover_link(conn, user_schema, text) do + if user_schema.recoverable?, do: [recover_link(conn, text)], else: [] + end + + @spec recover_link(conn, String.t()) :: tuple + def recover_link(conn, text \\ @recover_link), + do: link(text, to: coherence_path(@helpers, :password_path, conn, :new)) + + @spec register_link(conn, module, false | String.t()) :: [any] | [] + def register_link(_conn, _user_schema, false), do: [] + + def register_link(conn, user_schema, text) do + if user_schema.registerable?, do: [register_link(conn, text)], else: [] + end + + @spec register_link(conn, String.t()) :: tuple + def register_link(conn, text \\ @register_link), + do: link(text, to: coherence_path(@helpers, :registration_path, conn, :new)) + + @spec unlock_link(conn, module, false | String.t()) :: [any] | [] + def unlock_link(_conn, _user_schema, false), do: [] + + def unlock_link(conn, _user_schema, text) do + if conn.assigns[:locked], do: [unlock_link(conn, text)], else: [] + end + + @spec unlock_link(conn, String.t()) :: tuple + def unlock_link(conn, text \\ @unlock_link), + do: link(text, to: coherence_path(@helpers, :unlock_path, conn, :new)) + + @spec invitation_link(conn, String.t()) :: tuple + def invitation_link(conn, text \\ @invite_link) do + link(text, to: coherence_path(@helpers, :invitation_path, conn, :new)) + end + + @spec signout_link(conn, String.t(), String.t()) :: tuple + def signout_link(conn, text \\ @signout_link, signout_class \\ "") do + link(text, + to: coherence_path(@helpers, :session_path, conn, :delete), + method: :delete, + class: signout_class + ) + end + + @spec confirmation_link(conn, module, false | String.t()) :: [any] | [] + def confirmation_link(_conn, _user_schema, false), do: [] + + def confirmation_link(conn, user_schema, text) do + if user_schema.confirmable?, do: [confirmation_link(conn, text)], else: [] + end + + @spec confirmation_link(conn, String.t()) :: tuple + def confirmation_link(conn, text \\ @confirm_link) do + link(text, to: coherence_path(@helpers, :confirmation_path, conn, :new)) + end + + @spec required_label(atom, String.t() | atom, Keyword.t()) :: tuple + def required_label(f, name, opts \\ []) do + label f, name, opts do + [ + "#{humanize(name)}\n", + content_tag(:abbr, "*", class: "required", title: "required") + ] + end + end + + @spec current_user(conn) :: schema + def current_user(conn) do + Coherence.current_user(conn) + end + + @spec logged_in?(conn) :: boolean + def logged_in?(conn) do + Coherence.logged_in?(conn) + end + + defp profile_link(current_user, conn) do + if Config.user_schema().registerable? do + link(current_user.name, to: coherence_path(@helpers, :registration_path, conn, :show)) + else + current_user.name + end + end +end diff --git a/api/sync/lib/sync_web/views/coherence/confirmation_view.ex b/api/sync/lib/sync_web/views/coherence/confirmation_view.ex new file mode 100644 index 00000000000..edaf1894b8a --- /dev/null +++ b/api/sync/lib/sync_web/views/coherence/confirmation_view.ex @@ -0,0 +1,3 @@ +defmodule SyncWeb.Coherence.ConfirmationView do + use SyncWeb.Coherence, :view +end diff --git a/api/sync/lib/sync_web/views/coherence/email_view.ex b/api/sync/lib/sync_web/views/coherence/email_view.ex new file mode 100644 index 00000000000..ab86b05b308 --- /dev/null +++ b/api/sync/lib/sync_web/views/coherence/email_view.ex @@ -0,0 +1,3 @@ +defmodule SyncWeb.Coherence.EmailView do + use SyncWeb.Coherence, :view +end diff --git a/api/sync/lib/sync_web/views/coherence/layout_view.ex b/api/sync/lib/sync_web/views/coherence/layout_view.ex new file mode 100644 index 00000000000..7b7735546df --- /dev/null +++ b/api/sync/lib/sync_web/views/coherence/layout_view.ex @@ -0,0 +1,3 @@ +defmodule SyncWeb.Coherence.LayoutView do + use SyncWeb.Coherence, :view +end diff --git a/api/sync/lib/sync_web/views/coherence/password_view.ex b/api/sync/lib/sync_web/views/coherence/password_view.ex new file mode 100644 index 00000000000..9aaf9ee1e76 --- /dev/null +++ b/api/sync/lib/sync_web/views/coherence/password_view.ex @@ -0,0 +1,3 @@ +defmodule SyncWeb.Coherence.PasswordView do + use SyncWeb.Coherence, :view +end diff --git a/api/sync/lib/sync_web/views/coherence/registration_view.ex b/api/sync/lib/sync_web/views/coherence/registration_view.ex new file mode 100644 index 00000000000..732a2d6fa18 --- /dev/null +++ b/api/sync/lib/sync_web/views/coherence/registration_view.ex @@ -0,0 +1,3 @@ +defmodule SyncWeb.Coherence.RegistrationView do + use SyncWeb.Coherence, :view +end diff --git a/api/sync/lib/sync_web/views/coherence/session_view.ex b/api/sync/lib/sync_web/views/coherence/session_view.ex new file mode 100644 index 00000000000..4120336b412 --- /dev/null +++ b/api/sync/lib/sync_web/views/coherence/session_view.ex @@ -0,0 +1,3 @@ +defmodule SyncWeb.Coherence.SessionView do + use SyncWeb.Coherence, :view +end diff --git a/api/sync/lib/sync_web/views/coherence/unlock_view.ex b/api/sync/lib/sync_web/views/coherence/unlock_view.ex new file mode 100644 index 00000000000..b881ccd0f6f --- /dev/null +++ b/api/sync/lib/sync_web/views/coherence/unlock_view.ex @@ -0,0 +1,3 @@ +defmodule SyncWeb.Coherence.UnlockView do + use SyncWeb.Coherence, :view +end diff --git a/api/sync/mix.exs b/api/sync/mix.exs index d03d70d5807..7dcdaccaa56 100644 --- a/api/sync/mix.exs +++ b/api/sync/mix.exs @@ -20,7 +20,7 @@ defmodule Sync.Mixfile do def application do [ mod: {Sync.Application, []}, - extra_applications: [:logger, :runtime_tools] + extra_applications: [:logger, :runtime_tools, :coherence] ] end @@ -40,7 +40,8 @@ defmodule Sync.Mixfile do {:phoenix_html, "~> 2.10"}, {:phoenix_live_reload, "~> 1.0", only: :dev}, {:gettext, "~> 0.11"}, - {:cowboy, "~> 1.0"} + {:cowboy, "~> 1.0"}, + {:coherence, "~> 0.5"} ] end diff --git a/api/sync/mix.lock b/api/sync/mix.lock index 401724968d8..d5284d07ab5 100644 --- a/api/sync/mix.lock +++ b/api/sync/mix.lock @@ -1,21 +1,40 @@ %{ + "certifi": {:hex, :certifi, "2.3.1", "d0f424232390bf47d82da8478022301c561cf6445b5b5fb6a84d49a9e76d2639", [:rebar3], [{:parse_trans, "3.2.0", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, + "coherence": {:hex, :coherence, "0.5.0", "aaa785aa29e47d140030502b66b08fb58ec84e8120acbfaa6e6a61d3322ffa76", [:mix], [{:comeonin, "~> 3.0", [hex: :comeonin, repo: "hexpm", optional: false]}, {:ecto, "~> 2.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:gettext, "~> 0.13", [hex: :gettext, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.3", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.10", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_swoosh, "~> 0.2", [hex: :phoenix_swoosh, repo: "hexpm", optional: false]}, {:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: false]}, {:timex_ecto, "~> 3.1", [hex: :timex_ecto, repo: "hexpm", optional: false]}, {:uuid, "~> 1.0", [hex: :uuid, repo: "hexpm", optional: false]}], "hexpm"}, + "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm"}, + "comeonin": {:hex, :comeonin, "3.2.0", "cb10995a22aed6812667efb3856f548818c85d85394d8132bc116fbd6995c1ef", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"}, "cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], [], "hexpm"}, "db_connection": {:hex, :db_connection, "1.1.3", "89b30ca1ef0a3b469b1c779579590688561d586694a3ce8792985d4d7e575a61", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, "decimal": {:hex, :decimal, "1.5.0", "b0433a36d0e2430e3d50291b1c65f53c37d56f83665b43d79963684865beab68", [:mix], [], "hexpm"}, "ecto": {:hex, :ecto, "2.2.10", "e7366dc82f48f8dd78fcbf3ab50985ceeb11cb3dc93435147c6e13f2cda0992e", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, + "elixir_make": {:hex, :elixir_make, "0.4.2", "332c649d08c18bc1ecc73b1befc68c647136de4f340b548844efc796405743bf", [:mix], [], "hexpm"}, "file_system": {:hex, :file_system, "0.2.6", "fd4dc3af89b9ab1dc8ccbcc214a0e60c41f34be251d9307920748a14bf41f1d3", [:mix], [], "hexpm"}, "gettext": {:hex, :gettext, "0.15.0", "40a2b8ce33a80ced7727e36768499fc9286881c43ebafccae6bab731e2b2b8ce", [:mix], [], "hexpm"}, + "hackney": {:hex, :hackney, "1.13.0", "24edc8cd2b28e1c652593833862435c80661834f6c9344e84b6a2255e7aeef03", [:rebar3], [{:certifi, "2.3.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.2", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, + "idna": {:hex, :idna, "5.1.2", "e21cb58a09f0228a9e0b95eaa1217f1bcfc31a1aaa6e1fdf2f53a33f7dbd9494", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, + "jason": {:hex, :jason, "1.1.1", "d3ccb840dfb06f2f90a6d335b536dd074db748b3e7f5b11ab61d239506585eb2", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, "mime": {:hex, :mime, "1.3.0", "5e8d45a39e95c650900d03f897fbf99ae04f60ab1daa4a34c7a20a5151b7a5fe", [:mix], [], "hexpm"}, + "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"}, + "parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"}, "phoenix": {:hex, :phoenix, "1.3.4", "aaa1b55e5523083a877bcbe9886d9ee180bf2c8754905323493c2ac325903dc5", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_ecto": {:hex, :phoenix_ecto, "3.3.0", "702f6e164512853d29f9d20763493f2b3bcfcb44f118af2bc37bb95d0801b480", [:mix], [{:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_html": {:hex, :phoenix_html, "2.12.0", "1fb3c2e48b4b66d75564d8d63df6d53655469216d6b553e7e14ced2b46f97622", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.1.5", "8d4c9b1ef9ca82deee6deb5a038d6d8d7b34b9bb909d99784a49332e0d15b3dc", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.0 or ~> 1.2 or ~> 1.3", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.0", "d55e25ff1ff8ea2f9964638366dfd6e361c52dedfd50019353598d11d4441d14", [:mix], [], "hexpm"}, + "phoenix_swoosh": {:hex, :phoenix_swoosh, "0.2.0", "a7e0b32077cd6d2323ae15198839b05d9caddfa20663fd85787479e81f89520e", [:mix], [{:phoenix, "~> 1.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.2", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 0.1", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm"}, "plug": {:hex, :plug, "1.6.2", "e06a7bd2bb6de5145da0dd950070110dce88045351224bd98e84edfdaaf5ffee", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], [], "hexpm"}, "postgrex": {:hex, :postgrex, "0.13.5", "3d931aba29363e1443da167a4b12f06dcd171103c424de15e5f3fc2ba3e6d9c5", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"}, "ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"}, + "swoosh": {:hex, :swoosh, "0.16.1", "bbe8e7fd90099ac9ad4c7ae751a003e1ecd5726962b26f13af7f95e741f81b5c", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.12", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm"}, + "timex": {:hex, :timex, "3.3.0", "e0695aa0ddb37d460d93a2db34d332c2c95a40c27edf22fbfea22eb8910a9c8d", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"}, + "timex_ecto": {:hex, :timex_ecto, "3.3.0", "d5bdef09928e7a60f10a0baa47ce653f29b43d6fee87b30b236b216d0e36b98d", [:mix], [{:ecto, "~> 2.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm"}, + "tzdata": {:hex, :tzdata, "0.5.17", "50793e3d85af49736701da1a040c415c97dc1caf6464112fd9bd18f425d3053b", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"}, + "uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm"}, } diff --git a/api/sync/priv/repo/migrations/20180814110855_create_coherence_user.exs b/api/sync/priv/repo/migrations/20180814110855_create_coherence_user.exs new file mode 100644 index 00000000000..c2363e3615f --- /dev/null +++ b/api/sync/priv/repo/migrations/20180814110855_create_coherence_user.exs @@ -0,0 +1,31 @@ +defmodule Sync.Repo.Migrations.CreateCoherenceUser do + use Ecto.Migration + def change do + create table(:users) do + + add :name, :string + add :email, :string + # authenticatable + add :password_hash, :string + # recoverable + add :reset_password_token, :string + add :reset_password_sent_at, :utc_datetime + # trackable + add :sign_in_count, :integer, default: 0 + add :current_sign_in_at, :utc_datetime + add :last_sign_in_at, :utc_datetime + add :current_sign_in_ip, :string + add :last_sign_in_ip, :string + # unlockable_with_token + add :unlock_token, :string + # confirmable + add :confirmation_token, :string + add :confirmed_at, :utc_datetime + add :confirmation_sent_at, :utc_datetime + + timestamps() + end + create unique_index(:users, [:email]) + + end +end diff --git a/api/sync/priv/repo/seeds.exs b/api/sync/priv/repo/seeds.exs index b9eff79488d..7a9bcbbaadb 100644 --- a/api/sync/priv/repo/seeds.exs +++ b/api/sync/priv/repo/seeds.exs @@ -9,3 +9,14 @@ # # We recommend using the bang functions (`insert!`, `update!` # and so on) as they will fail if something goes wrong. + +Sync.Repo.delete_all(Sync.Coherence.User) + +Sync.Coherence.User.changeset(%Sync.Coherence.User{}, %{ + name: "Test User", + email: "testuser@example.com", + password: "secret", + password_confirmation: "secret" +}) +|> Sync.Repo.insert!() +|> Coherence.ControllerHelpers.confirm!()