Compare commits

...

10 Commits

Author SHA1 Message Date
Inhji fa866cc10e set content to textarea and remove from index pages 2023-03-05 23:51:08 +01:00
Inhji 127757b12a add many_to_many relation for channel<->note 2023-03-05 23:46:23 +01:00
Inhji 370bdfe99d remove timestamp and guid from channels_notes table 2023-03-05 19:52:16 +01:00
Inhji 13d1a67a0e add channels_note relation 2023-03-05 18:24:47 +01:00
Inhji 4d20f5c4d0 create channel<->note join table 2023-03-05 18:14:06 +01:00
Inhji 58d11699f3 customize menu 2023-03-05 18:09:32 +01:00
Inhji 26e6bf2c42 make channel slug unique 2023-03-05 18:09:09 +01:00
Inhji 21ee3298b6 add notes 2023-03-05 17:23:16 +01:00
Inhji ae59609b28 add channels 2023-03-05 17:16:24 +01:00
Inhji e9038c1636 add scheduler 2023-03-05 16:21:35 +01:00
37 changed files with 1164 additions and 23 deletions

View File

@ -59,6 +59,11 @@ config :logger, :console,
# Use Jason for JSON parsing in Phoenix
config :phoenix, :json_library, Jason
config :chiya, Oban,
repo: Chiya.Repo,
plugins: [Oban.Plugins.Pruner],
queues: [default: 10]
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{config_env()}.exs"

View File

@ -34,3 +34,6 @@ config :logger, level: :warning
# Initialize plugs at runtime for faster test compilation
config :phoenix, :plug_init_mode, :runtime
# Prevent oban from running jobs and plugins during test
config :chiya, Oban, testing: :inline

View File

@ -17,7 +17,9 @@ defmodule Chiya.Application do
# Start Finch
{Finch, name: Chiya.Finch},
# Start the Endpoint (http/https)
ChiyaWeb.Endpoint
ChiyaWeb.Endpoint,
# Start Scheduler
{Oban, Application.fetch_env!(:chiya, Oban)}
# Start a worker by calling: Chiya.Worker.start_link(arg)
# {Chiya.Worker, arg}
]

104
lib/chiya/channels.ex Normal file
View File

