Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

create tags and captures_tags tables #16

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ app_api-*.tar
# we ignore priv/static. You may want to comment
# this depending on your deployment strategy.
/priv/static/
.elixir_ls
.elixir_ls
cover
22 changes: 22 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
language: elixir
elixir:
- 1.9
otp_release:
- 22.1.8
env:
- MIX_ENV=test
cache:
directories:
- _build
- deps
services:
- postgresql
env:
global:
- MIX_ENV=test
before_script:
- mix do ecto.create, ecto.migrate
script:
- mix cover
after_success:
- bash <(curl -s https://codecov.io/bash)
9 changes: 9 additions & 0 deletions coveralls.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"coverage_options": {
"minimum_coverage": 80
},
"skip_files": [
"test/",
"lib/app_api.ex"
]
}
11 changes: 10 additions & 1 deletion lib/app_api/captures.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ defmodule AppApi.Captures do

alias AppApi.Captures.Capture
alias AppApi.Timers.Timer
alias AppApi.Tags.Tag

@doc """
Returns the list of captures.
Expand All @@ -20,6 +21,8 @@ defmodule AppApi.Captures do
"""
def list_captures do
Repo.all(Capture)
|> Repo.preload(timers: from(t in Timer, order_by: [desc: t.inserted_at]))
|> Repo.preload(tags: from(t in Tag, order_by: [asc: t.text]))
end

@doc """
Expand All @@ -39,14 +42,18 @@ defmodule AppApi.Captures do
def get_capture!(id) do
Repo.get!(Capture, id)
|> Repo.preload(timers: from(t in Timer, order_by: [desc: t.inserted_at]))
|> Repo.preload(tags: from(t in Tag, order_by: [asc: t.text]))
end

def get_capture_by_id_person(id_person) do
query =
from c in Capture,
where: c.id_person == ^id_person,
order_by: [desc: c.inserted_at],
preload: [timers: ^from(t in Timer, order_by: [desc: t.inserted_at])]
preload: [
timers: ^from(t in Timer, order_by: [desc: t.inserted_at]),
tags: ^from(t in Tag, order_by: [asc: t.text])
]

Repo.all(query)
end
Expand All @@ -68,6 +75,7 @@ defmodule AppApi.Captures do
|> Capture.changeset(attrs)
|> Repo.insert!()
|> Repo.preload(timers: from(t in Timer, order_by: [desc: t.inserted_at]))
|> Repo.preload(tags: from(t in Tag, order_by: [asc: t.text]))
end

@doc """
Expand All @@ -87,6 +95,7 @@ defmodule AppApi.Captures do
|> Capture.changeset(attrs)
|> Repo.update!()
|> Repo.preload(timers: from(t in Timer, order_by: [desc: t.inserted_at]))
|> Repo.preload(tags: from(t in Tag, order_by: [asc: t.text]))
end

@doc """
Expand Down
32 changes: 32 additions & 0 deletions lib/app_api/captures/capture.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,20 @@ defmodule AppApi.Captures.Capture do
use Ecto.Schema
import Ecto.Changeset
alias AppApi.Timers.Timer
alias AppApi.Tags.Tag
import Ecto.Query, warn: false
alias AppApi.Repo

schema "captures" do
field :completed, :boolean, default: false
field :id_person, :integer
field :text, :string
has_many :timers, Timer

many_to_many :tags, Tag,
join_through: "captures_tags",
on_replace: :delete

timestamps()
end

Expand All @@ -17,5 +24,30 @@ defmodule AppApi.Captures.Capture do
capture
|> cast(attrs, [:id_person, :text, :completed])
|> validate_required([:id_person, :text, :completed])
|> put_assoc(:tags, parse_tags(attrs))
end

defp parse_tags(params) do
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tags are passed as a string separated by a comma

tags =
(params[:tags] || "")
|> String.split(",")
|> Enum.map(&String.trim/1)
|> Enum.reject(&(&1 == ""))

insert_and_get_all(tags, params[:id_person])
end

defp insert_and_get_all([], _id_person) do
[]
end

defp insert_and_get_all(tags, id_person) do
Enum.each(tags, fn t ->
attrs = %{text: t, id_person: id_person}
changeset = Tag.changeset(%Tag{}, attrs)
Repo.insert!(changeset, on_conflict: :nothing)
end)

Repo.all(from t in Tag, where: t.text in ^tags)
end
end
45 changes: 45 additions & 0 deletions lib/app_api/tags.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
defmodule AppApi.Tags do
@moduledoc """
The Tags context.
"""

import Ecto.Query, warn: false
alias AppApi.Repo

alias AppApi.Tags.Tag

@doc """
Returns the list of tags created by a person
i.e. default tags
"""
def get_tags_by_id_person(id_person) do
query =
from t in Tag,
where: t.id_person == ^id_person,
order_by: [asc: t.text]

Repo.all(query)
end

@doc """
Returns the list of tags where the person is not defined
i.e. default tags
"""
def get_default_tags() do
query =
from t in Tag,
where: is_nil(t.id_person),
order_by: [asc: t.text]

Repo.all(query)
end

@doc """
Creates a tag.
"""
def create_tag(attrs \\ %{}) do
%Tag{}
|> Tag.changeset(attrs)
|> Repo.insert!()
end
end
18 changes: 18 additions & 0 deletions lib/app_api/tags/tag.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
defmodule AppApi.Tags.Tag do
use Ecto.Schema
import Ecto.Changeset

schema "tags" do
field :text, :string
field :id_person, :integer

timestamps()
end

@doc false
def changeset(tag, attrs) do
tag
|> cast(attrs, [:text, :id_person])
|> validate_required([:text])
end
end
13 changes: 0 additions & 13 deletions lib/app_api/timers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,6 @@ defmodule AppApi.Timers do

alias AppApi.Timers.Timer

@doc """
Returns the list of timers.

