From 7b2a5f2baa5a28f2b3e75f43fedd99a83a453be2 Mon Sep 17 00:00:00 2001 From: Ryan Pandya Date: Fri, 21 Oct 2022 13:01:58 -0700 Subject: [PATCH] Add migrations, helpers, dev config, models --- friends/.gitignore | 3 + friends/config/dev-old.exs | 74 ++++++++++ friends/config/dev.exs | 9 +- friends/lib/friends/friend.ex | 118 ++++++++++++++++ friends/lib/friends/relationship.ex | 133 ++++++++++++++++++ friends/lib/helpers/helpers.ex | 78 ++++++++++ friends/lib/helpers/names.ex | 34 +++++ friends/mix.exs | 2 +- .../20220923074939_create_friends.exs | 12 ++ .../20220923083745_create_relationships.exs | 25 ++++ .../20220923110148_create_places.exs | 10 ++ .../20220923115907_create_encounters.exs | 12 ++ ...23808_add_emails_and_phones_to_friends.exs | 10 ++ .../20220925124118_make_names_unique.exs | 7 + .../20220925135217_friends_have_slugs.exs | 9 ++ ...0927010105_rename_encounters_to_events.exs | 7 + ...d_relationship_type_and_defining_event.exs | 13 ++ .../20220929064813_add_latlon_to_places.exs | 9 ++ .../20220929070040_add_zoom_to_places.exs | 9 ++ .../20221004105755_add_self_to_events.exs | 9 ++ 20 files changed, 578 insertions(+), 5 deletions(-) create mode 100644 friends/config/dev-old.exs create mode 100644 friends/lib/friends/friend.ex create mode 100644 friends/lib/friends/relationship.ex create mode 100644 friends/lib/helpers/helpers.ex create mode 100644 friends/lib/helpers/names.ex create mode 100644 friends/priv/repo/migrations/20220923074939_create_friends.exs create mode 100644 friends/priv/repo/migrations/20220923083745_create_relationships.exs create mode 100644 friends/priv/repo/migrations/20220923110148_create_places.exs create mode 100644 friends/priv/repo/migrations/20220923115907_create_encounters.exs create mode 100644 friends/priv/repo/migrations/20220925123808_add_emails_and_phones_to_friends.exs create mode 100644 friends/priv/repo/migrations/20220925124118_make_names_unique.exs create mode 100644 friends/priv/repo/migrations/20220925135217_friends_have_slugs.exs create mode 100644 friends/priv/repo/migrations/20220927010105_rename_encounters_to_events.exs create mode 100644 friends/priv/repo/migrations/20220927010826_add_relationship_type_and_defining_event.exs create mode 100644 friends/priv/repo/migrations/20220929064813_add_latlon_to_places.exs create mode 100644 friends/priv/repo/migrations/20220929070040_add_zoom_to_places.exs create mode 100644 friends/priv/repo/migrations/20221004105755_add_self_to_events.exs diff --git a/friends/.gitignore b/friends/.gitignore index f0d1c00..e4baff9 100644 --- a/friends/.gitignore +++ b/friends/.gitignore @@ -1,6 +1,9 @@ # The directory Mix will write compiled artifacts to. /_build/ +# My old umbrella code, just for reference / copying things over. +/_old/ + # If you run "mix test --cover", coverage assets end up here. /cover/ diff --git a/friends/config/dev-old.exs b/friends/config/dev-old.exs new file mode 100644 index 0000000..845e85e --- /dev/null +++ b/friends/config/dev-old.exs @@ -0,0 +1,74 @@ +import Config + +# For development, we disable any cache and enable +# debugging and code reloading. +# +# The watchers configuration can be used to run external +# watchers to your application. For example, we use it +# with esbuild to bundle .js and .css sources. +config :friends_web, FriendsWeb.Endpoint, + # Binding to loopback ipv4 address prevents access from other machines. + # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. + http: [ip: {127, 0, 0, 1}, port: 4000], + check_origin: false, + code_reloader: true, + debug_errors: true, + secret_key_base: "qT0o4eu1vjzJ+v51AGuduGXxBqskKT7e8C7z/Kk34odJQjzKW5mGtB8p4XF+piVO", + watchers: [ + # Start the esbuild watcher by calling Esbuild.install_and_run(:default, args) + esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]}, + tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]} + ] + +# ## SSL Support +# +# In order to use HTTPS in development, a self-signed +# certificate can be generated by running the following +# Mix task: +# +# mix phx.gen.cert +# +# Note that this task requires Erlang/OTP 20 or later. +# Run `mix help phx.gen.cert` for more information. +# +# The `http:` config above can be replaced with: +# +# https: [ +# port: 4001, +# cipher_suite: :strong, +# keyfile: "priv/cert/selfsigned_key.pem", +# certfile: "priv/cert/selfsigned.pem" +# ], +# +# If desired, both `http:` and `https:` keys can be +# configured to run both http and https servers on +# different ports. + +# Do not include metadata nor timestamps in development logs +config :logger, :console, format: "[$level] $message\n" + +# Initialize plugs at runtime for faster development compilation +config :phoenix, :plug_init_mode, :runtime + +# Set a higher stacktrace during development. Avoid configuring such +# in production as building large stacktraces may be expensive. +config :phoenix, :stacktrace_depth, 20 + +config :friends_web, FriendsWeb.Endpoint, + live_reload: [ + patterns: [ + ~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$", + ~r"priv/gettext/.*(po)$", + ~r"lib/friends_web/(live|views)/.*(ex)$", + ~r"lib/friends_web/templates/.*(eex)$", + ] + ] + +config :friends, dir: "db_dev" + +config :friends, Friends.Repo, + database: "friends_crm", + username: "postgres", + password: "pleasework", + hostname: "10.0.0.22", + port: "2345" diff --git a/friends/config/dev.exs b/friends/config/dev.exs index d367864..9d03071 100644 --- a/friends/config/dev.exs +++ b/friends/config/dev.exs @@ -3,9 +3,10 @@ import Config # Configure your database config :friends, Friends.Repo, username: "postgres", - password: "postgres", - hostname: "localhost", - database: "friends_dev", + password: "pleasework", + hostname: "10.0.0.22", + database: "friends_crm", + port: "2345", stacktrace: true, show_sensitive_data_on_connection_error: true, pool_size: 10 @@ -19,7 +20,7 @@ config :friends, Friends.Repo, config :friends, FriendsWeb.Endpoint, # Binding to loopback ipv4 address prevents access from other machines. # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. - http: [ip: {127, 0, 0, 1}, port: 4000], + http: [ip: {0, 0, 0, 0}, port: 4000], check_origin: false, code_reloader: true, debug_errors: true, diff --git a/friends/lib/friends/friend.ex b/friends/lib/friends/friend.ex new file mode 100644 index 0000000..98197c6 --- /dev/null +++ b/friends/lib/friends/friend.ex @@ -0,0 +1,118 @@ +defmodule Friends.Friend do + use Ecto.Schema + + alias Friends.{Relationship, Repo, Friend} + import Helpers + import Ecto.Query + + @repo Friends.Repo + + schema "friends" do + field(:name, :string) + field(:nickname, :string) + field(:born, :date) + field(:phone, :string) + field(:email, :string) + field(:slug, :string) + field(:memories, {:array, :string}) + + # has_many(:photos, Media.Photo) + + many_to_many( + :relationships, + Friends.Friend, + join_through: Relationship, + join_keys: [friend_id: :id, relation_id: :id] + ) + + many_to_many( + :reverse_relationships, + Friends.Friend, + join_through: Relationship, + join_keys: [relation_id: :id, friend_id: :id] + ) + end + + def changeset(friend, params \\ %{}) do + friend + |> Ecto.Changeset.cast(params, [:name, :born, :nickname, :email, :phone, :slug]) + |> Ecto.Changeset.validate_required([:name, :email, :phone, :born]) + |> Ecto.Changeset.validate_format(:name, ~r/\w+\ \w+/) + |> Ecto.Changeset.validate_format(:email, Regex.compile!("^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$")) + |> Ecto.Changeset.validate_format(:phone, Regex.compile!("^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$")) + |> Ecto.Changeset.unique_constraint(:name) + end + + def all() do + preloads = [:relationships, :reverse_relationships] + Repo.all(from(f in Friends.Friend, preload: ^preloads)) + end + + + def new(params \\ %{}) do + %Friend{id: "new"} + |> struct(params) + end + + def get_by_slug(slug) do + Repo.one( + from(f in Friend, + where: f.slug == ^slug, + preload: [:relationships, :reverse_relationships] + ) + ) + end + def get_by_id(id) do + Repo.one( + from(f in Friend, + where: f.id == ^id, + preload: [:relationships, :reverse_relationships] + ) + ) + end + + def create_or_update(params) do + case params.id do + "new" -> + %Friend{} |> Friend.changeset(%{params | id: nil}) + |> Map.put(:action, :insert) + |> Friends.Repo.insert! + number -> + Friend.get_by_id(params.id |> String.to_integer) + |> Friend.changeset(params) + |> Map.put(:action, :update) + |> Friends.Repo.update! + end + end + + def get_relationships(friend) do + friend + |> relations + |> Enum.map(&(relation(friend, &1))) + end + + def get_events(friend) do + friend + |> get_relationships + |> Enum.map(&(&1.events)) + |> List.flatten + end + + def age(friend) do + # Age in years + Date.diff(Date.utc_today,friend.born) |> div(365) + end + + def create_birth_event(friend) do + if is_nil Friends.Repo.all( + from(e in Friends.Event, + where: e.name == "born" + )) |> List.flatten do + else + "find" + end + end + + + +end diff --git a/friends/lib/friends/relationship.ex b/friends/lib/friends/relationship.ex new file mode 100644 index 0000000..1960ef3 --- /dev/null +++ b/friends/lib/friends/relationship.ex @@ -0,0 +1,133 @@ +defmodule Friends.Relationship do + use Ecto.Schema + import Ecto.Query + alias Friends.{Relationship,Event,Friend} + + @repo Friends.Repo + + schema "relationships" do + field(:friend_id, :id) + field(:relation_id, :id) + field(:type, :integer) + + has_many(:events, Friends.Event) + end + + @attrs [:friend_id, :relation_id] + + def types(index) do + types() |> elem(index) + end + + def types do + # Tuple: name of the type, associated color, and what that person "is" to the other + { + {:acquaintances, :info, nil}, + {:family, :primary, :relative}, + {:friends, :secondary, :friend}, + {:partners, :info, :partner}, + {:dating, :success, :date}, + {:engaged, :success, :fiancé}, + {:married, :success, :spouse}, + {:divorced, :error, :ex}, + {:complicated, :warning, :something}, + {:cofounders, :accent, :cofounder} + } + end + + def get_type(rel) do + rel.type |> types |> elem(0) + end + + def get_color(rel) do + rel.type |> types |> elem(1) + end + + def get_relation(rel) do + rel.type |> types |> elem(2) + end + + def defining_event(rel) do + @repo.one( + from(e in Event, + where: e.relationship_id == ^rel.id and e.defining + ) + ) + end + + def changeset(struct, params \\ %{}) do + struct + |> Ecto.Changeset.cast(params, @attrs) + |> Ecto.Changeset.unique_constraint( + [:friend_id, :relation_id], + name: :relationships_friend_id_relation_id_index + ) + |> Ecto.Changeset.unique_constraint( + [:relation_id, :friend_id], + name: :relationships_relation_id_friend_id_index + ) + end + + def new(friend1, friend2, type \\ 0) do + id1 = friend1.id + id2 = friend2.id + {:ok, relationship} = @repo.insert( + %Relationship{ + friend_id: id1, + relation_id: id2, + type: type + } + ) + relationship + end + + def get(friend1, friend2) do + id1 = friend1.id + id2 = friend2.id + rel = @repo.one( + from(r in Relationship, + where: r.friend_id == ^id1 and r.relation_id == ^id2, + preload: [:events] + ) + ) + if rel == nil do + @repo.one( + from(r in Relationship, + where: r.friend_id == ^id2 and r.relation_id == ^id1, + preload: [:events] + ) + ) + else + rel + end + end + + def get_or_new(a,b) do + case get(a, b) do + nil -> new(a,b) + relationship -> relationship + end + end + + def get_by_slugs([slug1, slug2]) do + friend1 = slug1 |> Friend.get_by_slug + friend2 = slug2 |> Friend.get_by_slug + get(friend1, friend2) + end + + def members(rel) do + [ + rel.friend_id, + rel.relation_id + ] + |> Enum.map(&Friend.get_by_id/1) + end + + def age(relationship) do + relationship.events + |> Enum.map(fn(event) -> + Date.diff(Date.utc_today, event.date) + end) |> Enum.sort |> List.last |> div(365) + end + +end diff --git a/friends/lib/helpers/helpers.ex b/friends/lib/helpers/helpers.ex new file mode 100644 index 0000000..e637716 --- /dev/null +++ b/friends/lib/helpers/helpers.ex @@ -0,0 +1,78 @@ +defmodule Helpers do + import Helpers.Names + + def do!(thing) do + case thing do + {:ok, answer} -> answer + {_, _error} -> nil + end + end + + + def pluralize(qty, word) do + plural = if qty == 1 do + "#{word}" + else + "#{word}s" + end + "#{qty} #{plural}" + end + + def parse_date(str), do: str |> to_string |> Timex.parse("%Y-%m-%d", :strftime) + + def format_phone(str) do + str + |> String.replace(" ", "") |> String.replace("-", "") |> String.replace(".", "") + end + + def to_slug(name) when is_binary(name) do + name + |> String.replace(" ", "-") + |> String.downcase() + end + def to_slug(obj), do: to_slug(obj.name) + + def from_slug(name) do + name + |> String.replace("-", " ") + |> String.split(" ") + |> Enum.map(&:string.titlecase/1) + |> Enum.join(" ") + end + + def birthday(friend) do + this_year = (Date.utc_today + |> Calendar.strftime("%Y") + |> String.to_integer) + + next_birthday = friend.born + |> Calendar.strftime("%m-%d") + + next_birthdate = "#{this_year}-#{next_birthday}" + |> Date.from_iso8601!() + + if next_birthdate |> Date.diff(Date.utc_today) < 0 do + "#{this_year + 1}-#{next_birthday}" |> Date.from_iso8601!() + else + next_birthdate + end + end + + def time_until_birthday(friend) do + birthday(friend) |> Date.diff(Date.utc_today) + end + + def relations(friend) do + [friend.relationships, friend.reverse_relationships] + |> List.flatten() + end + + def relation(friend, friend2) do + Friends.Relationship.get(friend, friend2) + end + + def events(relationship) do + relationship.events + end + +end diff --git a/friends/lib/helpers/names.ex b/friends/lib/helpers/names.ex new file mode 100644 index 0000000..09862e2 --- /dev/null +++ b/friends/lib/helpers/names.ex @@ -0,0 +1,34 @@ +defmodule Helpers.Names do + def full_name(friend) do + friend.name + |> String.reverse() + |> String.split(" ", parts: 2) + |> Enum.map(&String.reverse/1) + |> Enum.reverse() + + # This is all so that "First Middle Last" becomes ["First Middle", "Last"] + # Is there a more elegant solution? DEFINITELY. + end + + def first_name(friend) do + friend + |> first_and_middle_name() + |> String.split() + |> List.first() + end + + def first_and_middle_name(friend) do + friend + |> full_name + |> List.first() + end + + def middle_name(friend) do + [first | middles] = friend |> first_and_middle_name() |> String.split() + middles |> Enum.join(" ") + end + + def last_name(friend) do + friend |> full_name |> List.last() + end +end diff --git a/friends/mix.exs b/friends/mix.exs index 47ed9aa..9ee2b9d 100644 --- a/friends/mix.exs +++ b/friends/mix.exs @@ -7,7 +7,7 @@ defmodule Friends.MixProject do version: "0.1.0", elixir: "~> 1.12", elixirc_paths: elixirc_paths(Mix.env()), - compilers: [:gettext] ++ Mix.compilers(), + compilers: Mix.compilers(), start_permanent: Mix.env() == :prod, aliases: aliases(), deps: deps() diff --git a/friends/priv/repo/migrations/20220923074939_create_friends.exs b/friends/priv/repo/migrations/20220923074939_create_friends.exs new file mode 100644 index 0000000..85b6790 --- /dev/null +++ b/friends/priv/repo/migrations/20220923074939_create_friends.exs @@ -0,0 +1,12 @@ +defmodule Friends.Repo.Migrations.CreateFriends do + use Ecto.Migration + + def change do + create table(:friends) do + add :name, :string + add :nickname, :string + add :born, :date + add :memories, {:array, :string} + end + end +end diff --git a/friends/priv/repo/migrations/20220923083745_create_relationships.exs b/friends/priv/repo/migrations/20220923083745_create_relationships.exs new file mode 100644 index 0000000..66a1473 --- /dev/null +++ b/friends/priv/repo/migrations/20220923083745_create_relationships.exs @@ -0,0 +1,25 @@ +defmodule Friends.Repo.Migrations.CreateRelationships do + use Ecto.Migration + + def change do + create table(:relationships) do + add :friend_id, references(:friends) + add :relation_id, references(:friends) + end + + create index(:relationships, [:friend_id]) + create index(:relationships, [:relation_id]) + + create unique_index( + :relationships, + [:friend_id, :relation_id], + name: :relationships_friend_id_relation_id_index + ) + + create unique_index( + :relationships, + [:relation_id, :friend_id], + name: :relationships_relation_id_friend_id_index + ) + end +end diff --git a/friends/priv/repo/migrations/20220923110148_create_places.exs b/friends/priv/repo/migrations/20220923110148_create_places.exs new file mode 100644 index 0000000..42175c3 --- /dev/null +++ b/friends/priv/repo/migrations/20220923110148_create_places.exs @@ -0,0 +1,10 @@ +defmodule Friends.Repo.Migrations.CreatePlaces do + use Ecto.Migration + + def change do + create table(:places) do + add :name, :string + add :type, :string + end + end +end diff --git a/friends/priv/repo/migrations/20220923115907_create_encounters.exs b/friends/priv/repo/migrations/20220923115907_create_encounters.exs new file mode 100644 index 0000000..015f817 --- /dev/null +++ b/friends/priv/repo/migrations/20220923115907_create_encounters.exs @@ -0,0 +1,12 @@ +defmodule Friends.Repo.Migrations.CreateEncounters do + use Ecto.Migration + + def change do + create table(:encounters) do + add :relationship_id, references(:relationships) + add :date, :date + add :story, :string + add :place_id, references(:places) + end + end +end diff --git a/friends/priv/repo/migrations/20220925123808_add_emails_and_phones_to_friends.exs b/friends/priv/repo/migrations/20220925123808_add_emails_and_phones_to_friends.exs new file mode 100644 index 0000000..0d66081 --- /dev/null +++ b/friends/priv/repo/migrations/20220925123808_add_emails_and_phones_to_friends.exs @@ -0,0 +1,10 @@ +defmodule Friends.Repo.Migrations.AddEmailsAndPhonesToFriends do + use Ecto.Migration + + def change do + alter table("friends") do + add :email, :string + add :phone, :string + end + end +end diff --git a/friends/priv/repo/migrations/20220925124118_make_names_unique.exs b/friends/priv/repo/migrations/20220925124118_make_names_unique.exs new file mode 100644 index 0000000..030da49 --- /dev/null +++ b/friends/priv/repo/migrations/20220925124118_make_names_unique.exs @@ -0,0 +1,7 @@ +defmodule Friends.Repo.Migrations.MakeNamesUnique do + use Ecto.Migration + + def change do + create unique_index(:friends, [:name]) + end +end diff --git a/friends/priv/repo/migrations/20220925135217_friends_have_slugs.exs b/friends/priv/repo/migrations/20220925135217_friends_have_slugs.exs new file mode 100644 index 0000000..d7e8d06 --- /dev/null +++ b/friends/priv/repo/migrations/20220925135217_friends_have_slugs.exs @@ -0,0 +1,9 @@ +defmodule Friends.Repo.Migrations.FriendsHaveSlugs do + use Ecto.Migration + + def change do + alter table("friends") do + add :slug, :string + end + end +end diff --git a/friends/priv/repo/migrations/20220927010105_rename_encounters_to_events.exs b/friends/priv/repo/migrations/20220927010105_rename_encounters_to_events.exs new file mode 100644 index 0000000..9c8dbd9 --- /dev/null +++ b/friends/priv/repo/migrations/20220927010105_rename_encounters_to_events.exs @@ -0,0 +1,7 @@ +defmodule Friends.Repo.Migrations.RenameEncountersToEvents do + use Ecto.Migration + + def change do + rename table(:encounters), to: table(:events) + end +end diff --git a/friends/priv/repo/migrations/20220927010826_add_relationship_type_and_defining_event.exs b/friends/priv/repo/migrations/20220927010826_add_relationship_type_and_defining_event.exs new file mode 100644 index 0000000..516a0d0 --- /dev/null +++ b/friends/priv/repo/migrations/20220927010826_add_relationship_type_and_defining_event.exs @@ -0,0 +1,13 @@ +defmodule Friends.Repo.Migrations.AddRelationshipTypeAndDefiningEvent do + use Ecto.Migration + + def change do + alter table("events") do + add :name, :string + add :defining, :boolean + end + alter table("relationships") do + add :type, :integer + end + end +end diff --git a/friends/priv/repo/migrations/20220929064813_add_latlon_to_places.exs b/friends/priv/repo/migrations/20220929064813_add_latlon_to_places.exs new file mode 100644 index 0000000..57ae645 --- /dev/null +++ b/friends/priv/repo/migrations/20220929064813_add_latlon_to_places.exs @@ -0,0 +1,9 @@ +defmodule Friends.Repo.Migrations.AddLatlonToPlaces do + use Ecto.Migration + + def change do + alter table("places") do + add :latlon, {:array, :float} + end + end +end diff --git a/friends/priv/repo/migrations/20220929070040_add_zoom_to_places.exs b/friends/priv/repo/migrations/20220929070040_add_zoom_to_places.exs new file mode 100644 index 0000000..012be81 --- /dev/null +++ b/friends/priv/repo/migrations/20220929070040_add_zoom_to_places.exs @@ -0,0 +1,9 @@ +defmodule Friends.Repo.Migrations.AddZoomToPlaces do + use Ecto.Migration + + def change do + alter table("places") do + add :zoom, :integer + end + end +end diff --git a/friends/priv/repo/migrations/20221004105755_add_self_to_events.exs b/friends/priv/repo/migrations/20221004105755_add_self_to_events.exs new file mode 100644 index 0000000..6a77b98 --- /dev/null +++ b/friends/priv/repo/migrations/20221004105755_add_self_to_events.exs @@ -0,0 +1,9 @@ +defmodule Friends.Repo.Migrations.AddSelfToEvents do + use Ecto.Migration + + def change do + alter table("events") do + add :solo, :boolean + end + end +end