Skip to content

Commit e58b3b5

Browse files
Implement basic API
1 parent b0af706 commit e58b3b5

11 files changed

Lines changed: 436 additions & 0 deletions

File tree

lib/todo_backend/todos.ex

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
defmodule TodoBackend.Todos do
2+
@moduledoc """
3+
The Todos context.
4+
"""
5+
6+
import Ecto.Query, warn: false
7+
alias TodoBackend.Repo
8+
9+
alias TodoBackend.Todos.Todo
10+
11+
@doc """
12+
Returns the list of todos.
13+
14+
## Examples
15+
16+
iex> list_todos()
17+
[%Todo{}, ...]
18+
19+
"""
20+
def list_todos do
21+
Repo.all(Todo)
22+
end
23+
24+
@doc """
25+
Gets a single todo.
26+
27+
Raises `Ecto.NoResultsError` if the Todo does not exist.
28+
29+
## Examples
30+
31+
iex> get_todo!(123)
32+
%Todo{}
33+
34+
iex> get_todo!(456)
35+
** (Ecto.NoResultsError)
36+
37+
"""
38+
def get_todo!(id), do: Repo.get!(Todo, id)
39+
40+
@doc """
41+
Creates a todo.
42+
43+
## Examples
44+
45+
iex> create_todo(%{field: value})
46+
{:ok, %Todo{}}
47+
48+
iex> create_todo(%{field: bad_value})
49+
{:error, %Ecto.Changeset{}}
50+
51+
"""
52+
def create_todo(attrs \\ %{}) do
53+
%Todo{}
54+
|> Todo.changeset(attrs)
55+
|> Repo.insert()
56+
end
57+
58+
@doc """
59+
Updates a todo.
60+
61+
## Examples
62+
63+
iex> update_todo(todo, %{field: new_value})
64+
{:ok, %Todo{}}
65+
66+
iex> update_todo(todo, %{field: bad_value})
67+
{:error, %Ecto.Changeset{}}
68+
69+
"""
70+
def update_todo(%Todo{} = todo, attrs) do
71+
todo
72+
|> Todo.changeset(attrs)
73+
|> Repo.update()
74+
end
75+
76+
@doc """
77+
Deletes a todo.
78+
79+
## Examples
80+
81+
iex> delete_todo(todo)
82+
{:ok, %Todo{}}
83+
84+
iex> delete_todo(todo)
85+
{:error, %Ecto.Changeset{}}
86+
87+
"""
88+
def delete_todo(%Todo{} = todo) do
89+
Repo.delete(todo)
90+
end
91+
92+
@doc """
93+
Deletes all todos.
94+
95+
## Examples
96+
97+
iex> delete_all_todos()
98+
{deleted_count, nil}
99+
100+
"""
101+
def delete_all_todos() do
102+
Repo.delete_all(Todo)
103+
end
104+
105+
@doc """
106+
Returns an `%Ecto.Changeset{}` for tracking todo changes.
107+
108+
## Examples
109+
110+
iex> change_todo(todo)
111+
%Ecto.Changeset{data: %Todo{}}
112+
113+
"""
114+
def change_todo(%Todo{} = todo, attrs \\ %{}) do
115+
Todo.changeset(todo, attrs)
116+
end
117+
end

lib/todo_backend/todos/todo.ex

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
defmodule TodoBackend.Todos.Todo do
2+
use Ecto.Schema
3+
import Ecto.Changeset
4+
5+
schema "todos" do
6+
field :completed, :boolean, default: false
7+
field :title, :string
8+
9+
timestamps()
10+
end
11+
12+
@doc false
13+
def changeset(todo, attrs) do
14+
todo
15+
|> cast(attrs, [:title, :completed])
16+
|> validate_required([:title, :completed])
17+
end
18+
end
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
defmodule TodoBackendWeb.FallbackController do
2+
@moduledoc """
3+
Translates controller action results into valid `Plug.Conn` responses.
4+
5+
See `Phoenix.Controller.action_fallback/1` for more details.
6+
"""
7+
use TodoBackendWeb, :controller
8+
9+
# This clause handles errors returned by Ecto's insert/update/delete.
10+
def call(conn, {:error, %Ecto.Changeset{} = changeset}) do
11+
conn
12+
|> put_status(:unprocessable_entity)
13+
|> put_view(TodoBackendWeb.ChangesetView)
14+
|> render("error.json", changeset: changeset)
15+
end
16+
17+
# This clause is an example of how to handle resources that cannot be found.
18+
def call(conn, {:error, :not_found}) do
19+
conn
20+
|> put_status(:not_found)
21+
|> put_view(TodoBackendWeb.ErrorView)
22+
|> render(:"404")
23+
end
24+
end
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
defmodule TodoBackendWeb.TodoController do
2+
use TodoBackendWeb, :controller
3+
4+
alias TodoBackend.Todos
5+
alias TodoBackend.Todos.Todo
6+
7+
action_fallback TodoBackendWeb.FallbackController
8+
9+
def index(conn, _params) do
10+
todos = Todos.list_todos()
11+
render(conn, "index.json", todos: todos)
12+
end
13+
14+
def create(conn, todo_params) do
15+
with {:ok, %Todo{} = todo} <- Todos.create_todo(todo_params) do
16+
conn
17+
|> put_status(:created)
18+
|> put_resp_header("location", Routes.todo_path(conn, :show, todo))
19+
|> render("show.json", todo: todo)
20+
end
21+
end
22+
23+
def show(conn, %{"id" => id}) do
24+
todo = Todos.get_todo!(id)
25+
render(conn, "show.json", todo: todo)
26+
end
27+
28+
def update(conn, %{"id" => id}) do
29+
todo = Todos.get_todo!(id)
30+
31+
update_values = conn.body_params
32+
33+
with {:ok, %Todo{} = todo} <- Todos.update_todo(todo, update_values) do
34+
render(conn, "show.json", todo: todo)
35+
end
36+
end
37+
38+
def delete(conn, %{"id" => id}) do
39+
todo = Todos.get_todo!(id)
40+
41+
with {:ok, %Todo{}} <- Todos.delete_todo(todo) do
42+
send_resp(conn, :no_content, "")
43+
end
44+
end
45+
46+
def delete_all(conn, _params) do
47+
Todos.delete_all_todos()
48+
49+
send_resp(conn, :no_content, "")
50+
end
51+
end

