diff --git a/README.md b/README.md index 20c97a64..c77b69dc 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,106 @@ # Divvy Homework Assignment +### Chad Jemmett's Divvy Homework Project -- Updated Jan 9, 2022 + +To Divvy Devs, +Thank you for your feedback on my homework project. Kris Rickard sent me the feedback you gave on my work. I've copied +the pros and cons list here for your convenience. +To reiterate, this is the kind of work you can expect from me as a memeber of the Divvy team. I welcome feedback. I +research my mistakes and upate my work in a timely manner. I enjoy collaboration and being part of a team. I look +forward to doing an in-person code review with the Divvy Team. + +> Pros: +> Company addition is clean and tests look good +> Seeds file is really robust… but it’s in python +> Cons: +> Definitely signs of a mid-level. Replaced default database creds instead of using system env, wrote a python seeds file instead of using elixir because it was more familiar. +> Questions: +> What is the risk/reward of adding python to an elixir project? +> How will you plan to learn elixir? + +The following are my corrections listed under CONS. + +I've written a [seed file in Elixir here.](https://github.com/ceejaay/web-homework/blob/master/elixir/priv/repo/seeds.exs) +This is written entirely in Elixir. I enumerate over a range of integers and lists of items retreived from the database +to insert Companies, Users, Merchants and Transactions into the database. I used the Faker module to write dummy data. + +I solved the problem of altering the default database credentials. I did research on Elixir's `System` module. I +set my PostgreSQL username in my `.zshrc` file. It solved the problem and I restored the default variables in the +[config/dev.exs](https://github.com/ceejaay/web-homework/blob/master/elixir/config/dev.exs) and the [prod.secret.exs](https://github.com/ceejaay/web-homework/blob/master/elixir/config/prod.secret.exs) files. + + +### Answers to Questions + +#### What is the risk/reward of adding Python to an Elixir project? + +I would say the risks outweigh the rewards when adding Python to an Elixir application. I currently work on a Python and Django project with several apps. It’s beneficial to my team and I that we only have one language to manage when doing the majority of our work. Only a very serious problem would cause me to consider adding another language. And the solution to that problem would be something that *only* a new language could solve. I would first attempt to solve the problem using Python. If that didn’t work, I’d consider something else. + + +I think the most frustrating risk of adding Python to an Elixir app would be a workflow problem. I personally dislike stopping my work in Python and moving to another language in my process. It’s more efficient to stay working in one language and its related framework. + + + +As far as rewards, any script or app written in Python would be easy to get up and running. For a short-term solution to a problem, Python is a good choice. Right now, [Python is the second most popular language on Github.](https://madnight.github.io/githut/#/pull_requests/2021/4) Many experienced developers would easily find the right Python library to solve the problem. You may not even need extra libraries. Python has many options right out of the box. And the popularity of Python means any future developer wouldn’t need much to write new features for the app. + + +Overall, It wouldn’t be my first choice to add Python to an Elixir app. But if I had to, Python wouldn’t be a bad choice. + +#### How will you plan to learn Elixir? + +This bit of pseudo-code illustrates my learning process. +``` +while elixir_expert == False: + study_elixir_topic() + apply_knowledge_to_simple_project() + ask_for_feedback_from_experienced_devs() + +``` + +I’ve already started the feedback process. I’ve taken a Udemy course on Elixir. It’s a bit dated but I learned the +basics of the language. The tutorial builds two small programs. [The first is a basic program to shuffle and deal a +deck of cards.](https://github.com/ceejaay/elixir_card_shuffler) The second is far more interesting and fun. [It’s a script that generates identicon images.](https://github.com/ceejaay/elixir_project) Identicons are those random geometric images you see as profile pictures on some websites. + +As for questions and feedback. I’ve asked some questions in the Discord Elixir channel. The people there are very helpful. I look forward to working on the Divvy team and getting feedback from experienced developers. + +Part of my strategy is looking for interesting problem when building a simple project.I really enjoyed the problems presented in writing the `seed.exs` file for the homework. I understood the basics of creating and inserting an item into the database. But I needed a lot of research on how to handle the relationships and inserting a large amount of data. + +This process has resulted in success for me. Doing hobby projects in Ruby laid the foundation for the work I would do in my bootcamp, and that success brought me to my current job. You’ll see that this process will make me an excellent addition to the Divvy team. + + +# Below is my original homework project you gave feedback on. + + +The first is in this repository. It demonstrates my abilities in Elixir, Ecto and Phoenix. +The second is here: [It is a demonstration of my ability to write an api in +Python.](https://github.com/ceejaay/transaction_tracker) + +Please visit my other repository to review the project. It is an example of the kind of work I do at my current job. + +### My work in this Elixir project. +I made two accomplishments on this project. + +1. I wrote a python script to seed the database. +2. I created the components of a new `Company` model. + +The python script to seed the database is here: [Seed File.](https://github.com/ceejaay/web-homework/blob/master/seed.py) +At first I researched how to seed the database with the existing `seed` file included in the Homework app. I found the +learning curve to be steep, so I accomplished the task with the tools I am most familiar with. I used python and a +couple of libraries to seed the Postgres database. +You can read instructions to run the seed in the `seed.py` file. + +For the `Company` model. I researched the Ecto documentation on their website. First I generated a context for the +Company model. I then manually created the relation among the `Company`, `User`, and `Transaction`. +I created the shchema and resolvers for the model. + +Thank you for your consideration. I look forward to your feedback on my work in our future interview. +Please contact me at your earliest convenience so we can talk about my work. + +- Chad Jemmett +chad.jemmett@gmail.com +208-305-0359 + + +************************************************************************************************* This repository provides a starting point for a basic React + GraphQL application. All of the configuration boilerplate is complete so you can start by writing the code that you want us to see. diff --git a/elixir/.env b/elixir/.env new file mode 100644 index 00000000..fb3fad1b --- /dev/null +++ b/elixir/.env @@ -0,0 +1 @@ +"POSTGRES_USER" = "cjem" diff --git a/elixir/config/config.exs b/elixir/config/config.exs index adeea69b..4657cabd 100644 --- a/elixir/config/config.exs +++ b/elixir/config/config.exs @@ -5,7 +5,8 @@ # is restricted to this project. # General application configuration -use Mix.Config +# use Mix.Config +import Config config :homework, ecto_repos: [Homework.Repo] diff --git a/elixir/config/dev.exs b/elixir/config/dev.exs index f2d078f5..12747eb6 100644 --- a/elixir/config/dev.exs +++ b/elixir/config/dev.exs @@ -1,4 +1,6 @@ -use Mix.Config +# use Mix.Config + +import Config # Configure your database config :homework, Homework.Repo, diff --git a/elixir/config/test.exs b/elixir/config/test.exs index 37314737..58fc2948 100644 --- a/elixir/config/test.exs +++ b/elixir/config/test.exs @@ -7,7 +7,7 @@ use Mix.Config # Run `mix help test` for more information. config :homework, Homework.Repo, username: System.get_env("POSTGRES_USER") || "postgres", - password: System.get_env("POSTGRES_PASSWORD") || "postgres", + password: System.get_env("POSTGRES_PASSWORD") || "", hostname: System.get_env("POSTGRES_HOST") || "localhost", database: System.get_env("POSTGRES_DATABASE") || "homework_dev", pool: Ecto.Adapters.SQL.Sandbox diff --git a/elixir/lib/homework/account.ex b/elixir/lib/homework/account.ex new file mode 100644 index 00000000..c24c8d71 --- /dev/null +++ b/elixir/lib/homework/account.ex @@ -0,0 +1,104 @@ +defmodule Homework.Account do + @moduledoc """ + The Account context. + """ + + import Ecto.Query, warn: false + alias Homework.Repo + + alias Homework.Account.Card + + @doc """ + Returns the list of cards. + + ## Examples + + iex> list_cards() + [%Card{}, ...] + + """ + def list_cards do + Repo.all(Card) + end + + @doc """ + Gets a single card. + + Raises `Ecto.NoResultsError` if the Card does not exist. + + ## Examples + + iex> get_card!(123) + %Card{} + + iex> get_card!(456) + ** (Ecto.NoResultsError) + + """ + def get_card!(id), do: Repo.get!(Card, id) + + @doc """ + Creates a card. + + ## Examples + + iex> create_card(%{field: value}) + {:ok, %Card{}} + + iex> create_card(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_card(attrs \\ %{}) do + %Card{} + |> Card.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a card. + + ## Examples + + iex> update_card(card, %{field: new_value}) + {:ok, %Card{}} + + iex> update_card(card, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_card(%Card{} = card, attrs) do + card + |> Card.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a card. + + ## Examples + + iex> delete_card(card) + {:ok, %Card{}} + + iex> delete_card(card) + {:error, %Ecto.Changeset{}} + + """ + def delete_card(%Card{} = card) do + Repo.delete(card) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking card changes. + + ## Examples + + iex> change_card(card) + %Ecto.Changeset{data: %Card{}} + + """ + def change_card(%Card{} = card, attrs \\ %{}) do + Card.changeset(card, attrs) + end +end diff --git a/elixir/lib/homework/account/card.ex b/elixir/lib/homework/account/card.ex new file mode 100644 index 00000000..395e7c73 --- /dev/null +++ b/elixir/lib/homework/account/card.ex @@ -0,0 +1,17 @@ +defmodule Homework.Account.Card do + use Ecto.Schema + import Ecto.Changeset + + schema "cards" do + field :description, :string + + timestamps() + end + + @doc false + def changeset(card, attrs) do + card + |> cast(attrs, [:description]) + |> validate_required([:description]) + end +end diff --git a/elixir/lib/homework/companies.ex b/elixir/lib/homework/companies.ex new file mode 100644 index 00000000..e96f0495 --- /dev/null +++ b/elixir/lib/homework/companies.ex @@ -0,0 +1,104 @@ +defmodule Homework.Companies do + @moduledoc """ + The Companies context. + """ + + import Ecto.Query, warn: false + alias Homework.Repo + + alias Homework.Companies.Company + + @doc """ + Returns the list of company. + + ## Examples + + iex> list_company() + [%Company{}, ...] + + """ + def list_company(_args) do + Repo.all(Company) + end + + @doc """ + Gets a single company. + + Raises `Ecto.NoResultsError` if the Company does not exist. + + ## Examples + + iex> get_company!(123) + %Company{} + + iex> get_company!(456) + ** (Ecto.NoResultsError) + + """ + def get_company!(id), do: Repo.get!(Company, id) + + @doc """ + Creates a company. + + ## Examples + + iex> create_company(%{field: value}) + {:ok, %Company{}} + + iex> create_company(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_company(attrs \\ %{}) do + %Company{} + |> Company.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a company. + + ## Examples + + iex> update_company(company, %{field: new_value}) + {:ok, %Company{}} + + iex> update_company(company, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_company(%Company{} = company, attrs) do + company + |> Company.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a company. + + ## Examples + + iex> delete_company(company) + {:ok, %Company{}} + + iex> delete_company(company) + {:error, %Ecto.Changeset{}} + + """ + def delete_company(%Company{} = company) do + Repo.delete(company) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking company changes. + + ## Examples + + iex> change_company(company) + %Ecto.Changeset{data: %Company{}} + + """ + def change_company(%Company{} = company, attrs \\ %{}) do + Company.changeset(company, attrs) + end +end diff --git a/elixir/lib/homework/companies/company.ex b/elixir/lib/homework/companies/company.ex new file mode 100644 index 00000000..b15fe1b4 --- /dev/null +++ b/elixir/lib/homework/companies/company.ex @@ -0,0 +1,22 @@ +defmodule Homework.Companies.Company do + use Ecto.Schema + import Ecto.Changeset + alias Homework.Companies.Company + + @primary_key {:id, :binary_id, autogenerate: true} + schema "company" do + field :available_credit, :integer + field :credit_line, :integer + field :name, :string + has_many :users, Company + + timestamps() + end + + @doc false + def changeset(company, attrs) do + company + |> cast(attrs, [:name, :credit_line, :available_credit]) + |> validate_required([:name, :credit_line, :available_credit]) + end +end diff --git a/elixir/lib/homework/shiren.ex b/elixir/lib/homework/shiren.ex new file mode 100644 index 00000000..5562b5da --- /dev/null +++ b/elixir/lib/homework/shiren.ex @@ -0,0 +1,104 @@ +defmodule Homework.Shiren do + @moduledoc """ + The Shiren context. + """ + + import Ecto.Query, warn: false + alias Homework.Repo + + alias Homework.Shiren.Prices + + @doc """ + Returns the list of price. + + ## Examples + + iex> list_price() + [%Prices{}, ...] + + """ + def list_price do + Repo.all(Prices) + end + + @doc """ + Gets a single prices. + + Raises `Ecto.NoResultsError` if the Prices does not exist. + + ## Examples + + iex> get_prices!(123) + %Prices{} + + iex> get_prices!(456) + ** (Ecto.NoResultsError) + + """ + def get_prices!(id), do: Repo.get!(Prices, id) + + @doc """ + Creates a prices. + + ## Examples + + iex> create_prices(%{field: value}) + {:ok, %Prices{}} + + iex> create_prices(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_prices(attrs \\ %{}) do + %Prices{} + |> Prices.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a prices. + + ## Examples + + iex> update_prices(prices, %{field: new_value}) + {:ok, %Prices{}} + + iex> update_prices(prices, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_prices(%Prices{} = prices, attrs) do + prices + |> Prices.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a prices. + + ## Examples + + iex> delete_prices(prices) + {:ok, %Prices{}} + + iex> delete_prices(prices) + {:error, %Ecto.Changeset{}} + + """ + def delete_prices(%Prices{} = prices) do + Repo.delete(prices) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking prices changes. + + ## Examples + + iex> change_prices(prices) + %Ecto.Changeset{data: %Prices{}} + + """ + def change_prices(%Prices{} = prices, attrs \\ %{}) do + Prices.changeset(prices, attrs) + end +end diff --git a/elixir/lib/homework/shiren/prices.ex b/elixir/lib/homework/shiren/prices.ex new file mode 100644 index 00000000..ff0f889f --- /dev/null +++ b/elixir/lib/homework/shiren/prices.ex @@ -0,0 +1,19 @@ +defmodule Homework.Shiren.Prices do + use Ecto.Schema + import Ecto.Changeset + + schema "price" do + field :amount, :integer + field :category, :string + field :type, :string + + timestamps() + end + + @doc false + def changeset(prices, attrs) do + prices + |> cast(attrs, [:category, :amount, :type]) + |> validate_required([:category, :amount, :type]) + end +end diff --git a/elixir/lib/homework/transactions.ex b/elixir/lib/homework/transactions.ex index f7ec3dfb..5e8f6b69 100644 --- a/elixir/lib/homework/transactions.ex +++ b/elixir/lib/homework/transactions.ex @@ -7,6 +7,7 @@ defmodule Homework.Transactions do alias Homework.Repo alias Homework.Transactions.Transaction + alias Homework.Companies @doc """ Returns the list of transactions. @@ -50,11 +51,16 @@ defmodule Homework.Transactions do """ def create_transaction(attrs \\ %{}) do + company_id = Map.get(attrs, :company_id) + co = Companies.get_company!(company_id) + amount = Map.get(attrs, :amount) + credit = Map.get(co, :available_credit) + new_amount = credit - amount + Companies.update_company(co, %{available_credit: new_amount}) %Transaction{} |> Transaction.changeset(attrs) |> Repo.insert() end - @doc """ Updates a transaction. diff --git a/elixir/lib/homework/transactions/transaction.ex b/elixir/lib/homework/transactions/transaction.ex index e7884314..080b2d7e 100644 --- a/elixir/lib/homework/transactions/transaction.ex +++ b/elixir/lib/homework/transactions/transaction.ex @@ -3,6 +3,7 @@ defmodule Homework.Transactions.Transaction do import Ecto.Changeset alias Homework.Merchants.Merchant alias Homework.Users.User + alias Homework.Companies.Company @primary_key {:id, :binary_id, autogenerate: true} schema "transactions" do @@ -13,6 +14,7 @@ defmodule Homework.Transactions.Transaction do belongs_to(:merchant, Merchant, type: :binary_id, foreign_key: :merchant_id) belongs_to(:user, User, type: :binary_id, foreign_key: :user_id) + belongs_to(:company, Company, type: :binary_id, foreign_key: :company_id) timestamps() end @@ -20,7 +22,7 @@ defmodule Homework.Transactions.Transaction do @doc false def changeset(transaction, attrs) do transaction - |> cast(attrs, [:user_id, :amount, :debit, :description, :merchant_id]) - |> validate_required([:user_id, :amount, :debit, :description, :merchant_id]) + |> cast(attrs, [:user_id, :amount, :debit, :description, :merchant_id, :company_id]) + |> validate_required([:user_id, :amount, :debit, :description, :merchant_id, :company_id]) end end diff --git a/elixir/lib/homework/users/user.ex b/elixir/lib/homework/users/user.ex index 51ea2f95..f93e24a3 100644 --- a/elixir/lib/homework/users/user.ex +++ b/elixir/lib/homework/users/user.ex @@ -1,12 +1,14 @@ defmodule Homework.Users.User do use Ecto.Schema import Ecto.Changeset + alias Homework.Companies.Company @primary_key {:id, :binary_id, autogenerate: true} schema "users" do field(:dob, :string) field(:first_name, :string) field(:last_name, :string) + belongs_to(:company, Company, type: :binary_id, foreign_key: :company_id) timestamps() end @@ -14,7 +16,7 @@ defmodule Homework.Users.User do @doc false def changeset(user, attrs) do user - |> cast(attrs, [:first_name, :last_name, :dob]) - |> validate_required([:first_name, :last_name, :dob]) + |> cast(attrs, [:company_id, :first_name, :last_name, :dob]) + |> validate_required([:company_id, :first_name, :last_name, :dob]) end end diff --git a/elixir/lib/homework_web/controllers/fallback_controller.ex b/elixir/lib/homework_web/controllers/fallback_controller.ex new file mode 100644 index 00000000..e0e7f474 --- /dev/null +++ b/elixir/lib/homework_web/controllers/fallback_controller.ex @@ -0,0 +1,16 @@ +defmodule HomeworkWeb.FallbackController do + @moduledoc """ + Translates controller action results into valid `Plug.Conn` responses. + + See `Phoenix.Controller.action_fallback/1` for more details. + """ + use HomeworkWeb, :controller + + # This clause is an example of how to handle resources that cannot be found. + def call(conn, {:error, :not_found}) do + conn + |> put_status(:not_found) + |> put_view(HomeworkWeb.ErrorView) + |> render(:"404") + end +end diff --git a/elixir/lib/homework_web/controllers/prices_controller.ex b/elixir/lib/homework_web/controllers/prices_controller.ex new file mode 100644 index 00000000..67f01781 --- /dev/null +++ b/elixir/lib/homework_web/controllers/prices_controller.ex @@ -0,0 +1,44 @@ +defmodule HomeworkWeb.PricesController do + use HomeworkWeb, :controller + + alias Homework.Shiren + alias Homework.Shiren.Prices + + action_fallback HomeworkWeb.FallbackController + + def index(conn, _params) do + price = Shiren.list_price() + render(conn, "index.json", price: price) + end + + def create(conn, %{"prices" => prices_params}) do + with {:ok, %Prices{} = prices} <- Shiren.create_prices(prices_params) do + conn + |> put_status(:created) + |> put_resp_header("location", Routes.prices_path(conn, :show, prices)) + |> render("show.json", prices: prices) + end + end + + + def show(conn, %{"id" => id}) do + prices = Shiren.get_prices!(id) + render(conn, "show.json", prices: prices) + end + + def update(conn, %{"id" => id, "prices" => prices_params}) do + prices = Shiren.get_prices!(id) + + with {:ok, %Prices{} = prices} <- Shiren.update_prices(prices, prices_params) do + render(conn, "show.json", prices: prices) + end + end + + def delete(conn, %{"id" => id}) do + prices = Shiren.get_prices!(id) + + with {:ok, %Prices{}} <- Shiren.delete_prices(prices) do + send_resp(conn, :no_content, "") + end + end +end diff --git a/elixir/lib/homework_web/resolvers/companies_resolvers.ex b/elixir/lib/homework_web/resolvers/companies_resolvers.ex new file mode 100644 index 00000000..4eb153ee --- /dev/null +++ b/elixir/lib/homework_web/resolvers/companies_resolvers.ex @@ -0,0 +1,54 @@ +defmodule HomeworkWeb.Resolvers.CompaniesResolver do + alias Homework.Companies + + @doc """ + Get a list of companies + """ + def companies(_root, args, _info) do + {:ok, Companies.list_company(args)} + end + + @doc """ + Creates a companies + """ + def create_company(_root, args, _info) do + case Companies.create_company(args) do + {:ok, company} -> + {:ok, company} + + error -> + {:error, "could not create company: #{inspect(error)}"} + end + end + + @doc """ + Updates a company for an id with args specified. + """ + def update_company(_root, %{id: id} = args, _info) do + company = Companies.get_company!(id) + !(id) + + case Companies.update_company(company, args) do + {:ok, company} -> + {:ok, company} + + error -> + {:error, "could not update company: #{inspect(error)}"} + end + end + + @doc """ + Deletes a user for an id + """ + def delete_company(_root, %{id: id}, _info) do + company = Companies.get_company!(id) + + case Companies.delete_company(company) do + {:ok, company } -> + {:ok, company } + + error -> + {:error, "could not update company: #{inspect(error)}"} + end + end +end diff --git a/elixir/lib/homework_web/router.ex b/elixir/lib/homework_web/router.ex index d6e78852..94707a77 100644 --- a/elixir/lib/homework_web/router.ex +++ b/elixir/lib/homework_web/router.ex @@ -1,12 +1,15 @@ defmodule HomeworkWeb.Router do + use HomeworkWeb, :router + pipeline :api do plug(:accepts, ["json"]) end - scope "/" do + scope "/api/" do pipe_through(:api) + resources "/price", HomeworkWeb.PricesController, except: [:edit] forward("/graphiql", Absinthe.Plug.GraphiQL, schema: HomeworkWeb.Schema, diff --git a/elixir/lib/homework_web/schema.ex b/elixir/lib/homework_web/schema.ex index 56888d2f..23bdff48 100644 --- a/elixir/lib/homework_web/schema.ex +++ b/elixir/lib/homework_web/schema.ex @@ -7,6 +7,7 @@ defmodule HomeworkWeb.Schema do alias HomeworkWeb.Resolvers.MerchantsResolver alias HomeworkWeb.Resolvers.TransactionsResolver alias HomeworkWeb.Resolvers.UsersResolver + alias HomeworkWeb.Resolvers.CompaniesResolver import_types(HomeworkWeb.Schemas.Types) query do @@ -24,11 +25,17 @@ defmodule HomeworkWeb.Schema do field(:merchants, list_of(:merchant)) do resolve(&MerchantsResolver.merchants/3) end + + @desc "Get all Companies" + field(:companies, list_of(:company)) do + resolve(&CompaniesResolver.companies/3) + end end mutation do import_fields(:transaction_mutations) import_fields(:user_mutations) import_fields(:merchant_mutations) + import_fields(:company_mutations) end end diff --git a/elixir/lib/homework_web/schemas/companies_schema.ex b/elixir/lib/homework_web/schemas/companies_schema.ex new file mode 100644 index 00000000..dffd11e1 --- /dev/null +++ b/elixir/lib/homework_web/schemas/companies_schema.ex @@ -0,0 +1,45 @@ +defmodule HomeworkWeb.Schemas.CompaniesSchema do + @moduledoc """ + Defines the graphql schema for user. + """ + use Absinthe.Schema.Notation + + alias HomeworkWeb.Resolvers.CompaniesResolver + + object :company do + field(:id, non_null(:id)) + field(:name, :string) + field(:credit_line, :integer) + field(:available_credit, :integer) + field(:inserted_at, :naive_datetime) + field(:updated_at, :naive_datetime) + end + + object :company_mutations do + @desc "Create a new Company" + field :create_company, :company do + arg(:name, non_null(:string)) + arg(:credit_line, non_null(:integer)) + arg(:available_credit, non_null(:integer)) + + resolve(&CompaniesResolver.create_company/3) + end + + @desc "Update a new user" + field :update_company, :user do + arg(:name, non_null(:string)) + arg(:credit_line, non_null(:integer)) + arg(:available_credit, non_null(:integer)) + arg(:id, non_null(:id)) + + resolve(&CompaniesResolver.update_company/3) + end + + @desc "delete an existing company" + field :delete_company, :company do + arg(:id, non_null(:id)) + + resolve(&CompaniesResolver.delete_company/3) + end + end +end diff --git a/elixir/lib/homework_web/schemas/types.ex b/elixir/lib/homework_web/schemas/types.ex index 75540319..cb9a1c56 100644 --- a/elixir/lib/homework_web/schemas/types.ex +++ b/elixir/lib/homework_web/schemas/types.ex @@ -8,4 +8,5 @@ defmodule HomeworkWeb.Schemas.Types do import_types(HomeworkWeb.Schemas.MerchantsSchema) import_types(HomeworkWeb.Schemas.TransactionsSchema) import_types(HomeworkWeb.Schemas.UsersSchema) + import_types(HomeworkWeb.Schemas.CompaniesSchema) end diff --git a/elixir/lib/homework_web/views/changeset_view.ex b/elixir/lib/homework_web/views/changeset_view.ex new file mode 100644 index 00000000..3f5c1238 --- /dev/null +++ b/elixir/lib/homework_web/views/changeset_view.ex @@ -0,0 +1,19 @@ +defmodule HomeworkWeb.ChangesetView do + use HomeworkWeb, :view + + @doc """ + Traverses and translates changeset errors. + + See `Ecto.Changeset.traverse_errors/2` and + `HomeworkWeb.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/elixir/lib/homework_web/views/prices_view.ex b/elixir/lib/homework_web/views/prices_view.ex new file mode 100644 index 00000000..d998e548 --- /dev/null +++ b/elixir/lib/homework_web/views/prices_view.ex @@ -0,0 +1,19 @@ +defmodule HomeworkWeb.PricesView do + use HomeworkWeb, :view + alias HomeworkWeb.PricesView + + def render("index.json", %{price: price}) do + %{data: render_many(price, PricesView, "prices.json")} + end + + def render("show.json", %{prices: prices}) do + %{data: render_one(prices, PricesView, "prices.json")} + end + + def render("prices.json", %{prices: prices}) do + %{id: prices.id, + amount: prices.amount, + category: prices.category, + type: prices.type} + end +end diff --git a/elixir/mix.exs b/elixir/mix.exs index 5b7745a8..a77c2f00 100644 --- a/elixir/mix.exs +++ b/elixir/mix.exs @@ -45,7 +45,8 @@ defmodule Homework.MixProject do {:poison, "~> 2.1.0"}, {:postgrex, ">= 0.0.0"}, {:telemetry_metrics, "~> 0.4"}, - {:telemetry_poller, "~> 0.4"} + {:telemetry_poller, "~> 0.4"}, + {:faker, "~> 0.17"} ] end diff --git a/elixir/mix.lock b/elixir/mix.lock index e1906184..05f65a79 100644 --- a/elixir/mix.lock +++ b/elixir/mix.lock @@ -8,6 +8,7 @@ "decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"}, "ecto": {:hex, :ecto, "3.4.6", "08f7afad3257d6eb8613309af31037e16c36808dfda5a3cd0cb4e9738db030e4", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6f13a9e2a62e75c2dcfc7207bfc65645ab387af8360db4c89fee8b5a4bf3f70b"}, "ecto_sql": {:hex, :ecto_sql, "3.4.5", "30161f81b167d561a9a2df4329c10ae05ff36eca7ccc84628f2c8b9fa1e43323", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "31990c6a3579b36a3c0841d34a94c275e727de8b84f58509da5f1b2032c98ac2"}, + "faker": {:hex, :faker, "0.17.0", "671019d0652f63aefd8723b72167ecdb284baf7d47ad3a82a15e9b8a6df5d1fa", [:mix], [], "hexpm", "a7d4ad84a93fd25c5f5303510753789fc2433ff241bf3b4144d3f6f291658a6a"}, "gettext": {:hex, :gettext, "0.18.1", "89e8499b051c7671fa60782faf24409b5d2306aa71feb43d79648a8bc63d0522", [:mix], [], "hexpm", "e70750c10a5f88cb8dc026fc28fa101529835026dec4a06dba3b614f2a99c7a9"}, "jason": {:hex, :jason, "1.2.1", "12b22825e22f468c02eb3e4b9985f3d0cb8dc40b9bd704730efa11abd2708c44", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b659b8571deedf60f79c5a608e15414085fa141344e2716fbd6988a084b5f993"}, "mime": {:hex, :mime, "1.4.0", "5066f14944b470286146047d2f73518cf5cca82f8e4815cf35d196b58cf07c47", [:mix], [], "hexpm", "75fa42c4228ea9a23f70f123c74ba7cece6a03b1fd474fe13f6a7a85c6ea4ff6"}, diff --git a/elixir/priv/repo/migrations/20211127183521_company_model.exs b/elixir/priv/repo/migrations/20211127183521_company_model.exs new file mode 100644 index 00000000..8fa72df0 --- /dev/null +++ b/elixir/priv/repo/migrations/20211127183521_company_model.exs @@ -0,0 +1,14 @@ +defmodule Homework.Repo.Migrations.CompanyModel do + use Ecto.Migration + + def change do + create table(:company, primary_key: false) do + add(:id, :uuid, primary_key: true) + add(:credit_line, :integer) + add(:available_credit, :integer) + add(:name, :string) + timestamps() + end + + end +end diff --git a/elixir/priv/repo/migrations/20211127184041_associate_users_company.exs b/elixir/priv/repo/migrations/20211127184041_associate_users_company.exs new file mode 100644 index 00000000..ab1ea54f --- /dev/null +++ b/elixir/priv/repo/migrations/20211127184041_associate_users_company.exs @@ -0,0 +1,9 @@ +defmodule Homework.Repo.Migrations.AssociateUsersCompany do + use Ecto.Migration + + def change do + alter table(:users) do + add :company_id, references(:company, type: :uuid, on_delete: :nothing) + end + end +end diff --git a/elixir/priv/repo/migrations/20211205005058_add-transaction-relation-company.exs b/elixir/priv/repo/migrations/20211205005058_add-transaction-relation-company.exs new file mode 100644 index 00000000..1f3b2186 --- /dev/null +++ b/elixir/priv/repo/migrations/20211205005058_add-transaction-relation-company.exs @@ -0,0 +1,10 @@ +defmodule :"Elixir.Homework.Repo.Migrations.Add-transaction-relation-company" do + use Ecto.Migration + + + def change do + alter table(:transactions) do + add(:company_id, references(:company, type: :uuid, on_delete: :nothing)) + end + end +end diff --git a/elixir/priv/repo/migrations/20220503015957_create_cards.exs b/elixir/priv/repo/migrations/20220503015957_create_cards.exs new file mode 100644 index 00000000..a130b050 --- /dev/null +++ b/elixir/priv/repo/migrations/20220503015957_create_cards.exs @@ -0,0 +1,12 @@ +defmodule Homework.Repo.Migrations.CreateCards do + use Ecto.Migration + + def change do + create table(:cards) do + add :description, :string + + timestamps() + end + + end +end diff --git a/elixir/priv/repo/migrations/20220503021205_create_price.exs b/elixir/priv/repo/migrations/20220503021205_create_price.exs new file mode 100644 index 00000000..cb007bde --- /dev/null +++ b/elixir/priv/repo/migrations/20220503021205_create_price.exs @@ -0,0 +1,14 @@ +defmodule Homework.Repo.Migrations.CreatePrice do + use Ecto.Migration + + def change do + create table(:price) do + add :category, :string + add :amount, :integer + add :type, :string + + timestamps() + end + + end +end diff --git a/elixir/priv/repo/seeds.exs b/elixir/priv/repo/seeds.exs index 718e38ca..5a8e98f3 100644 --- a/elixir/priv/repo/seeds.exs +++ b/elixir/priv/repo/seeds.exs @@ -1,11 +1,69 @@ -# 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: -# -# Homework.Repo.insert!(%Homework.SomeSchema{}) -# -# We recommend using the bang functions (`insert!`, `update!` -# and so on) as they will fail if something goes wrong. +defmodule Homework.DatabaseSeeder do + alias Homework.Companies.Company + alias Homework.Repo + alias Homework.Users.User + alias Homework.Merchants.Merchant + alias Homework.Transactions.Transaction + + def insert_company do + Repo.insert! %Company{ + available_credit: 99, + credit_line: 1000, + name: Faker.Company.name() + } + end + + def insert_user(company) do + Repo.insert! %User{ + first_name: Faker.Person.first_name(), + last_name: Faker.Person.last_name(), + dob: Date.to_string(Faker.Date.date_of_birth(1970 .. 1990)), + company_id: Map.get(company, :id) + } + end + + def insert_merchant do + Repo.insert! %Merchant{ + description: Faker.Lorem.sentence(6 .. 10), + name: Faker.Commerce.En.department() + } + end + + def insert_transaction(user, merchant) do + Repo.insert! %Transaction{ + amount: :rand.uniform(100), + credit: true, + debit: false, + description: Faker.Commerce.En.department(), + merchant_id: Map.get(merchant, :id), + user_id: Map.get(user, :id), + company_id: Map.get(user, :company_id) + } + end + + def generate_data_set() do + # create companies and merchants + (1 .. 10) |> Enum.each(fn _ -> Homework.DatabaseSeeder.insert_merchant() end) + (1 .. 5) |> Enum.each(fn _ -> Homework.DatabaseSeeder.insert_company() end) + # get companaies and merchants in a list + c = Homework.Repo.all(Homework.Companies.Company) + m = Homework.Repo.all(Homework.Merchants.Merchant) + # create users beloning to companies + Enum.each(c, fn comp -> (1 .. 10) |> Enum.each(fn _ -> Homework.DatabaseSeeder.insert_user(comp)end)end) + + u = Homework.Repo.all(Homework.Users.User) + # loop through merchants, loop through users. Create tranactions assign transaction to user, company and merchant + Enum.each(m, fn (merch) -> + Enum.each(u, fn (usr) -> + (1 .. 10) |> Enum.each(fn _ -> Homework.DatabaseSeeder.insert_transaction(usr, merch) end) + end) + + end) + + end +end + + + + +# Homework.DatabaseSeeder.generate_data_set() diff --git a/elixir/test/homework/account_test.exs b/elixir/test/homework/account_test.exs new file mode 100644 index 00000000..08facf6f --- /dev/null +++ b/elixir/test/homework/account_test.exs @@ -0,0 +1,64 @@ +defmodule Homework.AccountTest do + use Homework.DataCase + + alias Homework.Account + + describe "cards" do + alias Homework.Account.Card + + @valid_attrs %{description: "some description"} + @update_attrs %{description: "some updated description"} + @invalid_attrs %{description: nil} + + def card_fixture(attrs \\ %{}) do + {:ok, card} = + attrs + |> Enum.into(@valid_attrs) + |> Account.create_card() + + card + end + + test "list_cards/0 returns all cards" do + card = card_fixture() + assert Account.list_cards() == [card] + end + + test "get_card!/1 returns the card with given id" do + card = card_fixture() + assert Account.get_card!(card.id) == card + end + + test "create_card/1 with valid data creates a card" do + assert {:ok, %Card{} = card} = Account.create_card(@valid_attrs) + assert card.description == "some description" + end + + test "create_card/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Account.create_card(@invalid_attrs) + end + + test "update_card/2 with valid data updates the card" do + card = card_fixture() + assert {:ok, %Card{} = card} = Account.update_card(card, @update_attrs) + assert card.description == "some updated description" + end + + test "update_card/2 with invalid data returns error changeset" do + card = card_fixture() + assert {:error, %Ecto.Changeset{}} = Account.update_card(card, @invalid_attrs) + assert card == Account.get_card!(card.id) + end + + test "delete_card/1 deletes the card" do + card = card_fixture() + assert {:ok, %Card{}} = Account.delete_card(card) + assert_raise Ecto.NoResultsError, fn -> Account.get_card!(card.id) end + end + + test "change_card/1 returns a card changeset" do + card = card_fixture() + assert %Ecto.Changeset{} = Account.change_card(card) + end + end +end diff --git a/elixir/test/homework/companies_test.exs b/elixir/test/homework/companies_test.exs new file mode 100644 index 00000000..d34e384b --- /dev/null +++ b/elixir/test/homework/companies_test.exs @@ -0,0 +1,137 @@ +defmodule Homework.CompaniesTest do + use Homework.DataCase + + alias Homework.Companies + alias Homework.Transactions + alias Homework.Merchants + alias Homework.Users + + describe "company" do + alias Homework.Companies.Company + + setup do + valid_attrs = %{available_credit: 42, credit_line: 42, name: "some name"} + update_attrs = %{available_credit: 43, credit_line: 43, name: "some updated name"} + invalid_attrs = %{available_credit: nil, credit_line: nil, name: nil} + + {:ok, company1} = Companies.create_company(%{ + available_credit: 100, + credit_line: 300, + name: "Company One" + }) + + {:ok, merchant} = + Merchants.create_merchant(%{ + description: "some updated description", + name: "some updated name" + }) + + {:ok, user} = + Users.create_user(%{ + dob: "some dob", + first_name: "some first_name", + company_id: company1.id, + last_name: "some last_name", + }) + + {:ok, %{ + valid_attrs: valid_attrs, + update_attrs: update_attrs, + invalid_attrs: invalid_attrs, + merchant: merchant, + user: user, + company1: company1 + }} + end + + + def company_fixture(valid_attrs, attrs \\ %{}) do + {:ok, company} = + attrs + |> Enum.into(valid_attrs) + |> Companies.create_company() + + company + end + + test "list_company/0 returns all company", %{valid_attrs: valid_attrs, company1: company1} do + company = company_fixture(valid_attrs) + assert Companies.list_company([]) == [company1, company] + end + + test "get_company!/1 returns the company with given id", %{valid_attrs: valid_attrs} do + company = company_fixture(valid_attrs) + assert Companies.get_company!(company.id) == company + end + + test "create_company/1 with valid data creates a company", %{valid_attrs: valid_attrs} do + assert {:ok, %Company{} = company} = Companies.create_company(valid_attrs) + assert company.available_credit == 42 + assert company.credit_line == 42 + assert company.name == "some name" + end + + test "create_company/1 with invalid data returns error changeset", %{invalid_attrs: invalid_attrs} do + assert {:error, %Ecto.Changeset{}} = Companies.create_company(invalid_attrs) + end + + test "update_company/2 with valid data updates the company", %{ + update_attrs: update_attrs, + valid_attrs: valid_attrs + + } do + company = company_fixture(valid_attrs) + assert {:ok, %Company{} = company} = Companies.update_company(company, update_attrs) + assert company.available_credit == 43 + assert company.credit_line == 43 + assert company.name == "some updated name" + end + + test "update_company/2 with invalid data returns error changeset", %{ + invalid_attrs: invalid_attrs, + valid_attrs: valid_attrs + } do + company = company_fixture(valid_attrs) + assert {:error, %Ecto.Changeset{}} = Companies.update_company(company, invalid_attrs) + assert company == Companies.get_company!(company.id) + end + + test "delete_company/1 deletes the company", %{valid_attrs: valid_attrs} do + company = company_fixture(valid_attrs) + assert {:ok, %Company{}} = Companies.delete_company(company) + assert_raise Ecto.NoResultsError, fn -> Companies.get_company!(company.id) end + end + + test "change_company/1 returns a company changeset", %{ + valid_attrs: valid_attrs, + } + do + company = company_fixture(valid_attrs) + assert %Ecto.Changeset{} = Companies.change_company(company) + end + + test "chanage_company/1 updates avaialble credit after transaction", %{ + user: user1, + merchant: merchant, + company1: company1 + }do + + {:ok, transaction} = Transactions.create_transaction(%{ + amount: 100, + credit: true, + debit: false, + description: "some description", + company_id: company1.id, + user_id: user1.id, + merchant_id: merchant.id + }) + co = Companies.get_company!(company1.id) + + + assert transaction.amount == 100 + assert co.available_credit == 0 + + + end + end +end diff --git a/elixir/test/homework/shiren_test.exs b/elixir/test/homework/shiren_test.exs new file mode 100644 index 00000000..579bc1d8 --- /dev/null +++ b/elixir/test/homework/shiren_test.exs @@ -0,0 +1,68 @@ +defmodule Homework.ShirenTest do + use Homework.DataCase + + alias Homework.Shiren + + describe "price" do + alias Homework.Shiren.Prices + + @valid_attrs %{amount: 42, category: "some category", type: "some type"} + @update_attrs %{amount: 43, category: "some updated category", type: "some updated type"} + @invalid_attrs %{amount: nil, category: nil, type: nil} + + def prices_fixture(attrs \\ %{}) do + {:ok, prices} = + attrs + |> Enum.into(@valid_attrs) + |> Shiren.create_prices() + + prices + end + + test "list_price/0 returns all price" do + prices = prices_fixture() + assert Shiren.list_price() == [prices] + end + + test "get_prices!/1 returns the prices with given id" do + prices = prices_fixture() + assert Shiren.get_prices!(prices.id) == prices + end + + test "create_prices/1 with valid data creates a prices" do + assert {:ok, %Prices{} = prices} = Shiren.create_prices(@valid_attrs) + assert prices.amount == 42 + assert prices.category == "some category" + assert prices.type == "some type" + end + + test "create_prices/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Shiren.create_prices(@invalid_attrs) + end + + test "update_prices/2 with valid data updates the prices" do + prices = prices_fixture() + assert {:ok, %Prices{} = prices} = Shiren.update_prices(prices, @update_attrs) + assert prices.amount == 43 + assert prices.category == "some updated category" + assert prices.type == "some updated type" + end + + test "update_prices/2 with invalid data returns error changeset" do + prices = prices_fixture() + assert {:error, %Ecto.Changeset{}} = Shiren.update_prices(prices, @invalid_attrs) + assert prices == Shiren.get_prices!(prices.id) + end + + test "delete_prices/1 deletes the prices" do + prices = prices_fixture() + assert {:ok, %Prices{}} = Shiren.delete_prices(prices) + assert_raise Ecto.NoResultsError, fn -> Shiren.get_prices!(prices.id) end + end + + test "change_prices/1 returns a prices changeset" do + prices = prices_fixture() + assert %Ecto.Changeset{} = Shiren.change_prices(prices) + end + end +end diff --git a/elixir/test/homework/transactions_test.exs b/elixir/test/homework/transactions_test.exs index 03071aff..53427d73 100644 --- a/elixir/test/homework/transactions_test.exs +++ b/elixir/test/homework/transactions_test.exs @@ -5,11 +5,26 @@ defmodule Homework.TransactionsTest do alias Homework.Merchants alias Homework.Transactions alias Homework.Users + alias Homework.Companies describe "transactions" do alias Homework.Transactions.Transaction setup do + {:ok, company1} = + Companies.create_company(%{ + available_credit: 100, + credit_line: 200, + name: "company1" + }) + + {:ok, company2} = + Companies.create_company(%{ + available_credit: 200, + credit_line: 300, + name: "company2" + }) + {:ok, merchant1} = Merchants.create_merchant(%{description: "some description", name: "some name"}) @@ -23,6 +38,7 @@ defmodule Homework.TransactionsTest do Users.create_user(%{ dob: "some dob", first_name: "some first_name", + company_id: company1.id, last_name: "some last_name" }) @@ -30,25 +46,29 @@ defmodule Homework.TransactionsTest do Users.create_user(%{ dob: "some updated dob", first_name: "some updated first_name", - last_name: "some updated last_name" + last_name: "some updated last_name", + company_id: company2.id, }) + valid_attrs = %{ amount: 42, credit: true, - debit: true, + debit: false, description: "some description", merchant_id: merchant1.id, - user_id: user1.id + user_id: user1.id, + company_id: company1.id } update_attrs = %{ amount: 43, credit: false, - debit: false, + debit: true, description: "some updated description", merchant_id: merchant2.id, - user_id: user2.id + user_id: user2.id, + company_id: company2.id } invalid_attrs = %{ @@ -57,7 +77,8 @@ defmodule Homework.TransactionsTest do debit: nil, description: nil, merchant_id: nil, - user_id: nil + user_id: nil, + company_id: nil } {:ok, @@ -68,7 +89,9 @@ defmodule Homework.TransactionsTest do merchant1: merchant1, merchant2: merchant2, user1: user1, - user2: user2 + user2: user2, + company1: company1, + company2: company2 }} end @@ -94,7 +117,8 @@ defmodule Homework.TransactionsTest do test "create_transaction/1 with valid data creates a transaction", %{ valid_attrs: valid_attrs, merchant1: merchant1, - user1: user1 + user1: user1, + company1: company1 } do assert {:ok, %Transaction{} = transaction} = Transactions.create_transaction(valid_attrs) assert transaction.amount == 42 @@ -103,6 +127,7 @@ defmodule Homework.TransactionsTest do assert transaction.description == "some description" assert transaction.merchant_id == merchant1.id assert transaction.user_id == user1.id + assert transaction.company_id == company1.id end test "create_transaction/1 with invalid data returns error changeset", %{ diff --git a/elixir/test/homework/users_test.exs b/elixir/test/homework/users_test.exs index e1ae65c2..0e2a8369 100644 --- a/elixir/test/homework/users_test.exs +++ b/elixir/test/homework/users_test.exs @@ -2,70 +2,89 @@ defmodule Homework.UsersTest do use Homework.DataCase alias Homework.Users + alias Homework.Companies describe "users" do alias Homework.Users.User + setup do + {:ok, company1} = Companies.create_company(%{name: "some company", credit_line: 99, available_credit: 100}) - @valid_attrs %{dob: "some dob", first_name: "some first_name", last_name: "some last_name"} - @update_attrs %{ + {:ok, company2} = Companies.create_company(%{name: "some company2", credit_line: 199, available_credit: 200}) + + valid_attrs = %{dob: "some dob", first_name: "some first_name", last_name: "some last_name", company_id: company1.id } + + update_attrs = %{ dob: "some updated dob", first_name: "some updated first_name", - last_name: "some updated last_name" + last_name: "some updated last_name", + company_id: company1.id } - @invalid_attrs %{dob: nil, first_name: nil, last_name: nil} - def user_fixture(attrs \\ %{}) do - {:ok, user} = - attrs - |> Enum.into(@valid_attrs) - |> Users.create_user() + invalid_attrs = %{dob: nil, first_name: nil, last_name: nil, company_id: company1.id} + {:ok, + %{ + valid_attrs: valid_attrs, + update_attrs: update_attrs, + invalid_attrs: invalid_attrs, + company1: company1, + comopany2: company2 + }} + + end + def user_fixture(valid_attrs, attrs \\ %{}) do + {:ok, user} = attrs |> Enum.into(valid_attrs) |> Users.create_user() user end - test "list_users/1 returns all users" do - user = user_fixture() + test "list_users/1 returns all users", %{valid_attrs: valid_attrs} do + user = user_fixture(valid_attrs) assert Users.list_users([]) == [user] end - test "get_user!/1 returns the user with given id" do - user = user_fixture() + test "get_user!/1 returns the user with given id", %{valid_attrs: valid_attrs} do + user = user_fixture(valid_attrs) assert Users.get_user!(user.id) == user end - test "create_user/1 with valid data creates a user" do - assert {:ok, %User{} = user} = Users.create_user(@valid_attrs) + test "create_user/1 with valid data creates a user", %{valid_attrs: valid_attrs} do + assert {:ok, %User{} = user} = Users.create_user(valid_attrs) assert user.dob == "some dob" 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{}} = Users.create_user(@invalid_attrs) + test "create_user/1 with invalid data returns error changeset", %{invalid_attrs: invalid_attrs} do + assert {:error, %Ecto.Changeset{}} = Users.create_user(invalid_attrs) end - test "update_user/2 with valid data updates the user" do - user = user_fixture() - assert {:ok, %User{} = user} = Users.update_user(user, @update_attrs) + test "update_user/2 with valid data updates the user", %{ + update_attrs: update_attrs, + valid_attrs: valid_attrs, + company2: company2, + } do + user = user_fixture(valid_attrs) + assert {:ok, %User{} = user} = Users.update_user(user, update_attrs) assert user.dob == "some updated dob" assert user.first_name == "some updated first_name" assert user.last_name == "some updated last_name" + assert user.compan_id == company2.id end - test "update_user/2 with invalid data returns error changeset" do - user = user_fixture() - assert {:error, %Ecto.Changeset{}} = Users.update_user(user, @invalid_attrs) + test "update_user/2 with invalid data returns error changeset", %{invalid_attrs: invalid_attrs} do + user = user_fixture(invalid_attrs) + assert {:error, %Ecto.Changeset{}} = Users.update_user(user, invalid_attrs) assert user == Users.get_user!(user.id) end - test "delete_user/1 deletes the user" do - user = user_fixture() + test "delete_user/1 deletes the user", %{valid_attrs: valid_attrs} do + user = user_fixture(valid_attrs) assert {:ok, %User{}} = Users.delete_user(user) assert_raise Ecto.NoResultsError, fn -> Users.get_user!(user.id) end end - test "change_user/1 returns a user changeset" do - user = user_fixture() + test "change_user/1 returns a user changeset", %{updated_user: updated_user} do + user = user_fixture(updated_user) assert %Ecto.Changeset{} = Users.change_user(user) end end diff --git a/elixir/test/homework_web/controllers/prices_controller_test.exs b/elixir/test/homework_web/controllers/prices_controller_test.exs new file mode 100644 index 00000000..a76afcff --- /dev/null +++ b/elixir/test/homework_web/controllers/prices_controller_test.exs @@ -0,0 +1,96 @@ +defmodule HomeworkWeb.PricesControllerTest do + use HomeworkWeb.ConnCase + + alias Homework.Shiren + alias Homework.Shiren.Prices + + @create_attrs %{ + amount: 42, + category: "some category", + type: "some type" + } + @update_attrs %{ + amount: 43, + category: "some updated category", + type: "some updated type" + } + @invalid_attrs %{amount: nil, category: nil, type: nil} + + def fixture(:prices) do + {:ok, prices} = Shiren.create_prices(@create_attrs) + prices + end + + setup %{conn: conn} do + {:ok, conn: put_req_header(conn, "accept", "application/json")} + end + + describe "index" do + test "lists all price", %{conn: conn} do + conn = get(conn, Routes.prices_path(conn, :index)) + assert json_response(conn, 200)["data"] == [] + end + end + + describe "create prices" do + test "renders prices when data is valid", %{conn: conn} do + conn = post(conn, Routes.prices_path(conn, :create), prices: @create_attrs) + assert %{"id" => id} = json_response(conn, 201)["data"] + + conn = get(conn, Routes.prices_path(conn, :show, id)) + + assert %{ + "id" => id, + "amount" => 42, + "category" => "some category", + "type" => "some type" + } = json_response(conn, 200)["data"] + end + + test "renders errors when data is invalid", %{conn: conn} do + conn = post(conn, Routes.prices_path(conn, :create), prices: @invalid_attrs) + assert json_response(conn, 422)["errors"] != %{} + end + end + + describe "update prices" do + setup [:create_prices] + + test "renders prices when data is valid", %{conn: conn, prices: %Prices{id: id} = prices} do + conn = put(conn, Routes.prices_path(conn, :update, prices), prices: @update_attrs) + assert %{"id" => ^id} = json_response(conn, 200)["data"] + + conn = get(conn, Routes.prices_path(conn, :show, id)) + + assert %{ + "id" => id, + "amount" => 43, + "category" => "some updated category", + "type" => "some updated type" + } = json_response(conn, 200)["data"] + end + + test "renders errors when data is invalid", %{conn: conn, prices: prices} do + conn = put(conn, Routes.prices_path(conn, :update, prices), prices: @invalid_attrs) + assert json_response(conn, 422)["errors"] != %{} + end + end + + describe "delete prices" do + setup [:create_prices] + + test "deletes chosen prices", %{conn: conn, prices: prices} do + conn = delete(conn, Routes.prices_path(conn, :delete, prices)) + assert response(conn, 204) + + assert_error_sent 404, fn -> + get(conn, Routes.prices_path(conn, :show, prices)) + end + end + end + + defp create_prices(_) do + prices = fixture(:prices) + %{prices: prices} + end +end diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..e5e0e4b0 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,8 @@ +Faker==9.9.0 +greenlet==1.1.2 +psycopg2==2.9.2 +python-dateutil==2.8.2 +pytz==2021.3 +six==1.16.0 +SQLAlchemy==1.4.27 +text-unidecode==1.3 diff --git a/seed.py b/seed.py new file mode 100644 index 00000000..47f41cd5 --- /dev/null +++ b/seed.py @@ -0,0 +1,171 @@ +import uuid +import random +from datetime import datetime +import pytz +from sqlalchemy import create_engine, Column, Integer, String, DateTime, Boolean, ForeignKey, select +from sqlalchemy.orm import declarative_base, sessionmaker, relationship, backref +from sqlalchemy.dialects.postgresql import UUID +from faker import Faker + + +################################# + +# How to use this file +# Before using these steps, use mix ecto.create, mix ecto.migrate to create and migrate the database. +# 1. Use pyenv to create a virtual environment to install the dependencies. +# 2. Type pyenv virtualenv 3.8.4 seed-environment. This will create a python environment with Python v. 3.8.4 +# 3. Activate the environment by typing pyenv activate seed-environment +# 4. Install the dependencies using the requiremnts.txt file. Type: pip install -r requirements.txt +# 5. Update the path to your postgres database. Set the DATABASE_URI url to 'postgresql+psycopg2://:@localhost/homework_dev' +# 5. When that is complete, type python seed.py and it will seed the database. +################################# + + + +faker = Faker() +DATABASE_URI = 'postgresql+psycopg2://cjem:password@localhost/homework_dev' +Base = declarative_base() + +engine = create_engine(DATABASE_URI, echo=True) +Session = sessionmaker(bind=engine) +session = Session() + +################################# + +# creates the table +# Base.metadata.create_all(engine) + +################################# + +class Company(Base): + __tablename__='company' + id = Column(UUID(as_uuid=True), primary_key=True) + name = Column(String) + available_credit = Column(Integer) + credit_line = Column(Integer) + inserted_at=Column(DateTime, nullable=False) + updated_at=Column(DateTime, nullable=False) + user = relationship("Users", back_populates="company", uselist=False) + transaction = relationship("Transactions", back_populates="company", uselist=False) + +class Users(Base): + __tablename__ = 'users' + id = Column(UUID(as_uuid=True), primary_key=True) + first_name = Column(String) + last_name = Column(String) + dob = Column(String) + inserted_at=Column(DateTime, nullable=False) + updated_at=Column(DateTime, nullable=False) + transaction = relationship("Transactions", back_populates="users", uselist=False) + company_id = Column(UUID(as_uuid=True), ForeignKey('company.id')) + company = relationship("Company", back_populates="user") + + + +class Merchants(Base): + __tablename__ = 'merchants' + id = Column(UUID(as_uuid=True), primary_key=True) + name = Column(String) + description = Column(String) + inserted_at=Column(DateTime, nullable=False) + updated_at=Column(DateTime, nullable=False) + transaction = relationship("Transactions", back_populates="merchants", uselist=False) + +class Transactions(Base): + __tablename__ = 'transactions' + id = Column(UUID(as_uuid=True), primary_key=True) + amount = Column(Integer) + credit = Column(Boolean) + debit = Column(Boolean) + description = Column(String) + user_id = Column(UUID(as_uuid=True), ForeignKey('users.id')) + users = relationship("Users", back_populates="transaction") + merchant_id = Column(UUID(as_uuid=True), ForeignKey('merchants.id')) + merchants = relationship("Merchants", back_populates="transaction") + inserted_at=Column(DateTime, nullable=False) + updated_at=Column(DateTime, nullable=False) + company_id = Column(UUID(as_uuid=True), ForeignKey('company.id')) + company = relationship("Company", back_populates="transaction") + + +# c = Company( +# id=uuid.uuid1(), +# name="Hello", +# available_credit=1000, +# credit_line=1000, +# inserted_at=datetime.utcnow().strftime('%Y-%m-%d %H:%m:%S'), +# updated_at=datetime.utcnow().strftime('%Y-%m-%d %H:%m:%S') +# ) +for entry in range (1, 10): + + c = Company( + id=uuid.uuid1(), + name=faker.company(), + credit_line=100000, + inserted_at=datetime.utcnow().strftime('%Y-%m-%d %H:%m:%S'), + updated_at=datetime.utcnow().strftime('%Y-%m-%d %H:%m:%S') + ) + session.add(c) + session.commit() + + for u in range(1, 20): + user = Users( + id=uuid.uuid1(), + first_name=faker.first_name(), + last_name=faker.last_name(), + dob=faker.date_of_birth(), + inserted_at=datetime.utcnow().strftime('%Y-%m-%d %H:%m:%S'), + updated_at=datetime.utcnow().strftime('%Y-%m-%d %H:%m:%S'), + company_id = c.id + ) + session.add(user) + session.commit() + + merch = Merchants( + id=uuid.uuid1(), + name=faker.company(), + description=faker.catch_phrase(), + inserted_at=datetime.utcnow().strftime('%Y-%m-%d %H:%m:%S'), + updated_at=datetime.utcnow().strftime('%Y-%m-%d %H:%m:%S') + ) + + session.add(merch) + session.commit() + + for purchase in range(1, 5): + purchase = Transactions( + id=uuid.uuid1(), + amount=random.randrange(1, 1000), + credit=False, + debit=True, + description=faker.sentence(nb_words=5), + user_id=user.id, + company_id=c.id, + merchant_id=merch.id, + inserted_at=datetime.utcnow().strftime('%Y-%m-%d %H:%m:%S'), + updated_at=datetime.utcnow().strftime('%Y-%m-%d %H:%m:%S') + + ) + session.add(purchase) + session.commit() + + +######################################### +## QUERIES + +## for row in session.query(User, User.first_name).all(): +## print(row.User, row.first_name) + +######################################### + + + +## for i in range(1, 10): +## print(i) +## user = User(name=fake.name()) +## session.add(user) +## session.commit() +# # Base.metadata.create_all(engine) + +