Fervor API implementation

This commit is contained in:
Shadowfacts 2019-03-31 10:52:56 -04:00
parent 73dee19294
commit 4d4c4d3508
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
11 changed files with 513 additions and 14 deletions

View File

@ -1,6 +1,8 @@
defmodule Frenzy.Feed do
use Ecto.Schema
import Ecto.Changeset
alias FrenzyWeb.Router.Helpers, as: Routes
alias FrenzyWeb.Endpoint
def to_fever(feed) do
%{
@ -14,6 +16,19 @@ defmodule Frenzy.Feed do
}
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
field :feed_url, :string
field :last_updated, :utc_datetime

View File

@ -1,6 +1,8 @@
defmodule Frenzy.Group do
use Ecto.Schema
import Ecto.Changeset
alias FrenzyWeb.Router.Helpers, as: Routes
alias FrenzyWeb.Endpoint
def to_fever_group(group) do
%{
@ -16,6 +18,16 @@ defmodule Frenzy.Group do
}
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
field :title, :string

View File

@ -1,6 +1,8 @@
defmodule Frenzy.Item do
use Ecto.Schema
import Ecto.Changeset
alias FrenzyWeb.Router.Helpers, as: Routes
alias FrenzyWeb.Endpoint
def to_fever(item) do
%{
@ -16,6 +18,21 @@ defmodule Frenzy.Item do
}
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
field :content, :string
field :date, :utc_datetime

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,9 +1,6 @@
defmodule FrenzyWeb.FervorController do
defmodule FrenzyWeb.Fervor.MiscController do
use FrenzyWeb, :controller
alias Frenzy.{Repo, FervorClient, Group, Feed, Filter, Item}
alias FrenzyWeb.Router.Helpers, as: Routes
alias FrenzyWeb.Endpoint
import Ecto.Query
alias Frenzy.{Repo, FervorClient}
plug Plug.Parsers, parsers: [:urlencoded, :multipart]
@ -27,4 +24,14 @@ defmodule FrenzyWeb.FervorController do
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

View File

@ -1,4 +1,4 @@
defmodule FrenzyWeb.OauthController do
defmodule FrenzyWeb.Fervor.OauthController do
use FrenzyWeb, :controller
alias Frenzy.{Repo, FervorClient, User, ApprovedClient}
alias FrenzyWeb.Router.Helpers, as: Routes

View File

@ -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

View File

@ -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

View File

@ -9,7 +9,7 @@ defmodule FrenzyWeb.Router do
plug :put_secure_browser_headers
end
pipeline :authenticate do
pipeline :browser_authenticate do
plug FrenzyWeb.Plug.Authenticate
end
@ -17,16 +17,23 @@ defmodule FrenzyWeb.Router do
plug :accepts, ["json"]
end
pipeline :fervor_authenticate do
plug FrenzyWeb.Plug.FervorAuthenticate
end
scope "/", FrenzyWeb do
pipe_through :browser
get "/login", LoginController, :login
post "/login", LoginController, :login_post
get "/oauth/authorize", Fervor.OauthController, :authorize_get
post "/oauth/authorize", Fervor.OauthController, :authorize_post
end
scope "/", FrenzyWeb do
pipe_through :browser
pipe_through :authenticate
pipe_through :browser_authenticate
get "/", GroupController, :index
resources "/groups", GroupController, except: [:edit, :update]
@ -54,18 +61,38 @@ defmodule FrenzyWeb.Router do
post "/api/fever.php", FeverController, :post
end
scope "/", FrenzyWeb do
scope "/", FrenzyWeb.Fervor do
pipe_through :api
post "/api/v1/register", FervorController, :register
post "/api/v1/register", MiscController, :register
post "/oauth/token", OauthController, :token
get "/api/v1/instance", MiscController, :instance
end
scope "/", FrenzyWeb do
pipe_through :browser
scope "/", FrenzyWeb.Fervor do
pipe_through :api
pipe_through :fervor_authenticate
get "/oauth/authorize", OauthController, :authorize_get
post "/oauth/authorize", OauthController, :authorize_post
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