lib/todo_backend_web/router.ex

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,8 @@ defmodule TodoBackendWeb.Router do
77

88
scope "/api", TodoBackendWeb do
99
pipe_through :api
10+
11+
resources "/todos", TodoController
12+
delete "/todos", TodoController, :delete_all
1013
end
1114
end
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
defmodule TodoBackendWeb.ChangesetView do
2+
use TodoBackendWeb, :view
3+
4+
@doc """
5+
Traverses and translates changeset errors.
6+
7+
See `Ecto.Changeset.traverse_errors/2` and
8+
`TodoBackendWeb.ErrorHelpers.translate_error/1` for more details.
9+
"""
10+
def translate_errors(changeset) do
11+
Ecto.Changeset.traverse_errors(changeset, &translate_error/1)
12+
end
13+
14+
def render("error.json", %{changeset: changeset}) do
15+
# When encoded, the changeset returns its errors
16+
# as a JSON object. So we just pass it forward.
17+
%{errors: translate_errors(changeset)}
18+
end
19+
end
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
defmodule TodoBackendWeb.TodoView do
2+
use TodoBackendWeb, :view
3+
alias TodoBackendWeb.TodoView
4+
alias TodoBackendWeb.Endpoint
5+
6+
def render("index.json", %{todos: todos}) do
7+
render_many(todos, TodoView, "todo.json")
8+
end
9+
10+
def render("show.json", %{todo: todo}) do
11+
render_one(todo, TodoView, "todo.json")
12+
end
13+
14+
def render("todo.json", %{todo: todo}) do
15+
%{
16+
id: todo.id,
17+
title: todo.title,
18+
completed: todo.completed,
19+
url: Routes.todo_url(Endpoint, :show, todo.id)
20+
}
21+
end
22+
end
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
defmodule TodoBackend.Repo.Migrations.CreateTodos do
2+
use Ecto.Migration
3+
4+
def change do
5+
create table(:todos) do
6+
add :title, :string
7+
add :completed, :boolean, default: false, null: false
8+
9+
timestamps()
10+
end
11+
end
12+
end
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
defmodule TodoBackend.TodosFixtures do
2+
@moduledoc """
3+
This module defines test helpers for creating
4+
entities via the `TodoBackend.Todos` context.
5+
"""
6+
7+
@doc """
8+
Generate a todo.
9+
"""
10+
def todo_fixture(attrs \\ %{}) do
11+
{:ok, todo} =
12+
attrs
13+
|> Enum.into(%{
14+
completed: true,
15+
title: "some title"
16+
})
17+
|> TodoBackend.Todos.create_todo()
18+
19+
todo
20+
end
21+
end

test/todo_backend/todos_test.exs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
defmodule TodoBackend.TodosTest do
2+
use TodoBackend.DataCase
3+
4+
alias TodoBackend.Todos
5+
6+
describe "todos" do
7+
alias TodoBackend.Todos.Todo
8+
9+
import TodoBackend.TodosFixtures
10+
11+
@invalid_attrs %{completed: nil, title: nil}
12+
13+
test "list_todos/0 returns all todos" do
14+
todo = todo_fixture()
15+
assert Todos.list_todos() == [todo]
16+
end
17+
18+
test "get_todo!/1 returns the todo with given id" do
19+
todo = todo_fixture()
20+
assert Todos.get_todo!(todo.id) == todo
21+
end
22+
23+
test "create_todo/1 with valid data creates a todo" do
24+
valid_attrs = %{completed: true, title: "some title"}
25+
26+
assert {:ok, %Todo{} = todo} = Todos.create_todo(valid_attrs)
27+
assert todo.completed == true
28+
assert todo.title == "some title"
29+
end
30+
31+
test "create_todo/1 with invalid data returns error changeset" do
32+
assert {:error, %Ecto.Changeset{}} = Todos.create_todo(@invalid_attrs)
33+
end
34+
35+
test "update_todo/2 with valid data updates the todo" do
36+
todo = todo_fixture()
37+
update_attrs = %{completed: false, title: "some updated title"}
38+
39+
assert {:ok, %Todo{} = todo} = Todos.update_todo(todo, update_attrs)
40+
assert todo.completed == false
41+
assert todo.title == "some updated title"
42+
end
43+
44+
test "update_todo/2 with invalid data returns error changeset" do
45+
todo = todo_fixture()
46+
assert {:error, %Ecto.Changeset{}} = Todos.update_todo(todo, @invalid_attrs)
47+
assert todo == Todos.get_todo!(todo.id)
48+
end
49+
50+
test "delete_todo/1 deletes the todo" do
51+
todo = todo_fixture()
52+
assert {:ok, %Todo{}} = Todos.delete_todo(todo)
53+
assert_raise Ecto.NoResultsError, fn -> Todos.get_todo!(todo.id) end
54+
end
55+
56+
test "change_todo/1 returns a todo changeset" do
57+
todo = todo_fixture()
58+
assert %Ecto.Changeset{} = Todos.change_todo(todo)
59+
end
60+
end
61+
end

0 commit comments

Comments
 (0)