@ -0,0 +1,104 @@
defmodule Chiya.Channels do
@moduledoc """
The Channels context.
"""
import Ecto.Query, warn: false
alias Chiya.Repo
alias Chiya.Channels.Channel
@doc """
Returns the list of channels.
## Examples
iex> list_channels()
[%Channel{}, ...]
"""
def list_channels do
Repo.all(Channel)
end
@doc """
Gets a single channel.
Raises `Ecto.NoResultsError` if the Channel does not exist.
## Examples
iex> get_channel!(123)
%Channel{}
iex> get_channel!(456)
** (Ecto.NoResultsError)
"""
def get_channel!(id), do: Repo.get!(Channel, id)
@doc """
Creates a channel.
## Examples
iex> create_channel(%{field: value})
{:ok, %Channel{}}
iex> create_channel(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def create_channel(attrs \\ %{}) do
%Channel{}
|> Channel.changeset(attrs)
|> Repo.insert()
end
@doc """
Updates a channel.
## Examples
iex> update_channel(channel, %{field: new_value})
{:ok, %Channel{}}
iex> update_channel(channel, %{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def update_channel(%Channel{} = channel, attrs) do
channel
|> Channel.changeset(attrs)
|> Repo.update()
end
@doc """
Deletes a channel.
## Examples
iex> delete_channel(channel)
{:ok, %Channel{}}
iex> delete_channel(channel)
{:error, %Ecto.Changeset{}}
"""
def delete_channel(%Channel{} = channel) do
Repo.delete(channel)
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking channel changes.
## Examples
iex> change_channel(channel)
%Ecto.Changeset{data: %Channel{}}
"""
def change_channel(%Channel{} = channel, attrs \\ %{}) do
Channel.changeset(channel, attrs)
end
end

View File

@ -0,0 +1,23 @@
defmodule Chiya.Channels.Channel do
use Ecto.Schema
import Ecto.Changeset
schema "channels" do
field :content, :string
field :name, :string
field :slug, :string
field :visibility, Ecto.Enum, values: [:public, :private, :unlisted]
many_to_many :notes, Chiya.Notes.Note, join_through: "channels_notes"
timestamps()
end
@doc false
def changeset(channel, attrs) do
channel
|> cast(attrs, [:name, :content, :visibility, :slug])
|> validate_required([:name, :content, :visibility, :slug])
|> unique_constraint(:slug)
end
end

132
lib/chiya/notes.ex Normal file
View File

@ -0,0 +1,132 @@
defmodule Chiya.Notes do
@moduledoc """
The Notes context.
"""
import Ecto.Query, warn: false
alias Chiya.Repo
alias Chiya.Notes.Note
@preloads [:channels]
@doc """
Returns the list of notes.
## Examples
iex> list_notes()
[%Note{}, ...]
"""
def list_notes do
Repo.all(Note) |> Repo.preload(@preloads)
end
@doc """
Preloads a note
## Examples
iex> preload_note(note)
%Note{}
"""
def preload_note(note), do: Repo.preload(note, @preloads)
@doc """
Gets a single note.
Raises `Ecto.NoResultsError` if the Note does not exist.
## Examples
iex> get_note!(123)
%Note{}
iex> get_note!(456)
** (Ecto.NoResultsError)
"""
def get_note!(id), do: Repo.get!(Note, id)
@doc """
Gets a single note and preloads it.
Raises `Ecto.NoResultsError` if the Note does not exist.
## Examples
iex> get_note_preloaded!(123)
%Note{}
iex> get_note_preloaded!(456)
** (Ecto.NoResultsError)
"""
def get_note_preloaded!(id), do: Repo.get!(Note, id) |> preload_note()
@doc """
Creates a note.
## Examples
iex> create_note(%{field: value})
{:ok, %Note{}}
iex> create_note(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def create_note(attrs \\ %{}) do
%Note{}
|> Note.changeset(attrs)
|> Repo.insert()
end
@doc """
Updates a note.
## Examples
iex> update_note(note, %{field: new_value})
{:ok, %Note{}}
iex> update_note(note, %{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def update_note(%Note{} = note, attrs) do
note
|> Note.changeset(attrs)
|> Repo.update()
end
@doc """
Deletes a note.
## Examples
iex> delete_note(note)
{:ok, %Note{}}
iex> delete_note(note)
{:error, %Ecto.Changeset{}}
"""
def delete_note(%Note{} = note) do
Repo.delete(note)
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking note changes.
## Examples
iex> change_note(note)
%Ecto.Changeset{data: %Note{}}
"""
def change_note(%Note{} = note, attrs \\ %{}) do
Note.changeset(note, attrs)
end
end

31
lib/chiya/notes/note.ex Normal file
View File

@ -0,0 +1,31 @@
defmodule Chiya.Notes.Note do
use Ecto.Schema
import Ecto.Changeset
schema "notes" do
field :content, :string
field :kind, :string
field :name, :string
field :published_at, :naive_datetime
field :slug, :string
field :url, :string
many_to_many :channels, Chiya.Channels.Channel,
# join_through: Chiya.Channels.ChannelNote,
join_through: "channels_notes",
join_keys: [note: :id, channel: :id],
on_replace: :delete
timestamps()
end
@doc false
def changeset(note, attrs) do
note
|> Chiya.Notes.preload_note()
|> cast(attrs, [:name, :content, :slug, :published_at, :kind, :url])
|> put_assoc(:channels, attrs["channels"] || [])
|> validate_required([:name, :content, :slug, :published_at, :kind, :url])
|> unique_constraint(:slug)
end
end

View File

@ -14,24 +14,18 @@
</p>
</div>
<div class="flex items-center gap-4">
<a
href="https://twitter.com/elixirphoenix"
<.link
href={~p"/channels"}
class="text-[0.8125rem] font-semibold leading-6 text-zinc-900 hover:text-zinc-700"
>
@elixirphoenix
</a>
<a
href="https://github.com/phoenixframework/phoenix"
Channels
</.link>
<.link
href={~p"/notes"}
class="text-[0.8125rem] font-semibold leading-6 text-zinc-900 hover:text-zinc-700"
>
GitHub
</a>
<a
href="https://hexdocs.pm/phoenix/overview.html"
class="rounded-lg bg-zinc-100 px-2 py-1 text-[0.8125rem] font-semibold leading-6 text-zinc-900 hover:bg-zinc-200/80 active:text-zinc-900/70"
>
Get Started <span aria-hidden="true">&rarr;</span>
</a>
Notes
</.link>
</div>
</div>
</header>

View File

@ -12,7 +12,7 @@
</script>
</head>
<body class="bg-white antialiased">
<ul class="relative z-10 flex items-center gap-4 px-4 sm:px-6 lg:px-8 justify-end">
<ul class="relative z-10 flex items-center gap-4 px-4 sm:px-6 lg:px-8 justify-end bg-black">
<%= if @current_user do %>
<li class="text-[0.8125rem] leading-6 text-zinc-900">
<%= @current_user.email %>
@ -20,7 +20,7 @@
<li>
<.link
href={~p"/users/settings"}
class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
class="text-[0.8125rem] leading-6 text-zinc-100 font-semibold hover:text-zinc-300"
>
Settings
</.link>
@ -29,7 +29,7 @@
<.link
href={~p"/users/log_out"}
method="delete"
class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
class="text-[0.8125rem] leading-6 text-zinc-100 font-semibold hover:text-zinc-300"
>
Log out
</.link>
@ -38,7 +38,7 @@
<li>
<.link
href={~p"/users/register"}
class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
class="text-[0.8125rem] leading-6 text-zinc-100 font-semibold hover:text-zinc-300"
>
Register
</.link>
@ -46,7 +46,7 @@
<li>
<.link
href={~p"/users/log_in"}
class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
class="text-[0.8125rem] leading-6 text-zinc-100 font-semibold hover:text-zinc-300"
>
Log in
</.link>

View File

@ -0,0 +1,62 @@
defmodule ChiyaWeb.ChannelController do
use ChiyaWeb, :controller
alias Chiya.Channels
alias Chiya.Channels.Channel
def index(conn, _params) do
channels = Channels.list_channels()
render(conn, :index, channels: channels)
end
def new(conn, _params) do
changeset = Channels.change_channel(%Channel{})
render(conn, :new, changeset: changeset)
end
def create(conn, %{"channel" => channel_params}) do
case Channels.create_channel(channel_params) do
{:ok, channel} ->
conn
|> put_flash(:info, "Channel created successfully.")
|> redirect(to: ~p"/channels/#{channel}")
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, :new, changeset: changeset)
end
end
def show(conn, %{"id" => id}) do
channel = Channels.get_channel!(id)
render(conn, :show, channel: channel)
end
def edit(conn, %{"id" => id}) do
channel = Channels.get_channel!(id)
changeset = Channels.change_channel(channel)
render(conn, :edit, channel: channel, changeset: changeset)
end
def update(conn, %{"id" => id, "channel" => channel_params}) do
channel = Channels.get_channel!(id)
case Channels.update_channel(channel, channel_params) do
{:ok, channel} ->
conn
|> put_flash(:info, "Channel updated successfully.")
|> redirect(to: ~p"/channels/#{channel}")
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, :edit, channel: channel, changeset: changeset)
end
end
def delete(conn, %{"id" => id}) do
channel = Channels.get_channel!(id)
{:ok, _channel} = Channels.delete_channel(channel)
conn
|> put_flash(:info, "Channel deleted successfully.")
|> redirect(to: ~p"/channels")
end
end

View File

@ -0,0 +1,13 @@
defmodule ChiyaWeb.ChannelHTML do
use ChiyaWeb, :html
embed_templates "channel_html/*"
@doc """
Renders a channel form.
"""
attr :changeset, Ecto.Changeset, required: true
attr :action, :string, required: true
def channel_form(assigns)
end

View File

@ -0,0 +1,18 @@
<.simple_form :let={f} for={@changeset} action={@action}>
<.error :if={@changeset.action}>
Oops, something went wrong! Please check the errors below.
</.error>
<.input field={f[:name]} type="text" label="Name" />
<.input field={f[:content]} type="textarea" label="Content" />
<.input
field={f[:visibility]}
type="select"
label="Visibility"
prompt="Choose a value"
options={Ecto.Enum.values(Chiya.Channels.Channel, :visibility)}
/>
<.input field={f[:slug]} type="text" label="Slug" />
<:actions>
<.button>Save Channel</.button>
</:actions>
</.simple_form>

View File

@ -0,0 +1,8 @@
<.header>
Edit Channel <%= @channel.id %>
<:subtitle>Use this form to manage channel records in your database.</:subtitle>
</.header>
<.channel_form changeset={@changeset} action={~p"/channels/#{@channel}"} />
<.back navigate={~p"/channels"}>Back to channels</.back>

View File

@ -0,0 +1,25 @@
<.header>
Listing Channels
<:actions>
<.link href={~p"/channels/new"}>
<.button>New Channel</.button>
</.link>
</:actions>
</.header>
<.table id="channels" rows={@channels} row_click={&JS.navigate(~p"/channels/#{&1}")}>
<:col :let={channel} label="Name"><%= channel.name %></:col>
<:col :let={channel} label="Visibility"><%= channel.visibility %></:col>
<:col :let={channel} label="Slug"><%= channel.slug %></:col>
<:action :let={channel}>
<div class="sr-only">
<.link navigate={~p"/channels/#{channel}"}>Show</.link>
</div>
<.link navigate={~p"/channels/#{channel}/edit"}>Edit</.link>
</:action>
<:action :let={channel}>
<.link href={~p"/channels/#{channel}"} method="delete" data-confirm="Are you sure?">
Delete
</.link>
</:action>
</.table>

View File

@ -0,0 +1,8 @@
<.header>
New Channel
<:subtitle>Use this form to manage channel records in your database.</:subtitle>
</.header>
<.channel_form changeset={@changeset} action={~p"/channels"} />
<.back navigate={~p"/channels"}>Back to channels</.back>

View File

@ -0,0 +1,18 @@
<.header>
Channel <%= @channel.id %>
<:subtitle>This is a channel record from your database.</:subtitle>
<:actions>
<.link href={~p"/channels/#{@channel}/edit"}>
<.button>Edit channel</.button>
</.link>
</:actions>
</.header>
<.list>
<:item title="Name"><%= @channel.name %></:item>
<:item title="Content"><%= @channel.content %></:item>
<:item title="Visibility"><%= @channel.visibility %></:item>
<:item title="Slug"><%= @channel.slug %></:item>
</.list>
<.back navigate={~p"/channels"}>Back to channels</.back>

View File

@ -0,0 +1,78 @@
defmodule ChiyaWeb.NoteController do
use ChiyaWeb, :controller
alias Chiya.Notes
alias Chiya.Notes.Note
def index(conn, _params) do
notes = Notes.list_notes()
render(conn, :index, notes: notes)
end
def new(conn, _params) do
changeset = Notes.change_note(%Note{})
render(conn, :new, changeset: changeset, channels: to_channel_options())
end
def create(conn, %{"note" => note_params}) do
note_params = from_channel_ids(note_params)
case Notes.create_note(note_params) do
{:ok, note} ->
conn
|> put_flash(:info, "Note created successfully.")
|> redirect(to: ~p"/notes/#{note}")
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, :new, changeset: changeset, channels: to_channel_options())
end
end
def show(conn, %{"id" => id}) do
note = Notes.get_note!(id)
render(conn, :show, note: note)
end
def edit(conn, %{"id" => id}) do
note = Notes.get_note_preloaded!(id)
changeset = Notes.change_note(note)
render(conn, :edit, note: note, changeset: changeset, channels: to_channel_options())
end
def update(conn, %{"id" => id, "note" => note_params}) do
note_params = from_channel_ids(note_params)
note = Notes.get_note_preloaded!(id)
case Notes.update_note(note, note_params) do
{:ok, note} ->
conn
|> put_flash(:info, "Note updated successfully.")
|> redirect(to: ~p"/notes/#{note}")
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, :edit, note: note, changeset: changeset, channels: to_channel_options())
end
end
def delete(conn, %{"id" => id}) do
note = Notes.get_note!(id)
{:ok, _note} = Notes.delete_note(note)
conn
|> put_flash(:info, "Note deleted successfully.")
|> redirect(to: ~p"/notes")
end
defp from_channel_ids(note_params) do
selected_ids = Enum.map(note_params["channels"] || [], &String.to_integer/1)
selected_channels =
Chiya.Channels.list_channels()
|> Enum.filter(fn c -> Enum.member?(selected_ids, c.id) end)
Map.put(note_params, "channels", selected_channels)
end
defp to_channel_options(items \\ nil),
do: Enum.map(items || Chiya.Channels.list_channels(), fn c -> {c.name, c.id} end)
end

