6 Commits

  1. 15
      CHANGELOG.md
  2. 49
      assets/css/app.scss
  3. 4
      lib/mirage/markdown.ex
  4. 104
      lib/mirage/notes.ex
  5. 16
      lib/mirage/notes/note.ex
  6. 2
      lib/mirage/notes/note_link.ex
  7. 21
      lib/mirage/notes/note_topic.ex
  8. 136
      lib/mirage/notes/tags.ex
  9. 23
      lib/mirage/notes/topic.ex
  10. 42
      lib/mirage/slug.ex
  11. 1
      lib/mirage_web.ex
  12. 10
      lib/mirage_web/controllers/note_controller.ex
  13. 64
      lib/mirage_web/controllers/topic_controller.ex
  14. 1
      lib/mirage_web/router.ex
  15. 11
      lib/mirage_web/templates/note/form.html.eex
  16. 5
      lib/mirage_web/templates/note/show.html.eex
  17. 5
      lib/mirage_web/templates/note_topic/edit.html.eex
  18. 15
      lib/mirage_web/templates/note_topic/form.html.eex
  19. 26
      lib/mirage_web/templates/note_topic/index.html.eex
  20. 5
      lib/mirage_web/templates/note_topic/new.html.eex
  21. 13
      lib/mirage_web/templates/note_topic/show.html.eex
  22. 3
      lib/mirage_web/views/note_topic_view.ex
  23. 3
      mix.exs
  24. 1
      mix.lock
  25. 27
      priv/repo/migrations/20210207093552_create_note_topics.exs
  26. 59
      test/mirage/notes_test.exs
  27. 88
      test/mirage_web/controllers/note_topic_controller_test.exs

15
CHANGELOG.md

@ -5,6 +5,21 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline
<!-- changelog -->
## [v0.10.0](https://git.inhji.de/inhji/mirage/compare/v0.9.0...v0.10.0) (2021-02-07)
### Features:
* feat show note tags
* make topics work, rename note_topics to topics
* add note topics
* add slug helper
## [v0.9.0](https://git.inhji.de/inhji/mirage/compare/v0.8.0...v0.9.0) (2021-02-06)

49
assets/css/app.scss

