diff --git a/mix.exs b/mix.exs index fe6be79..6713923 100644 --- a/mix.exs +++ b/mix.exs @@ -21,6 +21,7 @@ defmodule PhoenixChat.Mixfile do applications: [ :comeonin, :cowboy, + :faker, :gettext, :logger, :phoenix, @@ -45,6 +46,7 @@ defmodule PhoenixChat.Mixfile do {:comeonin, "~> 2.3"}, {:corsica, "~> 0.4"}, {:cowboy, "~> 1.0"}, + {:faker, "~> 0.7"}, {:gettext, "~> 0.11"}, {:guardian, "~> 0.10"}, {:phoenix, "~> 1.2.0"}, diff --git a/mix.lock b/mix.lock index 3e56fd5..3ea002b 100644 --- a/mix.lock +++ b/mix.lock @@ -7,6 +7,7 @@ "db_connection": {:hex, :db_connection, "1.0.0-rc.5", "1d9ab6e01387bdf2de7a16c56866971f7c2f75aea7c69cae2a0346e4b537ae0d", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, optional: true]}, {:sbroker, "~> 1.0.0-beta.3", [hex: :sbroker, optional: true]}]}, "decimal": {:hex, :decimal, "1.1.2", "79a769d4657b2d537b51ef3c02d29ab7141d2b486b516c109642d453ee08e00c", [:mix], []}, "ecto": {:hex, :ecto, "2.0.5", "7f4c79ac41ffba1a4c032b69d7045489f0069c256de606523c65d9f8188e502d", [:mix], [{:db_connection, "~> 1.0-rc.4", [hex: :db_connection, optional: true]}, {:decimal, "~> 1.1.2 or ~> 1.2", [hex: :decimal, optional: false]}, {:mariaex, "~> 0.7.7", [hex: :mariaex, optional: true]}, {:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, optional: false]}, {:postgrex, "~> 0.12.0", [hex: :postgrex, optional: true]}, {:sbroker, "~> 1.0-beta", [hex: :sbroker, optional: true]}]}, + "faker": {:hex, :faker, "0.7.0", "2c42deeac7be717173c78c77fb3edc749fb5d5e460e33d01fe592ae99acc2f0d", [:mix], []}, "fs": {:hex, :fs, "0.9.2", "ed17036c26c3f70ac49781ed9220a50c36775c6ca2cf8182d123b6566e49ec59", [:rebar], []}, "gettext": {:hex, :gettext, "0.11.0", "80c1dd42d270482418fa158ec5ba073d2980e3718bacad86f3d4ad71d5667679", [:mix], []}, "guardian": {:hex, :guardian, "0.12.0", "ab1f0a1ab0cd8f4f9c8cca6e28d61136ca682684cf0f82e55a50e8061be7575a", [:mix], [{:jose, "~> 1.6", [hex: :jose, optional: false]}, {:plug, "~> 1.0", [hex: :plug, optional: false]}, {:poison, ">= 1.3.0", [hex: :poison, optional: false]}, {:uuid, ">=1.1.1", [hex: :uuid, optional: false]}]}, diff --git a/priv/repo/migrations/20160915111446_create_anonymous_users.exs b/priv/repo/migrations/20160915111446_create_anonymous_users.exs new file mode 100644 index 0000000..55d51ba --- /dev/null +++ b/priv/repo/migrations/20160915111446_create_anonymous_users.exs @@ -0,0 +1,18 @@ +defmodule PhoenixChat.Repo.Migrations.CreateAnonymousUsers do + use Ecto.Migration + + def change do + # We want to use a `uuid` as primary key so we need to set `primary_key: false`. + create table(:anonymous_users, primary_key: false) do + # We add the `:id` column manually with a type of `uuid` and set + # it as `primary_key`. + add :id, :uuid, primary_key: true + add :name, :string + add :avatar, :string + add :public_key, :string + add :last_viewed_by_admin_at, :datetime + + timestamps + end + end +end diff --git a/priv/repo/migrations/20160915111609_message_belongs_to_anonymous_user.exs b/priv/repo/migrations/20160915111609_message_belongs_to_anonymous_user.exs new file mode 100644 index 0000000..ec8dd03 --- /dev/null +++ b/priv/repo/migrations/20160915111609_message_belongs_to_anonymous_user.exs @@ -0,0 +1,18 @@ +defmodule PhoenixChat.Repo.Migrations.MessageBelongsToAnonymousUser do + use Ecto.Migration + + def up do + alter table(:messages) do + # We need to set `type` as `uuid` so it does not default to `integer`. + add :anonymous_user_id, references(:anonymous_users, on_delete: :nilify_all, type: :uuid) + remove :from + end + end + + def down do + alter table(:messages) do + remove :anonymous_user_id + add :from, :string + end + end +end diff --git a/web/models/anonymous_user.ex b/web/models/anonymous_user.ex new file mode 100644 index 0000000..c19a851 --- /dev/null +++ b/web/models/anonymous_user.ex @@ -0,0 +1,65 @@ +defmodule PhoenixChat.AnonymousUser do + use PhoenixChat.Web, :model + + alias PhoenixChat.Message + + # Since we provide the `id` for our AnonymousUser record, we will need to set + # the primary key to not autogenerate it. + @primary_key {:id, :binary_id, autogenerate: false} + # We need to set `@foreign_key_type` below since it defaults to `:integer`. + # We are using a UUID as `id` so we need to set type as `:binary_id`. + @foreign_key_type :binary_id + + schema "anonymous_users" do + field :name + field :avatar + field :public_key + field :last_viewed_by_admin_at, PhoenixChat.DateTime + has_many :messages, Message + + timestamps + end + + def changeset(model, params \\ :empty) do + model + |> cast(params, ~w(public_key id), ~w()) + |> put_avatar + |> put_name + end + + def last_viewed_changeset(model) do + params = %{last_viewed_by_admin_at: System.system_time(:milliseconds)} + model + |> cast(params, ~w(last_viewed_by_admin_at), []) + end + + @doc """ + This query returns all users and the respective last messages they + have sent. + + Once the query is run, the return value is a tuple of two elements: + `{user, message}` + """ + def by_public_key(public_key, limit \\ 20) do + from u in __MODULE__, + join: m in Message, on: m.anonymous_user_id == u.id, + where: u.public_key == ^public_key, + limit: ^limit, + distinct: u.id, + order_by: [desc: m.inserted_at], + select: {u, m} + end + + # Set a fake name for our anonymous user every time we create one + defp put_name(changeset) do + name = (Faker.Color.fancy_name <> " " <> Faker.Company.buzzword()) |> String.downcase + changeset + |> put_change(:name, name) + end + + # Set a fake avatar for our anonymous user every time we create one + defp put_avatar(changeset) do + changeset + |> put_change(:avatar, Faker.Avatar.image_url(25, 25)) + end +end diff --git a/web/models/message.ex b/web/models/message.ex index 1a033fc..db56ddc 100644 --- a/web/models/message.ex +++ b/web/models/message.ex @@ -1,18 +1,23 @@ defmodule PhoenixChat.Message do use PhoenixChat.Web, :model + alias PhoenixChat.{DateTime, User, AnonymousUser} + schema "messages" do field :body, :string - field :timestamp, PhoenixChat.DateTime + field :timestamp, DateTime field :room, :string - field :from, :string - belongs_to :user, PhoenixChat.User + + belongs_to :user, User + # Note that we set `:type` below. This is so Ecto is aware the type of the + # foreign_key is not an `:integer` but a `:binary_id`. + belongs_to :user, AnonymousUser, type: :binary_id timestamps end @required_fields ~w(body timestamp room) - @optional_fields ~w(user_id from) + @optional_fields ~w(anonymous_user_id user_id) @doc """ Creates a changeset based on the `model` and `params`.