View File

@ -0,0 +1,16 @@
defmodule ChiyaWeb.NoteHTML do
use ChiyaWeb, :html
embed_templates "note_html/*"
@doc """
Renders a note form.
"""
attr :changeset, Ecto.Changeset, required: true
attr :action, :string, required: true
attr :channels, :list, required: true
def note_form(assigns)
def selected_channels(changeset), do: Enum.map(changeset.data.channels, fn c -> c.id end)
end

View File

@ -0,0 +1,8 @@
<.header>
Edit Note <%= @note.id %>
<:subtitle>Use this form to manage note records in your database.</:subtitle>
</.header>
<.note_form changeset={@changeset} action={~p"/notes/#{@note}"} channels={@channels} />
<.back navigate={~p"/notes"}>Back to notes</.back>

View File

@ -0,0 +1,27 @@
<.header>
Listing Notes
<:actions>
<.link href={~p"/notes/new"}>
<.button>New Note</.button>
</.link>
</:actions>
</.header>
<.table id="notes" rows={@notes} row_click={&JS.navigate(~p"/notes/#{&1}")}>
<:col :let={note} label="Name"><%= note.name %></:col>
<:col :let={note} label="Slug"><%= note.slug %></:col>
<:col :let={note} label="Published at"><%= note.published_at %></:col>
<:col :let={note} label="Kind"><%= note.kind %></:col>
<:col :let={note} label="Url"><%= note.url %></:col>
<:action :let={note}>
<div class="sr-only">
<.link navigate={~p"/notes/#{note}"}>Show</.link>
</div>
<.link navigate={~p"/notes/#{note}/edit"}>Edit</.link>
</:action>
<:action :let={note}>
<.link href={~p"/notes/#{note}"} method="delete" data-confirm="Are you sure?">
Delete
</.link>
</:action>
</.table>