## Examples

iex> list_timers()
[%Timer{}, ...]

"""
def list_timers do
Repo.all(Timer)
end

@doc """
Gets a single timer.

Expand Down
3 changes: 2 additions & 1 deletion lib/app_api_web/controllers/capture_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ defmodule AppApiWeb.CaptureController do

updates = %{
completed: params["completed"],
text: params["text"]
text: params["text"],
tags: params["tags"]
}

udpatedCatpure = Captures.update_capture(capture, updates)
Expand Down
10 changes: 10 additions & 0 deletions lib/app_api_web/controllers/tag_controller.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
defmodule AppApiWeb.TagController do
use AppApiWeb, :controller
alias AppApi.Tags

def index(conn, _params) do
tags = Tags.get_tags_by_id_person(conn.assigns.person.id_person)
default_tags = Tags.get_default_tags()
render(conn, "index.json", tags: default_tags ++ tags)
end
end
1 change: 1 addition & 0 deletions lib/app_api_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ defmodule AppApiWeb.Router do
resources "/capture", CaptureController, only: [:index, :create, :show, :update] do
resources "/timers", TimerController, only: [:index, :create, :update]
end
get "/tags", TagController, :index
end
end
4 changes: 3 additions & 1 deletion lib/app_api_web/views/capture_view.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
defmodule AppApiWeb.CaptureView do
use AppApiWeb, :view
alias AppApiWeb.TimerView
alias AppApiWeb.TagView

def render("index.json", %{captures: captures}) do
%{data: Enum.map(captures, &capture_to_json/1)}
Expand All @@ -20,7 +21,8 @@ defmodule AppApiWeb.CaptureView do
id_person: capture.id_person,
text: capture.text,
completed: capture.completed,
timers: Enum.map(capture.timers, &TimerView.timer_to_json/1)
timers: Enum.map(capture.timers, &TimerView.timer_to_json/1),
tags: Enum.map(capture.tags, &TagView.tag_to_json/1)
}
end
end
14 changes: 14 additions & 0 deletions lib/app_api_web/views/tag_view.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
defmodule AppApiWeb.TagView do
use AppApiWeb, :view

def render("index.json", %{tags: tags}) do
%{data: Enum.map(tags, &tag_to_json/1)}
end

def tag_to_json(tag) do
%{
text: tag.text,
id_person: tag.id_person
}
end
end
11 changes: 7 additions & 4 deletions lib/plugs/validate_token.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ defmodule AppApi.Plugs.ValidateToken do
def init(opts), do: opts

def call(conn, _) do
case AppApiWeb.AuthServiceApi.get_person_information(conn) do
{:error, _} -> unauthorized(conn)
{:ok, person} -> assign(conn, :person, person)
if Mix.env() == :test do
assign(conn, :person, %{email: "email", name: "name", id_person: 42})
else
case AppApiWeb.AuthServiceApi.get_person_information(conn) do
{:error, _} -> unauthorized(conn)
{:ok, person} -> assign(conn, :person, person)
end
end
end

Expand All @@ -21,5 +25,4 @@ defmodule AppApi.Plugs.ValidateToken do
|> send_resp(401, "unauthorized")
|> halt()
end

end
16 changes: 13 additions & 3 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,14 @@ defmodule AppApi.MixProject do
compilers: [:phoenix, :gettext] ++ Mix.compilers(),
start_permanent: Mix.env() == :prod,
aliases: aliases(),
deps: deps()
deps: deps(),
test_coverage: [tool: ExCoveralls],
preferred_cli_env: [
coveralls: :test,
"coveralls.detail": :test,
"coveralls.post": :test,
"coveralls.html": :test
]
]
end

