Add migrations, helpers, dev config, models

This commit is contained in:
Ryan Pandya 2022-10-21 13:01:58 -07:00
parent c22df96bd2
commit 7b2a5f2baa
20 changed files with 578 additions and 5 deletions

3
friends/.gitignore vendored
View File

@ -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/

View File

@ -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"

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,7 @@
defmodule Friends.Repo.Migrations.MakeNamesUnique do
use Ecto.Migration
def change do
create unique_index(:friends, [:name])
end
end

View File

@ -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

View File

@ -0,0 +1,7 @@
defmodule Friends.Repo.Migrations.RenameEncountersToEvents do
use Ecto.Migration
def change do
rename table(:encounters), to: table(:events)
end
end

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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