diff --git a/.env b/.env new file mode 100644 index 0000000..42bc2fb --- /dev/null +++ b/.env @@ -0,0 +1 @@ +export SLACK_TOKEN=xoxb-2151756753-411261156134-FEJXvyN59Bix3QsOgNflzAaV diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d26faf8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# App artifacts +/_build +/db +/deps +/*.ez + +# Generated on crash by the VM +erl_crash.dump + +# Files matching config/*.secret.exs pattern contain sensitive +# data and you should not commit them into version control. +# +# Alternatively, you may comment the line below and commit the +# secrets files as long as you replace their contents by environment +# variables. +/config/*.secret.exs diff --git a/.hex/cache.ets b/.hex/cache.ets new file mode 100644 index 0000000..c51221c Binary files /dev/null and b/.hex/cache.ets differ diff --git a/.hex/packages/hexpm/certifi-2.3.1.tar b/.hex/packages/hexpm/certifi-2.3.1.tar new file mode 100644 index 0000000..57f1dc5 Binary files /dev/null and b/.hex/packages/hexpm/certifi-2.3.1.tar differ diff --git a/.hex/packages/hexpm/connection-1.0.4.tar b/.hex/packages/hexpm/connection-1.0.4.tar new file mode 100644 index 0000000..d2c09dc Binary files /dev/null and b/.hex/packages/hexpm/connection-1.0.4.tar differ diff --git a/.hex/packages/hexpm/cowboy-1.1.2.tar b/.hex/packages/hexpm/cowboy-1.1.2.tar new file mode 100644 index 0000000..a7fd0da Binary files /dev/null and b/.hex/packages/hexpm/cowboy-1.1.2.tar differ diff --git a/.hex/packages/hexpm/cowlib-1.0.2.tar b/.hex/packages/hexpm/cowlib-1.0.2.tar new file mode 100644 index 0000000..c0ca406 Binary files /dev/null and b/.hex/packages/hexpm/cowlib-1.0.2.tar differ diff --git a/.hex/packages/hexpm/db_connection-1.1.3.tar b/.hex/packages/hexpm/db_connection-1.1.3.tar new file mode 100644 index 0000000..fe5d600 Binary files /dev/null and b/.hex/packages/hexpm/db_connection-1.1.3.tar differ diff --git a/.hex/packages/hexpm/decimal-1.5.0.tar b/.hex/packages/hexpm/decimal-1.5.0.tar new file mode 100644 index 0000000..432e53b Binary files /dev/null and b/.hex/packages/hexpm/decimal-1.5.0.tar differ diff --git a/.hex/packages/hexpm/ecto-2.2.10.tar b/.hex/packages/hexpm/ecto-2.2.10.tar new file mode 100644 index 0000000..bf73234 Binary files /dev/null and b/.hex/packages/hexpm/ecto-2.2.10.tar differ diff --git a/.hex/packages/hexpm/elixir_uuid-1.2.0.tar b/.hex/packages/hexpm/elixir_uuid-1.2.0.tar new file mode 100644 index 0000000..e6d0e82 Binary files /dev/null and b/.hex/packages/hexpm/elixir_uuid-1.2.0.tar differ diff --git a/.hex/packages/hexpm/gettext-0.15.0.tar b/.hex/packages/hexpm/gettext-0.15.0.tar new file mode 100644 index 0000000..d044e42 Binary files /dev/null and b/.hex/packages/hexpm/gettext-0.15.0.tar differ diff --git a/.hex/packages/hexpm/hackney-1.13.0.tar b/.hex/packages/hexpm/hackney-1.13.0.tar new file mode 100644 index 0000000..d380655 Binary files /dev/null and b/.hex/packages/hexpm/hackney-1.13.0.tar differ diff --git a/.hex/packages/hexpm/httpoison-0.13.0.tar b/.hex/packages/hexpm/httpoison-0.13.0.tar new file mode 100644 index 0000000..295e54d Binary files /dev/null and b/.hex/packages/hexpm/httpoison-0.13.0.tar differ diff --git a/.hex/packages/hexpm/idna-5.1.2.tar b/.hex/packages/hexpm/idna-5.1.2.tar new file mode 100644 index 0000000..d614bb5 Binary files /dev/null and b/.hex/packages/hexpm/idna-5.1.2.tar differ diff --git a/.hex/packages/hexpm/metrics-1.0.1.tar b/.hex/packages/hexpm/metrics-1.0.1.tar new file mode 100644 index 0000000..199cd96 Binary files /dev/null and b/.hex/packages/hexpm/metrics-1.0.1.tar differ diff --git a/.hex/packages/hexpm/mime-1.3.0.tar b/.hex/packages/hexpm/mime-1.3.0.tar new file mode 100644 index 0000000..6f1cd68 Binary files /dev/null and b/.hex/packages/hexpm/mime-1.3.0.tar differ diff --git a/.hex/packages/hexpm/mimerl-1.0.2.tar b/.hex/packages/hexpm/mimerl-1.0.2.tar new file mode 100644 index 0000000..f2b4341 Binary files /dev/null and b/.hex/packages/hexpm/mimerl-1.0.2.tar differ diff --git a/.hex/packages/hexpm/parse_trans-3.2.0.tar b/.hex/packages/hexpm/parse_trans-3.2.0.tar new file mode 100644 index 0000000..53bba0b Binary files /dev/null and b/.hex/packages/hexpm/parse_trans-3.2.0.tar differ diff --git a/.hex/packages/hexpm/phoenix-1.3.3.tar b/.hex/packages/hexpm/phoenix-1.3.3.tar new file mode 100644 index 0000000..73e7660 Binary files /dev/null and b/.hex/packages/hexpm/phoenix-1.3.3.tar differ diff --git a/.hex/packages/hexpm/phoenix_ecto-3.3.0.tar b/.hex/packages/hexpm/phoenix_ecto-3.3.0.tar new file mode 100644 index 0000000..fc70985 Binary files /dev/null and b/.hex/packages/hexpm/phoenix_ecto-3.3.0.tar differ diff --git a/.hex/packages/hexpm/phoenix_pubsub-1.0.2.tar b/.hex/packages/hexpm/phoenix_pubsub-1.0.2.tar new file mode 100644 index 0000000..23aa037 Binary files /dev/null and b/.hex/packages/hexpm/phoenix_pubsub-1.0.2.tar differ diff --git a/.hex/packages/hexpm/plug-1.6.1.tar b/.hex/packages/hexpm/plug-1.6.1.tar new file mode 100644 index 0000000..c1e89e3 Binary files /dev/null and b/.hex/packages/hexpm/plug-1.6.1.tar differ diff --git a/.hex/packages/hexpm/poison-3.1.0.tar b/.hex/packages/hexpm/poison-3.1.0.tar new file mode 100644 index 0000000..473e336 Binary files /dev/null and b/.hex/packages/hexpm/poison-3.1.0.tar differ diff --git a/.hex/packages/hexpm/poolboy-1.5.1.tar b/.hex/packages/hexpm/poolboy-1.5.1.tar new file mode 100644 index 0000000..fb61d5b Binary files /dev/null and b/.hex/packages/hexpm/poolboy-1.5.1.tar differ diff --git a/.hex/packages/hexpm/postgrex-0.13.5.tar b/.hex/packages/hexpm/postgrex-0.13.5.tar new file mode 100644 index 0000000..7109eb7 Binary files /dev/null and b/.hex/packages/hexpm/postgrex-0.13.5.tar differ diff --git a/.hex/packages/hexpm/ranch-1.3.2.tar b/.hex/packages/hexpm/ranch-1.3.2.tar new file mode 100644 index 0000000..ca068a7 Binary files /dev/null and b/.hex/packages/hexpm/ranch-1.3.2.tar differ diff --git a/.hex/packages/hexpm/slack-0.14.0.tar b/.hex/packages/hexpm/slack-0.14.0.tar new file mode 100644 index 0000000..8849267 Binary files /dev/null and b/.hex/packages/hexpm/slack-0.14.0.tar differ diff --git a/.hex/packages/hexpm/ssl_verify_fun-1.1.1.tar b/.hex/packages/hexpm/ssl_verify_fun-1.1.1.tar new file mode 100644 index 0000000..f126c77 Binary files /dev/null and b/.hex/packages/hexpm/ssl_verify_fun-1.1.1.tar differ diff --git a/.hex/packages/hexpm/unicode_util_compat-0.3.1.tar b/.hex/packages/hexpm/unicode_util_compat-0.3.1.tar new file mode 100644 index 0000000..eddd107 Binary files /dev/null and b/.hex/packages/hexpm/unicode_util_compat-0.3.1.tar differ diff --git a/.hex/packages/hexpm/websocket_client-1.2.4.tar b/.hex/packages/hexpm/websocket_client-1.2.4.tar new file mode 100644 index 0000000..e3c347a Binary files /dev/null and b/.hex/packages/hexpm/websocket_client-1.2.4.tar differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/.elixir b/.mix/archives/hex-0.18.1/hex-0.18.1/.elixir new file mode 100644 index 0000000..463544a --- /dev/null +++ b/.mix/archives/hex-0.18.1/hex-0.18.1/.elixir @@ -0,0 +1 @@ +~> 1.0 \ No newline at end of file diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.API.Auth.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.API.Auth.beam new file mode 100644 index 0000000..89d3d55 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.API.Auth.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.API.Key.Organization.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.API.Key.Organization.beam new file mode 100644 index 0000000..92fc514 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.API.Key.Organization.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.API.Key.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.API.Key.beam new file mode 100644 index 0000000..ccb9e9a Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.API.Key.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.API.Package.Owner.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.API.Package.Owner.beam new file mode 100644 index 0000000..86e441a Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.API.Package.Owner.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.API.Package.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.API.Package.beam new file mode 100644 index 0000000..fccdf7b Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.API.Package.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.API.Release.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.API.Release.beam new file mode 100644 index 0000000..15cb67f Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.API.Release.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.API.ReleaseDocs.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.API.ReleaseDocs.beam new file mode 100644 index 0000000..44abc04 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.API.ReleaseDocs.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.API.User.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.API.User.beam new file mode 100644 index 0000000..1e238cc Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.API.User.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.API.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.API.beam new file mode 100644 index 0000000..f2bfce2 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.API.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Config.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Config.beam new file mode 100644 index 0000000..6b580d5 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Config.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Crypto.AES_CBC_HMAC_SHA2.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Crypto.AES_CBC_HMAC_SHA2.beam new file mode 100644 index 0000000..fd66eb4 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Crypto.AES_CBC_HMAC_SHA2.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Crypto.AES_GCM.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Crypto.AES_GCM.beam new file mode 100644 index 0000000..fa5cc3b Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Crypto.AES_GCM.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Crypto.ContentEncryptor.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Crypto.ContentEncryptor.beam new file mode 100644 index 0000000..46f97cc Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Crypto.ContentEncryptor.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Crypto.Encryption.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Crypto.Encryption.beam new file mode 100644 index 0000000..b96c4b1 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Crypto.Encryption.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Crypto.KeyManager.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Crypto.KeyManager.beam new file mode 100644 index 0000000..62082da Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Crypto.KeyManager.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Crypto.PBES2_HMAC_SHA2.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Crypto.PBES2_HMAC_SHA2.beam new file mode 100644 index 0000000..3187b43 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Crypto.PBES2_HMAC_SHA2.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Crypto.PKCS5.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Crypto.PKCS5.beam new file mode 100644 index 0000000..06557af Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Crypto.PKCS5.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Crypto.PublicKey.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Crypto.PublicKey.beam new file mode 100644 index 0000000..1d87b88 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Crypto.PublicKey.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Crypto.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Crypto.beam new file mode 100644 index 0000000..adcb4a4 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Crypto.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.HTTP.Certs.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.HTTP.Certs.beam new file mode 100644 index 0000000..685e3eb Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.HTTP.Certs.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.HTTP.SSL.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.HTTP.SSL.beam new file mode 100644 index 0000000..46d0bde Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.HTTP.SSL.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.HTTP.VerifyHostname.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.HTTP.VerifyHostname.beam new file mode 100644 index 0000000..7d658c2 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.HTTP.VerifyHostname.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.HTTP.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.HTTP.beam new file mode 100644 index 0000000..7584b8b Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.HTTP.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Mix.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Mix.beam new file mode 100644 index 0000000..7e00474 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Mix.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.OptionParser.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.OptionParser.beam new file mode 100644 index 0000000..96e7701 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.OptionParser.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Parallel.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Parallel.beam new file mode 100644 index 0000000..9326548 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Parallel.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Registry.Server.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Registry.Server.beam new file mode 100644 index 0000000..dc16283 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Registry.Server.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Registry.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Registry.beam new file mode 100644 index 0000000..16654a4 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Registry.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.RemoteConverger.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.RemoteConverger.beam new file mode 100644 index 0000000..37b9753 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.RemoteConverger.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Repo.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Repo.beam new file mode 100644 index 0000000..bc6d0f1 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Repo.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Resolver.Backtracks.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Resolver.Backtracks.beam new file mode 100644 index 0000000..9aa27bd Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Resolver.Backtracks.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Resolver.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Resolver.beam new file mode 100644 index 0000000..05947ef Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Resolver.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.SCM.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.SCM.beam new file mode 100644 index 0000000..526c549 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.SCM.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Server.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Server.beam new file mode 100644 index 0000000..245e553 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Server.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Set.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Set.beam new file mode 100644 index 0000000..4ee4e08 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Set.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Shell.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Shell.beam new file mode 100644 index 0000000..8636429 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Shell.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.State.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.State.beam new file mode 100644 index 0000000..6e69083 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.State.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.UpdateChecker.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.UpdateChecker.beam new file mode 100644 index 0000000..b3a64e9 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.UpdateChecker.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Utils.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Utils.beam new file mode 100644 index 0000000..53f1201 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Utils.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Version.InvalidRequirementError.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Version.InvalidRequirementError.beam new file mode 100644 index 0000000..b69495f Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Version.InvalidRequirementError.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Version.InvalidVersionError.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Version.InvalidVersionError.beam new file mode 100644 index 0000000..e1480f9 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Version.InvalidVersionError.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Version.Requirement.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Version.Requirement.beam new file mode 100644 index 0000000..6afae09 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Version.Requirement.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Version.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Version.beam new file mode 100644 index 0000000..e70c320 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.Version.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.beam new file mode 100644 index 0000000..19dd304 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Hex.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Audit.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Audit.beam new file mode 100644 index 0000000..cc14540 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Audit.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Build.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Build.beam new file mode 100644 index 0000000..5f89843 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Build.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Config.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Config.beam new file mode 100644 index 0000000..5db226e Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Config.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Docs.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Docs.beam new file mode 100644 index 0000000..7709b85 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Docs.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Info.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Info.beam new file mode 100644 index 0000000..7cfb73e Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Info.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Install.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Install.beam new file mode 100644 index 0000000..b6984da Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Install.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Organization.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Organization.beam new file mode 100644 index 0000000..7dabcfc Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Organization.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Outdated.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Outdated.beam new file mode 100644 index 0000000..6ef9eac Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Outdated.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Owner.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Owner.beam new file mode 100644 index 0000000..4eba138 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Owner.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Publish.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Publish.beam new file mode 100644 index 0000000..87f9bb7 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Publish.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Repo.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Repo.beam new file mode 100644 index 0000000..25c428f Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Repo.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Retire.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Retire.beam new file mode 100644 index 0000000..770d830 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Retire.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Search.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Search.beam new file mode 100644 index 0000000..b56098a Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.Search.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.User.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.User.beam new file mode 100644 index 0000000..39550c8 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.User.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.beam new file mode 100644 index 0000000..78d32b7 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/Elixir.Mix.Tasks.Hex.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/hex.app b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/hex.app new file mode 100644 index 0000000..f9f8bbb --- /dev/null +++ b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/hex.app @@ -0,0 +1,52 @@ +{application,hex, + [{description,"hex"}, + {modules,['Elixir.Hex','Elixir.Hex.API','Elixir.Hex.API.Auth', + 'Elixir.Hex.API.Key', + 'Elixir.Hex.API.Key.Organization', + 'Elixir.Hex.API.Package', + 'Elixir.Hex.API.Package.Owner', + 'Elixir.Hex.API.Release','Elixir.Hex.API.ReleaseDocs', + 'Elixir.Hex.API.User','Elixir.Hex.Config', + 'Elixir.Hex.Crypto', + 'Elixir.Hex.Crypto.AES_CBC_HMAC_SHA2', + 'Elixir.Hex.Crypto.AES_GCM', + 'Elixir.Hex.Crypto.ContentEncryptor', + 'Elixir.Hex.Crypto.Encryption', + 'Elixir.Hex.Crypto.KeyManager', + 'Elixir.Hex.Crypto.PBES2_HMAC_SHA2', + 'Elixir.Hex.Crypto.PKCS5', + 'Elixir.Hex.Crypto.PublicKey','Elixir.Hex.HTTP', + 'Elixir.Hex.HTTP.Certs','Elixir.Hex.HTTP.SSL', + 'Elixir.Hex.HTTP.VerifyHostname','Elixir.Hex.Mix', + 'Elixir.Hex.OptionParser','Elixir.Hex.Parallel', + 'Elixir.Hex.Registry','Elixir.Hex.Registry.Server', + 'Elixir.Hex.RemoteConverger','Elixir.Hex.Repo', + 'Elixir.Hex.Resolver', + 'Elixir.Hex.Resolver.Backtracks','Elixir.Hex.SCM', + 'Elixir.Hex.Server','Elixir.Hex.Set', + 'Elixir.Hex.Shell','Elixir.Hex.State', + 'Elixir.Hex.UpdateChecker','Elixir.Hex.Utils', + 'Elixir.Hex.Version', + 'Elixir.Hex.Version.InvalidRequirementError', + 'Elixir.Hex.Version.InvalidVersionError', + 'Elixir.Hex.Version.Requirement', + 'Elixir.Mix.Tasks.Hex','Elixir.Mix.Tasks.Hex.Audit', + 'Elixir.Mix.Tasks.Hex.Build', + 'Elixir.Mix.Tasks.Hex.Config', + 'Elixir.Mix.Tasks.Hex.Docs', + 'Elixir.Mix.Tasks.Hex.Info', + 'Elixir.Mix.Tasks.Hex.Install', + 'Elixir.Mix.Tasks.Hex.Organization', + 'Elixir.Mix.Tasks.Hex.Outdated', + 'Elixir.Mix.Tasks.Hex.Owner', + 'Elixir.Mix.Tasks.Hex.Publish', + 'Elixir.Mix.Tasks.Hex.Repo', + 'Elixir.Mix.Tasks.Hex.Retire', + 'Elixir.Mix.Tasks.Hex.Search', + 'Elixir.Mix.Tasks.Hex.User',mix_hex_erl_tar, + mix_hex_filename,mix_hex_pb_package,mix_hex_pb_signed, + mix_hex_registry,mix_hex_tarball,mix_safe_erl_term]}, + {registered,[]}, + {vsn,"0.18.1"}, + {applications,[kernel,stdlib,elixir,ssl,inets]}, + {mod,{'Elixir.Hex',[]}}]}. diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/mix_hex_erl_tar.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/mix_hex_erl_tar.beam new file mode 100644 index 0000000..d18b318 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/mix_hex_erl_tar.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/mix_hex_filename.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/mix_hex_filename.beam new file mode 100644 index 0000000..b4ad4a4 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/mix_hex_filename.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/mix_hex_pb_package.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/mix_hex_pb_package.beam new file mode 100644 index 0000000..b763bef Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/mix_hex_pb_package.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/mix_hex_pb_signed.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/mix_hex_pb_signed.beam new file mode 100644 index 0000000..a7839a9 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/mix_hex_pb_signed.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/mix_hex_registry.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/mix_hex_registry.beam new file mode 100644 index 0000000..009182f Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/mix_hex_registry.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/mix_hex_tarball.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/mix_hex_tarball.beam new file mode 100644 index 0000000..086c350 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/mix_hex_tarball.beam differ diff --git a/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/mix_safe_erl_term.beam b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/mix_safe_erl_term.beam new file mode 100644 index 0000000..2238d49 Binary files /dev/null and b/.mix/archives/hex-0.18.1/hex-0.18.1/ebin/mix_safe_erl_term.beam differ diff --git a/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/.elixir b/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/.elixir new file mode 100644 index 0000000..7f78873 --- /dev/null +++ b/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/.elixir @@ -0,0 +1 @@ +~> 1.3 or ~> 1.4 \ No newline at end of file diff --git a/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Mix.Tasks.Local.Phoenix.beam b/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Mix.Tasks.Local.Phoenix.beam new file mode 100644 index 0000000..8848e40 Binary files /dev/null and b/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Mix.Tasks.Local.Phoenix.beam differ diff --git a/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Mix.Tasks.Local.Phx.beam b/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Mix.Tasks.Local.Phx.beam new file mode 100644 index 0000000..b2b4f10 Binary files /dev/null and b/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Mix.Tasks.Local.Phx.beam differ diff --git a/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Mix.Tasks.Phoenix.New.beam b/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Mix.Tasks.Phoenix.New.beam new file mode 100644 index 0000000..d85ad61 Binary files /dev/null and b/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Mix.Tasks.Phoenix.New.beam differ diff --git a/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Mix.Tasks.Phx.New.Ecto.beam b/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Mix.Tasks.Phx.New.Ecto.beam new file mode 100644 index 0000000..1de506c Binary files /dev/null and b/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Mix.Tasks.Phx.New.Ecto.beam differ diff --git a/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Mix.Tasks.Phx.New.Web.beam b/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Mix.Tasks.Phx.New.Web.beam new file mode 100644 index 0000000..7f821fd Binary files /dev/null and b/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Mix.Tasks.Phx.New.Web.beam differ diff --git a/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Mix.Tasks.Phx.New.beam b/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Mix.Tasks.Phx.New.beam new file mode 100644 index 0000000..ed59ed6 Binary files /dev/null and b/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Mix.Tasks.Phx.New.beam differ diff --git a/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Phx.New.Ecto.beam b/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Phx.New.Ecto.beam new file mode 100644 index 0000000..9e8be56 Binary files /dev/null and b/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Phx.New.Ecto.beam differ diff --git a/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Phx.New.Generator.beam b/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Phx.New.Generator.beam new file mode 100644 index 0000000..617c616 Binary files /dev/null and b/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Phx.New.Generator.beam differ diff --git a/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Phx.New.Project.beam b/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Phx.New.Project.beam new file mode 100644 index 0000000..42a02af Binary files /dev/null and b/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Phx.New.Project.beam differ diff --git a/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Phx.New.Single.beam b/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Phx.New.Single.beam new file mode 100644 index 0000000..4e1ef01 Binary files /dev/null and b/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Phx.New.Single.beam differ diff --git a/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Phx.New.Umbrella.beam b/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Phx.New.Umbrella.beam new file mode 100644 index 0000000..7aed67d Binary files /dev/null and b/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Phx.New.Umbrella.beam differ diff --git a/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Phx.New.Web.beam b/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Phx.New.Web.beam new file mode 100644 index 0000000..dae2f81 Binary files /dev/null and b/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/Elixir.Phx.New.Web.beam differ diff --git a/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/phx_new.app b/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/phx_new.app new file mode 100644 index 0000000..6844031 --- /dev/null +++ b/.mix/archives/phx_new-1.3.3/phx_new-1.3.3/ebin/phx_new.app @@ -0,0 +1,15 @@ +{application,phx_new, + [{applications,[kernel,stdlib,elixir]}, + {description,"phx_new"}, + {modules,['Elixir.Mix.Tasks.Local.Phoenix', + 'Elixir.Mix.Tasks.Local.Phx', + 'Elixir.Mix.Tasks.Phoenix.New', + 'Elixir.Mix.Tasks.Phx.New', + 'Elixir.Mix.Tasks.Phx.New.Ecto', + 'Elixir.Mix.Tasks.Phx.New.Web','Elixir.Phx.New.Ecto', + 'Elixir.Phx.New.Generator','Elixir.Phx.New.Project', + 'Elixir.Phx.New.Single','Elixir.Phx.New.Umbrella', + 'Elixir.Phx.New.Web']}, + {registered,[]}, + {vsn,"1.3.3"}, + {extra_applications,[]}]}. diff --git a/.mix/rebar b/.mix/rebar new file mode 100755 index 0000000..13bcbf9 Binary files /dev/null and b/.mix/rebar differ diff --git a/.mix/rebar3 b/.mix/rebar3 new file mode 100755 index 0000000..b9e599d Binary files /dev/null and b/.mix/rebar3 differ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..741ea0b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,26 @@ +FROM elixir:1.6 +MAINTAINER hex337 + +ARG GITHUB_TOKEN + +ENV HOME=/usr/src/alex-koin + +RUN apt-get update && apt-get install --yes \ + postgresql-client \ + vim \ + git \ + curl \ + inotify-tools + +RUN mix local.hex --force \ + && mix local.rebar --force \ + && mix archive.install --force https://github.com/phoenixframework/archives/raw/master/phx_new-1.3.3.ez + +COPY ./docker-entrypoint.sh / +RUN chmod +x /docker-entrypoint.sh +ENTRYPOINT ["/docker-entrypoint.sh"] + +ADD . $HOME + +WORKDIR $HOME +EXPOSE 4000 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2d814bb --- /dev/null +++ b/Makefile @@ -0,0 +1,53 @@ +.PHONY: assets bash build deps help iex logs migrate ps restart seed setup_db stop test up + +SERVICE?=api + +default: help + +assets: #: install npm assets for the project + docker-compose exec $(SERVICE) bash -c "cd assets && npm install" + +bash: #: Bash prompt on running container + docker-compose exec $(SERVICE) bash + +build: #: Build containers + docker-compose build + +deps: #: Install the dependencies + docker-compose run --rm $(SERVICE) mix deps.get + +iex: #: Interactive elixir shell on container + docker-compose exec $(SERVICE) iex -S mix + +logs: #: Tail the service container's logs + docker-compose logs -tf $(SERVICE) + +migrate: #: Run migrations + docker-compose run --rm $(SERVICE) mix ecto.migrate + +ps: #: Show running processes + docker-compose ps + +restart: #: Restart the service container + docker-compose restart $(SERVICE) + +seed: #: Seed the DB + docker-compose exec -T $(SERVICE) mix run priv/repo/seeds.exs + +setup_db: #: Create the db table(s) + docker-compose run --rm $(SERVICE) mix ecto.create + +stop: #: Stop running containers + docker-compose stop + +test: #: Run tests + docker-compose exec $(SERVICE) mix test + +up: #: Start containers + docker-compose up -d + +down: + docker-compose down + +help: #: Show help topics + @grep "#:" Makefile | grep -v "@grep" | sed "s/.*:\([A-Za-z_ -]*\):.*#\(.*\)/$$(tput setaf 3)\1$$(tput sgr0)\2/g" | sort diff --git a/README.md b/README.md new file mode 100644 index 0000000..11e3bd4 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# AlexKoin + +To start your Phoenix server: + + * Install dependencies with `mix deps.get` + * Create and migrate your database with `mix ecto.create && mix ecto.migrate` + * Start Phoenix endpoint with `mix phx.server` + +Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. + +Ready to run in production? Please [check our deployment guides](http://www.phoenixframework.org/docs/deployment). + +## Learn more + + * Official website: http://www.phoenixframework.org/ + * Guides: http://phoenixframework.org/docs/overview + * Docs: https://hexdocs.pm/phoenix + * Mailing list: http://groups.google.com/group/phoenix-talk + * Source: https://github.com/phoenixframework/phoenix diff --git a/config/config.exs b/config/config.exs new file mode 100644 index 0000000..276607a --- /dev/null +++ b/config/config.exs @@ -0,0 +1,31 @@ +# This file is responsible for configuring your application +# and its dependencies with the aid of the Mix.Config module. +# +# This configuration file is loaded before any dependency and +# is restricted to this project. +use Mix.Config + +# General application configuration +config :alex_koin, + ecto_repos: [AlexKoin.Repo] + +# Configures the endpoint +config :alex_koin, AlexKoinWeb.Endpoint, + url: [host: "localhost"], + secret_key_base: "bB7leglq64+Lvhx7d7W0BY6m6rJDv0+X9NJEoaZfKMea1RTpUPWNawG4y+w/yz7+", + render_errors: [view: AlexKoinWeb.ErrorView, accepts: ~w(json)], + pubsub: [name: AlexKoin.PubSub, + adapter: Phoenix.PubSub.PG2] + +# Configures Elixir's Logger +config :logger, :console, + format: "$time $metadata[$level] $message\n", + metadata: [:user_id] + +# Configure Slack Bot +config :alex_koin, AlexKoin.Slack, + token: System.get_env("SLACK_TOKEN") + +# Import environment specific config. This must remain at the bottom +# of this file so it overrides the configuration defined above. +import_config "#{Mix.env}.exs" diff --git a/config/dev.exs b/config/dev.exs new file mode 100644 index 0000000..9c5ead6 --- /dev/null +++ b/config/dev.exs @@ -0,0 +1,46 @@ +use Mix.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 brunch.io to recompile .js and .css sources. +config :alex_koin, AlexKoinWeb.Endpoint, + http: [port: 4000], + debug_errors: true, + code_reloader: true, + check_origin: false, + watchers: [] + +# ## SSL Support +# +# In order to use HTTPS in development, a self-signed +# certificate can be generated by running the following +# command from your terminal: +# +# openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 -subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=www.example.com" -keyout priv/server.key -out priv/server.pem +# +# The `http:` config above can be replaced with: +# +# https: [port: 4000, keyfile: "priv/server.key", certfile: "priv/server.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" + +# Set a higher stacktrace during development. Avoid configuring such +# in production as building large stacktraces may be expensive. +config :phoenix, :stacktrace_depth, 20 + +# Configure your database +config :alex_koin, AlexKoin.Repo, + adapter: Ecto.Adapters.Postgres, + username: "postgres", + password: "postgres", + database: "alex_koin_dev", + hostname: "db", + pool_size: 10 diff --git a/config/prod.exs b/config/prod.exs new file mode 100644 index 0000000..2e344fe --- /dev/null +++ b/config/prod.exs @@ -0,0 +1,64 @@ +use Mix.Config + +# For production, we often load configuration from external +# sources, such as your system environment. For this reason, +# you won't find the :http configuration below, but set inside +# AlexKoinWeb.Endpoint.init/2 when load_from_system_env is +# true. Any dynamic configuration should be done there. +# +# Don't forget to configure the url host to something meaningful, +# Phoenix uses this information when generating URLs. +# +# Finally, we also include the path to a cache manifest +# containing the digested version of static files. This +# manifest is generated by the mix phx.digest task +# which you typically run after static files are built. +config :alex_koin, AlexKoinWeb.Endpoint, + load_from_system_env: true, + url: [host: "example.com", port: 80], + cache_static_manifest: "priv/static/cache_manifest.json" + +# Do not print debug messages in production +config :logger, level: :info + +# ## SSL Support +# +# To get SSL working, you will need to add the `https` key +# to the previous section and set your `:url` port to 443: +# +# config :alex_koin, AlexKoinWeb.Endpoint, +# ... +# url: [host: "example.com", port: 443], +# https: [:inet6, +# port: 443, +# keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), +# certfile: System.get_env("SOME_APP_SSL_CERT_PATH")] +# +# Where those two env variables return an absolute path to +# the key and cert in disk or a relative path inside priv, +# for example "priv/ssl/server.key". +# +# We also recommend setting `force_ssl`, ensuring no data is +# ever sent via http, always redirecting to https: +# +# config :alex_koin, AlexKoinWeb.Endpoint, +# force_ssl: [hsts: true] +# +# Check `Plug.SSL` for all available options in `force_ssl`. + +# ## Using releases +# +# If you are doing OTP releases, you need to instruct Phoenix +# to start the server for all endpoints: +# +# config :phoenix, :serve_endpoints, true +# +# Alternatively, you can configure exactly which server to +# start per endpoint: +# +# config :alex_koin, AlexKoinWeb.Endpoint, server: true +# + +# Finally import the config/prod.secret.exs +# which should be versioned separately. +import_config "prod.secret.exs" diff --git a/config/test.exs b/config/test.exs new file mode 100644 index 0000000..1eb85d0 --- /dev/null +++ b/config/test.exs @@ -0,0 +1,19 @@ +use Mix.Config + +# We don't run a server during test. If one is required, +# you can enable the server option below. +config :alex_koin, AlexKoinWeb.Endpoint, + http: [port: 4001], + server: false + +# Print only warnings and errors during test +config :logger, level: :warn + +# Configure your database +config :alex_koin, AlexKoin.Repo, + adapter: Ecto.Adapters.Postgres, + username: "postgres", + password: "postgres", + database: "alex_koin_test", + hostname: "localhost", + pool: Ecto.Adapters.SQL.Sandbox diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..a6441d9 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,26 @@ +version: '3' + +services: + api: + build: + context: . + dockerfile: ./Dockerfile + args: + - SLACK_TOKEN=${SLACK_TOKEN} + command: "mix run --no-halt" + ports: + - "4000:4000" + volumes: + - .:/usr/src/alex-koin + depends_on: + - db + environment: + SLACK_TOKEN: "${SLACK_TOKEN}" + + db: + image: "postgres:9.6.3" + volumes: + - postgres-data:/var/lib/postgresql/data + +volumes: + postgres-data: diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100755 index 0000000..5a8c777 --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# fail if we error out +set -e + +mix local.rebar --force + +# Wait for mysql to come up +until psql -h db -U "postgres" -c '\q' 2>/dev/null; do + >&2 echo "Postgres is unavailable - sleeping" + sleep 1 +done + +# call the command that the compose defines +exec "$@" diff --git a/lib/alex_koin.ex b/lib/alex_koin.ex new file mode 100644 index 0000000..ba166d0 --- /dev/null +++ b/lib/alex_koin.ex @@ -0,0 +1,9 @@ +defmodule AlexKoin do + @moduledoc """ + AlexKoin keeps the contexts that define your domain + and business logic. + + Contexts are also responsible for managing your data, regardless + if it comes from the database, an external API or others. + """ +end diff --git a/lib/alex_koin/account/account.ex b/lib/alex_koin/account/account.ex new file mode 100644 index 0000000..8c4eee7 --- /dev/null +++ b/lib/alex_koin/account/account.ex @@ -0,0 +1,296 @@ +defmodule AlexKoin.Account do + @moduledoc """ + The Account context. + """ + + import Ecto.Query, warn: false + alias AlexKoin.Repo + + alias AlexKoin.Account.User + + @doc """ + Returns the list of users. + + ## Examples + + iex> list_users() + [%User{}, ...] + + """ + def list_users do + Repo.all(User) + end + + @doc """ + Gets a single user. + + Raises `Ecto.NoResultsError` if the User does not exist. + + ## Examples + + iex> get_user!(123) + %User{} + + iex> get_user!(456) + ** (Ecto.NoResultsError) + + """ + def get_user!(id), do: Repo.get!(User, id) + + @doc """ + Creates a user. + + ## Examples + + iex> create_user(%{field: value}) + {:ok, %User{}} + + iex> create_user(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_user(attrs \\ %{}) do + %User{} + |> User.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a user. + + ## Examples + + iex> update_user(user, %{field: new_value}) + {:ok, %User{}} + + iex> update_user(user, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_user(%User{} = user, attrs) do + user + |> User.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a User. + + ## Examples + + iex> delete_user(user) + {:ok, %User{}} + + iex> delete_user(user) + {:error, %Ecto.Changeset{}} + + """ + def delete_user(%User{} = user) do + Repo.delete(user) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking user changes. + + ## Examples + + iex> change_user(user) + %Ecto.Changeset{source: %User{}} + + """ + def change_user(%User{} = user) do + User.changeset(user, %{}) + end + + alias AlexKoin.Account.Wallet + + @doc """ + Returns the list of wallets. + + ## Examples + + iex> list_wallets() + [%Wallet{}, ...] + + """ + def list_wallets do + Repo.all(Wallet) + end + + @doc """ + Gets a single wallet. + + Raises `Ecto.NoResultsError` if the Wallet does not exist. + + ## Examples + + iex> get_wallet!(123) + %Wallet{} + + iex> get_wallet!(456) + ** (Ecto.NoResultsError) + + """ + def get_wallet!(id), do: Repo.get!(Wallet, id) + + @doc """ + Creates a wallet. + + ## Examples + + iex> create_wallet(%{field: value}) + {:ok, %Wallet{}} + + iex> create_wallet(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_wallet(attrs \\ %{}) do + %Wallet{} + |> Wallet.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a wallet. + + ## Examples + + iex> update_wallet(wallet, %{field: new_value}) + {:ok, %Wallet{}} + + iex> update_wallet(wallet, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_wallet(%Wallet{} = wallet, attrs) do + wallet + |> Wallet.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a Wallet. + + ## Examples + + iex> delete_wallet(wallet) + {:ok, %Wallet{}} + + iex> delete_wallet(wallet) + {:error, %Ecto.Changeset{}} + + """ + def delete_wallet(%Wallet{} = wallet) do + Repo.delete(wallet) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking wallet changes. + + ## Examples + + iex> change_wallet(wallet) + %Ecto.Changeset{source: %Wallet{}} + + """ + def change_wallet(%Wallet{} = wallet) do + Wallet.changeset(wallet, %{}) + end + + alias AlexKoin.Account.Transaction + + @doc """ + Returns the list of transactions. + + ## Examples + + iex> list_transactions() + [%Transaction{}, ...] + + """ + def list_transactions do + Repo.all(Transaction) + end + + @doc """ + Gets a single transaction. + + Raises `Ecto.NoResultsError` if the Transaction does not exist. + + ## Examples + + iex> get_transaction!(123) + %Transaction{} + + iex> get_transaction!(456) + ** (Ecto.NoResultsError) + + """ + def get_transaction!(id), do: Repo.get!(Transaction, id) + + @doc """ + Creates a transaction. + + ## Examples + + iex> create_transaction(%{field: value}) + {:ok, %Transaction{}} + + iex> create_transaction(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_transaction(attrs \\ %{}) do + %Transaction{} + |> Transaction.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a transaction. + + ## Examples + + iex> update_transaction(transaction, %{field: new_value}) + {:ok, %Transaction{}} + + iex> update_transaction(transaction, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_transaction(%Transaction{} = transaction, attrs) do + transaction + |> Transaction.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a Transaction. + + ## Examples + + iex> delete_transaction(transaction) + {:ok, %Transaction{}} + + iex> delete_transaction(transaction) + {:error, %Ecto.Changeset{}} + + """ + def delete_transaction(%Transaction{} = transaction) do + Repo.delete(transaction) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking transaction changes. + + ## Examples + + iex> change_transaction(transaction) + %Ecto.Changeset{source: %Transaction{}} + + """ + def change_transaction(%Transaction{} = transaction) do + Transaction.changeset(transaction, %{}) + end +end diff --git a/lib/alex_koin/account/transaction.ex b/lib/alex_koin/account/transaction.ex new file mode 100644 index 0000000..2cf6aba --- /dev/null +++ b/lib/alex_koin/account/transaction.ex @@ -0,0 +1,22 @@ +defmodule AlexKoin.Account.Transaction do + use Ecto.Schema + import Ecto.Changeset + + + schema "transactions" do + field :amount, :float + field :memo, :string + field :from_id, :id + field :to_id, :id + field :coin_id, :id + + timestamps() + end + + @doc false + def changeset(transaction, attrs) do + transaction + |> cast(attrs, [:amount, :memo]) + |> validate_required([:amount, :memo]) + end +end diff --git a/lib/alex_koin/account/user.ex b/lib/alex_koin/account/user.ex new file mode 100644 index 0000000..5459f8e --- /dev/null +++ b/lib/alex_koin/account/user.ex @@ -0,0 +1,21 @@ +defmodule AlexKoin.Account.User do + use Ecto.Schema + import Ecto.Changeset + + + schema "users" do + field :email, :string + field :first_name, :string + field :last_name, :string + field :slack_id, :string + + timestamps() + end + + @doc false + def changeset(user, attrs) do + user + |> cast(attrs, [:email, :first_name, :last_name, :slack_id]) + |> validate_required([:email, :first_name, :last_name, :slack_id]) + end +end diff --git a/lib/alex_koin/account/wallet.ex b/lib/alex_koin/account/wallet.ex new file mode 100644 index 0000000..385ae54 --- /dev/null +++ b/lib/alex_koin/account/wallet.ex @@ -0,0 +1,19 @@ +defmodule AlexKoin.Account.Wallet do + use Ecto.Schema + import Ecto.Changeset + + + schema "wallets" do + field :balance, :float + field :user_id, :id + + timestamps() + end + + @doc false + def changeset(wallet, attrs) do + wallet + |> cast(attrs, [:balance]) + |> validate_required([:balance]) + end +end diff --git a/lib/alex_koin/application.ex b/lib/alex_koin/application.ex new file mode 100644 index 0000000..81c2baf --- /dev/null +++ b/lib/alex_koin/application.ex @@ -0,0 +1,37 @@ +defmodule AlexKoin.Application do + use Application + + # See https://hexdocs.pm/elixir/Application.html + # for more information on OTP Applications + def start(_type, _args) do + import Supervisor.Spec + + slack_token = Application.get_env(:alex_koin, AlexKoin.Slack)[:token] + + # Define workers and child supervisors to be supervised + children = [ + # Start the Ecto repository + supervisor(AlexKoin.Repo, []), + # Start the endpoint when the application starts + supervisor(AlexKoinWeb.Endpoint, []), + # Start your own worker by calling: AlexKoin.Worker.start_link(arg1, arg2, arg3) + # worker(AlexKoin.Worker, [arg1, arg2, arg3]), + %{ + id: Slack.Bot, + start: { Slack.Bot, :start_link, [AlexKoin.SlackRtm, [], slack_token] } + } + ] + + # See https://hexdocs.pm/elixir/Supervisor.html + # for other strategies and supported options + opts = [strategy: :one_for_one, name: AlexKoin.Supervisor] + Supervisor.start_link(children, opts) + end + + # Tell Phoenix to update the endpoint configuration + # whenever the application is updated. + def config_change(changed, _new, removed) do + AlexKoinWeb.Endpoint.config_change(changed, removed) + :ok + end +end diff --git a/lib/alex_koin/coins/coin.ex b/lib/alex_koin/coins/coin.ex new file mode 100644 index 0000000..226c67f --- /dev/null +++ b/lib/alex_koin/coins/coin.ex @@ -0,0 +1,21 @@ +defmodule AlexKoin.Coins.Coin do + use Ecto.Schema + import Ecto.Changeset + + + schema "coins" do + field :hash, :string + field :origin, :string + field :mined_by_id, :id + field :wallet_id, :id + + timestamps() + end + + @doc false + def changeset(coin, attrs) do + coin + |> cast(attrs, [:hash, :origin, :mined_by_id, :wallet_id]) + |> validate_required([:hash, :origin, :mined_by_id, :wallet_id]) + end +end diff --git a/lib/alex_koin/coins/coins.ex b/lib/alex_koin/coins/coins.ex new file mode 100644 index 0000000..fc35b13 --- /dev/null +++ b/lib/alex_koin/coins/coins.ex @@ -0,0 +1,104 @@ +defmodule AlexKoin.Coins do + @moduledoc """ + The Coins context. + """ + + import Ecto.Query, warn: false + alias AlexKoin.Repo + + alias AlexKoin.Coins.Coin + + @doc """ + Returns the list of coins. + + ## Examples + + iex> list_coins() + [%Coin{}, ...] + + """ + def list_coins do + Repo.all(Coin) + end + + @doc """ + Gets a single coin. + + Raises `Ecto.NoResultsError` if the Coin does not exist. + + ## Examples + + iex> get_coin!(123) + %Coin{} + + iex> get_coin!(456) + ** (Ecto.NoResultsError) + + """ + def get_coin!(id), do: Repo.get!(Coin, id) + + @doc """ + Creates a coin. + + ## Examples + + iex> create_coin(%{field: value}) + {:ok, %Coin{}} + + iex> create_coin(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_coin(attrs \\ %{}) do + %Coin{} + |> Coin.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a coin. + + ## Examples + + iex> update_coin(coin, %{field: new_value}) + {:ok, %Coin{}} + + iex> update_coin(coin, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_coin(%Coin{} = coin, attrs) do + coin + |> Coin.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a Coin. + + ## Examples + + iex> delete_coin(coin) + {:ok, %Coin{}} + + iex> delete_coin(coin) + {:error, %Ecto.Changeset{}} + + """ + def delete_coin(%Coin{} = coin) do + Repo.delete(coin) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking coin changes. + + ## Examples + + iex> change_coin(coin) + %Ecto.Changeset{source: %Coin{}} + + """ + def change_coin(%Coin{} = coin) do + Coin.changeset(coin, %{}) + end +end diff --git a/lib/alex_koin/repo.ex b/lib/alex_koin/repo.ex new file mode 100644 index 0000000..d487357 --- /dev/null +++ b/lib/alex_koin/repo.ex @@ -0,0 +1,11 @@ +defmodule AlexKoin.Repo do + use Ecto.Repo, otp_app: :alex_koin + + @doc """ + Dynamically loads the repository url from the + DATABASE_URL environment variable. + """ + def init(_, opts) do + {:ok, Keyword.put(opts, :url, System.get_env("DATABASE_URL"))} + end +end diff --git a/lib/alex_koin/slack.ex b/lib/alex_koin/slack.ex new file mode 100644 index 0000000..021e259 --- /dev/null +++ b/lib/alex_koin/slack.ex @@ -0,0 +1,94 @@ +defmodule AlexKoin.SlackRtm do + use Slack + + alias AlexKoin.SlackCommands + + def handle_connect(slack, state) do + IO.puts "Connected as #{slack.me.name}" + {:ok, state} + end + + def handle_event(message = %{type: "message"}, slack, state) do + bot_user_id = "UC37P4L3Y" + admin_id = "U8BBZEB35" + + if Map.has_key?(message, :text) && Map.has_key?(message, :user) && String.starts_with?(message.text, "<@#{bot_user_id}>") do + text_with_bot = message.text + "<@UC37P4L3Y> " <> text = text_with_bot + user = SlackCommands.get_or_create(message.user) + user_wallet = AlexKoin.Account.Wallet |> AlexKoin.Repo.get_by(user_id: user.id) + is_admin = user.slack_id == admin_id + + IO.puts inspect(message) + + if text =~ "my balance" do + msg_ts = case Map.has_key?(message, :thread_ts) do + true -> message.thread_ts + false -> message.ts + end + + IO.puts "#{user.id} is asking about their balance" + + balance = user_wallet |> SlackCommands.get_balance + + msg = "You have #{balance}:akc:." + + %{ + type: "message", + text: msg, + channel: message.channel, + thread_ts: msg_ts + } + |> Poison.encode!() + |> send_raw(slack) + end + + if text =~ "create koin" && is_admin do + "create koin " <> reason = text + coin = user |> SlackCommands.create_coin(reason) + + send_message("Created a new coin: `#{coin.hash}` with origin: '#{coin.origin}'", message.channel, slack) + end + + if text =~ "transfer" do + regex = ~r/transfer (?[0-9a-zA-Z-]+) to <@(?[A-Z0-9]+)> (?.*)/ + captures = Regex.named_captures(regex, text) + IO.puts inspect(captures) + + to_user = SlackCommands.get_or_create(captures["to_slack_id"]) + coin = AlexKoin.Coins.Coin |> AlexKoin.Repo.get_by(hash: captures["coin_uuid"]) + + # validate this coin belongs to the user currently + if coin.wallet_id == user_wallet.id do + SlackCommands.transfer_coin(coin, user, to_user, captures["memo"]) + + send_message("Transfered coin.", message.channel, slack) + else + send_message("You don't own that coin.", message.channel, slack) + end + end + + if text =~ "list koins" do + SlackCommands.get_coins(user_wallet) + end + + if text =~ "leaderboard" do + end + end + + #send_message("I got a message!", message.channel, slack) + {:ok, state} + end + + def handle_event(_, _, state), do: {:ok, state} + + def handle_info({:message, text, channel}, slack, state) do + IO.puts "Sending your message, captain!" + + send_message(text, channel, slack) + + {:ok, state} + end + + def handle_info(_, _, state), do: {:ok, state} +end diff --git a/lib/alex_koin/slack_commands.ex b/lib/alex_koin/slack_commands.ex new file mode 100644 index 0000000..94e5baa --- /dev/null +++ b/lib/alex_koin/slack_commands.ex @@ -0,0 +1,75 @@ +defmodule AlexKoin.SlackCommands do + alias AlexKoin.Repo + alias AlexKoin.Account.User + alias AlexKoin.Account.Wallet + alias AlexKoin.Account.Transaction + alias AlexKoin.Coins.Coin + + @admin_user_id "U8BBZEB35" + + def get_or_create(slack_id) do + case User |> Repo.get_by(slack_id: slack_id) do + nil -> + new_user = %User{ slack_id: slack_id } + {:ok, user_obj} = Repo.insert(new_user) + + new_wallet = %Wallet{ user_id: user_obj.id, balance: 0.0 } + {:ok, _wallet} = Repo.insert(new_wallet) + + user_obj + db_user -> + db_user + end + end + + def get_balance(wallet) do + wallet.balance + end + + def get_coins(wallet) do + end + + def create_coin(user, reason) do + user_wallet = Wallet |> Repo.get_by(user_id: user.id) + + new_coin = %Coin{ + origin: reason, + mined_by_id: user.id, + hash: UUID.uuid1(), + wallet_id: user_wallet.id + } + + {:ok, coin} = Repo.insert(new_coin) + + # Now we create the initial transaction to set the coin up + transfer_coin(coin, user, user, "Initial creation.") + + coin + end + + def transfer_coin(coin, from, to_user, memo) do + new_txn = %Transaction{ + amount: 1.0, + memo: memo, + from_id: from.id, + to_id: to_user.id, + coin_id: coin.id + } + + {:ok, txn} = Repo.insert(new_txn) + + # update the balance + to_wallet = Wallet |> Repo.get_by(user_id: to_user.id) + + AlexKoin.Account.update_wallet(to_wallet, %{balance: to_wallet.balance + 1}) + AlexKoin.Coins.update_coin(coin, %{wallet_id: to_wallet.id}) + + if from.id != to_user.id do + from_wallet = Wallet |> Repo.get_by(user_id: from.id) + + AlexKoin.Account.update_wallet(from_wallet, %{balance: from_wallet.balance - 1}) + end + + {:ok, txn} + end +end diff --git a/lib/alex_koin_web.ex b/lib/alex_koin_web.ex new file mode 100644 index 0000000..3cd75e9 --- /dev/null +++ b/lib/alex_koin_web.ex @@ -0,0 +1,64 @@ +defmodule AlexKoinWeb do + @moduledoc """ + The entrypoint for defining your web interface, such + as controllers, views, channels and so on. + + This can be used in your application as: + + use AlexKoinWeb, :controller + use AlexKoinWeb, :view + + The definitions below will be executed for every view, + controller, etc, so keep them short and clean, focused + on imports, uses and aliases. + + Do NOT define functions inside the quoted expressions + below. Instead, define any helper function in modules + and import those modules here. + """ + + def controller do + quote do + use Phoenix.Controller, namespace: AlexKoinWeb + import Plug.Conn + import AlexKoinWeb.Router.Helpers + import AlexKoinWeb.Gettext + end + end + + def view do + quote do + use Phoenix.View, root: "lib/alex_koin_web/templates", + namespace: AlexKoinWeb + + # Import convenience functions from controllers + import Phoenix.Controller, only: [get_flash: 2, view_module: 1] + + import AlexKoinWeb.Router.Helpers + import AlexKoinWeb.ErrorHelpers + import AlexKoinWeb.Gettext + end + end + + def router do + quote do + use Phoenix.Router + import Plug.Conn + import Phoenix.Controller + end + end + + def channel do + quote do + use Phoenix.Channel + import AlexKoinWeb.Gettext + end + end + + @doc """ + When used, dispatch to the appropriate controller/view/etc. + """ + defmacro __using__(which) when is_atom(which) do + apply(__MODULE__, which, []) + end +end diff --git a/lib/alex_koin_web/channels/user_socket.ex b/lib/alex_koin_web/channels/user_socket.ex new file mode 100644 index 0000000..eebd5ed --- /dev/null +++ b/lib/alex_koin_web/channels/user_socket.ex @@ -0,0 +1,37 @@ +defmodule AlexKoinWeb.UserSocket do + use Phoenix.Socket + + ## Channels + # channel "room:*", AlexKoinWeb.RoomChannel + + ## Transports + transport :websocket, Phoenix.Transports.WebSocket + # transport :longpoll, Phoenix.Transports.LongPoll + + # Socket params are passed from the client and can + # be used to verify and authenticate a user. After + # verification, you can put default assigns into + # the socket that will be set for all channels, ie + # + # {:ok, assign(socket, :user_id, verified_user_id)} + # + # To deny connection, return `:error`. + # + # See `Phoenix.Token` documentation for examples in + # performing token verification on connect. + def connect(_params, socket) do + {:ok, socket} + end + + # Socket id's are topics that allow you to identify all sockets for a given user: + # + # def id(socket), do: "user_socket:#{socket.assigns.user_id}" + # + # Would allow you to broadcast a "disconnect" event and terminate + # all active sockets and channels for a given user: + # + # AlexKoinWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{}) + # + # Returning `nil` makes this socket anonymous. + def id(_socket), do: nil +end diff --git a/lib/alex_koin_web/controllers/coin_controller.ex b/lib/alex_koin_web/controllers/coin_controller.ex new file mode 100644 index 0000000..63a89e9 --- /dev/null +++ b/lib/alex_koin_web/controllers/coin_controller.ex @@ -0,0 +1,42 @@ +defmodule AlexKoinWeb.CoinController do + use AlexKoinWeb, :controller + + alias AlexKoin.Coins + alias AlexKoin.Coins.Coin + + action_fallback AlexKoinWeb.FallbackController + + def index(conn, _params) do + coins = Coins.list_coins() + render(conn, "index.json", coins: coins) + end + + def create(conn, %{"coin" => coin_params}) do + with {:ok, %Coin{} = coin} <- Coins.create_coin(coin_params) do + conn + |> put_status(:created) + |> put_resp_header("location", coin_path(conn, :show, coin)) + |> render("show.json", coin: coin) + end + end + + def show(conn, %{"id" => id}) do + coin = Coins.get_coin!(id) + render(conn, "show.json", coin: coin) + end + + def update(conn, %{"id" => id, "coin" => coin_params}) do + coin = Coins.get_coin!(id) + + with {:ok, %Coin{} = coin} <- Coins.update_coin(coin, coin_params) do + render(conn, "show.json", coin: coin) + end + end + + def delete(conn, %{"id" => id}) do + coin = Coins.get_coin!(id) + with {:ok, %Coin{}} <- Coins.delete_coin(coin) do + send_resp(conn, :no_content, "") + end + end +end diff --git a/lib/alex_koin_web/controllers/fallback_controller.ex b/lib/alex_koin_web/controllers/fallback_controller.ex new file mode 100644 index 0000000..9312b7a --- /dev/null +++ b/lib/alex_koin_web/controllers/fallback_controller.ex @@ -0,0 +1,20 @@ +defmodule AlexKoinWeb.FallbackController do + @moduledoc """ + Translates controller action results into valid `Plug.Conn` responses. + + See `Phoenix.Controller.action_fallback/1` for more details. + """ + use AlexKoinWeb, :controller + + def call(conn, {:error, %Ecto.Changeset{} = changeset}) do + conn + |> put_status(:unprocessable_entity) + |> render(AlexKoinWeb.ChangesetView, "error.json", changeset: changeset) + end + + def call(conn, {:error, :not_found}) do + conn + |> put_status(:not_found) + |> render(AlexKoinWeb.ErrorView, :"404") + end +end diff --git a/lib/alex_koin_web/controllers/transaction_controller.ex b/lib/alex_koin_web/controllers/transaction_controller.ex new file mode 100644 index 0000000..8caa89f --- /dev/null +++ b/lib/alex_koin_web/controllers/transaction_controller.ex @@ -0,0 +1,42 @@ +defmodule AlexKoinWeb.TransactionController do + use AlexKoinWeb, :controller + + alias AlexKoin.Account + alias AlexKoin.Account.Transaction + + action_fallback AlexKoinWeb.FallbackController + + def index(conn, _params) do + transactions = Account.list_transactions() + render(conn, "index.json", transactions: transactions) + end + + def create(conn, %{"transaction" => transaction_params}) do + with {:ok, %Transaction{} = transaction} <- Account.create_transaction(transaction_params) do + conn + |> put_status(:created) + |> put_resp_header("location", transaction_path(conn, :show, transaction)) + |> render("show.json", transaction: transaction) + end + end + + def show(conn, %{"id" => id}) do + transaction = Account.get_transaction!(id) + render(conn, "show.json", transaction: transaction) + end + + def update(conn, %{"id" => id, "transaction" => transaction_params}) do + transaction = Account.get_transaction!(id) + + with {:ok, %Transaction{} = transaction} <- Account.update_transaction(transaction, transaction_params) do + render(conn, "show.json", transaction: transaction) + end + end + + def delete(conn, %{"id" => id}) do + transaction = Account.get_transaction!(id) + with {:ok, %Transaction{}} <- Account.delete_transaction(transaction) do + send_resp(conn, :no_content, "") + end + end +end diff --git a/lib/alex_koin_web/controllers/user_controller.ex b/lib/alex_koin_web/controllers/user_controller.ex new file mode 100644 index 0000000..6df5428 --- /dev/null +++ b/lib/alex_koin_web/controllers/user_controller.ex @@ -0,0 +1,42 @@ +defmodule AlexKoinWeb.UserController do + use AlexKoinWeb, :controller + + alias AlexKoin.Account + alias AlexKoin.Account.User + + action_fallback AlexKoinWeb.FallbackController + + def index(conn, _params) do + users = Account.list_users() + render(conn, "index.json", users: users) + end + + def create(conn, %{"user" => user_params}) do + with {:ok, %User{} = user} <- Account.create_user(user_params) do + conn + |> put_status(:created) + |> put_resp_header("location", user_path(conn, :show, user)) + |> render("show.json", user: user) + end + end + + def show(conn, %{"id" => id}) do + user = Account.get_user!(id) + render(conn, "show.json", user: user) + end + + def update(conn, %{"id" => id, "user" => user_params}) do + user = Account.get_user!(id) + + with {:ok, %User{} = user} <- Account.update_user(user, user_params) do + render(conn, "show.json", user: user) + end + end + + def delete(conn, %{"id" => id}) do + user = Account.get_user!(id) + with {:ok, %User{}} <- Account.delete_user(user) do + send_resp(conn, :no_content, "") + end + end +end diff --git a/lib/alex_koin_web/controllers/wallet_controller.ex b/lib/alex_koin_web/controllers/wallet_controller.ex new file mode 100644 index 0000000..775606c --- /dev/null +++ b/lib/alex_koin_web/controllers/wallet_controller.ex @@ -0,0 +1,42 @@ +defmodule AlexKoinWeb.WalletController do + use AlexKoinWeb, :controller + + alias AlexKoin.Account + alias AlexKoin.Account.Wallet + + action_fallback AlexKoinWeb.FallbackController + + def index(conn, _params) do + wallets = Account.list_wallets() + render(conn, "index.json", wallets: wallets) + end + + def create(conn, %{"wallet" => wallet_params}) do + with {:ok, %Wallet{} = wallet} <- Account.create_wallet(wallet_params) do + conn + |> put_status(:created) + |> put_resp_header("location", wallet_path(conn, :show, wallet)) + |> render("show.json", wallet: wallet) + end + end + + def show(conn, %{"id" => id}) do + wallet = Account.get_wallet!(id) + render(conn, "show.json", wallet: wallet) + end + + def update(conn, %{"id" => id, "wallet" => wallet_params}) do + wallet = Account.get_wallet!(id) + + with {:ok, %Wallet{} = wallet} <- Account.update_wallet(wallet, wallet_params) do + render(conn, "show.json", wallet: wallet) + end + end + + def delete(conn, %{"id" => id}) do + wallet = Account.get_wallet!(id) + with {:ok, %Wallet{}} <- Account.delete_wallet(wallet) do + send_resp(conn, :no_content, "") + end + end +end diff --git a/lib/alex_koin_web/endpoint.ex b/lib/alex_koin_web/endpoint.ex new file mode 100644 index 0000000..d03594f --- /dev/null +++ b/lib/alex_koin_web/endpoint.ex @@ -0,0 +1,54 @@ +defmodule AlexKoinWeb.Endpoint do + use Phoenix.Endpoint, otp_app: :alex_koin + + socket "/socket", AlexKoinWeb.UserSocket + + # Serve at "/" the static files from "priv/static" directory. + # + # You should set gzip to true if you are running phoenix.digest + # when deploying your static files in production. + plug Plug.Static, + at: "/", from: :alex_koin, gzip: false, + only: ~w(css fonts images js favicon.ico robots.txt) + + # Code reloading can be explicitly enabled under the + # :code_reloader configuration of your endpoint. + if code_reloading? do + plug Phoenix.CodeReloader + end + + plug Plug.Logger + + plug Plug.Parsers, + parsers: [:urlencoded, :multipart, :json], + pass: ["*/*"], + json_decoder: Poison + + plug Plug.MethodOverride + plug Plug.Head + + # The session will be stored in the cookie and signed, + # this means its contents can be read but not tampered with. + # Set :encryption_salt if you would also like to encrypt it. + plug Plug.Session, + store: :cookie, + key: "_alex_koin_key", + signing_salt: "H+j0RtL2" + + plug AlexKoinWeb.Router + + @doc """ + Callback invoked for dynamically configuring the endpoint. + + It receives the endpoint configuration and checks if + configuration should be loaded from the system environment. + """ + def init(_key, config) do + if config[:load_from_system_env] do + port = System.get_env("PORT") || raise "expected the PORT environment variable to be set" + {:ok, Keyword.put(config, :http, [:inet6, port: port])} + else + {:ok, config} + end + end +end diff --git a/lib/alex_koin_web/gettext.ex b/lib/alex_koin_web/gettext.ex new file mode 100644 index 0000000..e4c6a1c --- /dev/null +++ b/lib/alex_koin_web/gettext.ex @@ -0,0 +1,24 @@ +defmodule AlexKoinWeb.Gettext do + @moduledoc """ + A module providing Internationalization with a gettext-based API. + + By using [Gettext](https://hexdocs.pm/gettext), + your module gains a set of macros for translations, for example: + + import AlexKoinWeb.Gettext + + # Simple translation + gettext "Here is the string to translate" + + # Plural translation + ngettext "Here is the string to translate", + "Here are the strings to translate", + 3 + + # Domain-based translation + dgettext "errors", "Here is the error message to translate" + + See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage. + """ + use Gettext, otp_app: :alex_koin +end diff --git a/lib/alex_koin_web/router.ex b/lib/alex_koin_web/router.ex new file mode 100644 index 0000000..ccd244c --- /dev/null +++ b/lib/alex_koin_web/router.ex @@ -0,0 +1,16 @@ +defmodule AlexKoinWeb.Router do + use AlexKoinWeb, :router + + pipeline :api do + plug :accepts, ["json"] + end + + scope "/api", AlexKoinWeb do + pipe_through :api + + resources "/users", UserController, except: [:new, :edit] + resources "/wallets", WalletController, except: [:new, :edit] + resources "/transactions", TransactionController, except: [:new, :edit] + resources "/coins", CoinController, except: [:new, :edit] + end +end diff --git a/lib/alex_koin_web/views/changeset_view.ex b/lib/alex_koin_web/views/changeset_view.ex new file mode 100644 index 0000000..26f292a --- /dev/null +++ b/lib/alex_koin_web/views/changeset_view.ex @@ -0,0 +1,19 @@ +defmodule AlexKoinWeb.ChangesetView do + use AlexKoinWeb, :view + + @doc """ + Traverses and translates changeset errors. + + See `Ecto.Changeset.traverse_errors/2` and + `AlexKoinWeb.ErrorHelpers.translate_error/1` for more details. + """ + def translate_errors(changeset) do + Ecto.Changeset.traverse_errors(changeset, &translate_error/1) + end + + def render("error.json", %{changeset: changeset}) do + # When encoded, the changeset returns its errors + # as a JSON object. So we just pass it forward. + %{errors: translate_errors(changeset)} + end +end diff --git a/lib/alex_koin_web/views/coin_view.ex b/lib/alex_koin_web/views/coin_view.ex new file mode 100644 index 0000000..ca55eae --- /dev/null +++ b/lib/alex_koin_web/views/coin_view.ex @@ -0,0 +1,18 @@ +defmodule AlexKoinWeb.CoinView do + use AlexKoinWeb, :view + alias AlexKoinWeb.CoinView + + def render("index.json", %{coins: coins}) do + %{data: render_many(coins, CoinView, "coin.json")} + end + + def render("show.json", %{coin: coin}) do + %{data: render_one(coin, CoinView, "coin.json")} + end + + def render("coin.json", %{coin: coin}) do + %{id: coin.id, + hash: coin.hash, + origin: coin.origin} + end +end diff --git a/lib/alex_koin_web/views/error_helpers.ex b/lib/alex_koin_web/views/error_helpers.ex new file mode 100644 index 0000000..11ed9b8 --- /dev/null +++ b/lib/alex_koin_web/views/error_helpers.ex @@ -0,0 +1,33 @@ +defmodule AlexKoinWeb.ErrorHelpers do + @moduledoc """ + Conveniences for translating and building error messages. + """ + + @doc """ + Translates an error message using gettext. + """ + def translate_error({msg, opts}) do + # When using gettext, we typically pass the strings we want + # to translate as a static argument: + # + # # Translate "is invalid" in the "errors" domain + # dgettext "errors", "is invalid" + # + # # Translate the number of files with plural rules + # dngettext "errors", "1 file", "%{count} files", count + # + # Because the error messages we show in our forms and APIs + # are defined inside Ecto, we need to translate them dynamically. + # This requires us to call the Gettext module passing our gettext + # backend as first argument. + # + # Note we use the "errors" domain, which means translations + # should be written to the errors.po file. The :count option is + # set by Ecto and indicates we should also apply plural rules. + if count = opts[:count] do + Gettext.dngettext(AlexKoinWeb.Gettext, "errors", msg, msg, count, opts) + else + Gettext.dgettext(AlexKoinWeb.Gettext, "errors", msg, opts) + end + end +end diff --git a/lib/alex_koin_web/views/error_view.ex b/lib/alex_koin_web/views/error_view.ex new file mode 100644 index 0000000..cc7efae --- /dev/null +++ b/lib/alex_koin_web/views/error_view.ex @@ -0,0 +1,16 @@ +defmodule AlexKoinWeb.ErrorView do + use AlexKoinWeb, :view + + # If you want to customize a particular status code + # for a certain format, you may uncomment below. + # def render("500.json", _assigns) do + # %{errors: %{detail: "Internal Server Error"}} + # end + + # By default, Phoenix returns the status message from + # the template name. For example, "404.json" becomes + # "Not Found". + def template_not_found(template, _assigns) do + %{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}} + end +end diff --git a/lib/alex_koin_web/views/transaction_view.ex b/lib/alex_koin_web/views/transaction_view.ex new file mode 100644 index 0000000..753712b --- /dev/null +++ b/lib/alex_koin_web/views/transaction_view.ex @@ -0,0 +1,18 @@ +defmodule AlexKoinWeb.TransactionView do + use AlexKoinWeb, :view + alias AlexKoinWeb.TransactionView + + def render("index.json", %{transactions: transactions}) do + %{data: render_many(transactions, TransactionView, "transaction.json")} + end + + def render("show.json", %{transaction: transaction}) do + %{data: render_one(transaction, TransactionView, "transaction.json")} + end + + def render("transaction.json", %{transaction: transaction}) do + %{id: transaction.id, + amount: transaction.amount, + memo: transaction.memo} + end +end diff --git a/lib/alex_koin_web/views/user_view.ex b/lib/alex_koin_web/views/user_view.ex new file mode 100644 index 0000000..29e2ea5 --- /dev/null +++ b/lib/alex_koin_web/views/user_view.ex @@ -0,0 +1,20 @@ +defmodule AlexKoinWeb.UserView do + use AlexKoinWeb, :view + alias AlexKoinWeb.UserView + + def render("index.json", %{users: users}) do + %{data: render_many(users, UserView, "user.json")} + end + + def render("show.json", %{user: user}) do + %{data: render_one(user, UserView, "user.json")} + end + + def render("user.json", %{user: user}) do + %{id: user.id, + email: user.email, + first_name: user.first_name, + last_name: user.last_name, + slack_id: user.slack_id} + end +end diff --git a/lib/alex_koin_web/views/wallet_view.ex b/lib/alex_koin_web/views/wallet_view.ex new file mode 100644 index 0000000..24d6fef --- /dev/null +++ b/lib/alex_koin_web/views/wallet_view.ex @@ -0,0 +1,17 @@ +defmodule AlexKoinWeb.WalletView do + use AlexKoinWeb, :view + alias AlexKoinWeb.WalletView + + def render("index.json", %{wallets: wallets}) do + %{data: render_many(wallets, WalletView, "wallet.json")} + end + + def render("show.json", %{wallet: wallet}) do + %{data: render_one(wallet, WalletView, "wallet.json")} + end + + def render("wallet.json", %{wallet: wallet}) do + %{id: wallet.id, + balance: wallet.balance} + end +end diff --git a/mix.exs b/mix.exs new file mode 100644 index 0000000..90db851 --- /dev/null +++ b/mix.exs @@ -0,0 +1,60 @@ +defmodule AlexKoin.Mixfile do + use Mix.Project + + def project do + [ + app: :alex_koin, + version: "0.0.1", + elixir: "~> 1.4", + elixirc_paths: elixirc_paths(Mix.env), + compilers: [:phoenix, :gettext] ++ Mix.compilers, + start_permanent: Mix.env == :prod, + aliases: aliases(), + deps: deps() + ] + end + + # Configuration for the OTP application. + # + # Type `mix help compile.app` for more information. + def application do + [ + mod: {AlexKoin.Application, []}, + extra_applications: [:logger, :runtime_tools] + ] + end + + # Specifies which paths to compile per environment. + defp elixirc_paths(:test), do: ["lib", "test/support"] + defp elixirc_paths(_), do: ["lib"] + + # Specifies your project dependencies. + # + # Type `mix help deps` for examples and options. + defp deps do + [ + {:phoenix, "~> 1.3.3"}, + {:phoenix_pubsub, "~> 1.0"}, + {:phoenix_ecto, "~> 3.2"}, + {:postgrex, ">= 0.0.0"}, + {:gettext, "~> 0.11"}, + {:cowboy, "~> 1.0"}, + {:slack, "~> 0.14.0"}, + {:elixir_uuid, "~> 1.2"} + ] + end + + # Aliases are shortcuts or tasks specific to the current project. + # For example, to create, migrate and run the seeds file at once: + # + # $ mix ecto.setup + # + # See the documentation for `Mix` for more info on aliases. + defp aliases do + [ + "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"], + "ecto.reset": ["ecto.drop", "ecto.setup"], + "test": ["ecto.create --quiet", "ecto.migrate", "test"] + ] + end +end diff --git a/mix.lock b/mix.lock new file mode 100644 index 0000000..23d3358 --- /dev/null +++ b/mix.lock @@ -0,0 +1,30 @@ +%{ + "certifi": {:hex, :certifi, "2.3.1", "d0f424232390bf47d82da8478022301c561cf6445b5b5fb6a84d49a9e76d2639", [:rebar3], [{:parse_trans, "3.2.0", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, + "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"}, + "cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, + "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], [], "hexpm"}, + "db_connection": {:hex, :db_connection, "1.1.3", "89b30ca1ef0a3b469b1c779579590688561d586694a3ce8792985d4d7e575a61", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, + "decimal": {:hex, :decimal, "1.5.0", "b0433a36d0e2430e3d50291b1c65f53c37d56f83665b43d79963684865beab68", [:mix], [], "hexpm"}, + "ecto": {:hex, :ecto, "2.2.10", "e7366dc82f48f8dd78fcbf3ab50985ceeb11cb3dc93435147c6e13f2cda0992e", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, + "elixir_uuid": {:hex, :elixir_uuid, "1.2.0", "ff26e938f95830b1db152cb6e594d711c10c02c6391236900ddd070a6b01271d", [:mix], [], "hexpm"}, + "gettext": {:hex, :gettext, "0.15.0", "40a2b8ce33a80ced7727e36768499fc9286881c43ebafccae6bab731e2b2b8ce", [:mix], [], "hexpm"}, + "hackney": {:hex, :hackney, "1.13.0", "24edc8cd2b28e1c652593833862435c80661834f6c9344e84b6a2255e7aeef03", [:rebar3], [{:certifi, "2.3.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.2", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, + "httpoison": {:hex, :httpoison, "0.13.0", "bfaf44d9f133a6599886720f3937a7699466d23bb0cd7a88b6ba011f53c6f562", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, + "idna": {:hex, :idna, "5.1.2", "e21cb58a09f0228a9e0b95eaa1217f1bcfc31a1aaa6e1fdf2f53a33f7dbd9494", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, + "mime": {:hex, :mime, "1.3.0", "5e8d45a39e95c650900d03f897fbf99ae04f60ab1daa4a34c7a20a5151b7a5fe", [:mix], [], "hexpm"}, + "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"}, + "parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"}, + "phoenix": {:hex, :phoenix, "1.3.3", "bafb5fa408d202e8d9f739e781bdb908446a2c1c1e00797c1158918ed55566a4", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, + "phoenix_ecto": {:hex, :phoenix_ecto, "3.3.0", "702f6e164512853d29f9d20763493f2b3bcfcb44f118af2bc37bb95d0801b480", [:mix], [{:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.2", "bfa7fd52788b5eaa09cb51ff9fcad1d9edfeb68251add458523f839392f034c1", [:mix], [], "hexpm"}, + "plug": {:hex, :plug, "1.6.1", "c62fe7623d035020cf989820b38490460e6903ab7eee29e234b7586e9b6c91d6", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"}, + "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, + "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], [], "hexpm"}, + "postgrex": {:hex, :postgrex, "0.13.5", "3d931aba29363e1443da167a4b12f06dcd171103c424de15e5f3fc2ba3e6d9c5", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"}, + "ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"}, + "slack": {:hex, :slack, "0.14.0", "15ef6fc57706380075e241f97a24adfc0f01512fc8d5ee32c43d76c43dbeaa2f", [:mix], [{:httpoison, "~> 0.11", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}, {:websocket_client, "~> 1.2.4", [hex: :websocket_client, repo: "hexpm", optional: false]}], "hexpm"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"}, + "websocket_client": {:hex, :websocket_client, "1.2.4", "14ec1ca4b6d247b44ccd9a80af8f6ca98328070f6c1d52a5cb00bc9d939d63b8", [:rebar3], [], "hexpm"}, +} diff --git a/priv/gettext/en/LC_MESSAGES/errors.po b/priv/gettext/en/LC_MESSAGES/errors.po new file mode 100644 index 0000000..a589998 --- /dev/null +++ b/priv/gettext/en/LC_MESSAGES/errors.po @@ -0,0 +1,97 @@ +## `msgid`s in this file come from POT (.pot) files. +## +## Do not add, change, or remove `msgid`s manually here as +## they're tied to the ones in the corresponding POT file +## (with the same domain). +## +## Use `mix gettext.extract --merge` or `mix gettext.merge` +## to merge POT files into PO files. +msgid "" +msgstr "" +"Language: en\n" + +## From Ecto.Changeset.cast/4 +msgid "can't be blank" +msgstr "" + +## From Ecto.Changeset.unique_constraint/3 +msgid "has already been taken" +msgstr "" + +## From Ecto.Changeset.put_change/3 +msgid "is invalid" +msgstr "" + +## From Ecto.Changeset.validate_acceptance/3 +msgid "must be accepted" +msgstr "" + +## From Ecto.Changeset.validate_format/3 +msgid "has invalid format" +msgstr "" + +## From Ecto.Changeset.validate_subset/3 +msgid "has an invalid entry" +msgstr "" + +## From Ecto.Changeset.validate_exclusion/3 +msgid "is reserved" +msgstr "" + +## From Ecto.Changeset.validate_confirmation/3 +msgid "does not match confirmation" +msgstr "" + +## From Ecto.Changeset.no_assoc_constraint/3 +msgid "is still associated with this entry" +msgstr "" + +msgid "are still associated with this entry" +msgstr "" + +## From Ecto.Changeset.validate_length/3 +msgid "should be %{count} character(s)" +msgid_plural "should be %{count} character(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should have %{count} item(s)" +msgid_plural "should have %{count} item(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should be at least %{count} character(s)" +msgid_plural "should be at least %{count} character(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should have at least %{count} item(s)" +msgid_plural "should have at least %{count} item(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should be at most %{count} character(s)" +msgid_plural "should be at most %{count} character(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should have at most %{count} item(s)" +msgid_plural "should have at most %{count} item(s)" +msgstr[0] "" +msgstr[1] "" + +## From Ecto.Changeset.validate_number/3 +msgid "must be less than %{number}" +msgstr "" + +msgid "must be greater than %{number}" +msgstr "" + +msgid "must be less than or equal to %{number}" +msgstr "" + +msgid "must be greater than or equal to %{number}" +msgstr "" + +msgid "must be equal to %{number}" +msgstr "" diff --git a/priv/gettext/errors.pot b/priv/gettext/errors.pot new file mode 100644 index 0000000..7b2d5ca --- /dev/null +++ b/priv/gettext/errors.pot @@ -0,0 +1,95 @@ +## This file is a PO Template file. +## +## `msgid`s here are often extracted from source code. +## Add new translations manually only if they're dynamic +## translations that can't be statically extracted. +## +## Run `mix gettext.extract` to bring this file up to +## date. Leave `msgstr`s empty as changing them here as no +## effect: edit them in PO (`.po`) files instead. + +## From Ecto.Changeset.cast/4 +msgid "can't be blank" +msgstr "" + +## From Ecto.Changeset.unique_constraint/3 +msgid "has already been taken" +msgstr "" + +## From Ecto.Changeset.put_change/3 +msgid "is invalid" +msgstr "" + +## From Ecto.Changeset.validate_acceptance/3 +msgid "must be accepted" +msgstr "" + +## From Ecto.Changeset.validate_format/3 +msgid "has invalid format" +msgstr "" + +## From Ecto.Changeset.validate_subset/3 +msgid "has an invalid entry" +msgstr "" + +## From Ecto.Changeset.validate_exclusion/3 +msgid "is reserved" +msgstr "" + +## From Ecto.Changeset.validate_confirmation/3 +msgid "does not match confirmation" +msgstr "" + +## From Ecto.Changeset.no_assoc_constraint/3 +msgid "is still associated with this entry" +msgstr "" + +msgid "are still associated with this entry" +msgstr "" + +## From Ecto.Changeset.validate_length/3 +msgid "should be %{count} character(s)" +msgid_plural "should be %{count} character(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should have %{count} item(s)" +msgid_plural "should have %{count} item(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should be at least %{count} character(s)" +msgid_plural "should be at least %{count} character(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should have at least %{count} item(s)" +msgid_plural "should have at least %{count} item(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should be at most %{count} character(s)" +msgid_plural "should be at most %{count} character(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should have at most %{count} item(s)" +msgid_plural "should have at most %{count} item(s)" +msgstr[0] "" +msgstr[1] "" + +## From Ecto.Changeset.validate_number/3 +msgid "must be less than %{number}" +msgstr "" + +msgid "must be greater than %{number}" +msgstr "" + +msgid "must be less than or equal to %{number}" +msgstr "" + +msgid "must be greater than or equal to %{number}" +msgstr "" + +msgid "must be equal to %{number}" +msgstr "" diff --git a/priv/repo/migrations/20180714170658_create_users.exs b/priv/repo/migrations/20180714170658_create_users.exs new file mode 100644 index 0000000..bf322e3 --- /dev/null +++ b/priv/repo/migrations/20180714170658_create_users.exs @@ -0,0 +1,15 @@ +defmodule AlexKoin.Repo.Migrations.CreateUsers do + use Ecto.Migration + + def change do + create table(:users) do + add :email, :string + add :first_name, :string + add :last_name, :string + add :slack_id, :string + + timestamps() + end + + end +end diff --git a/priv/repo/migrations/20180714174902_create_wallets.exs b/priv/repo/migrations/20180714174902_create_wallets.exs new file mode 100644 index 0000000..e66b703 --- /dev/null +++ b/priv/repo/migrations/20180714174902_create_wallets.exs @@ -0,0 +1,14 @@ +defmodule AlexKoin.Repo.Migrations.CreateWallets do + use Ecto.Migration + + def change do + create table(:wallets) do + add :balance, :float + add :user_id, references(:users, on_delete: :nothing) + + timestamps() + end + + create index(:wallets, [:user_id]) + end +end diff --git a/priv/repo/migrations/20180714182456_create_transactions.exs b/priv/repo/migrations/20180714182456_create_transactions.exs new file mode 100644 index 0000000..ff5744c --- /dev/null +++ b/priv/repo/migrations/20180714182456_create_transactions.exs @@ -0,0 +1,17 @@ +defmodule AlexKoin.Repo.Migrations.CreateTransactions do + use Ecto.Migration + + def change do + create table(:transactions) do + add :amount, :float + add :memo, :string + add :from_id, references(:wallets, on_delete: :nothing) + add :to_id, references(:wallets, on_delete: :nothing) + + timestamps() + end + + create index(:transactions, [:from_id]) + create index(:transactions, [:to_id]) + end +end diff --git a/priv/repo/migrations/20180715000610_create_coins.exs b/priv/repo/migrations/20180715000610_create_coins.exs new file mode 100644 index 0000000..ea183d4 --- /dev/null +++ b/priv/repo/migrations/20180715000610_create_coins.exs @@ -0,0 +1,15 @@ +defmodule AlexKoin.Repo.Migrations.CreateCoins do + use Ecto.Migration + + def change do + create table(:coins) do + add :hash, :string + add :origin, :string + add :mined_by_id, references(:users, on_delete: :nothing) + + timestamps() + end + + create index(:coins, [:mined_by_id]) + end +end diff --git a/priv/repo/migrations/20180715000727_add_coin_to_transaction.exs b/priv/repo/migrations/20180715000727_add_coin_to_transaction.exs new file mode 100644 index 0000000..97a1a4d --- /dev/null +++ b/priv/repo/migrations/20180715000727_add_coin_to_transaction.exs @@ -0,0 +1,9 @@ +defmodule AlexKoin.Repo.Migrations.AddCoinToTransaction do + use Ecto.Migration + + def change do + alter table(:transactions) do + add :coin_id, references(:coins) + end + end +end diff --git a/priv/repo/migrations/20180803193407_add_wallet_to_coin.exs b/priv/repo/migrations/20180803193407_add_wallet_to_coin.exs new file mode 100644 index 0000000..045d448 --- /dev/null +++ b/priv/repo/migrations/20180803193407_add_wallet_to_coin.exs @@ -0,0 +1,9 @@ +defmodule AlexKoin.Repo.Migrations.AddWalletToCoin do + use Ecto.Migration + + def change do + alter table(:coins) do + add :wallet_id, references(:wallets) + end + end +end diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs new file mode 100644 index 0000000..58f95c2 --- /dev/null +++ b/priv/repo/seeds.exs @@ -0,0 +1,11 @@ +# Script for populating the database. You can run it as: +# +# mix run priv/repo/seeds.exs +# +# Inside the script, you can read and write to any of your +# repositories directly: +# +# AlexKoin.Repo.insert!(%AlexKoin.SomeSchema{}) +# +# We recommend using the bang functions (`insert!`, `update!` +# and so on) as they will fail if something goes wrong. diff --git a/test/alex_koin/account/account_test.exs b/test/alex_koin/account/account_test.exs new file mode 100644 index 0000000..a1f7188 --- /dev/null +++ b/test/alex_koin/account/account_test.exs @@ -0,0 +1,257 @@ +defmodule AlexKoin.AccountTest do + use AlexKoin.DataCase + + alias AlexKoin.Account + + describe "users" do + alias AlexKoin.Account.User + + @valid_attrs %{email: "some email", first_name: "some first_name", last_name: "some last_name"} + @update_attrs %{email: "some updated email", first_name: "some updated first_name", last_name: "some updated last_name"} + @invalid_attrs %{email: nil, first_name: nil, last_name: nil} + + def user_fixture(attrs \\ %{}) do + {:ok, user} = + attrs + |> Enum.into(@valid_attrs) + |> Account.create_user() + + user + end + + test "list_users/0 returns all users" do + user = user_fixture() + assert Account.list_users() == [user] + end + + test "get_user!/1 returns the user with given id" do + user = user_fixture() + assert Account.get_user!(user.id) == user + end + + test "create_user/1 with valid data creates a user" do + assert {:ok, %User{} = user} = Account.create_user(@valid_attrs) + assert user.email == "some email" + assert user.first_name == "some first_name" + assert user.last_name == "some last_name" + end + + test "create_user/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Account.create_user(@invalid_attrs) + end + + test "update_user/2 with valid data updates the user" do + user = user_fixture() + assert {:ok, user} = Account.update_user(user, @update_attrs) + assert %User{} = user + assert user.email == "some updated email" + assert user.first_name == "some updated first_name" + assert user.last_name == "some updated last_name" + end + + test "update_user/2 with invalid data returns error changeset" do + user = user_fixture() + assert {:error, %Ecto.Changeset{}} = Account.update_user(user, @invalid_attrs) + assert user == Account.get_user!(user.id) + end + + test "delete_user/1 deletes the user" do + user = user_fixture() + assert {:ok, %User{}} = Account.delete_user(user) + assert_raise Ecto.NoResultsError, fn -> Account.get_user!(user.id) end + end + + test "change_user/1 returns a user changeset" do + user = user_fixture() + assert %Ecto.Changeset{} = Account.change_user(user) + end + end + + describe "wallets" do + alias AlexKoin.Account.Wallet + + @valid_attrs %{balance: 120.5} + @update_attrs %{balance: 456.7} + @invalid_attrs %{balance: nil} + + def wallet_fixture(attrs \\ %{}) do + {:ok, wallet} = + attrs + |> Enum.into(@valid_attrs) + |> Account.create_wallet() + + wallet + end + + test "list_wallets/0 returns all wallets" do + wallet = wallet_fixture() + assert Account.list_wallets() == [wallet] + end + + test "get_wallet!/1 returns the wallet with given id" do + wallet = wallet_fixture() + assert Account.get_wallet!(wallet.id) == wallet + end + + test "create_wallet/1 with valid data creates a wallet" do + assert {:ok, %Wallet{} = wallet} = Account.create_wallet(@valid_attrs) + assert wallet.balance == 120.5 + end + + test "create_wallet/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Account.create_wallet(@invalid_attrs) + end + + test "update_wallet/2 with valid data updates the wallet" do + wallet = wallet_fixture() + assert {:ok, wallet} = Account.update_wallet(wallet, @update_attrs) + assert %Wallet{} = wallet + assert wallet.balance == 456.7 + end + + test "update_wallet/2 with invalid data returns error changeset" do + wallet = wallet_fixture() + assert {:error, %Ecto.Changeset{}} = Account.update_wallet(wallet, @invalid_attrs) + assert wallet == Account.get_wallet!(wallet.id) + end + + test "delete_wallet/1 deletes the wallet" do + wallet = wallet_fixture() + assert {:ok, %Wallet{}} = Account.delete_wallet(wallet) + assert_raise Ecto.NoResultsError, fn -> Account.get_wallet!(wallet.id) end + end + + test "change_wallet/1 returns a wallet changeset" do + wallet = wallet_fixture() + assert %Ecto.Changeset{} = Account.change_wallet(wallet) + end + end + + describe "users" do + alias AlexKoin.Account.User + + @valid_attrs %{email: "some email", first_name: "some first_name", last_name: "some last_name", slack_id: "some slack_id"} + @update_attrs %{email: "some updated email", first_name: "some updated first_name", last_name: "some updated last_name", slack_id: "some updated slack_id"} + @invalid_attrs %{email: nil, first_name: nil, last_name: nil, slack_id: nil} + + def user_fixture(attrs \\ %{}) do + {:ok, user} = + attrs + |> Enum.into(@valid_attrs) + |> Account.create_user() + + user + end + + test "list_users/0 returns all users" do + user = user_fixture() + assert Account.list_users() == [user] + end + + test "get_user!/1 returns the user with given id" do + user = user_fixture() + assert Account.get_user!(user.id) == user + end + + test "create_user/1 with valid data creates a user" do + assert {:ok, %User{} = user} = Account.create_user(@valid_attrs) + assert user.email == "some email" + assert user.first_name == "some first_name" + assert user.last_name == "some last_name" + assert user.slack_id == "some slack_id" + end + + test "create_user/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Account.create_user(@invalid_attrs) + end + + test "update_user/2 with valid data updates the user" do + user = user_fixture() + assert {:ok, user} = Account.update_user(user, @update_attrs) + assert %User{} = user + assert user.email == "some updated email" + assert user.first_name == "some updated first_name" + assert user.last_name == "some updated last_name" + assert user.slack_id == "some updated slack_id" + end + + test "update_user/2 with invalid data returns error changeset" do + user = user_fixture() + assert {:error, %Ecto.Changeset{}} = Account.update_user(user, @invalid_attrs) + assert user == Account.get_user!(user.id) + end + + test "delete_user/1 deletes the user" do + user = user_fixture() + assert {:ok, %User{}} = Account.delete_user(user) + assert_raise Ecto.NoResultsError, fn -> Account.get_user!(user.id) end + end + + test "change_user/1 returns a user changeset" do + user = user_fixture() + assert %Ecto.Changeset{} = Account.change_user(user) + end + end + + describe "transactions" do + alias AlexKoin.Account.Transaction + + @valid_attrs %{amount: 120.5, memo: "some memo"} + @update_attrs %{amount: 456.7, memo: "some updated memo"} + @invalid_attrs %{amount: nil, memo: nil} + + def transaction_fixture(attrs \\ %{}) do + {:ok, transaction} = + attrs + |> Enum.into(@valid_attrs) + |> Account.create_transaction() + + transaction + end + + test "list_transactions/0 returns all transactions" do + transaction = transaction_fixture() + assert Account.list_transactions() == [transaction] + end + + test "get_transaction!/1 returns the transaction with given id" do + transaction = transaction_fixture() + assert Account.get_transaction!(transaction.id) == transaction + end + + test "create_transaction/1 with valid data creates a transaction" do + assert {:ok, %Transaction{} = transaction} = Account.create_transaction(@valid_attrs) + assert transaction.amount == 120.5 + assert transaction.memo == "some memo" + end + + test "create_transaction/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Account.create_transaction(@invalid_attrs) + end + + test "update_transaction/2 with valid data updates the transaction" do + transaction = transaction_fixture() + assert {:ok, transaction} = Account.update_transaction(transaction, @update_attrs) + assert %Transaction{} = transaction + assert transaction.amount == 456.7 + assert transaction.memo == "some updated memo" + end + + test "update_transaction/2 with invalid data returns error changeset" do + transaction = transaction_fixture() + assert {:error, %Ecto.Changeset{}} = Account.update_transaction(transaction, @invalid_attrs) + assert transaction == Account.get_transaction!(transaction.id) + end + + test "delete_transaction/1 deletes the transaction" do + transaction = transaction_fixture() + assert {:ok, %Transaction{}} = Account.delete_transaction(transaction) + assert_raise Ecto.NoResultsError, fn -> Account.get_transaction!(transaction.id) end + end + + test "change_transaction/1 returns a transaction changeset" do + transaction = transaction_fixture() + assert %Ecto.Changeset{} = Account.change_transaction(transaction) + end + end +end diff --git a/test/alex_koin/accounts/accounts_test.exs b/test/alex_koin/accounts/accounts_test.exs new file mode 100644 index 0000000..cb0422c --- /dev/null +++ b/test/alex_koin/accounts/accounts_test.exs @@ -0,0 +1,69 @@ +defmodule AlexKoin.AccountsTest do + use AlexKoin.DataCase + + alias AlexKoin.Accounts + + describe "users" do + alias AlexKoin.Accounts.User + + @valid_attrs %{email: "some email", first_name: "some first_name", last_name: "some last_name"} + @update_attrs %{email: "some updated email", first_name: "some updated first_name", last_name: "some updated last_name"} + @invalid_attrs %{email: nil, first_name: nil, last_name: nil} + + def user_fixture(attrs \\ %{}) do + {:ok, user} = + attrs + |> Enum.into(@valid_attrs) + |> Accounts.create_user() + + user + end + + test "list_users/0 returns all users" do + user = user_fixture() + assert Accounts.list_users() == [user] + end + + test "get_user!/1 returns the user with given id" do + user = user_fixture() + assert Accounts.get_user!(user.id) == user + end + + test "create_user/1 with valid data creates a user" do + assert {:ok, %User{} = user} = Accounts.create_user(@valid_attrs) + assert user.email == "some email" + assert user.first_name == "some first_name" + assert user.last_name == "some last_name" + end + + test "create_user/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Accounts.create_user(@invalid_attrs) + end + + test "update_user/2 with valid data updates the user" do + user = user_fixture() + assert {:ok, user} = Accounts.update_user(user, @update_attrs) + assert %User{} = user + assert user.email == "some updated email" + assert user.first_name == "some updated first_name" + assert user.last_name == "some updated last_name" + end + + test "update_user/2 with invalid data returns error changeset" do + user = user_fixture() + assert {:error, %Ecto.Changeset{}} = Accounts.update_user(user, @invalid_attrs) + assert user == Accounts.get_user!(user.id) + end + + test "delete_user/1 deletes the user" do + user = user_fixture() + assert {:ok, %User{}} = Accounts.delete_user(user) + assert_raise Ecto.NoResultsError, fn -> Accounts.get_user!(user.id) end + end + + test "change_user/1 returns a user changeset" do + user = user_fixture() + assert %Ecto.Changeset{} = Accounts.change_user(user) + end + end +end diff --git a/test/alex_koin/coins/coins_test.exs b/test/alex_koin/coins/coins_test.exs new file mode 100644 index 0000000..4885284 --- /dev/null +++ b/test/alex_koin/coins/coins_test.exs @@ -0,0 +1,67 @@ +defmodule AlexKoin.CoinsTest do + use AlexKoin.DataCase + + alias AlexKoin.Coins + + describe "coins" do + alias AlexKoin.Coins.Coin + + @valid_attrs %{hash: "some hash", origin: "some origin"} + @update_attrs %{hash: "some updated hash", origin: "some updated origin"} + @invalid_attrs %{hash: nil, origin: nil} + + def coin_fixture(attrs \\ %{}) do + {:ok, coin} = + attrs + |> Enum.into(@valid_attrs) + |> Coins.create_coin() + + coin + end + + test "list_coins/0 returns all coins" do + coin = coin_fixture() + assert Coins.list_coins() == [coin] + end + + test "get_coin!/1 returns the coin with given id" do + coin = coin_fixture() + assert Coins.get_coin!(coin.id) == coin + end + + test "create_coin/1 with valid data creates a coin" do + assert {:ok, %Coin{} = coin} = Coins.create_coin(@valid_attrs) + assert coin.hash == "some hash" + assert coin.origin == "some origin" + end + + test "create_coin/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Coins.create_coin(@invalid_attrs) + end + + test "update_coin/2 with valid data updates the coin" do + coin = coin_fixture() + assert {:ok, coin} = Coins.update_coin(coin, @update_attrs) + assert %Coin{} = coin + assert coin.hash == "some updated hash" + assert coin.origin == "some updated origin" + end + + test "update_coin/2 with invalid data returns error changeset" do + coin = coin_fixture() + assert {:error, %Ecto.Changeset{}} = Coins.update_coin(coin, @invalid_attrs) + assert coin == Coins.get_coin!(coin.id) + end + + test "delete_coin/1 deletes the coin" do + coin = coin_fixture() + assert {:ok, %Coin{}} = Coins.delete_coin(coin) + assert_raise Ecto.NoResultsError, fn -> Coins.get_coin!(coin.id) end + end + + test "change_coin/1 returns a coin changeset" do + coin = coin_fixture() + assert %Ecto.Changeset{} = Coins.change_coin(coin) + end + end +end diff --git a/test/alex_koin_web/controllers/coin_controller_test.exs b/test/alex_koin_web/controllers/coin_controller_test.exs new file mode 100644 index 0000000..6b6ecdc --- /dev/null +++ b/test/alex_koin_web/controllers/coin_controller_test.exs @@ -0,0 +1,81 @@ +defmodule AlexKoinWeb.CoinControllerTest do + use AlexKoinWeb.ConnCase + + alias AlexKoin.Coins + alias AlexKoin.Coins.Coin + + @create_attrs %{hash: "some hash", origin: "some origin"} + @update_attrs %{hash: "some updated hash", origin: "some updated origin"} + @invalid_attrs %{hash: nil, origin: nil} + + def fixture(:coin) do + {:ok, coin} = Coins.create_coin(@create_attrs) + coin + end + + setup %{conn: conn} do + {:ok, conn: put_req_header(conn, "accept", "application/json")} + end + + describe "index" do + test "lists all coins", %{conn: conn} do + conn = get conn, coin_path(conn, :index) + assert json_response(conn, 200)["data"] == [] + end + end + + describe "create coin" do + test "renders coin when data is valid", %{conn: conn} do + conn = post conn, coin_path(conn, :create), coin: @create_attrs + assert %{"id" => id} = json_response(conn, 201)["data"] + + conn = get conn, coin_path(conn, :show, id) + assert json_response(conn, 200)["data"] == %{ + "id" => id, + "hash" => "some hash", + "origin" => "some origin"} + end + + test "renders errors when data is invalid", %{conn: conn} do + conn = post conn, coin_path(conn, :create), coin: @invalid_attrs + assert json_response(conn, 422)["errors"] != %{} + end + end + + describe "update coin" do + setup [:create_coin] + + test "renders coin when data is valid", %{conn: conn, coin: %Coin{id: id} = coin} do + conn = put conn, coin_path(conn, :update, coin), coin: @update_attrs + assert %{"id" => ^id} = json_response(conn, 200)["data"] + + conn = get conn, coin_path(conn, :show, id) + assert json_response(conn, 200)["data"] == %{ + "id" => id, + "hash" => "some updated hash", + "origin" => "some updated origin"} + end + + test "renders errors when data is invalid", %{conn: conn, coin: coin} do + conn = put conn, coin_path(conn, :update, coin), coin: @invalid_attrs + assert json_response(conn, 422)["errors"] != %{} + end + end + + describe "delete coin" do + setup [:create_coin] + + test "deletes chosen coin", %{conn: conn, coin: coin} do + conn = delete conn, coin_path(conn, :delete, coin) + assert response(conn, 204) + assert_error_sent 404, fn -> + get conn, coin_path(conn, :show, coin) + end + end + end + + defp create_coin(_) do + coin = fixture(:coin) + {:ok, coin: coin} + end +end diff --git a/test/alex_koin_web/controllers/transaction_controller_test.exs b/test/alex_koin_web/controllers/transaction_controller_test.exs new file mode 100644 index 0000000..0dec356 --- /dev/null +++ b/test/alex_koin_web/controllers/transaction_controller_test.exs @@ -0,0 +1,81 @@ +defmodule AlexKoinWeb.TransactionControllerTest do + use AlexKoinWeb.ConnCase + + alias AlexKoin.Account + alias AlexKoin.Account.Transaction + + @create_attrs %{amount: 120.5, memo: "some memo"} + @update_attrs %{amount: 456.7, memo: "some updated memo"} + @invalid_attrs %{amount: nil, memo: nil} + + def fixture(:transaction) do + {:ok, transaction} = Account.create_transaction(@create_attrs) + transaction + end + + setup %{conn: conn} do + {:ok, conn: put_req_header(conn, "accept", "application/json")} + end + + describe "index" do + test "lists all transactions", %{conn: conn} do + conn = get conn, transaction_path(conn, :index) + assert json_response(conn, 200)["data"] == [] + end + end + + describe "create transaction" do + test "renders transaction when data is valid", %{conn: conn} do + conn = post conn, transaction_path(conn, :create), transaction: @create_attrs + assert %{"id" => id} = json_response(conn, 201)["data"] + + conn = get conn, transaction_path(conn, :show, id) + assert json_response(conn, 200)["data"] == %{ + "id" => id, + "amount" => 120.5, + "memo" => "some memo"} + end + + test "renders errors when data is invalid", %{conn: conn} do + conn = post conn, transaction_path(conn, :create), transaction: @invalid_attrs + assert json_response(conn, 422)["errors"] != %{} + end + end + + describe "update transaction" do + setup [:create_transaction] + + test "renders transaction when data is valid", %{conn: conn, transaction: %Transaction{id: id} = transaction} do + conn = put conn, transaction_path(conn, :update, transaction), transaction: @update_attrs + assert %{"id" => ^id} = json_response(conn, 200)["data"] + + conn = get conn, transaction_path(conn, :show, id) + assert json_response(conn, 200)["data"] == %{ + "id" => id, + "amount" => 456.7, + "memo" => "some updated memo"} + end + + test "renders errors when data is invalid", %{conn: conn, transaction: transaction} do + conn = put conn, transaction_path(conn, :update, transaction), transaction: @invalid_attrs + assert json_response(conn, 422)["errors"] != %{} + end + end + + describe "delete transaction" do + setup [:create_transaction] + + test "deletes chosen transaction", %{conn: conn, transaction: transaction} do + conn = delete conn, transaction_path(conn, :delete, transaction) + assert response(conn, 204) + assert_error_sent 404, fn -> + get conn, transaction_path(conn, :show, transaction) + end + end + end + + defp create_transaction(_) do + transaction = fixture(:transaction) + {:ok, transaction: transaction} + end +end diff --git a/test/alex_koin_web/controllers/user_controller_test.exs b/test/alex_koin_web/controllers/user_controller_test.exs new file mode 100644 index 0000000..a61370b --- /dev/null +++ b/test/alex_koin_web/controllers/user_controller_test.exs @@ -0,0 +1,85 @@ +defmodule AlexKoinWeb.UserControllerTest do + use AlexKoinWeb.ConnCase + + alias AlexKoin.Account + alias AlexKoin.Account.User + + @create_attrs %{email: "some email", first_name: "some first_name", last_name: "some last_name", slack_id: "some slack_id"} + @update_attrs %{email: "some updated email", first_name: "some updated first_name", last_name: "some updated last_name", slack_id: "some updated slack_id"} + @invalid_attrs %{email: nil, first_name: nil, last_name: nil, slack_id: nil} + + def fixture(:user) do + {:ok, user} = Account.create_user(@create_attrs) + user + end + + setup %{conn: conn} do + {:ok, conn: put_req_header(conn, "accept", "application/json")} + end + + describe "index" do + test "lists all users", %{conn: conn} do + conn = get conn, user_path(conn, :index) + assert json_response(conn, 200)["data"] == [] + end + end + + describe "create user" do + test "renders user when data is valid", %{conn: conn} do + conn = post conn, user_path(conn, :create), user: @create_attrs + assert %{"id" => id} = json_response(conn, 201)["data"] + + conn = get conn, user_path(conn, :show, id) + assert json_response(conn, 200)["data"] == %{ + "id" => id, + "email" => "some email", + "first_name" => "some first_name", + "last_name" => "some last_name", + "slack_id" => "some slack_id"} + end + + test "renders errors when data is invalid", %{conn: conn} do + conn = post conn, user_path(conn, :create), user: @invalid_attrs + assert json_response(conn, 422)["errors"] != %{} + end + end + + describe "update user" do + setup [:create_user] + + test "renders user when data is valid", %{conn: conn, user: %User{id: id} = user} do + conn = put conn, user_path(conn, :update, user), user: @update_attrs + assert %{"id" => ^id} = json_response(conn, 200)["data"] + + conn = get conn, user_path(conn, :show, id) + assert json_response(conn, 200)["data"] == %{ + "id" => id, + "email" => "some updated email", + "first_name" => "some updated first_name", + "last_name" => "some updated last_name", + "slack_id" => "some updated slack_id"} + end + + test "renders errors when data is invalid", %{conn: conn, user: user} do + conn = put conn, user_path(conn, :update, user), user: @invalid_attrs + assert json_response(conn, 422)["errors"] != %{} + end + end + + describe "delete user" do + setup [:create_user] + + test "deletes chosen user", %{conn: conn, user: user} do + conn = delete conn, user_path(conn, :delete, user) + assert response(conn, 204) + assert_error_sent 404, fn -> + get conn, user_path(conn, :show, user) + end + end + end + + defp create_user(_) do + user = fixture(:user) + {:ok, user: user} + end +end diff --git a/test/alex_koin_web/controllers/wallet_controller_test.exs b/test/alex_koin_web/controllers/wallet_controller_test.exs new file mode 100644 index 0000000..8421c09 --- /dev/null +++ b/test/alex_koin_web/controllers/wallet_controller_test.exs @@ -0,0 +1,79 @@ +defmodule AlexKoinWeb.WalletControllerTest do + use AlexKoinWeb.ConnCase + + alias AlexKoin.Account + alias AlexKoin.Account.Wallet + + @create_attrs %{balance: 120.5} + @update_attrs %{balance: 456.7} + @invalid_attrs %{balance: nil} + + def fixture(:wallet) do + {:ok, wallet} = Account.create_wallet(@create_attrs) + wallet + end + + setup %{conn: conn} do + {:ok, conn: put_req_header(conn, "accept", "application/json")} + end + + describe "index" do + test "lists all wallets", %{conn: conn} do + conn = get conn, wallet_path(conn, :index) + assert json_response(conn, 200)["data"] == [] + end + end + + describe "create wallet" do + test "renders wallet when data is valid", %{conn: conn} do + conn = post conn, wallet_path(conn, :create), wallet: @create_attrs + assert %{"id" => id} = json_response(conn, 201)["data"] + + conn = get conn, wallet_path(conn, :show, id) + assert json_response(conn, 200)["data"] == %{ + "id" => id, + "balance" => 120.5} + end + + test "renders errors when data is invalid", %{conn: conn} do + conn = post conn, wallet_path(conn, :create), wallet: @invalid_attrs + assert json_response(conn, 422)["errors"] != %{} + end + end + + describe "update wallet" do + setup [:create_wallet] + + test "renders wallet when data is valid", %{conn: conn, wallet: %Wallet{id: id} = wallet} do + conn = put conn, wallet_path(conn, :update, wallet), wallet: @update_attrs + assert %{"id" => ^id} = json_response(conn, 200)["data"] + + conn = get conn, wallet_path(conn, :show, id) + assert json_response(conn, 200)["data"] == %{ + "id" => id, + "balance" => 456.7} + end + + test "renders errors when data is invalid", %{conn: conn, wallet: wallet} do + conn = put conn, wallet_path(conn, :update, wallet), wallet: @invalid_attrs + assert json_response(conn, 422)["errors"] != %{} + end + end + + describe "delete wallet" do + setup [:create_wallet] + + test "deletes chosen wallet", %{conn: conn, wallet: wallet} do + conn = delete conn, wallet_path(conn, :delete, wallet) + assert response(conn, 204) + assert_error_sent 404, fn -> + get conn, wallet_path(conn, :show, wallet) + end + end + end + + defp create_wallet(_) do + wallet = fixture(:wallet) + {:ok, wallet: wallet} + end +end diff --git a/test/alex_koin_web/views/error_view_test.exs b/test/alex_koin_web/views/error_view_test.exs new file mode 100644 index 0000000..4748a8a --- /dev/null +++ b/test/alex_koin_web/views/error_view_test.exs @@ -0,0 +1,16 @@ +defmodule AlexKoinWeb.ErrorViewTest do + use AlexKoinWeb.ConnCase, async: true + + # Bring render/3 and render_to_string/3 for testing custom views + import Phoenix.View + + test "renders 404.json" do + assert render(AlexKoinWeb.ErrorView, "404.json", []) == + %{errors: %{detail: "Not Found"}} + end + + test "renders 500.json" do + assert render(AlexKoinWeb.ErrorView, "500.json", []) == + %{errors: %{detail: "Internal Server Error"}} + end +end diff --git a/test/support/channel_case.ex b/test/support/channel_case.ex new file mode 100644 index 0000000..65c0b7a --- /dev/null +++ b/test/support/channel_case.ex @@ -0,0 +1,37 @@ +defmodule AlexKoinWeb.ChannelCase do + @moduledoc """ + This module defines the test case to be used by + channel tests. + + Such tests rely on `Phoenix.ChannelTest` and also + import other functionality to make it easier + to build common datastructures and query the data layer. + + Finally, if the test case interacts with the database, + it cannot be async. For this reason, every test runs + inside a transaction which is reset at the beginning + of the test unless the test case is marked as async. + """ + + use ExUnit.CaseTemplate + + using do + quote do + # Import conveniences for testing with channels + use Phoenix.ChannelTest + + # The default endpoint for testing + @endpoint AlexKoinWeb.Endpoint + end + end + + + setup tags do + :ok = Ecto.Adapters.SQL.Sandbox.checkout(AlexKoin.Repo) + unless tags[:async] do + Ecto.Adapters.SQL.Sandbox.mode(AlexKoin.Repo, {:shared, self()}) + end + :ok + end + +end diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex new file mode 100644 index 0000000..4aab126 --- /dev/null +++ b/test/support/conn_case.ex @@ -0,0 +1,38 @@ +defmodule AlexKoinWeb.ConnCase do + @moduledoc """ + This module defines the test case to be used by + tests that require setting up a connection. + + Such tests rely on `Phoenix.ConnTest` and also + import other functionality to make it easier + to build common datastructures and query the data layer. + + Finally, if the test case interacts with the database, + it cannot be async. For this reason, every test runs + inside a transaction which is reset at the beginning + of the test unless the test case is marked as async. + """ + + use ExUnit.CaseTemplate + + using do + quote do + # Import conveniences for testing with connections + use Phoenix.ConnTest + import AlexKoinWeb.Router.Helpers + + # The default endpoint for testing + @endpoint AlexKoinWeb.Endpoint + end + end + + + setup tags do + :ok = Ecto.Adapters.SQL.Sandbox.checkout(AlexKoin.Repo) + unless tags[:async] do + Ecto.Adapters.SQL.Sandbox.mode(AlexKoin.Repo, {:shared, self()}) + end + {:ok, conn: Phoenix.ConnTest.build_conn()} + end + +end diff --git a/test/support/data_case.ex b/test/support/data_case.ex new file mode 100644 index 0000000..3e2f699 --- /dev/null +++ b/test/support/data_case.ex @@ -0,0 +1,53 @@ +defmodule AlexKoin.DataCase do + @moduledoc """ + This module defines the setup for tests requiring + access to the application's data layer. + + You may define functions here to be used as helpers in + your tests. + + Finally, if the test case interacts with the database, + it cannot be async. For this reason, every test runs + inside a transaction which is reset at the beginning + of the test unless the test case is marked as async. + """ + + use ExUnit.CaseTemplate + + using do + quote do + alias AlexKoin.Repo + + import Ecto + import Ecto.Changeset + import Ecto.Query + import AlexKoin.DataCase + end + end + + setup tags do + :ok = Ecto.Adapters.SQL.Sandbox.checkout(AlexKoin.Repo) + + unless tags[:async] do + Ecto.Adapters.SQL.Sandbox.mode(AlexKoin.Repo, {:shared, self()}) + end + + :ok + end + + @doc """ + A helper that transform changeset errors to a map of messages. + + assert {:error, changeset} = Accounts.create_user(%{password: "short"}) + assert "password is too short" in errors_on(changeset).password + assert %{password: ["password is too short"]} = errors_on(changeset) + + """ + def errors_on(changeset) do + Ecto.Changeset.traverse_errors(changeset, fn {message, opts} -> + Enum.reduce(opts, message, fn {key, value}, acc -> + String.replace(acc, "%{#{key}}", to_string(value)) + end) + end) + end +end diff --git a/test/test_helper.exs b/test/test_helper.exs new file mode 100644 index 0000000..afd9a7f --- /dev/null +++ b/test/test_helper.exs @@ -0,0 +1,4 @@ +ExUnit.start() + +Ecto.Adapters.SQL.Sandbox.mode(AlexKoin.Repo, :manual) +