View File

@ -0,0 +1,8 @@
<.header>
New Note
<:subtitle>Use this form to manage note records in your database.</:subtitle>
</.header>
<.note_form changeset={@changeset} action={~p"/notes"} channels={@channels} />
<.back navigate={~p"/notes"}>Back to notes</.back>

View File

@ -0,0 +1,22 @@
<.simple_form :let={f} for={@changeset} action={@action}>
<.error :if={@changeset.action}>
Oops, something went wrong! Please check the errors below.
</.error>
<.input field={f[:name]} type="text" label="Name" />
<.input field={f[:content]} type="textarea" label="Content" />
<.input field={f[:slug]} type="text" label="Slug" />
<.input field={f[:published_at]} type="datetime-local" label="Published at" />
<.input field={f[:kind]} type="text" label="Kind" />
<.input field={f[:url]} type="text" label="Url" />
<.input
field={f[:channels]}
type="select"
label="Channel"
multiple={true}
options={@channels}
value={selected_channels(@changeset)}
/>
<:actions>
<.button>Save Note</.button>
</:actions>
</.simple_form>

View File

@ -0,0 +1,20 @@
<.header>
Note <%= @note.id %>
<:subtitle>This is a note record from your database.</:subtitle>
<:actions>
<.link href={~p"/notes/#{@note}/edit"}>
<.button>Edit note</.button>
</.link>
</:actions>
</.header>
<.list>
<:item title="Name"><%= @note.name %></:item>
<:item title="Content"><%= @note.content %></:item>
<:item title="Slug"><%= @note.slug %></:item>
<:item title="Published at"><%= @note.published_at %></:item>
<:item title="Kind"><%= @note.kind %></:item>
<:item title="Url"><%= @note.url %></:item>
</.list>
<.back navigate={~p"/notes"}>Back to notes</.back>

