Add migrations, helpers, dev config, models
This commit is contained in:
parent
c22df96bd2
commit
7b2a5f2baa
3
friends/.gitignore
vendored
3
friends/.gitignore
vendored
@ -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/
|
||||
|
||||
|
||||
74
friends/config/dev-old.exs
Normal file
74
friends/config/dev-old.exs
Normal 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"
|
||||
@ -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,
|
||||
|
||||
118
friends/lib/friends/friend.ex
Normal file
118
friends/lib/friends/friend.ex
Normal 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
|
||||
133
friends/lib/friends/relationship.ex
Normal file
133
friends/lib/friends/relationship.ex
Normal 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
|
||||
78
friends/lib/helpers/helpers.ex
Normal file
78
friends/lib/helpers/helpers.ex
Normal 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
|
||||
34
friends/lib/helpers/names.ex
Normal file
34
friends/lib/helpers/names.ex
Normal 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
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -0,0 +1,7 @@
|
||||
defmodule Friends.Repo.Migrations.MakeNamesUnique do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create unique_index(:friends, [:name])
|
||||
end
|
||||
end
|
||||
@ -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
|
||||
@ -0,0 +1,7 @@
|
||||
defmodule Friends.Repo.Migrations.RenameEncountersToEvents do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
rename table(:encounters), to: table(:events)
|
||||
end
|
||||
end
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
Loading…
Reference in New Issue
Block a user