Compare commits
10 Commits
9fa6968f6f
...
65371bbfbb
Author | SHA1 | Date |
---|---|---|
Shadowfacts | 65371bbfbb | |
Shadowfacts | a59412efda | |
Shadowfacts | 7230b52563 | |
Shadowfacts | 16884dffef | |
Shadowfacts | eb313cf695 | |
Shadowfacts | a759be3969 | |
Shadowfacts | bd0fe86a81 | |
Shadowfacts | cf1be80746 | |
Shadowfacts | 4d4c4d3508 | |
Shadowfacts | 73dee19294 |
|
@ -22,11 +22,6 @@ erl_crash.dump
|
||||||
# Ignore package tarball (built via "mix hex.build").
|
# Ignore package tarball (built via "mix hex.build").
|
||||||
frenzy-*.tar
|
frenzy-*.tar
|
||||||
|
|
||||||
# Since we are building assets from assets/,
|
|
||||||
# we ignore priv/static. You may want to comment
|
|
||||||
# this depending on your deployment strategy.
|
|
||||||
/priv/static/
|
|
||||||
|
|
||||||
# Files matching config/*.secret.exs pattern contain sensitive
|
# Files matching config/*.secret.exs pattern contain sensitive
|
||||||
# data and you should not commit them into version control.
|
# data and you should not commit them into version control.
|
||||||
#
|
#
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
defmodule Frenzy.ApprovedClient do
|
||||||
|
use Ecto.Schema
|
||||||
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
schema "approved_clients" do
|
||||||
|
field :client_id, :string
|
||||||
|
field :auth_code, :string
|
||||||
|
field :access_token, :string
|
||||||
|
|
||||||
|
belongs_to :user, Frenzy.User
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
def changeset(approved_client, attrs) do
|
||||||
|
approved_client
|
||||||
|
|> cast(attrs, [:client_id, :auth_code, :access_token])
|
||||||
|
|> validate_required([:client_id])
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,6 +1,8 @@
|
||||||
defmodule Frenzy.Feed do
|
defmodule Frenzy.Feed do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
alias FrenzyWeb.Router.Helpers, as: Routes
|
||||||
|
alias FrenzyWeb.Endpoint
|
||||||
|
|
||||||
def to_fever(feed) do
|
def to_fever(feed) do
|
||||||
%{
|
%{
|
||||||
|
@ -14,6 +16,19 @@ defmodule Frenzy.Feed do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def to_fervor(feed) do
|
||||||
|
%{
|
||||||
|
id: feed.id,
|
||||||
|
title: feed.title,
|
||||||
|
url: feed.site_url,
|
||||||
|
feed_url: feed.feed_url,
|
||||||
|
service_url:
|
||||||
|
Application.get_env(:frenzy, :base_url) <> Routes.feed_path(Endpoint, :show, feed.id),
|
||||||
|
last_updated: DateTime.to_iso8601(feed.last_updated),
|
||||||
|
group_ids: [feed.group_id]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
schema "feeds" do
|
schema "feeds" do
|
||||||
field :feed_url, :string
|
field :feed_url, :string
|
||||||
field :last_updated, :utc_datetime
|
field :last_updated, :utc_datetime
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
defmodule Frenzy.FervorClient do
|
||||||
|
use Ecto.Schema
|
||||||
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
def to_fervor(client) do
|
||||||
|
%{
|
||||||
|
client_name: client.client_name,
|
||||||
|
website: client.website,
|
||||||
|
redirect_uri: client.redirect_uri,
|
||||||
|
client_id: client.client_id,
|
||||||
|
client_secret: client.client_secret
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
schema "fervor_clients" do
|
||||||
|
field :client_name, :string
|
||||||
|
field :website, :string
|
||||||
|
field :redirect_uri, :string
|
||||||
|
field :client_id, :string
|
||||||
|
field :client_secret, :string
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
def changeset(client, attrs) do
|
||||||
|
client
|
||||||
|
|> cast(attrs, [:client_name, :website, :redirect_uri, :client_id, :client_secret])
|
||||||
|
|> validate_required([:client_name, :redirect_uri, :client_id, :client_secret])
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,6 +1,8 @@
|
||||||
defmodule Frenzy.Group do
|
defmodule Frenzy.Group do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
alias FrenzyWeb.Router.Helpers, as: Routes
|
||||||
|
alias FrenzyWeb.Endpoint
|
||||||
|
|
||||||
def to_fever_group(group) do
|
def to_fever_group(group) do
|
||||||
%{
|
%{
|
||||||
|
@ -16,6 +18,16 @@ defmodule Frenzy.Group do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def to_fervor(group) do
|
||||||
|
%{
|
||||||
|
id: group.id,
|
||||||
|
title: group.title,
|
||||||
|
feed_ids: group.feeds |> Enum.map(fn f -> f.id end),
|
||||||
|
service_url:
|
||||||
|
Application.get_env(:frenzy, :base_url) <> Routes.group_path(Endpoint, :show, group.id)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
schema "groups" do
|
schema "groups" do
|
||||||
field :title, :string
|
field :title, :string
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
defmodule Frenzy.Item do
|
defmodule Frenzy.Item do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
alias FrenzyWeb.Router.Helpers, as: Routes
|
||||||
|
alias FrenzyWeb.Endpoint
|
||||||
|
|
||||||
def to_fever(item) do
|
def to_fever(item) do
|
||||||
%{
|
%{
|
||||||
|
@ -16,6 +18,21 @@ defmodule Frenzy.Item do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def to_fervor(item) do
|
||||||
|
%{
|
||||||
|
id: item.id,
|
||||||
|
feed_id: item.feed_id,
|
||||||
|
title: item.title,
|
||||||
|
author: item.creator,
|
||||||
|
created_at: DateTime.to_iso8601(item.date),
|
||||||
|
content: item.content,
|
||||||
|
url: item.url,
|
||||||
|
service_url:
|
||||||
|
Application.get_env(:frenzy, :base_url) <> Routes.item_path(Endpoint, :show, item.id),
|
||||||
|
read: item.read
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
schema "items" do
|
schema "items" do
|
||||||
field :content, :string
|
field :content, :string
|
||||||
field :date, :utc_datetime
|
field :date, :utc_datetime
|
||||||
|
|
|
@ -9,6 +9,8 @@ defmodule Frenzy.User do
|
||||||
field :fever_password, :string, virtual: true
|
field :fever_password, :string, virtual: true
|
||||||
field :fever_auth_token, :string
|
field :fever_auth_token, :string
|
||||||
|
|
||||||
|
has_many :approved_clients, Frenzy.ApprovedClient, on_delete: :delete_all
|
||||||
|
|
||||||
has_many :groups, Frenzy.Group, on_delete: :delete_all
|
has_many :groups, Frenzy.Group, on_delete: :delete_all
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
|
@ -27,17 +29,39 @@ defmodule Frenzy.User do
|
||||||
|> validate_length(:password, min: 8)
|
|> validate_length(:password, min: 8)
|
||||||
|> validate_length(:fever_password, min: 8)
|
|> validate_length(:fever_password, min: 8)
|
||||||
|> put_password_hash()
|
|> put_password_hash()
|
||||||
|
|> put_fever_token()
|
||||||
|
end
|
||||||
|
|
||||||
|
def change_password_changeset(user, attrs) do
|
||||||
|
user
|
||||||
|
|> cast(attrs, [:password])
|
||||||
|
|> validate_length(:password, min: 8)
|
||||||
|
|> put_password_hash()
|
||||||
|
end
|
||||||
|
|
||||||
|
def change_fever_password_changeset(user, attrs) do
|
||||||
|
user
|
||||||
|
|> cast(attrs, [:username, :fever_password])
|
||||||
|
|> validate_length(:fever_password, min: 8)
|
||||||
|
|> put_fever_token()
|
||||||
end
|
end
|
||||||
|
|
||||||
defp put_password_hash(
|
defp put_password_hash(
|
||||||
|
%Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
|
||||||
|
) do
|
||||||
|
change(changeset, Bcrypt.add_hash(password))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp put_fever_token(
|
||||||
%Ecto.Changeset{
|
%Ecto.Changeset{
|
||||||
valid?: true,
|
valid?: true,
|
||||||
changes: %{username: username, password: password, fever_password: fever_password}
|
changes: %{fever_password: fever_password}
|
||||||
} = changeset
|
} = changeset
|
||||||
) do
|
) do
|
||||||
changeset
|
username = Map.get(changeset.changes, "username") || changeset.data.username
|
||||||
|> change(Bcrypt.add_hash(password))
|
IO.inspect(username)
|
||||||
|> change(%{
|
|
||||||
|
change(changeset, %{
|
||||||
fever_auth_token:
|
fever_auth_token:
|
||||||
:crypto.hash(:md5, "#{username}:#{fever_password}") |> Base.encode16(case: :lower)
|
:crypto.hash(:md5, "#{username}:#{fever_password}") |> Base.encode16(case: :lower)
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
defmodule FrenzyWeb.AccountController do
|
||||||
|
use FrenzyWeb, :controller
|
||||||
|
alias Frenzy.{Repo, User, FervorClient}
|
||||||
|
alias FrenzyWeb.Router.Helpers, as: Routes
|
||||||
|
alias FrenzyWeb.Endpoint
|
||||||
|
|
||||||
|
def show(conn, _params) do
|
||||||
|
user = conn.assigns[:user] |> Repo.preload(:approved_clients)
|
||||||
|
|
||||||
|
clients =
|
||||||
|
user.approved_clients
|
||||||
|
|> Enum.map(fn approved_client ->
|
||||||
|
fervor_client = Repo.get_by(FervorClient, client_id: approved_client.client_id)
|
||||||
|
{approved_client, fervor_client}
|
||||||
|
end)
|
||||||
|
|
||||||
|
render(conn, "show.html", %{
|
||||||
|
user: user,
|
||||||
|
clients: clients
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
def change_password(conn, _params) do
|
||||||
|
render(conn, "change_password.html")
|
||||||
|
end
|
||||||
|
|
||||||
|
def do_change_password(conn, %{
|
||||||
|
"old_password" => old,
|
||||||
|
"new_password" => new,
|
||||||
|
"confirm_new_password" => confirm
|
||||||
|
}) do
|
||||||
|
user = conn.assigns[:user] |> Repo.preload([:approved_clients, :groups])
|
||||||
|
|
||||||
|
case Bcrypt.check_pass(user, old) do
|
||||||
|
{:ok, user} ->
|
||||||
|
case new do
|
||||||
|
^old ->
|
||||||
|
conn
|
||||||
|
|> put_flash(:error, "New password cannot be the same as old password.")
|
||||||
|
|> redirect(to: Routes.account_path(Endpoint, :change_password))
|
||||||
|
|
||||||
|
^confirm ->
|
||||||
|
changeset = User.change_password_changeset(user, %{password: new})
|
||||||
|
|
||||||
|
{:ok, user} = Repo.update(changeset)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_flash(:info, "Password changed.")
|
||||||
|
|> redirect(to: Routes.account_path(Endpoint, :show))
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
conn
|
||||||
|
|> put_flash(:error, "New password and confirmation did not match.")
|
||||||
|
|> redirect(to: Routes.account_path(Endpoint, :change_password))
|
||||||
|
end
|
||||||
|
|
||||||
|
{:error, _reason} ->
|
||||||
|
conn
|
||||||
|
|> put_flash(:error, "Invalid old password.")
|
||||||
|
|> redirect(to: Routes.account_path(Endpoint, :change_password))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def do_change_password(conn, _params) do
|
||||||
|
redirect(conn, to: Routes.account_path(Endpoint, :change_password))
|
||||||
|
end
|
||||||
|
|
||||||
|
def change_fever_password(conn, _params) do
|
||||||
|
render(conn, "change_fever_password.html")
|
||||||
|
end
|
||||||
|
|
||||||
|
def do_change_fever_password(conn, %{
|
||||||
|
"new_password" => new
|
||||||
|
}) do
|
||||||
|
user = conn.assigns[:user] |> Repo.preload([:approved_clients, :groups])
|
||||||
|
|
||||||
|
changeset =
|
||||||
|
User.change_fever_password_changeset(user, %{
|
||||||
|
username: user.username,
|
||||||
|
fever_password: new
|
||||||
|
})
|
||||||
|
|
||||||
|
{:ok, user} = Repo.update(changeset)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_flash(:info, "Fever password changed.")
|
||||||
|
|> redirect(to: Routes.account_path(Endpoint, :show))
|
||||||
|
end
|
||||||
|
|
||||||
|
def do_change_fever_password(conn, _params) do
|
||||||
|
redirect(conn, to: Routes.account_path(Endpoint, :change_fever_password))
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_client(conn, %{"client_id" => client_id}) do
|
||||||
|
user = conn.assigns[:user] |> Repo.preload(:approved_clients)
|
||||||
|
|
||||||
|
approved_client = Enum.find(user.approved_clients, fn c -> c.client_id == client_id end)
|
||||||
|
|
||||||
|
unless is_nil(approved_client) do
|
||||||
|
{:ok, _} = Repo.delete(approved_client)
|
||||||
|
end
|
||||||
|
|
||||||
|
redirect(conn, to: Routes.account_path(Endpoint, :show))
|
||||||
|
end
|
||||||
|
end
|
|
@ -26,7 +26,7 @@ defmodule FrenzyWeb.FeedController do
|
||||||
defp user_owns_feed(conn, _opts), do: conn
|
defp user_owns_feed(conn, _opts), do: conn
|
||||||
|
|
||||||
def show(conn, %{"id" => id}) do
|
def show(conn, %{"id" => id}) do
|
||||||
feed = conn.assigns[:feed]
|
feed = conn.assigns[:feed] |> Repo.preload(:filter)
|
||||||
items = Repo.all(from Item, where: [feed_id: ^id, tombstone: false], order_by: [desc: :date])
|
items = Repo.all(from Item, where: [feed_id: ^id, tombstone: false], order_by: [desc: :date])
|
||||||
|
|
||||||
render(conn, "show.html", %{
|
render(conn, "show.html", %{
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
defmodule FrenzyWeb.Fervor.FeedsController do
|
||||||
|
use FrenzyWeb, :controller
|
||||||
|
alias Frenzy.{Repo, Feed, Filter, Item}
|
||||||
|
import Ecto.Query
|
||||||
|
alias FrenzyWeb.Fervor.Paginator
|
||||||
|
|
||||||
|
plug :get_specific_feed
|
||||||
|
|
||||||
|
def get_specific_feed(%Plug.Conn{path_params: %{"id" => id}} = conn, _opts) do
|
||||||
|
user = conn.assigns[:user] |> Repo.preload(groups: [:feeds])
|
||||||
|
{id, _} = Integer.parse(id)
|
||||||
|
|
||||||
|
feeds = Enum.flat_map(user.groups, fn g -> g.feeds end)
|
||||||
|
|
||||||
|
case Enum.find(feeds, fn f -> f.id == id end) do
|
||||||
|
nil ->
|
||||||
|
conn
|
||||||
|
|> put_status(404)
|
||||||
|
|> json(%{error: "Unknown feed"})
|
||||||
|
|> halt()
|
||||||
|
|
||||||
|
feed ->
|
||||||
|
assign(conn, :feed, feed)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_specific_feed(conn, _opts), do: conn
|
||||||
|
|
||||||
|
def feeds_list(conn, _params) do
|
||||||
|
user = conn.assigns[:user] |> Repo.preload(groups: [:feeds])
|
||||||
|
|
||||||
|
feeds =
|
||||||
|
user.groups
|
||||||
|
|> Enum.flat_map(fn g -> g.feeds end)
|
||||||
|
|> Enum.map(&Feed.to_fervor/1)
|
||||||
|
|
||||||
|
json(conn, feeds)
|
||||||
|
end
|
||||||
|
|
||||||
|
def specific_feed(conn, _params) do
|
||||||
|
feed = conn.assigns[:feed]
|
||||||
|
|
||||||
|
json(conn, Feed.to_fervor(feed))
|
||||||
|
end
|
||||||
|
|
||||||
|
def specific_feed_items(conn, params) do
|
||||||
|
feed = conn.assigns[:feed]
|
||||||
|
feed_id = feed.id
|
||||||
|
|
||||||
|
query = from(i in Item, where: i.feed_id == ^feed_id)
|
||||||
|
|
||||||
|
query =
|
||||||
|
case Map.get(params, "only") do
|
||||||
|
"read" -> from(i in query, where: i.read)
|
||||||
|
"unread" -> from(i in query, where: not i.read)
|
||||||
|
_ -> query
|
||||||
|
end
|
||||||
|
|> Paginator.paginate(params)
|
||||||
|
|> Paginator.limit(params)
|
||||||
|
|
||||||
|
items =
|
||||||
|
query
|
||||||
|
|> Repo.all()
|
||||||
|
|> Enum.map(&Item.to_fervor/1)
|
||||||
|
|
||||||
|
json(conn, items)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create(conn, %{"feed_url" => feed_url, "group_ids" => group_ids}) do
|
||||||
|
case Integer.parse(group_ids) do
|
||||||
|
{_, rest} when rest != "" ->
|
||||||
|
conn
|
||||||
|
|> put_status(400)
|
||||||
|
|> json(%{error: "Could not create feed. Exactly 1 group must be provided."})
|
||||||
|
|
||||||
|
{group_id, _} ->
|
||||||
|
user = conn.assigns[:user] |> Repo.preload(:groups)
|
||||||
|
|
||||||
|
case Enum.find(user.groups, fn g -> g.id == group_id end) do
|
||||||
|
nil ->
|
||||||
|
conn
|
||||||
|
|> put_status(400)
|
||||||
|
|> json(%{error: "Could not create feed. Invalid group."})
|
||||||
|
|
||||||
|
group ->
|
||||||
|
changeset =
|
||||||
|
Ecto.build_assoc(group, :feeds, %{
|
||||||
|
feed_url: feed_url,
|
||||||
|
filter: %Filter{
|
||||||
|
mode: "reject",
|
||||||
|
score: 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
{:ok, feed} = Repo.insert(changeset)
|
||||||
|
|
||||||
|
feed = Frenzy.UpdateFeeds.refresh(Frenzy.UpdateFeeds, feed)
|
||||||
|
|
||||||
|
json(conn, Feed.to_fervor(feed))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def create(conn, _params) do
|
||||||
|
conn
|
||||||
|
|> put_status(400)
|
||||||
|
|> json(%{
|
||||||
|
error: "Could not create feed",
|
||||||
|
error_description: "feed URL and one group ID must be provided"
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete(conn, _params) do
|
||||||
|
feed = conn.assigns[:feed]
|
||||||
|
|
||||||
|
{:ok, _} = Repo.delete(feed)
|
||||||
|
|
||||||
|
send_resp(conn, 204, "")
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,101 @@
|
||||||
|
defmodule FrenzyWeb.Fervor.GroupsController do
|
||||||
|
use FrenzyWeb, :controller
|
||||||
|
alias Frenzy.{Repo, Group, Feed, Item}
|
||||||
|
import Ecto.Query
|
||||||
|
alias FrenzyWeb.Fervor.Paginator
|
||||||
|
|
||||||
|
plug :get_specific_group
|
||||||
|
|
||||||
|
def get_specific_group(%Plug.Conn{path_params: %{"id" => id}} = conn, _opts) do
|
||||||
|
user = conn.assigns[:user] |> Repo.preload(groups: [:feeds])
|
||||||
|
{id, _} = Integer.parse(id)
|
||||||
|
|
||||||
|
case Enum.find(user.groups, fn g -> g.id == id end) do
|
||||||
|
nil ->
|
||||||
|
conn
|
||||||
|
|> put_status(404)
|
||||||
|
|> json(%{error: "Unknown group"})
|
||||||
|
|> halt()
|
||||||
|
|
||||||
|
group ->
|
||||||
|
assign(conn, :group, group)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_specific_group(conn, _opts), do: conn
|
||||||
|
|
||||||
|
def groups_list(conn, _params) do
|
||||||
|
user = conn.assigns[:user] |> Repo.preload(groups: [:feeds])
|
||||||
|
groups = Enum.map(user.groups, &Group.to_fervor/1)
|
||||||
|
json(conn, groups)
|
||||||
|
end
|
||||||
|
|
||||||
|
def specific_group(conn, _params) do
|
||||||
|
group = conn.assigns[:group]
|
||||||
|
|
||||||
|
json(conn, Group.to_fervor(group))
|
||||||
|
end
|
||||||
|
|
||||||
|
def specific_group_feeds(conn, _params) do
|
||||||
|
group = conn.assigns[:group]
|
||||||
|
|
||||||
|
feeds = Enum.map(group.feeds, &Feed.to_fervor/1)
|
||||||
|
json(conn, feeds)
|
||||||
|
end
|
||||||
|
|
||||||
|
def specific_group_items(conn, params) do
|
||||||
|
group = conn.assigns[:group]
|
||||||
|
|
||||||
|
feed_ids = Enum.map(group.feeds, fn f -> f.id end)
|
||||||
|
|
||||||
|
query = from(i in Item, where: i.feed_id in ^feed_ids)
|
||||||
|
|
||||||
|
query =
|
||||||
|
case Map.get(params, "only") do
|
||||||
|
"read" -> from(i in query, where: i.read)
|
||||||
|
"unread" -> from(i in query, where: not i.read)
|
||||||
|
_ -> query
|
||||||
|
end
|
||||||
|
|> Paginator.paginate(params)
|
||||||
|
|> Paginator.limit(params)
|
||||||
|
|
||||||
|
items =
|
||||||
|
query
|
||||||
|
|> Repo.all()
|
||||||
|
|> Enum.map(&Item.to_fervor/1)
|
||||||
|
|
||||||
|
json(conn, items)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create(conn, %{"title" => title}) do
|
||||||
|
user = conn.assigns[:user]
|
||||||
|
|
||||||
|
changeset =
|
||||||
|
Ecto.build_assoc(user, :groups, %{
|
||||||
|
title: title
|
||||||
|
})
|
||||||
|
|
||||||
|
{:ok, group} = Repo.insert(changeset)
|
||||||
|
|
||||||
|
group = Repo.preload(group, :feeds)
|
||||||
|
|
||||||
|
json(conn, Group.to_fervor(group))
|
||||||
|
end
|
||||||
|
|
||||||
|
def create(conn, _params) do
|
||||||
|
conn
|
||||||
|
|> put_status(400)
|
||||||
|
|> json(%{
|
||||||
|
error: "Could not create group",
|
||||||
|
error_description: "title parameter must be provided"
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete(conn, _params) do
|
||||||
|
group = conn.assigns[:group]
|
||||||
|
|
||||||
|
{:ok, _} = Repo.delete(group)
|
||||||
|
|
||||||
|
send_resp(conn, 204, "")
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,124 @@
|
||||||
|
defmodule FrenzyWeb.Fervor.ItemsController do
|
||||||
|
use FrenzyWeb, :controller
|
||||||
|
alias Frenzy.{Repo, FervorClient, Group, Feed, Filter, Item}
|
||||||
|
import Ecto.Query
|
||||||
|
alias FrenzyWeb.Fervor.Paginator
|
||||||
|
|
||||||
|
plug :get_specific_item
|
||||||
|
|
||||||
|
def get_specific_item(%Plug.Conn{path_params: %{"id" => id}} = conn, _opts) do
|
||||||
|
user = conn.assigns[:user] |> Repo.preload(groups: [:feeds])
|
||||||
|
|
||||||
|
feeds = Enum.flat_map(user.groups, fn g -> g.feeds end)
|
||||||
|
|
||||||
|
item = Repo.get(Item, id)
|
||||||
|
|
||||||
|
if Enum.any?(feeds, fn f -> f.id == item.feed_id end) do
|
||||||
|
assign(conn, :item, item)
|
||||||
|
else
|
||||||
|
conn
|
||||||
|
|> put_status(404)
|
||||||
|
|> json(%{error: "Unknown item"})
|
||||||
|
|> halt()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_specific_item(conn, _opts), do: conn
|
||||||
|
|
||||||
|
def items_list(conn, params) do
|
||||||
|
user = conn.assigns[:user] |> Repo.preload(groups: [:feeds])
|
||||||
|
|
||||||
|
feed_ids =
|
||||||
|
user.groups
|
||||||
|
|> Enum.flat_map(fn g -> g.feeds end)
|
||||||
|
|> Enum.map(fn f -> f.id end)
|
||||||
|
|
||||||
|
query = from(i in Item, where: i.feed_id in ^feed_ids)
|
||||||
|
|
||||||
|
query =
|
||||||
|
case Map.get(params, "only") do
|
||||||
|
"read" -> from(i in query, where: i.read)
|
||||||
|
"unread" -> from(i in query, where: not i.read)
|
||||||
|
nil -> query
|
||||||
|
end
|
||||||
|
|> Paginator.paginate(params)
|
||||||
|
|> Paginator.limit(params)
|
||||||
|
|
||||||
|
items =
|
||||||
|
query
|
||||||
|
|> Repo.all()
|
||||||
|
|> Enum.map(&Item.to_fervor/1)
|
||||||
|
|
||||||
|
json(conn, items)
|
||||||
|
end
|
||||||
|
|
||||||
|
def specific_item(conn, _params) do
|
||||||
|
item = conn.assigns[:item]
|
||||||
|
|
||||||
|
json(conn, Item.to_fervor(item))
|
||||||
|
end
|
||||||
|
|
||||||
|
def mark_item(conn, changes) do
|
||||||
|
item = conn.assigns[:item] |> Repo.preload(:feed)
|
||||||
|
|
||||||
|
changeset = Item.changeset(item, changes)
|
||||||
|
|
||||||
|
{:ok, item} = Repo.update(changeset)
|
||||||
|
|
||||||
|
json(conn, Item.to_fervor(item))
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_specific_item(conn, _params) do
|
||||||
|
mark_item(conn, %{read: true})
|
||||||
|
end
|
||||||
|
|
||||||
|
def unread_specific_item(conn, _params) do
|
||||||
|
mark_item(conn, %{read: false})
|
||||||
|
end
|
||||||
|
|
||||||
|
def mark_multiple_items(conn, %{"ids" => ids}, changes) do
|
||||||
|
user = conn.assigns[:user] |> Repo.preload(groups: [:feeds])
|
||||||
|
feeds = Enum.flat_map(user.groups, fn g -> g.feeds end)
|
||||||
|
|
||||||
|
read_ids =
|
||||||
|
ids
|
||||||
|
|> String.split(",")
|
||||||
|
|> Enum.map(fn s ->
|
||||||
|
{id, _} =
|
||||||
|
s
|
||||||
|
|> String.trim()
|
||||||
|
|> Integer.parse()
|
||||||
|
|
||||||
|
Repo.get(Item, id)
|
||||||
|
end)
|
||||||
|
|> Enum.filter(fn item ->
|
||||||
|
Enum.any?(feeds, fn f -> f.id == item.feed_id end)
|
||||||
|
end)
|
||||||
|
|> Enum.map(fn item ->
|
||||||
|
item = Repo.preload(item, :feed)
|
||||||
|
changeset = Item.changeset(item, changes)
|
||||||
|
|
||||||
|
case Repo.update(changeset) do
|
||||||
|
{:ok, item} -> item.id
|
||||||
|
_ -> nil
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|> Enum.reject(&is_nil/1)
|
||||||
|
|
||||||
|
json(conn, read_ids)
|
||||||
|
end
|
||||||
|
|
||||||
|
def mark_multiple_items(conn, _params, _changes) do
|
||||||
|
conn
|
||||||
|
|> put_status(400)
|
||||||
|
|> json(%{error: "No items provided."})
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_multiple(conn, params) do
|
||||||
|
mark_multiple_items(conn, params, %{read: true})
|
||||||
|
end
|
||||||
|
|
||||||
|
def unread_multiple(conn, params) do
|
||||||
|
mark_multiple_items(conn, params, %{read: false})
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,37 @@
|
||||||
|
defmodule FrenzyWeb.Fervor.MiscController do
|
||||||
|
use FrenzyWeb, :controller
|
||||||
|
alias Frenzy.{Repo, FervorClient}
|
||||||
|
|
||||||
|
plug Plug.Parsers, parsers: [:urlencoded, :multipart]
|
||||||
|
|
||||||
|
def register(conn, _params) do
|
||||||
|
%{"client_name" => client_name, "redirect_uri" => redirect_uri} = conn.body_params
|
||||||
|
website = Map.get(conn.body_params, "website")
|
||||||
|
|
||||||
|
changeset =
|
||||||
|
FervorClient.changeset(
|
||||||
|
%FervorClient{},
|
||||||
|
%{
|
||||||
|
"client_name" => client_name,
|
||||||
|
"website" => website,
|
||||||
|
"redirect_uri" => redirect_uri,
|
||||||
|
"client_id" => :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false),
|
||||||
|
"client_secret" => :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
{:ok, client} = Repo.insert(changeset)
|
||||||
|
|
||||||
|
json(conn, FervorClient.to_fervor(client))
|
||||||
|
end
|
||||||
|
|
||||||
|
def instance(conn, _params) do
|
||||||
|
json(conn, %{
|
||||||
|
name: "Frenzy",
|
||||||
|
url: Application.get_env(:frenzy, :base_url),
|
||||||
|
version: "0.1.0",
|
||||||
|
implementation_name: "Frenzy",
|
||||||
|
implementation_version: "0.1.0"
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,191 @@
|
||||||
|
defmodule FrenzyWeb.Fervor.OauthController do
|
||||||
|
use FrenzyWeb, :controller
|
||||||
|
alias Frenzy.{Repo, FervorClient, User, ApprovedClient}
|
||||||
|
alias FrenzyWeb.Router.Helpers, as: Routes
|
||||||
|
alias FrenzyWeb.Endpoint
|
||||||
|
|
||||||
|
def authorize_get(conn, params) do
|
||||||
|
user_token = get_session(conn, :user_token)
|
||||||
|
|
||||||
|
case Phoenix.Token.verify(Endpoint, "user token", user_token, max_age: 24 * 60 * 60) do
|
||||||
|
{:error, _reason} ->
|
||||||
|
continue = "#{conn.request_path}?#{conn.query_string}"
|
||||||
|
redirect(conn, to: Routes.login_path(Endpoint, :login, continue: continue))
|
||||||
|
|
||||||
|
{:ok, user_id} ->
|
||||||
|
case Repo.get(User, user_id) do
|
||||||
|
nil ->
|
||||||
|
continue = "#{conn.request_path}?#{conn.query_string}"
|
||||||
|
redirect(conn, to: Routes.login_path(Endpoint, :login, continue: continue))
|
||||||
|
|
||||||
|
user ->
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> try_render_authorize(params)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def try_render_authorize(
|
||||||
|
conn,
|
||||||
|
%{
|
||||||
|
"response_type" => "code",
|
||||||
|
"client_id" => client_id,
|
||||||
|
"redirect_uri" => redirect_uri
|
||||||
|
} = params
|
||||||
|
) do
|
||||||
|
case Repo.get_by(FervorClient, client_id: client_id) do
|
||||||
|
nil ->
|
||||||
|
conn
|
||||||
|
|
||||||
|
client ->
|
||||||
|
if redirect_uri == client.redirect_uri do
|
||||||
|
render(conn, "authorize.html", %{
|
||||||
|
client: client,
|
||||||
|
state: Map.get(params, "state")
|
||||||
|
})
|
||||||
|
else
|
||||||
|
conn
|
||||||
|
|> put_status(400)
|
||||||
|
|> json(%{error: "mismatched redirect uri"})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def try_render_authorize(conn, _params) do
|
||||||
|
conn
|
||||||
|
|> put_status(400)
|
||||||
|
|> json(%{error: "invalid parameters"})
|
||||||
|
end
|
||||||
|
|
||||||
|
def authorize_post(conn, params) do
|
||||||
|
user_token = get_session(conn, :user_token)
|
||||||
|
|
||||||
|
case Phoenix.Token.verify(Endpoint, "user token", user_token, max_age: 24 * 60 * 60) do
|
||||||
|
{:error, _reason} ->
|
||||||
|
continue = "#{conn.request_path}?#{conn.query_string}"
|
||||||
|
redirect(conn, to: Routes.login_path(Endpoint, :login, continue: continue))
|
||||||
|
|
||||||
|
{:ok, user_id} ->
|
||||||
|
case Repo.get(User, user_id) do
|
||||||
|
nil ->
|
||||||
|
continue = "#{conn.request_path}?#{conn.query_string}"
|
||||||
|
redirect(conn, to: Routes.login_path(Endpoint, :login, continue: continue))
|
||||||
|
|
||||||
|
user ->
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> try_authorize(params)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def try_authorize(conn, %{"client_id" => client_id} = params) do
|
||||||
|
user = conn.assigns[:user]
|
||||||
|
client = Repo.get_by(FervorClient, client_id: client_id)
|
||||||
|
|
||||||
|
state = Map.get(params, "state")
|
||||||
|
|
||||||
|
auth_code = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false)
|
||||||
|
|
||||||
|
changeset =
|
||||||
|
Ecto.build_assoc(user, :approved_clients, %{
|
||||||
|
auth_code: auth_code,
|
||||||
|
client_id: client_id
|
||||||
|
})
|
||||||
|
|
||||||
|
{:ok, _approved_client} = Repo.insert(changeset)
|
||||||
|
|
||||||
|
case client.redirect_uri do
|
||||||
|
"urn:ietf:wg:oauth:2.0:oob" ->
|
||||||
|
render(conn, "successfully_authorized.html", %{
|
||||||
|
auth_code: auth_code
|
||||||
|
})
|
||||||
|
|
||||||
|
redirect_uri ->
|
||||||
|
parsed = URI.parse(redirect_uri)
|
||||||
|
|
||||||
|
query =
|
||||||
|
URI.encode_query(
|
||||||
|
if state, do: %{code: auth_code, state: state}, else: %{code: auth_code}
|
||||||
|
)
|
||||||
|
|
||||||
|
uri = %URI{parsed | query: query}
|
||||||
|
redirect(conn, to: uri)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def try_authorize(conn, _params) do
|
||||||
|
conn
|
||||||
|
|> put_status(400)
|
||||||
|
|> json(%{error: "invalid parameters"})
|
||||||
|
end
|
||||||
|
|
||||||
|
def token(
|
||||||
|
conn,
|
||||||
|
%{
|
||||||
|
"redirect_uri" => redirect_uri,
|
||||||
|
"client_id" => client_id,
|
||||||
|
"client_secret" => client_secret
|
||||||
|
} = params
|
||||||
|
) do
|
||||||
|
case Repo.get_by(FervorClient, client_id: client_id) do
|
||||||
|
nil ->
|
||||||
|
conn
|
||||||
|
|> put_status(401)
|
||||||
|
|> json(%{error: "invalid_client", error_description: "incorrect client information"})
|
||||||
|
|
||||||
|
client ->
|
||||||
|
if client_secret == client.client_secret and redirect_uri == client.redirect_uri do
|
||||||
|
conn
|
||||||
|
|> assign(:client, client)
|
||||||
|
|> try_generate_token(params)
|
||||||
|
else
|
||||||
|
conn
|
||||||
|
|> put_status(400)
|
||||||
|
|> json(%{error: "invalid_grant", error_description: "incorrect client information"})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def token(conn, _params) do
|
||||||
|
json(conn, %{error: "invalid_request", error_description: "missing parameters"})
|
||||||
|
end
|
||||||
|
|
||||||
|
def try_generate_token(conn, %{
|
||||||
|
"grant_type" => "authorization_code",
|
||||||
|
"authorization_code" => auth_code
|
||||||
|
}) do
|
||||||
|
case Repo.get_by(ApprovedClient, auth_code: auth_code) do
|
||||||
|
nil ->
|
||||||
|
conn
|
||||||
|
|> put_status(400)
|
||||||
|
|> json(%{error: "invalid_grant", error_description: "invalid authorization code"})
|
||||||
|
|
||||||
|
approved_client ->
|
||||||
|
access_token = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false)
|
||||||
|
|
||||||
|
changeset =
|
||||||
|
ApprovedClient.changeset(approved_client, %{
|
||||||
|
auth_code: nil,
|
||||||
|
access_token: access_token
|
||||||
|
})
|
||||||
|
|
||||||
|
{:ok, _approved_client} = Repo.update(changeset)
|
||||||
|
|
||||||
|
json(conn, %{
|
||||||
|
access_token: access_token,
|
||||||
|
token_type: "bearer"
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def try_generate_token(conn, _params) do
|
||||||
|
conn
|
||||||
|
|> put_status(400)
|
||||||
|
|> json(%{
|
||||||
|
error: "unsupported_grant_type",
|
||||||
|
error_description: "only grant_type=authorization_code is supported"
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,27 @@
|
||||||
|
defmodule FrenzyWeb.Fervor.Paginator do
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
def paginate(query, %{"max_id" => max_id} = params) do
|
||||||
|
limit = Map.get(params, "limit", 20)
|
||||||
|
from(o in query, where: o.id < ^max_id, order_by: [desc: :id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def paginate(query, %{"min_id" => min_id} = params) do
|
||||||
|
limit = Map.get(params, "limit", 20)
|
||||||
|
from(o in query, where: o.id > ^min_id, order_by: [asc: :id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def paginate(query, %{"since_id" => since_id} = params) do
|
||||||
|
limit = Map.get(params, "limit", 20)
|
||||||
|
from(o in query, where: o.id > ^since_id, order_by: [desc: :id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def paginate(query, _params) do
|
||||||
|
from(query, order_by: [desc: :id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def limit(query, params) do
|
||||||
|
limit = Map.get(params, "limit", 20)
|
||||||
|
from(query, limit: ^limit)
|
||||||
|
end
|
||||||
|
end
|
|
@ -5,27 +5,34 @@ defmodule FrenzyWeb.LoginController do
|
||||||
alias FrenzyWeb.Endpoint
|
alias FrenzyWeb.Endpoint
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
def login(conn, _params) do
|
def login(conn, params) do
|
||||||
render(conn, "login.html")
|
render(conn, "login.html", %{
|
||||||
|
continue: Map.get(params, "continue")
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
@error_message "Invalid username or password"
|
def login_post(conn, %{"username" => username, "password" => password} = params) do
|
||||||
|
|
||||||
def login_post(conn, %{"username" => username, "password" => password}) do
|
|
||||||
user = Repo.get_by(User, username: username)
|
user = Repo.get_by(User, username: username)
|
||||||
|
|
||||||
case Bcrypt.check_pass(user, password) do
|
case Bcrypt.check_pass(user, password) do
|
||||||
{:ok, user} ->
|
{:ok, user} ->
|
||||||
user_token = Phoenix.Token.sign(Endpoint, "user token", user.id)
|
user_token = Phoenix.Token.sign(Endpoint, "user token", user.id)
|
||||||
|
conn = put_session(conn, :user_token, user_token)
|
||||||
|
|
||||||
conn
|
redirect_uri = Map.get(params, "continue") || Routes.group_path(Endpoint, :index)
|
||||||
|> put_session(:user_token, user_token)
|
redirect(conn, to: redirect_uri)
|
||||||
|> redirect(to: Routes.group_path(Endpoint, :index))
|
|
||||||
|
|
||||||
{:error, _reason} ->
|
{:error, _reason} ->
|
||||||
conn
|
conn
|
||||||
|> put_flash(:error, @error_message)
|
|> put_flash(:error, "Invalid username or password.")
|
||||||
|> redirect(to: Routes.login_path(Endpoint, :login))
|
|> redirect(to: Routes.login_path(Endpoint, :login))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def logout(conn, params) do
|
||||||
|
conn
|
||||||
|
|> put_flash(:info, "Logged out.")
|
||||||
|
|> clear_session()
|
||||||
|
|> redirect(to: "/")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
defmodule FrenzyWeb.PageController do
|
|
||||||
use FrenzyWeb, :controller
|
|
||||||
|
|
||||||
def index(conn, _params) do
|
|
||||||
render(conn, "index.html")
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
defmodule FrenzyWeb.Plug.FervorAuthenticate do
|
||||||
|
import Plug.Conn
|
||||||
|
alias Frenzy.{Repo, ApprovedClient, User}
|
||||||
|
alias FrenzyWeb.Router.Helpers, as: Routes
|
||||||
|
alias FrenzyWeb.Endpoint
|
||||||
|
|
||||||
|
def init(opts), do: opts
|
||||||
|
|
||||||
|
def call(conn, _opts) do
|
||||||
|
case get_req_header(conn, "authorization") do
|
||||||
|
[authorization | _] ->
|
||||||
|
case authorization do
|
||||||
|
"Bearer " <> access_token ->
|
||||||
|
case Repo.get_by(ApprovedClient, access_token: access_token) do
|
||||||
|
nil ->
|
||||||
|
conn
|
||||||
|
|> put_status(401)
|
||||||
|
|> Phoenix.Controller.json(%{
|
||||||
|
error: "Invalid authorization",
|
||||||
|
error_description: "The provided access token is not valid."
|
||||||
|
})
|
||||||
|
|> halt()
|
||||||
|
|
||||||
|
approved_client ->
|
||||||
|
assign(conn, :user, Repo.get(User, approved_client.user_id))
|
||||||
|
end
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
conn
|
||||||
|
|> put_status(401)
|
||||||
|
|> Phoenix.Controller.json(%{
|
||||||
|
error: "Invalid authorization",
|
||||||
|
error_description:
|
||||||
|
"The provided Authorization header does notmatc the expected format."
|
||||||
|
})
|
||||||
|
|> halt()
|
||||||
|
end
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
conn
|
||||||
|
|> put_status(401)
|
||||||
|
|> Phoenix.Controller.json(%{
|
||||||
|
error: "Missing authorization",
|
||||||
|
error_description: "No Authorization header was provided."
|
||||||
|
})
|
||||||
|
|> halt()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -9,12 +9,7 @@ defmodule FrenzyWeb.Router do
|
||||||
plug :put_secure_browser_headers
|
plug :put_secure_browser_headers
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline :authenticate do
|
pipeline :browser_authenticate do
|
||||||
# plug :accepts, ["html"]
|
|
||||||
# plug :fetch_session
|
|
||||||
# plug :fetch_flash
|
|
||||||
# plug :protect_from_forgery
|
|
||||||
# plug :put_secure_browser_headers
|
|
||||||
plug FrenzyWeb.Plug.Authenticate
|
plug FrenzyWeb.Plug.Authenticate
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -22,16 +17,32 @@ defmodule FrenzyWeb.Router do
|
||||||
plug :accepts, ["json"]
|
plug :accepts, ["json"]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
pipeline :fervor_authenticate do
|
||||||
|
plug FrenzyWeb.Plug.FervorAuthenticate
|
||||||
|
end
|
||||||
|
|
||||||
scope "/", FrenzyWeb do
|
scope "/", FrenzyWeb do
|
||||||
pipe_through :browser
|
pipe_through :browser
|
||||||
|
|
||||||
get "/login", LoginController, :login
|
get "/login", LoginController, :login
|
||||||
post "/login", LoginController, :login_post
|
post "/login", LoginController, :login_post
|
||||||
|
|
||||||
|
get "/logout", LoginController, :logout
|
||||||
|
|
||||||
|
get "/oauth/authorize", Fervor.OauthController, :authorize_get
|
||||||
|
post "/oauth/authorize", Fervor.OauthController, :authorize_post
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/", FrenzyWeb do
|
scope "/", FrenzyWeb do
|
||||||
pipe_through :browser
|
pipe_through :browser
|
||||||
pipe_through :authenticate
|
pipe_through :browser_authenticate
|
||||||
|
|
||||||
|
get "/account", AccountController, :show
|
||||||
|
get "/account/change_password", AccountController, :change_password
|
||||||
|
post "/account/change_password", AccountController, :do_change_password
|
||||||
|
get "/account/change_fever_password", AccountController, :change_fever_password
|
||||||
|
post "/account/change_fever_password", AccountController, :do_change_fever_password
|
||||||
|
post "/account/remove_client", AccountController, :remove_client
|
||||||
|
|
||||||
get "/", GroupController, :index
|
get "/", GroupController, :index
|
||||||
resources "/groups", GroupController, except: [:edit, :update]
|
resources "/groups", GroupController, except: [:edit, :update]
|
||||||
|
@ -59,8 +70,38 @@ defmodule FrenzyWeb.Router do
|
||||||
post "/api/fever.php", FeverController, :post
|
post "/api/fever.php", FeverController, :post
|
||||||
end
|
end
|
||||||
|
|
||||||
# Other scopes may use custom stacks.
|
scope "/", FrenzyWeb.Fervor do
|
||||||
# scope "/api", FrenzyWeb do
|
pipe_through :api
|
||||||
# pipe_through :api
|
|
||||||
# end
|
post "/api/v1/register", MiscController, :register
|
||||||
|
|
||||||
|
post "/oauth/token", OauthController, :token
|
||||||
|
|
||||||
|
get "/api/v1/instance", MiscController, :instance
|
||||||
|
end
|
||||||
|
|
||||||
|
scope "/", FrenzyWeb.Fervor do
|
||||||
|
pipe_through :api
|
||||||
|
pipe_through :fervor_authenticate
|
||||||
|
|
||||||
|
get "/api/v1/groups", GroupsController, :groups_list
|
||||||
|
get "/api/v1/groups/:id", GroupsController, :specific_group
|
||||||
|
get "/api/v1/groups/:id/feeds", GroupsController, :specific_group_feeds
|
||||||
|
get "/api/v1/groups/:id/items", GroupsController, :specific_group_items
|
||||||
|
post "/api/v1/groups/create", GroupsController, :create
|
||||||
|
post "/api/v1/groups/:id/delete", GroupsController, :delete
|
||||||
|
|
||||||
|
get "/api/v1/feeds", FeedsController, :feeds_list
|
||||||
|
get "/api/v1/feeds/:id", FeedsController, :specific_feed
|
||||||
|
get "/api/v1/feeds/:id/items", FeedsController, :specific_feed_items
|
||||||
|
post "/api/v1/feeds/create", FeedsController, :create
|
||||||
|
post "/api/v1/feeds/:id/delete", FeedsController, :delete
|
||||||
|
|
||||||
|
get "/api/v1/items", ItemsController, :items_list
|
||||||
|
get "/api/v1/items/:id", ItemsController, :specific_item
|
||||||
|
post "/api/v1/items/:id/read", ItemsController, :read_specific_item
|
||||||
|
post "/api/v1/items/:id/unread", ItemsController, :unread_specific_item
|
||||||
|
post "/api/v1/items/read", ItemsController, :read_multiple
|
||||||
|
post "/api/v1/items/unread", ItemsController, :unread_multiple
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
<h2>Change Fever Password</h2>
|
||||||
|
|
||||||
|
<%= form_tag Routes.account_path(@conn, :do_change_fever_password), method: :post do %>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label for="new_password" class="col-sm-2 col-form-label">New Fever Password</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input type="password" name="new_password" id="new_password" minlength="8" class="form-control">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<%= submit "Change Fever Password", class: "btn btn-primary" %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
|
@ -0,0 +1,27 @@
|
||||||
|
<h2>Change Password</h2>
|
||||||
|
|
||||||
|
<%= form_tag Routes.account_path(@conn, :do_change_password), method: :post do %>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label for="old_password" class="col-sm-2 col-form-label">Old Password</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input type="password" name="old_password" id="old_password" minlength="8" class="form-control">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label for="new_password" class="col-sm-2 col-form-label">New Password</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input type="password" name="new_password" id="new_password" minlength="8" class="form-control">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label for="confirm_new_password" class="col-sm-2 col-form-label">Confirm New Password</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input type="password" name="confirm_new_password" id="confirm_new_password" minlength="8" class="form-control">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<%= submit "Change Password", class: "btn btn-primary" %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
|
@ -0,0 +1,36 @@
|
||||||
|
<h1>User Settings</h1>
|
||||||
|
<h2><pre><%= @user.username %></pre></h2>
|
||||||
|
|
||||||
|
<a href="<%= Routes.account_path(@conn, :change_password) %>" class="btn btn-secondary">Change Password</a>
|
||||||
|
|
||||||
|
<a href="<%= Routes.account_path(@conn, :change_fever_password) %>" class="btn btn-secondary">Change Fever Password</a>
|
||||||
|
|
||||||
|
<h2 class="mt-4">Approved Clients</h2>
|
||||||
|
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Client</th>
|
||||||
|
<th>Revoke Access</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<%= for {approved, fervor} <- @clients do %>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<%= if fervor.website do %>
|
||||||
|
<a href="<%= fervor.website %>"><%= fervor.client_name %></a>
|
||||||
|
<% else %>
|
||||||
|
<%= fervor.client_name %>
|
||||||
|
<% end %>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<%= form_tag Routes.account_path(@conn, :remove_client), method: :post do %>
|
||||||
|
<input type="hidden" name="client_id" value="<%= approved.client_id %>">
|
||||||
|
<%= submit "Revoke", class: "btn btn-danger" %>
|
||||||
|
<% end %>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
|
@ -1,37 +1,42 @@
|
||||||
<%= form_tag Routes.feed_path(@conn, :refresh, @feed.id), method: :post do %>
|
<h2><%= @feed.title %></h2>
|
||||||
<%= submit "Refresh Feed" %>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
|
<%= form_tag Routes.feed_path(@conn, :refresh, @feed.id), method: :post, class: "d-inline" do %>
|
||||||
|
<%= submit "Refresh Feed", class: "btn btn-primary" %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<%= if @feed.filter_enabled do %>
|
<%= if @feed.filter_enabled do %>
|
||||||
<%= form_tag Routes.feed_path(@conn, :disable_filter, @feed.id), method: :post do %>
|
<%= form_tag Routes.feed_path(@conn, :disable_filter, @feed.id), method: :post, class: "d-inline" do %>
|
||||||
<%= submit "Disable Filter" %>
|
<%= submit "Disable Filter", class: "btn btn-secondary" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= form_tag Routes.filter_path(@conn, :edit, @feed.filter.id), method: :get do %>
|
<%= form_tag Routes.filter_path(@conn, :edit, @feed.filter.id), method: :get, class: "d-inline" do %>
|
||||||
<%= submit "Edit Filter" %>
|
<%= submit "Edit Filter", class: "btn btn-secondary" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= form_tag Routes.feed_path(@conn, :enable_filter, @feed.id), method: :post do %>
|
<%= form_tag Routes.feed_path(@conn, :enable_filter, @feed.id), method: :post, class: "d-inline" do %>
|
||||||
<%= submit "Enable Filter" %>
|
<%= submit "Enable Filter", class: "btn btn-secondary" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= form_tag Routes.feed_path(@conn, :delete, @feed.id), method: :delete do %>
|
<%= form_tag Routes.feed_path(@conn, :delete, @feed.id), method: :delete, class: "d-inline" do %>
|
||||||
<%= submit "Delete Feed" %>
|
<%= submit "Delete Feed", class: "btn btn-danger" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
|
||||||
<table>
|
<h3 class="mt-4">Items</h3>
|
||||||
<%= for item <- @items do %>
|
|
||||||
<tr <%= if item.read do %>class="item-read"<% end %>>
|
<table class="table table-striped">
|
||||||
<td>
|
<tbody>
|
||||||
<a href="<%= Routes.item_path(@conn, :show, item.id) %>"><%= item.title %></a>
|
<%= for item <- @items do %>
|
||||||
</td>
|
<tr <%= if item.read do %>class="item-read"<% end %>>
|
||||||
<td>
|
<td>
|
||||||
<% {:ok, date} = Timex.format(item.date, "{YYYY}-{M}-{D} {h12}:{m} {AM}") %>
|
<a href="<%= Routes.item_path(@conn, :show, item.id) %>"><%= item.title %></a>
|
||||||
<%= date %>
|
</td>
|
||||||
</td>
|
<td>
|
||||||
</tr>
|
<% {:ok, date} = Timex.format(item.date, "{YYYY}-{M}-{D} {h12}:{m} {AM}") %>
|
||||||
<% end %>
|
<%= date %>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
</tbody>
|
||||||
</table>
|
</table>
|
|
@ -0,0 +1,12 @@
|
||||||
|
<h2><%= @client.client_name %> wants to access your account</h2>
|
||||||
|
|
||||||
|
<%= form_tag Routes.oauth_path(@conn, :authorize_post), method: :post do %>
|
||||||
|
<input type="hidden" name="client_id" value="<%= @client.client_id %>">
|
||||||
|
<%= if @state do %>
|
||||||
|
<input type="hidden" name="state" value="<%= @state %>">
|
||||||
|
<% end %>
|
||||||
|
<div class="form-group">
|
||||||
|
<%= submit "Grant access", class: "btn btn-primary" %>
|
||||||
|
<p>To reject the request, close this page.</p>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
|
@ -0,0 +1,3 @@
|
||||||
|
<h1>Authorization Successful</h1>
|
||||||
|
<p>Authorization Code:</p>
|
||||||
|
<pre><%= @auth_code %></pre>
|
|
@ -1,53 +1,75 @@
|
||||||
<h2>Filter</h2>
|
<h2>Filter</h2>
|
||||||
<%= form_for @changeset, Routes.filter_path(@conn, :update, @changeset.data.id), fn form -> %>
|
<%= form_for @changeset, Routes.filter_path(@conn, :update, @changeset.data.id), fn form -> %>
|
||||||
<div class="form-group">
|
<div class="form-group row">
|
||||||
<label for="mode">Mode</label>
|
<label for="mode" class="col-sm-2 col-form-label">Mode</label>
|
||||||
<%= select form, :mode, [{"Accept", :accept}, {"Reject", :reject}] %>
|
<div class="col-sm-10">
|
||||||
|
<%= select form, :mode, [{"Accept", :accept}, {"Reject", :reject}], class: "form-control" %>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group row">
|
||||||
<label for="score">Score</label>
|
<label for="score" class="col-sm-2 col-form-label">Score</label>
|
||||||
<%= number_input form, :score %>
|
<div class="col-sm-10">
|
||||||
|
<%= number_input form, :score, class: "form-control" %>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table>
|
<div class="card mt-5 mb-5">
|
||||||
<tr>
|
<div class="card-body">
|
||||||
<th>Property</th>
|
<h3>Rules</h3>
|
||||||
<th>Mode</th>
|
|
||||||
<th>Param</th>
|
<table class="table">
|
||||||
<th>Weight</th>
|
<thead>
|
||||||
</tr>
|
<tr>
|
||||||
<%= inputs_for form, :rules, fn p -> %>
|
<th>Property</th>
|
||||||
<tr>
|
<th>Mode</th>
|
||||||
<td>
|
<th>Param</th>
|
||||||
<%= select p, :property, [{"URL", :url}, {"Title", :title}, {"Author", :author}, {"Content", :content}] %>
|
<th>Weight</th>
|
||||||
</td>
|
<th></th>
|
||||||
<td>
|
</tr>
|
||||||
<%= select p, :mode, [{"contains", :contains_string}, {"matches regex", :matches_regex}] %>
|
</thead>
|
||||||
</td>
|
<tbody>
|
||||||
<td>
|
<%= inputs_for form, :rules, fn p -> %>
|
||||||
<%= text_input p, :param %>
|
<tr>
|
||||||
</td>
|
<td>
|
||||||
<td>
|
<%= select p, :property, [{"URL", :url}, {"Title", :title}, {"Author", :author}, {"Content", :content}], class: "form-control" %>
|
||||||
<%= number_input p, :weight %>
|
</td>
|
||||||
</td>
|
<td>
|
||||||
<td>
|
<%= select p, :mode, [{"contains", :contains_string}, {"matches regex", :matches_regex}], class: "form-control" %>
|
||||||
<%# when nesting form tags, the first nested one seems to get removed %>
|
</td>
|
||||||
<%# not the first nested one in each row, but the first nested one overall %>
|
<td>
|
||||||
<%# so we include a dummy form with display: none that gets stripped from the first row %>
|
<%= text_input p, :param, class: "form-control" %>
|
||||||
<%# but is present and invisible in subsequent rows %>
|
</td>
|
||||||
<form style="display: none;"></form>
|
<td>
|
||||||
<%= form_tag Routes.filter_path(@conn, :remove_rule, @changeset.data.id, [rule_id: p.data.id]), method: :post do %>
|
<%= number_input p, :weight, class: "form-control" %>
|
||||||
<%= submit "Delete" %>
|
</td>
|
||||||
|
<td>
|
||||||
|
<%# when nesting form tags, the first nested one seems to get removed %>
|
||||||
|
<%# not the first nested one in each row, but the first nested one overall %>
|
||||||
|
<%# so we include a dummy form with display: none that gets stripped from the first row %>
|
||||||
|
<%# but is present and invisible in subsequent rows %>
|
||||||
|
<form style="display: none;"></form>
|
||||||
|
<%= form_tag Routes.filter_path(@conn, :remove_rule, @changeset.data.id, [rule_id: p.data.id]), method: :post do %>
|
||||||
|
<%= submit "Delete", class: "btn btn-danger" %>
|
||||||
|
<% end %>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<% end %>
|
<% end %>
|
||||||
</td>
|
</tbody>
|
||||||
</tr>
|
</table>
|
||||||
<% end %>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group row">
|
||||||
<%= submit "Update Filter" %>
|
<div class="col-sm-10">
|
||||||
|
<%= form_tag Routes.filter_path(@conn, :add_rule, @changeset.data.id), method: :post do %>
|
||||||
|
<%= submit "Add Rule", class: "btn btn-secondary" %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row">
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<%= submit "Update Filter", class: "btn btn-primary" %>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
|
||||||
<%= form_tag Routes.filter_path(@conn, :add_rule, @changeset.data.id), method: :post do %>
|
|
||||||
<%= submit "Add Rule" %>
|
|
||||||
<% end %>
|
<% end %>
|
|
@ -1,17 +1,25 @@
|
||||||
<a href="<%= Routes.group_path(@conn, :new) %>" class="button">Add Group</a>
|
<a href="<%= Routes.group_path(@conn, :new) %>" class="btn btn-primary">Add Group</a>
|
||||||
|
|
||||||
<table>
|
<table class="table table-striped mt-4">
|
||||||
<%= for group <- @groups do %>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<th>Name</th>
|
||||||
<a href="/groups/<%= group.id %>"><%= group.title %></a>
|
<th></th>
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<%= case Enum.count(group.feeds) do
|
|
||||||
1 -> "1 feed"
|
|
||||||
count -> "#{count} feeds"
|
|
||||||
end %>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<% end %>
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<%= for group <- @groups do %>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="/groups/<%= group.id %>"><%= group.title %></a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<%= case Enum.count(group.feeds) do
|
||||||
|
1 -> "1 feed"
|
||||||
|
count -> "#{count} feeds"
|
||||||
|
end %>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
</tbody>
|
||||||
</table>
|
</table>
|
|
@ -1,9 +1,13 @@
|
||||||
<%= form_for @changeset, Routes.group_path(@conn, :create), fn form -> %>
|
<%= form_for @changeset, Routes.group_path(@conn, :create), fn form -> %>
|
||||||
<div class="form-group">
|
<div class="form-group row">
|
||||||
<label for="title">Title</label>
|
<label for="title" class="col-sm-2 col-form-label">Title</label>
|
||||||
<%= text_input form, :title %>
|
<div class="col-sm-10">
|
||||||
|
<%= text_input form, :title, placeholder: "My New Group", class: "form-control" %>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group row">
|
||||||
<%= submit "Create Group" %>
|
<div class="col-sm-10">
|
||||||
|
<%= submit "Create Group", class: "btn btn-primary" %>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
|
@ -1,29 +1,37 @@
|
||||||
<h2><%= @group.title %></h2>
|
<h2><%= @group.title %></h2>
|
||||||
|
|
||||||
<%= form_tag Routes.group_path(@conn, :delete, @group.id), method: :delete do %>
|
|
||||||
<%= submit "Delete Group" %>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<%= form_for @create_feed_changeset, Routes.feed_path(@conn, :create), fn form -> %>
|
<%= form_for @create_feed_changeset, Routes.feed_path(@conn, :create), fn form -> %>
|
||||||
<%= hidden_input form, :group_id %>
|
<%= hidden_input form, :group_id %>
|
||||||
<div class="form-group">
|
<div class="form-group row">
|
||||||
<%= label form, :feed_url, "Feed URL" %>
|
<%= label form, :feed_url, "Feed URL", class: "col-sm-2 col-form-label" %>
|
||||||
<%= text_input form, :feed_url %>
|
<div class="col-sm-10">
|
||||||
|
<%= text_input form, :feed_url, placeholder: "https://example.com/feed.xml", class: "form-control" %>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group row">
|
||||||
<%= submit "Add Feed" %>
|
<div class="col-sm-10">
|
||||||
|
<%= submit "Add Feed", class: "btn btn-primary" %>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<table>
|
<%= form_tag Routes.group_path(@conn, :delete, @group.id), method: :delete do %>
|
||||||
<%= for feed <- @group.feeds do %>
|
<%= submit "Delete Group", class: "btn btn-danger" %>
|
||||||
<tr>
|
<% end %>
|
||||||
<td>
|
|
||||||
<a href="<%= Routes.feed_path(@conn, :show, feed.id) %>"><%= feed.feed_url %></a>
|
<h3 class="mt-4">Feeds</h3>
|
||||||
</td>
|
|
||||||
<td>
|
<table class="table table-striped">
|
||||||
<a href="<%= feed.site_url %>"><%= feed.title %></a>
|
<tbody>
|
||||||
</td>
|
<%= for feed <- @group.feeds do %>
|
||||||
</tr>
|
<tr>
|
||||||
<% end %>
|
<td>
|
||||||
|
<a href="<%= Routes.feed_path(@conn, :show, feed.id) %>"><%= feed.feed_url %></a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="<%= feed.site_url %>"><%= feed.title %></a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
</tbody>
|
||||||
</table>
|
</table>
|
|
@ -1,17 +1,17 @@
|
||||||
<%= if @item.read do %>
|
|
||||||
<%= form_tag Routes.item_path(@conn, :unread, @item.id), method: :post do %>
|
|
||||||
<%= submit "Mark as Unread" %>
|
|
||||||
<% end %>
|
|
||||||
<% else %>
|
|
||||||
<%= form_tag Routes.item_path(@conn, :read, @item.id), method: :post do %>
|
|
||||||
<%= submit "Mark as Read" %>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<a href="<%= Routes.feed_path(@conn, :show, @feed.id) %>"><%= @feed.title %></a>
|
|
||||||
<h1>
|
<h1>
|
||||||
<a href="<%= @item.url %>"><%= @item.title %></a>
|
<a href="<%= @item.url %>"><%= @item.title %></a>
|
||||||
</h1>
|
</h1>
|
||||||
<article>
|
|
||||||
|
<%= if @item.read do %>
|
||||||
|
<%= form_tag Routes.item_path(@conn, :unread, @item.id), method: :post do %>
|
||||||
|
<%= submit "Mark as Unread", class: "btn btn-secondary" %>
|
||||||
|
<% end %>
|
||||||
|
<% else %>
|
||||||
|
<%= form_tag Routes.item_path(@conn, :read, @item.id), method: :post do %>
|
||||||
|
<%= submit "Mark as Read", class: "btn btn-secondary" %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<article class="mt-4">
|
||||||
<%= raw(@item.content) %>
|
<%= raw(@item.content) %>
|
||||||
</article>
|
</article>
|
|
@ -3,25 +3,47 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
|
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
|
||||||
<title>Frenzy · Phoenix Framework</title>
|
|
||||||
|
<title>Frenzy</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
|
||||||
<link rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/>
|
<link rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<section class="container">
|
<nav rol="navigation" class="navbar navbar-expand-lg navbar-light bg-light">
|
||||||
<nav role="navigation">
|
<div class="container">
|
||||||
<ul>
|
<a href="/" class="navbar-brand">Frenzy</a>
|
||||||
<li><a href="/">Frenzy</a></li>
|
|
||||||
</ul>
|
<div class="" id="navbarContent">
|
||||||
</nav>
|
<ul class="navbar-nav mr-auto">
|
||||||
</section>
|
</ul>
|
||||||
|
<ul class="navbar-nav flex-row ml-md-auto">
|
||||||
|
<%= unless is_nil(@conn.assigns[:user]) do %>
|
||||||
|
<li class="nav-item"><a href="<%= Routes.account_path(@conn, :show) %>" class="nav-link">Account</a></li>
|
||||||
|
<li class="nav-item"><a href="<%= Routes.login_path(@conn, :logout) %>" class="nav-link">Log Out</a></li>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
<main role="main" class="container">
|
<main role="main" class="main mt-4">
|
||||||
<p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
|
<div class="container">
|
||||||
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
|
<%= if get_flash(@conn, :info) do %>
|
||||||
<%= render @view_module, @view_template, assigns %>
|
<p class="alert alert-primary" role="alert"><%= get_flash(@conn, :info) %></p>
|
||||||
|
<% end %>
|
||||||
|
<%= if get_flash(@conn, :error) do %>
|
||||||
|
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= render @view_module, @view_template, assigns %>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
|
||||||
<script type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
|
<script type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,15 +1,22 @@
|
||||||
<% IO.inspect(get_flash(@conn, :error)) %>
|
|
||||||
|
|
||||||
<%= form_tag Routes.login_path(@conn, :login_post), method: :post do %>
|
<%= form_tag Routes.login_path(@conn, :login_post), method: :post do %>
|
||||||
<div class="form-group">
|
<%= if @continue do %>
|
||||||
<label for="username">Username</label>
|
<input type="hidden" name="continue" value="<%= @continue %>">
|
||||||
<input type="text" name="username" id="username">
|
<% end %>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label for="username" class="col-sm-2 col-form-label">Username</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input type="text" name="username" id="username" class="form-control">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group row">
|
||||||
<label for="password">Password</label>
|
<label for="password" class="col-sm-2 col-form-label">Password</label>
|
||||||
<input type="password" name="password" id="password">
|
<div class="col-sm-10">
|
||||||
|
<input type="password" name="password" id="password" class="form-control">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group row">
|
||||||
<%= submit "Log In" %>
|
<div class="col-sm-10">
|
||||||
|
<%= submit "Log In", class: "btn btn-primary" %>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
|
@ -1,35 +0,0 @@
|
||||||
<section class="phx-hero">
|
|
||||||
<h1><%= gettext "Welcome to %{name}!", name: "Phoenix" %></h1>
|
|
||||||
<p>A productive web framework that<br/>does not compromise speed and maintainability.</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="row">
|
|
||||||
<article class="column">
|
|
||||||
<h2>Resources</h2>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a href="https://hexdocs.pm/phoenix/overview.html">Guides & Docs</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://github.com/phoenixframework/phoenix">Source</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://github.com/phoenixframework/phoenix/blob/v1.4/CHANGELOG.md">v1.4 Changelog</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</article>
|
|
||||||
<article class="column">
|
|
||||||
<h2>Help</h2>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a href="https://elixirforum.com/c/phoenix-forum">Forum</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://webchat.freenode.net/?channels=elixir-lang">#elixir-lang on Freenode IRC</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://twitter.com/elixirphoenix">Twitter @elixirphoenix</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</article>
|
|
||||||
</section>
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
defmodule FrenzyWeb.AccountView do
|
||||||
|
use FrenzyWeb, :view
|
||||||
|
end
|
|
@ -0,0 +1,3 @@
|
||||||
|
defmodule FrenzyWeb.Fervor.OauthView do
|
||||||
|
use FrenzyWeb, :view
|
||||||
|
end
|
|
@ -1,3 +0,0 @@
|
||||||
defmodule FrenzyWeb.PageView do
|
|
||||||
use FrenzyWeb, :view
|
|
||||||
end
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
defmodule Frenzy.Repo.Migrations.CreateFervorClients do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create table(:fervor_clients) do
|
||||||
|
add :client_name, :string
|
||||||
|
add :website, :string
|
||||||
|
add :redirect_uri, :string
|
||||||
|
add :client_id, :string
|
||||||
|
add :client_secret, :string
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,15 @@
|
||||||
|
defmodule Frenzy.Repo.Migrations.CreateApprovedClients do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create table(:approved_clients) do
|
||||||
|
add :client_id, :string
|
||||||
|
add :auth_code, :string
|
||||||
|
add :access_token, :string
|
||||||
|
|
||||||
|
add :user_id, references(:users, on_delete: :delete_all)
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
/* This file is for your main application css. */
|
||||||
|
|
||||||
|
.item-read a {
|
||||||
|
color: #606c76;
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
|
@ -0,0 +1,3 @@
|
||||||
|
// for phoenix_html support, including form and button helpers
|
||||||
|
// copy the following scripts into your javascript bundle:
|
||||||
|
// * https://raw.githubusercontent.com/phoenixframework/phoenix_html/v2.10.0/priv/static/phoenix_html.js
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,5 @@
|
||||||
|
# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
|
||||||
|
#
|
||||||
|
# To ban all spiders from the entire site uncomment the next two lines:
|
||||||
|
User-agent: *
|
||||||
|
Disallow: /
|
Loading…
Reference in New Issue