View File

@ -21,6 +21,9 @@ defmodule ChiyaWeb.Router do
pipe_through :browser
get "/", PageController, :home
resources "/channels", ChannelController
resources "/notes", NoteController
end
# Other scopes may use custom stacks.

View File

@ -50,7 +50,8 @@ defmodule Chiya.MixProject do
{:telemetry_poller, "~> 1.0"},
{:gettext, "~> 0.20"},
{:jason, "~> 1.2"},
{:plug_cowboy, "~> 2.5"}
{:plug_cowboy, "~> 2.5"},
{:oban, "~> 2.14"}
]
end

View File

@ -23,6 +23,7 @@
"mint": {:hex, :mint, "1.5.1", "8db5239e56738552d85af398798c80648db0e90f343c8469f6c6d8898944fb6f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4a63e1e76a7c3956abd2c72f370a0d0aecddc3976dea5c27eccbecfa5e7d5b1e"},
"nimble_options": {:hex, :nimble_options, "0.5.2", "42703307b924880f8c08d97719da7472673391905f528259915782bb346e0a1b", [:mix], [], "hexpm", "4da7f904b915fd71db549bcdc25f8d56f378ef7ae07dc1d372cbe72ba950dce0"},
"nimble_pool": {:hex, :nimble_pool, "0.2.6", "91f2f4c357da4c4a0a548286c84a3a28004f68f05609b4534526871a22053cde", [:mix], [], "hexpm", "1c715055095d3f2705c4e236c18b618420a35490da94149ff8b580a2144f653f"},
"oban": {:hex, :oban, "2.14.2", "ae925d9a33e110addaa59ff7ec1b2fd84270ac7eb00fbb4b4a179d74c407bba3", [:mix], [{:ecto_sql, "~> 3.6", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "32bf30127c8c44ac42f05f229a50fadc2177b3e799c29499f5daf90d5e5b5d3c"},
"phoenix": {:hex, :phoenix, "1.7.1", "a029bde19d9c3b559e5c3d06c78b76e81396bedd456a6acedb42f9c7b2e535a9", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.4", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "ea9d4a85c3592e37efa07d0dc013254fda445885facaefddcbf646375c116457"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.4.0", "0672ed4e4808b3fbed494dded89958e22fb882de47a97634c0b13e7b0b5f7720", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "09864e558ed31ee00bd48fcc1d4fc58ae9678c9e81649075431e69dbabb43cc1"},
"phoenix_html": {:hex, :phoenix_html, "3.3.1", "4788757e804a30baac6b3fc9695bf5562465dd3f1da8eb8460ad5b404d9a2178", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bed1906edd4906a15fd7b412b85b05e521e1f67c9a85418c55999277e553d0d3"},

View File

@ -0,0 +1,13 @@
defmodule Chiya.Repo.Migrations.AddObanJobsTable do
use Ecto.Migration
def up do
Oban.Migration.up(version: 11)
end
# We specify `version: 1` in `down`, ensuring that we'll roll all the way back down if
# necessary, regardless of which version we've migrated `up` to.
def down do
Oban.Migration.down(version: 1)
end
end

View File

@ -0,0 +1,16 @@
defmodule Chiya.Repo.Migrations.CreateChannels do
use Ecto.Migration
def change do
create table(:channels) do
add :name, :string
add :content, :text
add :visibility, :string
add :slug, :string
timestamps()
end
create unique_index(:channels, [:slug])
end
end

View File

@ -0,0 +1,18 @@
defmodule Chiya.Repo.Migrations.CreateNotes do
use Ecto.Migration
def change do
create table(:notes) do
add :name, :string
add :content, :text
add :slug, :string
add :published_at, :naive_datetime
add :kind, :string
add :url, :string
timestamps()
end
create unique_index(:notes, [:slug])
end
end

View File

@ -0,0 +1,13 @@
defmodule Chiya.Repo.Migrations.CreateChannelsNotes do
use Ecto.Migration
def change do
create table(:channels_notes, primary_key: false) do
add :channel, references(:channels, on_delete: :delete_all)
add :note, references(:notes, on_delete: :delete_all)
end
create index(:channels_notes, [:channel])
create index(:channels_notes, [:note])
end
end

View File

@ -1,9 +1,9 @@
defmodule Chiya.AccountsTest do
use Chiya.DataCase
alias Chiya.Accounts
import Chiya.AccountsFixtures
alias Chiya.Accounts
alias Chiya.Accounts.{User, UserToken}
describe "get_user_by_email/1" do

View File

@ -0,0 +1,75 @@
defmodule Chiya.ChannelsTest do
use Chiya.DataCase
import Chiya.ChannelsFixtures
alias Chiya.Channels
alias Chiya.Channels.Channel
describe "channels" do
@invalid_attrs %{content: nil, name: nil, slug: nil, visibility: nil}
test "list_channels/0 returns all channels" do
channel = channel_fixture()
assert Channels.list_channels() == [channel]
end
test "get_channel!/1 returns the channel with given id" do
channel = channel_fixture()
assert Channels.get_channel!(channel.id) == channel
end
test "create_channel/1 with valid data creates a channel" do
valid_attrs = %{
content: "some content",
name: "some name",
slug: "some slug",
visibility: :public
}
assert {:ok, %Channel{} = channel} = Channels.create_channel(valid_attrs)
assert channel.content == "some content"
assert channel.name == "some name"
assert channel.slug == "some slug"
assert channel.visibility == :public
end
test "create_channel/1 with invalid data returns error changeset" do
assert {:error, %Ecto.Changeset{}} = Channels.create_channel(@invalid_attrs)
end
test "update_channel/2 with valid data updates the channel" do
channel = channel_fixture()
update_attrs = %{
content: "some updated content",
name: "some updated name",
slug: "some updated slug",
visibility: :private
}
assert {:ok, %Channel{} = channel} = Channels.update_channel(channel, update_attrs)
assert channel.content == "some updated content"
assert channel.name == "some updated name"
assert channel.slug == "some updated slug"
assert channel.visibility == :private
end
test "update_channel/2 with invalid data returns error changeset" do
channel = channel_fixture()
assert {:error, %Ecto.Changeset{}} = Channels.update_channel(channel, @invalid_attrs)
assert channel == Channels.get_channel!(channel.id)
end
test "delete_channel/1 deletes the channel" do
channel = channel_fixture()
assert {:ok, %Channel{}} = Channels.delete_channel(channel)
assert_raise Ecto.NoResultsError, fn -> Channels.get_channel!(channel.id) end
end
test "change_channel/1 returns a channel changeset" do
channel = channel_fixture()
assert %Ecto.Changeset{} = Channels.change_channel(channel)
end
end
end

83
test/chiya/notes_test.exs Normal file
View File

@ -0,0 +1,83 @@
defmodule Chiya.NotesTest do
use Chiya.DataCase
import Chiya.NotesFixtures
alias Chiya.Notes
alias Chiya.Notes.Note
describe "notes" do
@invalid_attrs %{content: nil, kind: nil, name: nil, published_at: nil, slug: nil, url: nil}
test "list_notes/0 returns all notes" do
note = note_fixture()
assert Notes.list_notes() == [note]
end
test "get_note!/1 returns the note with given id" do
note = note_fixture()
assert Notes.get_note_preloaded!(note.id) == note
end
test "create_note/1 with valid data creates a note" do
valid_attrs = %{
content: "some content",
kind: "some kind",
name: "some name",
published_at: ~N[2023-03-04 16:22:00],
slug: "some slug",
url: "some url"
}
assert {:ok, %Note{} = note} = Notes.create_note(valid_attrs)
assert note.content == "some content"
assert note.kind == "some kind"
assert note.name == "some name"
assert note.published_at == ~N[2023-03-04 16:22:00]
assert note.slug == "some slug"
assert note.url == "some url"
end
test "create_note/1 with invalid data returns error changeset" do
assert {:error, %Ecto.Changeset{}} = Notes.create_note(@invalid_attrs)
end
test "update_note/2 with valid data updates the note" do
note = note_fixture()
update_attrs = %{
content: "some updated content",
kind: "some updated kind",
name: "some updated name",
published_at: ~N[2023-03-05 16:22:00],
slug: "some updated slug",
url: "some updated url"
}
assert {:ok, %Note{} = note} = Notes.update_note(note, update_attrs)
assert note.content == "some updated content"
assert note.kind == "some updated kind"
assert note.name == "some updated name"
assert note.published_at == ~N[2023-03-05 16:22:00]
assert note.slug == "some updated slug"
assert note.url == "some updated url"
end
test "update_note/2 with invalid data returns error changeset" do
note = note_fixture()
assert {:error, %Ecto.Changeset{}} = Notes.update_note(note, @invalid_attrs)
assert note == Notes.get_note_preloaded!(note.id)
end
test "delete_note/1 deletes the note" do
note = note_fixture()
assert {:ok, %Note{}} = Notes.delete_note(note)
assert_raise Ecto.NoResultsError, fn -> Notes.get_note!(note.id) end
end
test "change_note/1 returns a note changeset" do
note = note_fixture()
assert %Ecto.Changeset{} = Notes.change_note(note)
end
end
end

View File

@ -0,0 +1,94 @@
defmodule ChiyaWeb.ChannelControllerTest do
use ChiyaWeb.ConnCase
import Chiya.ChannelsFixtures
@create_attrs %{
content: "some content",
name: "some name",
slug: "some slug",
visibility: :public
}
@update_attrs %{
content: "some updated content",
name: "some updated name",
slug: "some updated slug",
visibility: :private
}
@invalid_attrs %{content: nil, name: nil, slug: nil, visibility: nil}
describe "index" do
test "lists all channels", %{conn: conn} do
conn = get(conn, ~p"/channels")
assert html_response(conn, 200) =~ "Listing Channels"
end
end
describe "new channel" do
test "renders form", %{conn: conn} do
conn = get(conn, ~p"/channels/new")
assert html_response(conn, 200) =~ "New Channel"
end
end
describe "create channel" do
test "redirects to show when data is valid", %{conn: conn} do
conn = post(conn, ~p"/channels", channel: @create_attrs)
assert %{id: id} = redirected_params(conn)
assert redirected_to(conn) == ~p"/channels/#{id}"
conn = get(conn, ~p"/channels/#{id}")
assert html_response(conn, 200) =~ "Channel #{id}"
end
test "renders errors when data is invalid", %{conn: conn} do
conn = post(conn, ~p"/channels", channel: @invalid_attrs)
assert html_response(conn, 200) =~ "New Channel"
end
end
describe "edit channel" do
setup [:create_channel]
test "renders form for editing chosen channel", %{conn: conn, channel: channel} do
conn = get(conn, ~p"/channels/#{channel}/edit")
assert html_response(conn, 200) =~ "Edit Channel"
end
end
describe "update channel" do
setup [:create_channel]
test "redirects when data is valid", %{conn: conn, channel: channel} do
conn = put(conn, ~p"/channels/#{channel}", channel: @update_attrs)
assert redirected_to(conn) == ~p"/channels/#{channel}"
conn = get(conn, ~p"/channels/#{channel}")
assert html_response(conn, 200) =~ "some updated content"
end
test "renders errors when data is invalid", %{conn: conn, channel: channel} do
conn = put(conn, ~p"/channels/#{channel}", channel: @invalid_attrs)
assert html_response(conn, 200) =~ "Edit Channel"
end
end
describe "delete channel" do
setup [:create_channel]
test "deletes chosen channel", %{conn: conn, channel: channel} do
conn = delete(conn, ~p"/channels/#{channel}")
assert redirected_to(conn) == ~p"/channels"
assert_error_sent 404, fn ->
get(conn, ~p"/channels/#{channel}")
end
end
end
defp create_channel(_) do
channel = channel_fixture()
%{channel: channel}
end
end

View File

@ -0,0 +1,141 @@
defmodule ChiyaWeb.NoteControllerTest do
use ChiyaWeb.ConnCase
import Chiya.NotesFixtures
@create_attrs %{
content: "some content",
kind: "some kind",
name: "some name",
published_at: ~N[2023-03-04 16:22:00],
slug: "some slug",
url: "some url"
}
@update_attrs %{
content: "some updated content",
kind: "some updated kind",
name: "some updated name",
published_at: ~N[2023-03-05 16:22:00],
slug: "some updated slug",
url: "some updated url"
}
@invalid_attrs %{content: nil, kind: nil, name: nil, published_at: nil, slug: nil, url: nil}
describe "index" do
test "lists all notes", %{conn: conn} do
conn = get(conn, ~p"/notes")
assert html_response(conn, 200) =~ "Listing Notes"
end
end
describe "new note" do
test "renders form", %{conn: conn} do
conn = get(conn, ~p"/notes/new")
assert html_response(conn, 200) =~ "New Note"
end
end
describe "create note" do
test "redirects to show when data is valid", %{conn: conn} do
conn = post(conn, ~p"/notes", note: @create_attrs)
assert %{id: id} = redirected_params(conn)
assert redirected_to(conn) == ~p"/notes/#{id}"
conn = get(conn, ~p"/notes/#{id}")
assert html_response(conn, 200) =~ "Note #{id}"
end
test "renders errors when data is invalid", %{conn: conn} do
conn = post(conn, ~p"/notes", note: @invalid_attrs)
assert html_response(conn, 200) =~ "New Note"
end
end
describe "create note with channel" do
setup [:create_channels]
test "redirects to show when selecting a channel", %{conn: conn, channel: channel} do
attrs = Map.put_new(@create_attrs, :channels, [to_string(channel.id)])
conn = post(conn, ~p"/notes", note: attrs)
assert %{id: id} = redirected_params(conn)
assert redirected_to(conn) == ~p"/notes/#{id}"
conn = get(conn, ~p"/notes/#{id}")
assert html_response(conn, 200) =~ "Note #{id}"
end
end
describe "edit note" do
setup [:create_note]
test "renders form for editing chosen note", %{conn: conn, note: note} do
conn = get(conn, ~p"/notes/#{note}/edit")
assert html_response(conn, 200) =~ "Edit Note"
end
end
describe "update note" do
setup [:create_note]
test "redirects when data is valid", %{conn: conn, note: note} do
conn = put(conn, ~p"/notes/#{note}", note: @update_attrs)
assert redirected_to(conn) == ~p"/notes/#{note}"
conn = get(conn, ~p"/notes/#{note}")
assert html_response(conn, 200) =~ "some updated content"
end
test "renders errors when data is invalid", %{conn: conn, note: note} do
conn = put(conn, ~p"/notes/#{note}", note: @invalid_attrs)
assert html_response(conn, 200) =~ "Edit Note"
end
end
describe "update note with channel" do
setup [:create_note, :create_channels]
test "adds and removes the correct channels from the note", %{
conn: conn,
note: note,
channel: channel,
channel2: channel2
} do
attrs = Map.put_new(@update_attrs, :channels, [to_string(channel.id)])
conn = put(conn, ~p"/notes/#{note}", note: attrs)
assert redirected_to(conn) == ~p"/notes/#{note}"
attrs = Map.put_new(@update_attrs, :channels, [to_string(channel2.id)])
conn = put(conn, ~p"/notes/#{note}", note: attrs)
assert redirected_to(conn) == ~p"/notes/#{note}"
conn = get(conn, ~p"/notes/#{note}")
assert html_response(conn, 200) =~ "some updated content"
end
end
describe "delete note" do
setup [:create_note]
test "deletes chosen note", %{conn: conn, note: note} do
conn = delete(conn, ~p"/notes/#{note}")
assert redirected_to(conn) == ~p"/notes"
assert_error_sent 404, fn ->
get(conn, ~p"/notes/#{note}")
end
end
end
defp create_note(_) do
note = note_fixture()
%{note: note}
end
defp create_channels(_) do
channel = Chiya.ChannelsFixtures.channel_fixture()
channel2 = Chiya.ChannelsFixtures.channel_fixture()
%{channel: channel, channel2: channel2}
end
end

View File

@ -0,0 +1,28 @@
defmodule Chiya.ChannelsFixtures do
@moduledoc """
This module defines test helpers for creating
entities via the `Chiya.Channels` context.
"""
@doc """
Generate a unique note slug.
"""
def unique_channel_slug, do: "some slug#{System.unique_integer([:positive])}"
@doc """
Generate a channel.
"""
def channel_fixture(attrs \\ %{}) do
{:ok, channel} =
attrs
|> Enum.into(%{
content: "some content",
name: "some name",
slug: unique_channel_slug(),
visibility: :public
})
|> Chiya.Channels.create_channel()
channel
end
end

View File

@ -0,0 +1,30 @@
defmodule Chiya.NotesFixtures do
@moduledoc """
This module defines test helpers for creating
entities via the `Chiya.Notes` context.
"""
@doc """
Generate a unique note slug.
"""
def unique_note_slug, do: "some slug#{System.unique_integer([:positive])}"
@doc """
Generate a note.
"""
def note_fixture(attrs \\ %{}) do
{:ok, note} =
attrs
|> Enum.into(%{
content: "some content",
kind: "some kind",
name: "some name",
published_at: ~N[2023-03-04 16:22:00],
slug: unique_note_slug(),
url: "some url"
})
|> Chiya.Notes.create_note()
note
end
end