Initial commit
This commit is contained in:
commit
e6cbccaf6f
19 changed files with 433 additions and 0 deletions
4
backend/.formatter.exs
Normal file
4
backend/.formatter.exs
Normal file
|
@ -0,0 +1,4 @@
|
|||
# Used by "mix format"
|
||||
[
|
||||
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||
]
|
24
backend/.gitignore
vendored
Normal file
24
backend/.gitignore
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
# The directory Mix will write compiled artifacts to.
|
||||
/_build/
|
||||
|
||||
# If you run "mix test --cover", coverage assets end up here.
|
||||
/cover/
|
||||
|
||||
# The directory Mix downloads your dependencies sources to.
|
||||
/deps/
|
||||
|
||||
# Where third-party dependencies like ExDoc output generated docs.
|
||||
/doc/
|
||||
|
||||
# Ignore .fetch files in case you like to edit your project deps locally.
|
||||
/.fetch
|
||||
|
||||
# If the VM crashes, it generates a dump, let's ignore it too.
|
||||
erl_crash.dump
|
||||
|
||||
# Also ignore archive artifacts (built via "mix archive.build").
|
||||
*.ez
|
||||
|
||||
# Ignore package tarball (built via "mix hex.build").
|
||||
week_budget-*.tar
|
||||
|
21
backend/README.md
Normal file
21
backend/README.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
# WeekBudget
|
||||
|
||||
**TODO: Add description**
|
||||
|
||||
## Installation
|
||||
|
||||
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
|
||||
by adding `week_budget` to your list of dependencies in `mix.exs`:
|
||||
|
||||
```elixir
|
||||
def deps do
|
||||
[
|
||||
{:week_budget, "~> 0.1.0"}
|
||||
]
|
||||
end
|
||||
```
|
||||
|
||||
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
|
||||
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
|
||||
be found at [https://hexdocs.pm/week_budget](https://hexdocs.pm/week_budget).
|
||||
|
38
backend/config/config.exs
Normal file
38
backend/config/config.exs
Normal file
|
@ -0,0 +1,38 @@
|
|||
# This file is responsible for configuring your application
|
||||
# and its dependencies with the aid of the Mix.Config module.
|
||||
use Mix.Config
|
||||
|
||||
# This configuration is loaded before any dependency and is restricted
|
||||
# to this project. If another project depends on this project, this
|
||||
# file won't be loaded nor affect the parent project. For this reason,
|
||||
# if you want to provide default values for your application for
|
||||
# third-party users, it should be done in your "mix.exs" file.
|
||||
|
||||
# You can configure your application as:
|
||||
#
|
||||
# config :week_budget, key: :value
|
||||
#
|
||||
# and access this configuration in your application as:
|
||||
#
|
||||
# Application.get_env(:week_budget, :key)
|
||||
#
|
||||
# You can also configure a third-party app:
|
||||
#
|
||||
# config :logger, level: :info
|
||||
#
|
||||
|
||||
# It is also possible to import configuration files, relative to this
|
||||
# directory. For example, you can emulate configuration per environment
|
||||
# by uncommenting the line below and defining dev.exs, test.exs and such.
|
||||
# Configuration from the imported file will override the ones defined
|
||||
# here (which is why it is important to import them last).
|
||||
#
|
||||
# import_config "#{Mix.env()}.exs"
|
||||
|
||||
config :week_budget,
|
||||
ecto_repos: [WeekBudget.DB.Repo],
|
||||
database_url: "ecto://weekbudget:week@127.0.0.1/weekbudget"
|
||||
|
||||
config :ex_money,
|
||||
exchange_rates_retrieve_every: :never,
|
||||
default_cldr_backend: WeekBudget.Core.Cldr
|
87
backend/lib/api/server.ex
Normal file
87
backend/lib/api/server.ex
Normal file
|
@ -0,0 +1,87 @@
|
|||
defmodule WeekBudget.API.Server do
|
||||
@moduledoc """
|
||||
Simple server handling/delegating all API calls of the system.
|
||||
"""
|
||||
|
||||
require Logger
|
||||
use Ace.HTTP.Service, port: 2019, cleartext: true
|
||||
use Raxx.SimpleServer
|
||||
|
||||
@impl Raxx.SimpleServer
|
||||
@spec handle_request(Raxx.Request.t(), %{}) :: Raxx.Response.t()
|
||||
def handle_request(req, state)
|
||||
|
||||
def handle_request(%{method: :GET, path: ["budgets", uuid]}, _state) when uuid != "" do
|
||||
with {:ok, _} <- Ecto.UUID.dump(uuid),
|
||||
%WeekBudget.DB.Budget{} = wb <- WeekBudget.DB.Budget.get_by_secret(uuid) do
|
||||
wb
|
||||
|> serialize_budget()
|
||||
|> json_resp()
|
||||
else
|
||||
:error -> json_resp(:bad_request, %{error: "Malformed UUID."})
|
||||
nil -> json_resp(:not_found, %{error: "Budget not found."})
|
||||
end
|
||||
end
|
||||
|
||||
def handle_request(%{method: :POST, path: ["budgets", uuid, "events"], body: body}, _state)
|
||||
when uuid != "" and body != "" do
|
||||
with {:uuid, {:ok, _}} <- {:uuid, Ecto.UUID.dump(uuid)},
|
||||
{:wb, %WeekBudget.DB.Budget{} = wb} <- {:wb, WeekBudget.DB.Budget.get_by_secret(uuid)},
|
||||
{:json, {:ok, %{"amount" => amnt}}} when is_number(amnt) <- {:json, Jason.decode(body)},
|
||||
{:currency, {:ok, curr}} <- {:currency, Money.validate_currency(wb.amount.currency)},
|
||||
{:money, %Money{} = money} <- {:money, WeekBudget.Core.MoneyUtils.create(curr, -amnt)} do
|
||||
{:ok, event} = WeekBudget.DB.Event.create(wb, money)
|
||||
event_json = serialize_event(event)
|
||||
json_resp(:created, event_json)
|
||||
else
|
||||
{:uuid, _} -> json_resp(:bad_request, %{error: "Malformed UUID."})
|
||||
{:wb, _} -> json_resp(:not_found, %{error: "Budget not found."})
|
||||
{:json, _} -> json_resp(:bad_request, %{error: "Cannot parse JSON."})
|
||||
{:currency, _} -> json_resp(:bad_request, %{error: "Invalid currency."})
|
||||
{:money, _} -> json_resp(:internal_server_error, %{error: "Cannot create money."})
|
||||
end
|
||||
end
|
||||
|
||||
def handle_request(%{method: :POST, path: ["budgets"], body: body}, _state) when body != "" do
|
||||
with {:json, {:ok, %{"amount" => amnt, "currency" => curr_str}}}
|
||||
when is_number(amnt) and is_binary(curr_str) <- {:json, Jason.decode(body)},
|
||||
{:currency, {:ok, curr}} <- {:currency, Money.validate_currency(curr_str)},
|
||||
{:money, %Money{} = money} <- {:money, WeekBudget.Core.MoneyUtils.create(curr, amnt)} do
|
||||
{:ok, budget} = WeekBudget.DB.Budget.create(money)
|
||||
budget_json = serialize_budget(%WeekBudget.DB.Budget{budget | events: []})
|
||||
json_resp(:created, budget_json)
|
||||
else
|
||||
{:json, _} -> json_resp(:bad_request, %{error: "Cannot parse JSON."})
|
||||
{:currency, _} -> json_resp(:bad_request, %{error: "Invalid currency."})
|
||||
{:money, _} -> json_resp(:internal_server_error, %{error: "Cannot create money."})
|
||||
end
|
||||
end
|
||||
|
||||
def handle_request(req, _) do
|
||||
Logger.debug("Failed request:")
|
||||
Logger.debug(inspect(req))
|
||||
json_resp(:not_found, %{error: "Unknown API path or invalid data."})
|
||||
end
|
||||
|
||||
defp serialize_budget(%WeekBudget.DB.Budget{secret: secret, amount: amount, events: events}) do
|
||||
# True amount is initial amount plus all event amounts
|
||||
true_amount = Enum.reduce(events, amount, &Money.add!(&2, &1.amount))
|
||||
|
||||
%{
|
||||
uuid: secret,
|
||||
init: Decimal.to_float(amount.amount),
|
||||
amount: Decimal.to_float(true_amount.amount),
|
||||
events: Enum.map(events, &serialize_event/1)
|
||||
}
|
||||
end
|
||||
|
||||
defp serialize_event(%WeekBudget.DB.Event{at: at, amount: amount}) do
|
||||
%{at: DateTime.to_iso8601(at), amount: Decimal.to_float(amount.amount)}
|
||||
end
|
||||
|
||||
defp json_resp(status \\ :ok, content) do
|
||||
response(status)
|
||||
|> set_header("content-type", "application/json")
|
||||
|> set_body(Jason.encode!(content))
|
||||
end
|
||||
end
|
30
backend/lib/core/application.ex
Normal file
30
backend/lib/core/application.ex
Normal file
|
@ -0,0 +1,30 @@
|
|||
defmodule WeekBudget.Core.Application do
|
||||
# See https://hexdocs.pm/elixir/Application.html
|
||||
# for more information on OTP Applications
|
||||
@moduledoc false
|
||||
|
||||
use Application
|
||||
|
||||
def start(_type, _args) do
|
||||
case Code.ensure_loaded(ExSync) do
|
||||
{:module, ExSync = mod} ->
|
||||
mod.start()
|
||||
|
||||
{:error, _} ->
|
||||
:ok
|
||||
end
|
||||
|
||||
port = Application.get_env(:week_budget, :port, 2019)
|
||||
|
||||
# List all child processes to be supervised
|
||||
children = [
|
||||
{WeekBudget.DB.Repo, []},
|
||||
{WeekBudget.API.Server, [[], [port: port]]}
|
||||
]
|
||||
|
||||
# See https://hexdocs.pm/elixir/Supervisor.html
|
||||
# for other strategies and supported options
|
||||
opts = [strategy: :one_for_one, name: WeekBudget.Core.Supervisor]
|
||||
Supervisor.start_link(children, opts)
|
||||
end
|
||||
end
|
3
backend/lib/core/cldr.ex
Normal file
3
backend/lib/core/cldr.ex
Normal file
|
@ -0,0 +1,3 @@
|
|||
defmodule WeekBudget.Core.Cldr do
|
||||
use Cldr, locales: ["en"], default_locale: "en"
|
||||
end
|
22
backend/lib/core/money_utils.ex
Normal file
22
backend/lib/core/money_utils.ex
Normal file
|
@ -0,0 +1,22 @@
|
|||
defmodule WeekBudget.Core.MoneyUtils do
|
||||
@moduledoc """
|
||||
Utilities to do with money operations.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Create money from given integer or float and currency. Float is used to easier accept money from the
|
||||
frontends. It is normalised to a Money instance at first opportunity so it is believed that the
|
||||
impact and potential for errors is minimal.
|
||||
"""
|
||||
@spec create(atom | string, integer | float) ::
|
||||
{:ok, Money.t()} | {:error, {module, String.t()}}
|
||||
def create(currency, amount)
|
||||
|
||||
def create(currency, amount) when is_integer(amount) do
|
||||
Money.new(currency, amount)
|
||||
end
|
||||
|
||||
def create(currency, amount) when is_float(amount) do
|
||||
Money.from_float(currency, amount)
|
||||
end
|
||||
end
|
43
backend/lib/db/budget.ex
Normal file
43
backend/lib/db/budget.ex
Normal file
|
@ -0,0 +1,43 @@
|
|||
defmodule WeekBudget.DB.Budget do
|
||||
@moduledoc """
|
||||
A single budget that has a secret token that can be used to join it and an initial sum.
|
||||
|
||||
The final sum can be calculated by subtracting all the events from the initial sum.
|
||||
"""
|
||||
use Ecto.Schema
|
||||
import Ecto.Query, only: [from: 2]
|
||||
|
||||
@primary_key {:secret, :binary_id, autogenerate: true}
|
||||
|
||||
schema "budgets" do
|
||||
field(:amount, Money.Ecto.Composite.Type)
|
||||
|
||||
timestamps()
|
||||
|
||||
has_many(:events, WeekBudget.DB.Event, foreign_key: :budget_id)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Get budget by its secret, if any exists.
|
||||
"""
|
||||
@spec get_by_secret(String.t()) :: __MODULE__.t() | nil
|
||||
def get_by_secret(secret) do
|
||||
from(w in __MODULE__,
|
||||
where: w.secret == ^secret,
|
||||
select: [:secret, :amount],
|
||||
preload: [:events]
|
||||
)
|
||||
|> WeekBudget.DB.Repo.one()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Create budget with given initial amount.
|
||||
"""
|
||||
@spec create(Money.t()) :: {:ok, __MODULE__.t()} | {:error, Ecto.Changeset.t()}
|
||||
def create(amount) do
|
||||
%__MODULE__{
|
||||
amount: amount
|
||||
}
|
||||
|> WeekBudget.DB.Repo.insert()
|
||||
end
|
||||
end
|
32
backend/lib/db/event.ex
Normal file
32
backend/lib/db/event.ex
Normal file
|
@ -0,0 +1,32 @@
|
|||
defmodule WeekBudget.DB.Event do
|
||||
@moduledoc """
|
||||
A money spending event that occurred on the budget. If amount is positive, money was spent. If amount
|
||||
is negative, money was received.
|
||||
"""
|
||||
use Ecto.Schema
|
||||
|
||||
schema "events" do
|
||||
field(:amount, Money.Ecto.Composite.Type)
|
||||
field(:at, :utc_datetime)
|
||||
|
||||
belongs_to(:budget, WeekBudget.DB.Budget,
|
||||
references: :secret,
|
||||
foreign_key: :budget_id,
|
||||
type: :binary_id
|
||||
)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Create a new event with the given amount. Note that normally the amount should be negative.
|
||||
"""
|
||||
@spec create(WeekBudget.DB.Budget.t(), Money.t()) ::
|
||||
{:ok, __MODULE__.t()} | {:error, Ecto.Changeset.t()}
|
||||
def create(%WeekBudget.DB.Budget{} = budget, %Money{} = amount) do
|
||||
%__MODULE__{
|
||||
amount: amount,
|
||||
budget: budget,
|
||||
at: DateTime.utc_now() |> DateTime.truncate(:second)
|
||||
}
|
||||
|> WeekBudget.DB.Repo.insert()
|
||||
end
|
||||
end
|
9
backend/lib/db/repo.ex
Normal file
9
backend/lib/db/repo.ex
Normal file
|
@ -0,0 +1,9 @@
|
|||
defmodule WeekBudget.DB.Repo do
|
||||
use Ecto.Repo,
|
||||
otp_app: :week_budget,
|
||||
adapter: Ecto.Adapters.Postgres
|
||||
|
||||
def init(_type, config) do
|
||||
{:ok, Keyword.put(config, :url, Application.get_env(:week_budget, :database_url))}
|
||||
end
|
||||
end
|
37
backend/mix.exs
Normal file
37
backend/mix.exs
Normal file
|
@ -0,0 +1,37 @@
|
|||
defmodule WeekBudget.MixProject do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[
|
||||
app: :week_budget,
|
||||
version: "0.1.0",
|
||||
elixir: "~> 1.8",
|
||||
start_permanent: Mix.env() == :prod,
|
||||
deps: deps()
|
||||
]
|
||||
end
|
||||
|
||||
# Run "mix help compile.app" to learn about applications.
|
||||
def application do
|
||||
[
|
||||
extra_applications: [:logger],
|
||||
mod: {WeekBudget.Core.Application, []}
|
||||
]
|
||||
end
|
||||
|
||||
# Run "mix help deps" to learn about dependencies.
|
||||
defp deps do
|
||||
[
|
||||
{:raxx, "~> 1.0.1"},
|
||||
{:raxx_logger, "~> 0.2.2"},
|
||||
{:ace, "~> 0.18.8"},
|
||||
{:ecto, "~> 3.1.4"},
|
||||
{:ecto_sql, "~> 3.1.3"},
|
||||
{:jason, "~> 1.1.2"},
|
||||
{:observer_cli, "~> 1.5.0"},
|
||||
{:postgrex, ">= 0.0.0"},
|
||||
{:ex_money, "~> 3.4.2"},
|
||||
{:exsync, "~> 0.2.3", only: :dev}
|
||||
]
|
||||
end
|
||||
end
|
26
backend/mix.lock
Normal file
26
backend/mix.lock
Normal file
|
@ -0,0 +1,26 @@
|
|||
%{
|
||||
"ace": {:hex, :ace, "0.18.8", "9853110247f769e7d1600be7cd2abd638b53391c8c7b1ab11baad15184cb95d4", [:mix], [{:hpack, "~> 0.2.3", [hex: :hpack_erl, repo: "hexpm", optional: false]}, {:raxx, "~> 0.17.0 or ~> 0.18.0 or ~> 1.0", [hex: :raxx, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"cldr_utils": {:hex, :cldr_utils, "2.2.0", "4f6ac090fc1871037ac41265c98bf0f785b2a4ede144dd2a5282986a5311dd14", [:mix], [{:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"},
|
||||
"db_connection": {:hex, :db_connection, "2.0.6", "bde2f85d047969c5b5800cb8f4b3ed6316c8cb11487afedac4aa5f93fd39abfa", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"decimal": {:hex, :decimal, "1.7.0", "30d6b52c88541f9a66637359ddf85016df9eb266170d53105f02e4a67e00c5aa", [:mix], [], "hexpm"},
|
||||
"ecto": {:hex, :ecto, "3.1.4", "69d852da7a9f04ede725855a35ede48d158ca11a404fe94f8b2fb3b2162cd3c9", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"ecto_sql": {:hex, :ecto_sql, "3.1.3", "2c536139190492d9de33c5fefac7323c5eaaa82e1b9bf93482a14649042f7cd9", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.1.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.9.1", [hex: :mariaex, repo: "hexpm", optional: true]}, {:myxql, "~> 0.2.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.14.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"ex_cldr": {:hex, :ex_cldr, "2.7.0", "be0f1c88f1baeb8364395df7d3266a8d7f075ebaeb00d20db743b1d72fa47e55", [:mix], [{:cldr_utils, "~> 2.1", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.13", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"ex_cldr_currencies": {:hex, :ex_cldr_currencies, "2.3.0", "bffae489416b8b05d4683403263f5d62aae17de70c24ff915a533541fea514de", [:mix], [{:ex_cldr, "~> 2.6", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.6.0", "431c744058a3980f561878f4886b78871d4067c6d9039ce78335ed9a41c831f8", [:mix], [{:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 2.6", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, "~> 2.3", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"ex_money": {:hex, :ex_money, "3.4.2", "7da06947ed92fab7ad265f7fe1f6d36b30404f2b33c7b46de54670921dfe3420", [:mix], [{:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}, {:ex_cldr, "~> 2.6", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 2.6", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"ex_sync": {:hex, :ex_sync, "0.0.4", "468839964dead69df738a3698e4318d18fa3322e4f684adf98d5441704d1bc8c", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"exsync": {:hex, :exsync, "0.2.3", "a1ac11b4bd3808706003dbe587902101fcc1387d9fc55e8b10972f13a563dd15", [:mix], [{:file_system, "~> 0.2", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"file_system": {:hex, :file_system, "0.2.7", "e6f7f155970975789f26e77b8b8d8ab084c59844d8ecfaf58cbda31c494d14aa", [:mix], [], "hexpm"},
|
||||
"hpack": {:hex, :hpack_erl, "0.2.3", "17670f83ff984ae6cd74b1c456edde906d27ff013740ee4d9efaa4f1bf999633", [:rebar3], [], "hexpm"},
|
||||
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"},
|
||||
"observer_cli": {:hex, :observer_cli, "1.5.0", "9944882b71f55b2503663d9cb54d3f1c7bbdf7cc6dd01cc40ea8ef51207601ec", [:mix, :rebar3], [{:recon, "2.5.0", [hex: :recon, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm"},
|
||||
"postgrex": {:hex, :postgrex, "0.14.3", "5754dee2fdf6e9e508cbf49ab138df964278700b764177e8f3871e658b345a1e", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"raxx": {:hex, :raxx, "1.0.1", "8c51ec5227c85f999360fc844fc1d4e2e5a2adf2b0ce068eb56243ee6b2f65e3", [:mix], [], "hexpm"},
|
||||
"raxx_logger": {:hex, :raxx_logger, "0.2.2", "51bbce9371298e7329de028fe13cf050bf59547bc8f1926910d479ba4b31c4b4", [:mix], [{:raxx, "~> 0.17.5 or ~> 0.18.0 or ~> 1.0", [hex: :raxx, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"recon": {:hex, :recon, "2.5.0", "2f7fcbec2c35034bade2f9717f77059dc54eb4e929a3049ca7ba6775c0bd66cd", [:mix, :rebar3], [], "hexpm"},
|
||||
"telemetry": {:hex, :telemetry, "0.4.0", "8339bee3fa8b91cb84d14c2935f8ecf399ccd87301ad6da6b71c09553834b2ab", [:rebar3], [], "hexpm"},
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
defmodule WeekBudget.DB.Repo.Migrations.AddMoneyWithCurrencyTypeToPostgres do
|
||||
use Ecto.Migration
|
||||
|
||||
def up do
|
||||
execute("CREATE TYPE public.money_with_currency AS (currency_code char(3), amount numeric);")
|
||||
end
|
||||
|
||||
def down do
|
||||
execute("DROP TYPE public.money_with_currency;")
|
||||
end
|
||||
end
|
12
backend/priv/repo/migrations/20190523122356_init.exs
Normal file
12
backend/priv/repo/migrations/20190523122356_init.exs
Normal file
|
@ -0,0 +1,12 @@
|
|||
defmodule WeekBudget.DB.Repo.Migrations.Init do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create table(:budgets, primary_key: false) do
|
||||
add(:secret, :binary_id, primary_key: true)
|
||||
add(:amount, :money_with_currency)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
end
|
||||
end
|
18
backend/priv/repo/migrations/20190523123548_add_events.exs
Normal file
18
backend/priv/repo/migrations/20190523123548_add_events.exs
Normal file
|
@ -0,0 +1,18 @@
|
|||
defmodule WeekBudget.DB.Repo.Migrations.AddEvents do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create table(:events) do
|
||||
add(:amount, :money_with_currency, null: false)
|
||||
add(:at, :utc_datetime, null: false)
|
||||
|
||||
add(
|
||||
:budget,
|
||||
references("budgets", column: :secret, type: :binary_id, on_delete: :delete_all),
|
||||
null: false
|
||||
)
|
||||
end
|
||||
|
||||
create(index("events", :budget))
|
||||
end
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
defmodule WeekBudget.DB.Repo.Migrations.BudgetFkeyName do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
rename(table("events"), :budget, to: :budget_id)
|
||||
end
|
||||
end
|
1
backend/test/test_helper.exs
Normal file
1
backend/test/test_helper.exs
Normal file
|
@ -0,0 +1 @@
|
|||
ExUnit.start()
|
8
backend/test/week_budget_test.exs
Normal file
8
backend/test/week_budget_test.exs
Normal file
|
@ -0,0 +1,8 @@
|
|||
defmodule WeekBudgetTest do
|
||||
use ExUnit.Case
|
||||
doctest WeekBudget
|
||||
|
||||
test "greets the world" do
|
||||
assert WeekBudget.hello() == :world
|
||||
end
|
||||
end
|
Reference in a new issue