Expand Down Expand Up @@ -43,7 +50,8 @@ defmodule AppApi.MixProject do
{:plug_cowboy, "~> 2.0"},
{:cors_plug, "~> 2.0"},
{:httpoison, "~> 1.6"},
{:poison, "~> 4.0"}
{:poison, "~> 4.0"},
{:excoveralls, "~> 0.12.2", only: [:dev, :test]}
]
end

Expand All @@ -57,7 +65,9 @@ defmodule AppApi.MixProject 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"]
test: ["ecto.create --quiet", "ecto.migrate", "test"],
cover: ["coveralls.json"],
"cover.html": ["coveralls.html"]
]
end
end
4 changes: 4 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
%{
"certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "805abd97539caf89ec6d4732c91e62ba9da0cda51ac462380bbd28ee697a8c42"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"},
"cors_plug": {:hex, :cors_plug, "2.0.1", "61b9fb38a599d1d4a7f8c7cec9f81ae548890e2f832426cea13897ecbf2f5f8b", [:mix], [{:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "571eea27ca6d08d46f44b27ce6ad59d29255111c3cefab833ea55c375db2b4eb"},
"cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "04fd8c6a39edc6aaa9c26123009200fc61f92a3a94f3178c527b70b767c6e605"},
Expand All @@ -8,6 +9,7 @@
"decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"},
"ecto": {:hex, :ecto, "3.3.2", "002aa428c752a8ee4bb65baa9d1041f0514d7435d2e21d58cb6aa69a1076721d", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "c651e3ea0919241da314f518404b84449c8569525c4d4cf0f3d16f1d5d0a560c"},
"ecto_sql": {:hex, :ecto_sql, "3.3.3", "7d8962d39f16181c1df1bbd0f64aa392bd9ce0b9f8ff5ff21d43dca3d624eee7", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4 or ~> 3.3.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3d5b8e14836d930448d79ab6ac325501c3c62caed83c34791d5af77718766795"},
"excoveralls": {:hex, :excoveralls, "0.12.3", "2142be7cb978a3ae78385487edda6d1aff0e482ffc6123877bb7270a8ffbcfe0", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "568a3e616c264283f5dea5b020783ae40eef3f7ee2163f7a67cbd7b35bcadada"},
"gettext": {:hex, :gettext, "0.17.4", "f13088e1ec10ce01665cf25f5ff779e7df3f2dc71b37084976cf89d1aa124d5c", [:mix], [], "hexpm", "3c75b5ea8288e2ee7ea503ff9e30dfe4d07ad3c054576a6e60040e79a801e14d"},
"hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "e0100f8ef7d1124222c11ad362c857d3df7cb5f4204054f9f0f4a728666591fc"},
"httpoison": {:hex, :httpoison, "1.6.2", "ace7c8d3a361cebccbed19c283c349b3d26991eff73a1eaaa8abae2e3c8089b6", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "aa2c74bd271af34239a3948779612f87df2422c2fdcfdbcec28d9c105f0773fe"},
Expand All @@ -28,5 +30,7 @@
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm", "13104d7897e38ed7f044c4de953a6c28597d1c952075eb2e328bc6d6f2bfc496"},
"telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"},
"timex": {:hex, :timex, "3.6.1", "efdf56d0e67a6b956cc57774353b0329c8ab7726766a11547e529357ffdc1d56", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "f354efb2400dd7a80fd9eb6c8419068c4f632da4ac47f3d8822d6e33f08bc852"},
"tzdata": {:hex, :tzdata, "1.0.3", "73470ad29dde46e350c60a66e6b360d3b99d2d18b74c4c349dbebbc27a09a3eb", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a6e1ee7003c4d04ecbd21dd3ec690d4c6662db5d3bbdd7262d53cdf5e7c746c1"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm", "1d1848c40487cdb0b30e8ed975e34e025860c02e419cb615d255849f3427439d"},
}
15 changes: 15 additions & 0 deletions priv/repo/migrations/20200420133108_create_tags.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
defmodule AppApi.Repo.Migrations.CreateTags do
use Ecto.Migration

def change do
create table(:tags) do
add :text, :string
add :id_person, :integer

timestamps()
end

create unique_index(:tags, [:text])
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This index makes sure the database doesn't contain tags with the same name

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SimonLab which issue requirement or acceptance criteria is this for?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tried to follow the requirements from dwyl/app#245

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SimonLab thanks for clarifying. In which case it could be worth having that issue linked in the PR description and having it "in-progress" on the Kanban board (which makes it easier for the reviewer) 😉

end

end
Loading