@ -22,7 +22,7 @@ $bg-danger: #a94442;
$color-danger: #f2dede;
$color-text: #ddd;
$border-base: #999;
$border-base: #666;
/* === Fonts ===*/
@ -141,30 +141,14 @@ nav[role=navigation] {
}
}
/* === Styling ===*/
input[readonly] {
background: $color-text;
color: #777;
}
input[type=text] {
width: 100%;
display: inline-block;
padding: 0.5rem;
margin-bottom: 2rem;
background: $bg-content;
border: 1px solid white;
color: white;
font-size: 1.3rem;
}
/* === Styling === */
button[type=submit], a.button {
display: inline-block;
color: white;
font-size: 1rem;
text-decoration: none;
border: 1px solid white;
border: 1px solid $border-base;
background: $bg-content;
padding: 0.5rem 1rem;
margin: 0;
@ -193,6 +177,10 @@ button[type=submit], a.button {
p {
font-size: 1.3rem;
&:not(last-child) {
margin-bottom: 0.5rem;
}
}
}
@ -212,6 +200,16 @@ kbd {
border-radius: 3px;
}
.tags {
.tag {
font-size:75%;
background: $bg-content;
padding: 0 0.3rem;
border-radius: 5px;
border: 1px solid $border-base;
}
}
/* === Forms === */
form {
@ -227,11 +225,22 @@ form {
input[type=password] {
background: $bg-content;
color: white;
border: 1px solid white;
margin-bottom: 1rem;
padding: 0.5rem;
width: 100%;
border-radius: 0;
border: 0;
}
input.title {
font-size: 1.25rem;
}
input[readonly] {
background: $color-text;
color: #777;
}
}
}

4
lib/mirage/markdown.ex

@ -1,6 +1,6 @@
import Ecto.Changeset, only: [get_change: 2, put_change: 3]
defmodule Mirage.Markdown do
import Ecto.Changeset, only: [get_change: 2, put_change: 3]
def maybe_render_markdown(changeset, markdown_field, html_field) do
if markdown = get_change(changeset, markdown_field) do
html = Earmark.as_html!(markdown)

104
lib/mirage/notes.ex

@ -6,7 +6,9 @@ defmodule Mirage.Notes do
import Ecto.Query, warn: false
alias Mirage.Repo
alias Mirage.Notes.Note
alias Mirage.Notes.{Note, Topic, Tags}
@note_preloads [:links, :topics]
@doc """
Returns the list of notes.
@ -22,7 +24,7 @@ defmodule Mirage.Notes do
|> limit(10)
|> order_by(desc: :inserted_at)
|> Repo.all()
|> Repo.preload(:links)
|> Repo.preload(@note_preloads)
end
@doc """
@ -39,7 +41,9 @@ defmodule Mirage.Notes do
** (Ecto.NoResultsError)
"""
def get_note!(id), do: Repo.get!(Note, id) |> Repo.preload(:links)
def get_note!(id), do: Repo.get!(Note, id) |> Repo.preload(@note_preloads)
def preload_note(note), do: Repo.preload(note, @note_preloads)
@doc """
Creates a note.
@ -105,4 +109,98 @@ defmodule Mirage.Notes do
def change_note(%Note{} = note, attrs \\ %{}) do
Note.changeset(note, attrs)
end
@doc """
Returns the list of topics.
## Examples
iex> list_topics()
[%Topic{}, ...]
"""
def list_topics do
Repo.all(Topic)
end
@doc """
Gets a single topic.
Raises `Ecto.NoResultsError` if the Note topic does not exist.
## Examples
iex> get_topic!(123)
%Topic{}
iex> get_topic!(456)
** (Ecto.NoResultsError)
"""
def get_topic!(id), do: Repo.get!(Topic, id)
@doc """
Creates a topic.
## Examples
iex> create_topic(%{field: value})
{:ok, %Topic{}}
iex> create_topic(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def create_topic(attrs \\ %{}) do
%Topic{}
|> Topic.changeset(attrs)
|> Repo.insert()
end
@doc """
Updates a topic.
## Examples
iex> update_topic(topic, %{field: new_value})
{:ok, %Topic{}}
iex> update_topic(topic, %{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def update_topic(%Topic{} = topic, attrs) do
topic
|> Topic.changeset(attrs)
|> Repo.update()
end
@doc """
Deletes a topic.
## Examples
iex> delete_topic(topic)
{:ok, %Topic{}}
iex> delete_topic(topic)
{:error, %Ecto.Changeset{}}
"""
def delete_topic(%Topic{} = topic) do
Repo.delete(topic)
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking topic changes.
## Examples
iex> change_topic(topic)
%Ecto.Changeset{data: %Topic{}}
"""
def change_topic(%Topic{} = topic, attrs \\ %{}) do
Topic.changeset(topic, attrs)
end
end

16
lib/mirage/notes/note.ex

@ -2,12 +2,17 @@ defmodule Mirage.Notes.Note do
use Ecto.Schema
import Ecto.Changeset
alias Mirage.Notes.{NoteLink, Topic, Tags}
schema "notes" do
field :title, :string
field :content, :string
field :content_html, :string
has_many :links, Mirage.Notes.Link
has_many :links, NoteLink
many_to_many :topics, Topic, join_through: "notes_topics"
field :topic_string, :string, virtual: true
timestamps()
end
@ -18,5 +23,14 @@ defmodule Mirage.Notes.Note do
|> cast(attrs, [:content, :title])
|> validate_required([:content, :title])
|> Mirage.Markdown.maybe_render_markdown(:content, :content_html)
|> maybe_load_tag_string()
end
def maybe_load_tag_string(changeset) do
case get_field(changeset, :topic_string) do
nil ->
put_change(changeset, :topic_string, Tags.get_tags(changeset.data))
_ -> changeset
end
end
end

2
lib/mirage/notes/link.ex → lib/mirage/notes/note_link.ex

@ -1,4 +1,4 @@
defmodule Mirage.Notes.Link do
defmodule Mirage.Notes.NoteLink do
use Ecto.Schema
import Ecto.Changeset

21
lib/mirage/notes/note_topic.ex

@ -0,0 +1,21 @@
defmodule Mirage.Notes.NoteTopic do
use Ecto.Schema
import Ecto.Changeset
alias Mirage.Notes.{Note, Topic}
schema "notes_topics" do
belongs_to :note, Note
belongs_to :topic, Topic
timestamps()
end
@attrs [:note_id, :topic_id]
@doc false
def changeset(note_topic, attrs) do
note_topic
|> cast(attrs, @attrs)
|> validate_required(@attrs)
end
end

136
lib/mirage/notes/tags.ex

@ -0,0 +1,136 @@
defmodule Mirage.Notes.Tags do
import Ecto.Query, warn: false
alias Mirage.Repo
alias Mirage.Notes.{Note, Topic, NoteTopic}
@doc utils: :tag
@doc """
Updates tags of a given note.
## Examples
iex> Tags.update_tags(note, "foo,bar")
"""
def update_tags(note, new_tags) when is_binary(new_tags) do
old_tags = tags_loaded(note) |> split_tags()
new_tags = new_tags |> split_tags()
note
|> add_tags(new_tags -- old_tags)
|> remove_tags(old_tags -- new_tags)
end
@doc """
Retrieves a string of tags for a given note.
## Examples
iex> Tags.get_tags(note)
"foo,bar"
"""
def get_tags(note), do: tags_loaded(note)
@doc utils: :tag
defp add_tag(note, topic_text) when is_binary(topic_text) do
slug =
topic_text
|> Slugger.slugify()
|> String.downcase()
topic =
case Repo.get_by(Topic, %{text_slug: slug}) do
nil ->
%Topic{}
|> Topic.changeset(%{text: topic_text})
|> Repo.insert!()
topic ->
topic
end
add_tag(note, topic.id)
end
@doc utils: :tag
defp add_tag(%{note_id: note_id}, topic_id) do
add_tag(note_id, topic_id)
end
@doc utils: :tag
defp add_tag(%Note{} = note, topic_id) do
add_tag(note.id, topic_id)
end
@doc utils: :tag
defp add_tag(note_id, topic_id) do
%NoteTopic{}
|> NoteTopic.changeset(%{note_id: note_id, topic_id: topic_id})
|> Repo.insert()
end
@doc utils: :tag
defp add_tags(content, tags) when is_binary(tags) do
Enum.each(split_tags(tags), &add_tag(content, &1))
content
end
@doc utils: :tag
defp add_tags(content, tags) do
Enum.each(tags, &add_tag(content, &1))
content
end
@doc utils: :tag
defp remove_tag(content, topic_text) when is_binary(topic_text) do
case Repo.get_by(Topic, %{text: topic_text}) do
nil -> nil
topic -> remove_tag(content, topic.id)
end
end
@doc utils: :tag
defp remove_tag(%{note_id: note_id}, topic_id) do
remove_tag(note_id, topic_id)
end
@doc utils: :tag
defp remove_tag(%Note{} = note, topic_id) do
remove_tag(note.id, topic_id)
end
@doc utils: :tag
defp remove_tag(note_id, topic_id) do
case Repo.get_by(NoteTopic, %{note_id: note_id, topic_id: topic_id}) do
nil -> nil
tag -> Repo.delete(tag)
end
end
@doc utils: :tag
defp remove_tags(content, tags) when is_binary(tags) do
Enum.each(split_tags(tags), &remove_tag(content, &1))
content
end
@doc utils: :tag
defp remove_tags(content, tags) do
Enum.each(tags, &remove_tag(content, &1))
content
end
@doc utils: :tag
defp tags_loaded(%{topics: topics}) do
topics |> Enum.map_join(", ", & &1.text)
end
@doc utils: :tag
defp split_tags(tags_string) when is_binary(tags_string) do
tags_string
|> String.split(",")
|> Enum.map(&String.trim/1)
|> Enum.filter(&(String.length(&1) > 0))
end
end

23
lib/mirage/notes/topic.ex

@ -0,0 +1,23 @@
defmodule Mirage.Notes.Topic do
use Ecto.Schema
import Ecto.Changeset
schema "topics" do
field :text, :string
field :text_slug, :string
field :description, :string
field :icon, :string
many_to_many :notes, Mirage.Notes.Note, join_through: "notes_topics"
timestamps()
end
@doc false
def changeset(topic, attrs) do
topic
|> cast(attrs, [:text, :description, :icon])
|> validate_required([:text])
|> Mirage.Slug.maybe_generate_slug(:text, :text_slug)
end
end

42
lib/mirage/slug.ex

@ -0,0 +1,42 @@
defmodule Mirage.Slug do
import Ecto.Changeset, only: [get_field: 2, put_change: 3]
@slug_unique_id_length 5
def maybe_generate_slug(changeset, field, slug_field, options \\ [add_random: false]) do
add_random = options[:add_random]
with true <- changeset.valid?,
nil <- get_field(changeset, slug_field) do
prepared_string = get_slug(changeset, field)
do_create_slug(changeset, prepared_string, slug_field, add_random)
else
_ -> changeset
end
end
defp do_create_slug(changeset, string, slug_field, add_random) do
slug =
if add_random do
Slugger.slugify("#{string}-#{random_string(@slug_unique_id_length)}")
else
Slugger.slugify(string)
end
changeset
|> put_change(slug_field, slug)
end
defp get_slug(changeset, field) do
changeset
|> get_field(field)
|> String.downcase()
end
defp random_string(length) do
length
|> :crypto.strong_rand_bytes()
|> Base.url_encode64()
|> binary_part(0, length)
end
end

1
lib/mirage_web.ex

@ -23,6 +23,7 @@ defmodule MirageWeb do
import Plug.Conn
import MirageWeb.Gettext
import MirageWeb.UserAuth, only: [require_authenticated_user: 2]
alias MirageWeb.Router.Helpers, as: Routes
end
end

10
lib/mirage_web/controllers/note_controller.ex

@ -4,8 +4,6 @@ defmodule MirageWeb.NoteController do
alias Mirage.Notes
alias Mirage.Notes.Note
import MirageWeb.UserAuth, only: [require_authenticated_user: 2]
plug :require_authenticated_user when action in [:new, :edit, :update, :delete]
def index(conn, _params) do
@ -21,6 +19,10 @@ defmodule MirageWeb.NoteController do
def create(conn, %{"note" => note_params}) do
case Notes.create_note(note_params) do
{:ok, note} ->
note
|> Notes.preload_note()
|> Notes.Tags.update_tags(note_params["topic_string"])
conn
|> put_flash(:info, "Note created successfully.")
|> redirect(to: Routes.note_path(conn, :show, note))
@ -46,6 +48,10 @@ defmodule MirageWeb.NoteController do
case Notes.update_note(note, note_params) do
{:ok, note} ->
note
|> Notes.preload_note()
|> Notes.Tags.update_tags(note_params["topic_string"])
conn
|> put_flash(:info, "Note updated successfully.")
|> redirect(to: Routes.note_path(conn, :show, note))

64
lib/mirage_web/controllers/topic_controller.ex

@ -0,0 +1,64 @@
defmodule MirageWeb.TopicController do
use MirageWeb, :controller
alias Mirage.Notes
alias Mirage.Notes.Topic
plug :require_authenticated_user when action in [:new, :edit, :update, :delete]
def index(conn, _params) do
topics = Notes.list_topics()
render(conn, "index.html", topics: topics)
end
def new(conn, _params) do
changeset = Notes.change_topic(%Topic{})
render(conn, "new.html", changeset: changeset)
end
def create(conn, %{"topic" => topic_params}) do
case Notes.create_topic(topic_params) do
{:ok, topic} ->
conn
|> put_flash(:info, "Note topic created successfully.")
|> redirect(to: Routes.topic_path(conn, :show, topic))
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, "new.html", changeset: changeset)
end
end
def show(conn, %{"id" => id}) do
topic = Notes.get_topic!(id)
render(conn, "show.html", topic: topic)
end
def edit(conn, %{"id" => id}) do
topic = Notes.get_topic!(id)
changeset = Notes.change_topic(topic)
render(conn, "edit.html", topic: topic, changeset: changeset)
end
def update(conn, %{"id" => id, "topic" => topic_params}) do
topic = Notes.get_topic!(id)
case Notes.update_topic(topic, topic_params) do
{:ok, topic} ->
conn
|> put_flash(:info, "Note topic updated successfully.")
|> redirect(to: Routes.topic_path(conn, :show, topic))
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, "edit.html", topic: topic, changeset: changeset)
end
end
def delete(conn, %{"id" => id}) do
topic = Notes.get_topic!(id)
{:ok, _topic} = Notes.delete_topic(topic)
conn
|> put_flash(:info, "Note topic deleted successfully.")
|> redirect(to: Routes.topic_path(conn, :index))
end
end

1
lib/mirage_web/router.ex

@ -29,6 +29,7 @@ defmodule MirageWeb.Router do
live "/", PageLive, :index
resources "/notes", NoteController
resources "/topics", TopicController
resources "/settings", SettingController, only: [:index, :show, :edit, :update]
end

11
lib/mirage_web/templates/note/form.html.eex

@ -5,12 +5,17 @@
</div>
<% end %>
<%= text_input f, :title %>
<%= error_tag f, :title %>
<fieldset>
<%= text_input f, :title, placeholder: "What is this note about?", class: "title" %>
</fieldset>
<%= hidden_input f, :content, class: "editor-content" %>
<div id="editor"></div>
<%= error_tag f, :content %>
<fieldset>
<%= text_input f, :topic_string, placeholder: "Tags, comma-separated" %>
<%= error_tag f, :topic_string %>
</fieldset>
<div>
<%= submit "Save" %>

5
lib/mirage_web/templates/note/show.html.eex

@ -1,6 +1,11 @@
<div class="hero">
<h1><%= @note.title %></h1>
<p><time datetime="<%= @note.inserted_at %>"><%= Timex.from_now(@note.inserted_at) %></time> / <time datetime="<%= @note.updated_at %>"><%= Timex.from_now(@note.updated_at) %></time></p>
<p class="tags">
<%= for topic <- @note.topics do %>
<span class="tag"><%= topic.text %></span>
<% end %>
</p>
</div>
<div class="width-full bg-content">

5
lib/mirage_web/templates/note_topic/edit.html.eex

@ -0,0 +1,5 @@
<h1>Edit Note topic</h1>
<%= render "form.html", Map.put(assigns, :action, Routes.topic_path(@conn, :update, @topic)) %>
<span><%= link "Back", to: Routes.topic_path(@conn, :index) %></span>

15
lib/mirage_web/templates/note_topic/form.html.eex

@ -0,0 +1,15 @@
<%= form_for @changeset, @action, fn f -> %>
<%= if @changeset.action do %>
<div class="alert alert-danger">
<p>Oops, something went wrong! Please check the errors below.</p>
</div>
<% end %>
<%= label f, :text %>
<%= text_input f, :text %>
<%= error_tag f, :text %>
<div>
<%= submit "Save" %>
</div>
<% end %>

26
lib/mirage_web/templates/note_topic/index.html.eex

@ -0,0 +1,26 @@
<h1>Listing Note topics</h1>
<table>
<thead>
<tr>
<th>Text</th>
<th></th>
</tr>
</thead>
<tbody>
<%= for topic <- @topics do %>
<tr>
<td><%= topic.text %></td>
<td>
<span><%= link "Show", to: Routes.topic_path(@conn, :show, topic) %></span>
<span><%= link "Edit", to: Routes.topic_path(@conn, :edit, topic) %></span>
<span><%= link "Delete", to: Routes.topic_path(@conn, :delete, topic), method: :delete, data: [confirm: "Are you sure?"] %></span>
</td>
</tr>
<% end %>
</tbody>
</table>
<span><%= link "New Note topic", to: Routes.topic_path(@conn, :new) %></span>

5
lib/mirage_web/templates/note_topic/new.html.eex

@ -0,0 +1,5 @@
<h1>New Note topic</h1>
<%= render "form.html", Map.put(assigns, :action, Routes.topic_path(@conn, :create)) %>
<span><%= link "Back", to: Routes.topic_path(@conn, :index) %></span>

13
lib/mirage_web/templates/note_topic/show.html.eex

@ -0,0 +1,13 @@
<h1>Show Note topic</h1>
<ul>
<li>
<strong>Text:</strong>
<%= @topic.text %>
</li>
</ul>
<span><%= link "Edit", to: Routes.topic_path(@conn, :edit, @topic) %></span>
<span><%= link "Back", to: Routes.topic_path(@conn, :index) %></span>

3
lib/mirage_web/views/note_topic_view.ex

@ -0,0 +1,3 @@
defmodule MirageWeb.TopicView do
use MirageWeb, :view
end

3
mix.exs

@ -1,7 +1,7 @@
defmodule Mirage.MixProject do
use Mix.Project
@version "0.9.0"
@version "0.10.0"
def project do
[
@ -51,6 +51,7 @@ defmodule Mirage.MixProject do
{:phx_gen_auth, "~> 0.6", only: [:dev], runtime: false},
{:plug_cowboy, "~> 2.0"},
{:postgrex, ">= 0.0.0"},
{:slugger, "~> 0.3"},
{:telemetry_metrics, "~> 0.4"},
{:telemetry_poller, "~> 0.4"},
{:timex, "~> 3.6"}

1
mix.lock

@ -41,6 +41,7 @@
"plug_crypto": {:hex, :plug_crypto, "1.2.0", "1cb20793aa63a6c619dd18bb33d7a3aa94818e5fd39ad357051a67f26dfa2df6", [:mix], [], "hexpm", "a48b538ae8bf381ffac344520755f3007cc10bd8e90b240af98ea29b69683fc2"},
"postgrex": {:hex, :postgrex, "0.15.7", "724410acd48abac529d0faa6c2a379fb8ae2088e31247687b16cacc0e0883372", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "88310c010ff047cecd73d5ceca1d99205e4b1ab1b9abfdab7e00f5c9d20ef8f9"},
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"},
"slugger": {:hex, :slugger, "0.3.0", "efc667ab99eee19a48913ccf3d038b1fb9f165fa4fbf093be898b8099e61b6ed", [:mix], [], "hexpm", "20d0ded0e712605d1eae6c5b4889581c3460d92623a930ddda91e0e609b5afba"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
"telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.0", "da9d49ee7e6bb1c259d36ce6539cd45ae14d81247a2b0c90edf55e2b50507f7b", [:mix], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5cfe67ad464b243835512aa44321cee91faed6ea868d7fb761d7016e02915c3d"},

27
priv/repo/migrations/20210207093552_create_note_topics.exs

@ -0,0 +1,27 @@
defmodule Mirage.Repo.Migrations.CreateNoteTopics do
use Ecto.Migration
def change do
create table(:topics) do
add :text, :string
add :text_slug, :string
add :description, :text
add :icon, :string
timestamps()
end
create unique_index(:topics, [:text])
create table(:notes_topics) do
add :note_id, references(:notes, on_delete: :delete_all)
add :topic_id, references(:topics, on_delete: :nothing)
timestamps()
end
create index(:notes_topics, [:note_id])
create index(:notes_topics, [:topic_id])
create unique_index(:notes_topics, [:note_id, :topic_id])
end
end

59
test/mirage/notes_test.exs

@ -61,4 +61,63 @@ defmodule Mirage.NotesTest do
assert %Ecto.Changeset{} = Notes.change_note(note)
end
end
describe "note_topics" do
alias Mirage.Notes.NoteTopic
@valid_attrs %{text: "some text"}
@update_attrs %{text: "some updated text"}
@invalid_attrs %{text: nil}
def note_topic_fixture(attrs \\ %{}) do
{:ok, note_topic} =
attrs
|> Enum.into(@valid_attrs)
|> Notes.create_note_topic()
note_topic
end
test "list_note_topics/0 returns all note_topics" do
note_topic = note_topic_fixture()
assert Notes.list_note_topics() == [note_topic]
end
test "get_note_topic!/1 returns the note_topic with given id" do
note_topic = note_topic_fixture()
assert Notes.get_note_topic!(note_topic.id) == note_topic
end
test "create_note_topic/1 with valid data creates a note_topic" do
assert {:ok, %NoteTopic{} = note_topic} = Notes.create_note_topic(@valid_attrs)
assert note_topic.text == "some text"
end
test "create_note_topic/1 with invalid data returns error changeset" do
assert {:error, %Ecto.Changeset{}} = Notes.create_note_topic(@invalid_attrs)
end
test "update_note_topic/2 with valid data updates the note_topic" do
note_topic = note_topic_fixture()
assert {:ok, %NoteTopic{} = note_topic} = Notes.update_note_topic(note_topic, @update_attrs)
assert note_topic.text == "some updated text"
end
test "update_note_topic/2 with invalid data returns error changeset" do
note_topic = note_topic_fixture()
assert {:error, %Ecto.Changeset{}} = Notes.update_note_topic(note_topic, @invalid_attrs)
assert note_topic == Notes.get_note_topic!(note_topic.id)
end
test "delete_note_topic/1 deletes the note_topic" do
note_topic = note_topic_fixture()
assert {:ok, %NoteTopic{}} = Notes.delete_note_topic(note_topic)
assert_raise Ecto.NoResultsError, fn -> Notes.get_note_topic!(note_topic.id) end
end
test "change_note_topic/1 returns a note_topic changeset" do
note_topic = note_topic_fixture()
assert %Ecto.Changeset{} = Notes.change_note_topic(note_topic)
end
end
end

88
test/mirage_web/controllers/note_topic_controller_test.exs

@ -0,0 +1,88 @@
defmodule MirageWeb.NoteTopicControllerTest do
use MirageWeb.ConnCase
alias Mirage.Notes
@create_attrs %{text: "some text"}
@update_attrs %{text: "some updated text"}
@invalid_attrs %{text: nil}
def fixture(:note_topic) do
{:ok, note_topic} = Notes.create_note_topic(@create_attrs)
note_topic
end
describe "index" do
test "lists all note_topics", %{conn: conn} do
conn = get(conn, Routes.note_topic_path(conn, :index))
assert html_response(conn, 200) =~ "Listing Note topics"
end
end
describe "new note_topic" do
test "renders form", %{conn: conn} do
conn = get(conn, Routes.note_topic_path(conn, :new))
assert html_response(conn, 200) =~ "New Note topic"
end
end
describe "create note_topic" do
test "redirects to show when data is valid", %{conn: conn} do
conn = post(conn, Routes.note_topic_path(conn, :create), note_topic: @create_attrs)
assert %{id: id} = redirected_params(conn)
assert redirected_to(conn) == Routes.note_topic_path(conn, :show, id)
conn = get(conn, Routes.note_topic_path(conn, :show, id))
assert html_response(conn, 200) =~ "Show Note topic"
end
test "renders errors when data is invalid", %{conn: conn} do
conn = post(conn, Routes.note_topic_path(conn, :create), note_topic: @invalid_attrs)
assert html_response(conn, 200) =~ "New Note topic"
end
end
describe "edit note_topic" do
setup [:create_note_topic]
test "renders form for editing chosen note_topic", %{conn: conn, note_topic: note_topic} do
conn = get(conn, Routes.note_topic_path(conn, :edit, note_topic))
assert html_response(conn, 200) =~ "Edit Note topic"
end
end
describe "update note_topic" do
setup [:create_note_topic]
test "redirects when data is valid", %{conn: conn, note_topic: note_topic} do
conn = put(conn, Routes.note_topic_path(conn, :update, note_topic), note_topic: @update_attrs)
assert redirected_to(conn) == Routes.note_topic_path(conn, :show, note_topic)
conn = get(conn, Routes.note_topic_path(conn, :show, note_topic))
assert html_response(conn, 200) =~ "some updated text"
end
test "renders errors when data is invalid", %{conn: conn, note_topic: note_topic} do
conn = put(conn, Routes.note_topic_path(conn, :update, note_topic), note_topic: @invalid_attrs)
assert html_response(conn, 200) =~ "Edit Note topic"
end
end
describe "delete note_topic" do
setup [:create_note_topic]
test "deletes chosen note_topic", %{conn: conn, note_topic: note_topic} do
conn = delete(conn, Routes.note_topic_path(conn, :delete, note_topic))
assert redirected_to(conn) == Routes.note_topic_path(conn, :index)
assert_error_sent 404, fn ->
get(conn, Routes.note_topic_path(conn, :show, note_topic))
end
end
end
defp create_note_topic(_) do
note_topic = fixture(:note_topic)
%{note_topic: note_topic}
end
end
Loading…
Cancel
Save