Compare commits

...

15 Commits

19 changed files with 329 additions and 116 deletions

View File

@ -36,6 +36,8 @@ defmodule Frenzy.Feed do
field :title, :string
field :favicon, :string
field :favicon_url, :string
field :last_refreshed_at, :utc_datetime
field :refresh_frequency, :integer, default: 30
belongs_to :group, Frenzy.Group
belongs_to :pipeline, Frenzy.Pipeline
@ -54,6 +56,8 @@ defmodule Frenzy.Feed do
title: String.t() | nil,
favicon: String.t() | nil,
favicon_url: String.t() | nil,
last_refreshed_at: DateTime.t() | nil,
refresh_frequency: integer(),
group: Frenzy.Group.t() | Ecto.Association.NotLoaded.t(),
pipeline: Frenzy.Pipeline.t() | nil | Ecto.Association.NotLoaded.t(),
items: [Frenzy.Item.t()] | Ecto.Association.NotLoaded.t(),
@ -71,8 +75,11 @@ defmodule Frenzy.Feed do
:last_updated,
:pipeline_id,
:favicon,
:favicon_url
:favicon_url,
:last_refreshed_at,
:refresh_frequency
])
|> validate_required([:feed_url])
|> validate_inclusion(:refresh_frequency, [30 * 60, 60 * 60, 6 * 60 * 60, 24 * 60 * 60])
end
end

View File

@ -2,8 +2,8 @@ defmodule Frenzy.HTTP do
require Logger
@redirect_codes [301, 302]
def get(url) do
case HTTPoison.get(url) do
def get(url, opts \\ []) do
case HTTPoison.get(url, opts) do
{:ok, %HTTPoison.Response{status_code: 200} = response} ->
{:ok, response}
@ -24,7 +24,7 @@ defmodule Frenzy.HTTP do
end
Logger.debug("Got 301 redirect from #{url} to #{new_url}")
get(new_url)
get(new_url, opts)
_ ->
{:error, "Missing Location header for redirect"}
@ -43,26 +43,4 @@ defmodule Frenzy.HTTP do
{:error, reason}
end
end
def resolve_uri(uri, site_uri) when is_binary(site_uri) do
resolve_uri(uri, URI.parse(site_uri))
end
def resolve_uri(%URI{host: nil, path: path}, %URI{} = site_uri) do
%URI{site_uri | path: path}
|> resolve_uri(site_uri)
end
def resolve_uri(%URI{scheme: nil} = uri, %URI{scheme: scheme} = site_uri) do
scheme =
case scheme do
nil -> "https"
_ -> scheme
end
%URI{uri | scheme: scheme}
|> resolve_uri(site_uri)
end
def resolve_uri(uri, _), do: uri
end

28
lib/frenzy/paginator.ex Normal file
View File

@ -0,0 +1,28 @@
defmodule Frenzy.Paginator do
import Ecto.Query
alias Frenzy.{Repo, Item}
@spec paginate(query :: Ecto.Queryable.t(), params :: map()) :: Ecto.Query.t()
def paginate(query, %{"before" => before_id}) do
before = Repo.get(Item, before_id)
query
# |> where([o], o.id < ^before_id)
|> where([i], i.date < ^before.date)
|> order_by(desc: :date)
end
def paginate(query, %{"after" => after_id}) do
after_item = Repo.get(Item, after_id)
query
|> where([i], i.date > ^after_item.date)
|> order_by(asc: :date)
end
def paginate(query, _) do
query
|> order_by(desc: :date)
end
end

View File

@ -18,11 +18,10 @@ defmodule Frenzy.Pipeline.ScrapeStage do
@impl Stage
def validate_opts(opts) when is_map(opts) do
# todo: figure out why this errors when an empty map is provided
opts =
case opts["extractor"] do
nil ->
{:ok, %{opts | extractor: "builtin"}}
{:ok, Map.put(opts, "extractor", "builtin")}
extractor when not is_binary(extractor) ->
{:error, "extractor must be a string"}
@ -44,7 +43,7 @@ defmodule Frenzy.Pipeline.ScrapeStage do
{:ok, opts} ->
case opts["convert_to_data_uris"] do
nil ->
{:ok, %{opts | convert_to_data_uris: true}}
{:ok, Map.put(opts, "convert_to_data_uris", true)}
value when is_boolean(value) ->
{:ok, opts}
@ -61,7 +60,7 @@ defmodule Frenzy.Pipeline.ScrapeStage do
@impl Stage
def validate_opts(_), do: {:error, "options must be a map"}
@spec get_article_content(String.t(), String.t()) :: {:ok, String.t()} | {:error, String.t()}
@spec get_article_content(String.t(), map()) :: {:ok, String.t()} | {:error, String.t()}
defp get_article_content(url, opts) when is_binary(url) and url != "" do
Logger.debug("Getting article from #{url}")
@ -76,9 +75,9 @@ defmodule Frenzy.Pipeline.ScrapeStage do
end
end
defp get_article_content(_url), do: {:error, "URL must be a non-empty string"}
defp get_article_content(_url, _opts), do: {:error, "URL must be a non-empty string"}
@spec handle_response(String.t(), HTTPoison.Response.t(), String.t()) ::
@spec handle_response(String.t(), HTTPoison.Response.t(), map()) ::
{:ok, String.t()} | {:error, String.t()}
defp handle_response(url, %HTTPoison.Response{body: body}, opts) do
case opts["extractor"] do
@ -115,7 +114,7 @@ defmodule Frenzy.Pipeline.ScrapeStage do
# Generates a helper function for the article with the given URI that takes an HTML element and,
# if it's an <img> element whose src attribute does not have a hostname, adds the hostname and
# scheme to the element.
defp rewrite_image_urls(convert_to_data_uris, %URI{host: host, scheme: scheme} = site_uri) do
defp rewrite_image_urls(convert_to_data_uris, site_uri) do
fn
{"img", attrs} ->
new_attrs =
@ -138,10 +137,7 @@ defmodule Frenzy.Pipeline.ScrapeStage do
# convert images to data URIs so that they're stored by clients as part of the body
defp image_to_data_uri(src, site_uri, true) do
absolute_url =
src
|> URI.parse()
|> HTTP.resolve_uri(site_uri)
absolute_url = URI.merge(site_uri, src) |> to_string()
case HTTP.get(absolute_url) do
{:ok, %HTTPoison.Response{body: body, headers: headers}} ->

View File

@ -25,8 +25,12 @@ defmodule Frenzy.Task.FetchFavicon do
with %Feed{favicon_url: old_url} when old_url != favicon_url <- feed,
{:ok, favicon_data} <- fetch_favicon_data(favicon_url) do
IO.inspect(favicon_url)
changeset = Feed.changeset(feed, %{favicon: favicon_data, favicon_url: favicon_url})
changeset =
Feed.changeset(feed, %{
favicon: favicon_data,
favicon_url: to_string(favicon_url)
})
{:ok, _feed} = Repo.update(changeset)
else
_ ->

View File

@ -10,7 +10,7 @@ defmodule Frenzy.UpdateFeeds do
end
def refresh(pid, feed) do
GenServer.call(pid, {:refresh, feed})
GenServer.cast(pid, {:refresh, feed})
end
def init(state) do
@ -19,10 +19,9 @@ defmodule Frenzy.UpdateFeeds do
{:ok, state}
end
def handle_call({:refresh, feed}, _from, state) do
def handle_cast({:refresh, feed}, state) do
update_feed(feed)
new_feed = Feed |> Repo.get(feed.id) |> Repo.preload(:items)
{:reply, new_feed, state}
{:noreply, state}
end
def handle_info(:update_feeds, state) do
@ -41,8 +40,17 @@ defmodule Frenzy.UpdateFeeds do
defp update_feeds() do
Logger.info("Updating all feeds")
Repo.all(from(Feed))
|> Enum.map(&update_feed/1)
{_count, feeds} =
Feed
|> where(
[f],
is_nil(f.last_refreshed_at) or
f.last_refreshed_at <= from_now(-1 * f.refresh_frequency, "second")
)
|> select([f], f)
|> Repo.update_all(set: [last_refreshed_at: DateTime.utc_now()])
Enum.map(feeds, &update_feed/1)
prune_old_items()
end
@ -67,7 +75,7 @@ defmodule Frenzy.UpdateFeeds do
defp update_feed(feed) do
Logger.debug("Updating #{feed.feed_url}")
case HTTPoison.get(feed.feed_url) do
case HTTP.get(feed.feed_url) do
{:ok,
%HTTPoison.Response{
status_code: 200,

View File

@ -1,6 +1,6 @@
defmodule FrenzyWeb.FeedController do
use FrenzyWeb, :controller
alias Frenzy.{Repo, Group, Feed, Item, Pipeline}
alias Frenzy.{Repo, Group, Feed, Item, Pipeline, Paginator}
alias FrenzyWeb.Router.Helpers, as: Routes
alias FrenzyWeb.Endpoint
import Ecto.Query
@ -25,13 +25,43 @@ defmodule FrenzyWeb.FeedController do
defp user_owns_feed(conn, _opts), do: conn
def show(conn, %{"id" => id}) do
def show(conn, %{"id" => id} = params) do
feed = conn.assigns[:feed]
items = Repo.all(from Item, where: [feed_id: ^id, tombstone: false], order_by: [desc: :date])
items =
Item
|> where([i], i.feed_id == ^id)
|> Paginator.paginate(params)
|> limit(50)
|> Repo.all()
|> Enum.sort_by(fn item -> item.date end, fn a, b ->
DateTime.compare(a, b) == :gt
end)
first_item = List.first(items)
last_item = List.last(items)
prev_page_path =
if !is_nil(first_item) &&
Repo.exists?(from i in Item, where: i.date > ^first_item.date and i.feed_id == ^id) do
current_path(conn, %{after: first_item.id})
else
nil
end
next_page_path =
if !is_nil(last_item) &&
Repo.exists?(from i in Item, where: i.date < ^last_item.date and i.feed_id == ^id) do
current_path(conn, %{before: last_item.id})
else
nil
end
render(conn, "show.html", %{
feed: feed,
items: items
items: items,
next_page_path: next_page_path,
prev_page_path: prev_page_path
})
end
@ -67,17 +97,24 @@ defmodule FrenzyWeb.FeedController do
end
def edit(conn, _params) do
user = conn.assigns[:user]
feed = conn.assigns[:feed]
edit_changeset = Feed.changeset(feed, %{})
pipelines =
Repo.all(Pipeline)
Repo.all(from p in Pipeline, where: p.user_id == ^user.id)
|> Enum.map(fn pipeline -> {pipeline.name, pipeline.id} end)
render(conn, "edit.html", %{
feed: feed,
changeset: edit_changeset,
pipelines: [{"No Pipeline", nil} | pipelines]
pipelines: [{"No Pipeline", nil} | pipelines],
refresh_frequencies: [
{"Every 30 minutes", 30 * 60},
{"Every hour", 60 * 60},
{"Every 6 hours", 6 * 60 * 60},
{"Every 24 hours", 24 * 60 * 60}
]
})
end
@ -110,7 +147,7 @@ defmodule FrenzyWeb.FeedController do
def refresh(conn, _params) do
feed = conn.assigns[:feed]
feed = Frenzy.UpdateFeeds.refresh(Frenzy.UpdateFeeds, feed)
Frenzy.UpdateFeeds.refresh(Frenzy.UpdateFeeds, feed)
conn
|> put_flash(:info, "Refreshing feed. Wait a moment before reloading...")

View File

@ -1,8 +1,9 @@
defmodule FrenzyWeb.GroupController do
use FrenzyWeb, :controller
alias Frenzy.{Repo, Group, Feed}
alias Frenzy.{Repo, Group, Feed, Item, Paginator}
alias FrenzyWeb.Router.Helpers, as: Routes
alias FrenzyWeb.Endpoint
import Ecto.Query
plug :user_owns_group
@ -72,10 +73,73 @@ defmodule FrenzyWeb.GroupController do
end
end
def edit(conn, _params) do
group = conn.assigns[:group]
edit_changeset = Group.changeset(group, %{})
render(conn, "edit.html", %{
group: group,
changeset: edit_changeset
})
end
def update(conn, %{"group" => group_params}) do
group = conn.assigns[:group]
changeset = Group.changeset(group, group_params)
{:ok, group} = Repo.update(changeset)
redirect(conn, to: Routes.group_path(Endpoint, :show, group.id))
end
def delete(conn, _params) do
group = conn.assigns[:group]
{:ok, _} = Repo.delete(group)
redirect(conn, to: Routes.group_path(Endpoint, :index))
end
def read(conn, params) do
group = conn.assigns[:group] |> Repo.preload(:feeds)
feed_ids = Enum.map(group.feeds, fn feed -> feed.id end)
items =
Item
|> where([i], i.feed_id in ^feed_ids)
|> Paginator.paginate(params)
|> limit(50)
|> preload(:feed)
|> Repo.all()
|> Enum.sort_by(fn item -> item.date end, fn a, b ->
DateTime.compare(a, b) == :gt
end)
first_item = List.first(items)
last_item = List.last(items)
prev_page_path =
if !is_nil(first_item) &&
Repo.exists?(
from i in Item, where: i.date > ^first_item.date and i.feed_id in ^feed_ids
) do
current_path(conn, %{after: first_item.id})
else
nil
end
next_page_path =
if !is_nil(last_item) &&
Repo.exists?(
from i in Item, where: i.date < ^last_item.date and i.feed_id in ^feed_ids
) do
current_path(conn, %{before: last_item.id})
else
nil
end
render(conn, "read.html", %{
group: group,
items: items,
prev_page_path: prev_page_path,
next_page_path: next_page_path
})
end
end

View File

@ -105,7 +105,7 @@ defmodule FrenzyWeb.PipelineController do
else
{:error, reason} ->
conn
|> put_flash(:error, "Unable to edit pipeline: #{inspect(reason)}")
|> put_flash(:error, "Unable to edit pipeline: #{reason}")
|> render("edit.html", %{
pipeline: pipeline,
name: name,
@ -123,7 +123,7 @@ defmodule FrenzyWeb.PipelineController do
{:cont, {:ok, new_stages ++ [stage]}}
{:error, reason} ->
{:error, "invalid stage at #{index}: #{reason}"}
{:halt, {:error, "invalid stage at index #{index}: #{reason}"}}
end
end)
end

View File

@ -47,7 +47,8 @@ defmodule FrenzyWeb.Router do
post "/account/export", AccountController, :export
get "/", GroupController, :index
resources "/groups", GroupController, except: [:edit, :update]
resources "/groups", GroupController
get "/groups/:id/read", GroupController, :read
resources "/feeds", FeedController, except: [:index, :new]
post "/feeds/:id/refresh", FeedController, :refresh

View File

@ -1,6 +1,18 @@
<h1>Edit Feed</h1>
<%= form_for @changeset, Routes.feed_path(@conn, :update, @feed.id), fn f -> %>
<div class="form-group row">
<label class="col-sm-2 col-form-label" for="feed_url">Feed URL</label>
<div class="col-sm-10">
<%= text_input f, :feed_url, class: "form-control" %>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label" for="refresh_frequency">Refresh Frequency</label>
<div class="col-sm-10">
<%= select f, :refresh_frequency, @refresh_frequencies, class: "custom-select" %>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label" for="pipeline_id">Pipeline ID</label>
<div class="col-sm-10">
@ -8,11 +20,11 @@
</div>
</div>
<%= if @feed.pipeline_id do %>
<div class="form-group row">
<div class="col-sm-10">
<a href="<%= Routes.pipeline_path(@conn, :show, @feed.pipeline_id) %>" class="col-sm-2">View Pipeline</a>
<div class="form-group row">
<div class="col-sm-10">
<a href="<%= Routes.pipeline_path(@conn, :show, @feed.pipeline_id) %>" class="col-sm-2">View Pipeline</a>
</div>
</div>
</div>
<% end %>
<div class="form-group row">
<div class="col-sm-10">

View File

@ -17,6 +17,12 @@
<h3 class="mt-4">Items</h3>
<%= unless is_nil(@prev_page_path) do %>
<p class="text-center">
<a href="<%= @prev_page_path %>" class="pagination-link">Newer</a>
</p>
<% end %>
<table class="table table-striped">
<tbody>
<%= for item <- @items do %>
@ -25,8 +31,8 @@
<a href="<%= Routes.item_path(@conn, :show, item.id) %>"><%= item.title || "(Untitled)" %></a>
</td>
<td>
<% if item.date do %>
<% {:ok, date} = Timex.format(item.date, "{YYYY}-{M}-{D} {h12}:{m} {AM}") %>
<%= if item.date do %>
<% {:ok, date} = Timex.format(item.date, "{YYYY}-{0M}-{0D} {0h12}:{m} {AM}") %>
<%= date %>
<% end %>
</td>
@ -34,3 +40,9 @@
<% end %>
</tbody>
</table>
<%= unless is_nil(@next_page_path) do %>
<p class="text-center">
<a href="<%= @next_page_path %>" class="pagination-link">Older</a>
</p>
<% end %>

View File

@ -0,0 +1,13 @@
<h1>Edit Group</h1>
<%= form_for @changeset, Routes.group_path(@conn, :update, @group.id), fn f -> %>
<div class="form-group row">
<label class="col-sm-2 col-form-label" for="title">Title</label>
<div class="col-sm-10">
<%= text_input f, :title, class: "form-control" %>
</div>
</div>
<div class="form-group row">
<%= submit "Update Group", class: "btn btn-primary" %>
</div>
<% end %>

View File

@ -0,0 +1,41 @@
<h1><%= @group.title %></h1>
<%= unless is_nil(@prev_page_path) do %>
<p class="text-center">
<a href="<%= @prev_page_path %>" class="pagination-link">Newer</a>
</p>
<% end %>
<table class="table table-striped">
<tbody>
<%= for item <- @items do %>
<tr <%= if item.read do %>class="item-read"<% end %>>
<td>
<a href="<%= Routes.item_path(@conn, :show, item.id) %>">
<%= item.title || "(Untitled)" %>
</a>
</td>
<td>
<a href="<%= Routes.feed_path(@conn, :show, item.feed.id) %>">
<%= if item.feed.favicon do %>
<img src="<%= item.feed.favicon %>" alt="<%= item.feed.title %> favicon" class="favicon">
<% end %>
<%= item.feed.title || "(Untitled)" %>
</a>
</td>
<td>
<%= if item.date do %>
<% {:ok, date} = Timex.format(item.date, "{YYYY}-{0M}-{0D} {0h12}:{m} {AM}") %>
<%= date %>
<% end %>
</td>
</tr>
<% end %>
</tbody>
</table>
<%= unless is_nil(@next_page_path) do %>
<p class="text-center">
<a href="<%= @next_page_path %>" class="pagination-link">Older</a>
</p>
<% end %>

View File

@ -1,23 +1,24 @@
<h1><%= @group.title %></h1>
<%= form_for @create_feed_changeset, Routes.feed_path(@conn, :create), fn form -> %>
<a href="<%= Routes.group_path(@conn, :edit, @group.id) %>" class="btn btn-primary">Edit Group</a>
<%= form_tag Routes.group_path(@conn, :delete, @group.id), method: :delete, class: "d-inline" do %>
<%= submit "Delete Group", class: "btn btn-danger" %>
<% end %>
<%= form_for @create_feed_changeset, Routes.feed_path(@conn, :create), [class: "mt-2 mb-2"], fn form -> %>
<%= hidden_input form, :group_id %>
<div class="form-group row">
<div class="row">
<%= label form, :feed_url, "Feed URL", class: "col-sm-2 col-form-label" %>
<div class="col-sm-10">
<div class="col-sm-8">
<%= text_input form, :feed_url, placeholder: "https://example.com/feed.xml", class: "form-control" %>
</div>
</div>
<div class="form-group row">
<div class="col-sm-10">
<div class="col-sm-2">
<%= submit "Add Feed", class: "btn btn-primary" %>
</div>
</div>
<% end %>
<%= form_tag Routes.group_path(@conn, :delete, @group.id), method: :delete do %>
<%= submit "Delete Group", class: "btn btn-danger" %>
<% end %>
<a href="<%= Routes.group_path(@conn, :read, @group.id) %>" class="btn btn-secondary">Read Articles</a>
<h3 class="mt-4">Feeds</h3>

View File

@ -44,6 +44,7 @@ defmodule Frenzy.MixProject do
{:jason, "~> 1.0"},
{:plug_cowboy, "~> 2.0"},
{:httpoison, "~> 1.6.2"},
{:hackney, "~> 1.16"},
{:feed_parser,
git: "https://git.shadowfacts.net/shadowfacts/feed_parser.git", branch: "master"},
{:timex, "~> 3.6"},

View File

@ -1,51 +1,51 @@
%{
"basic_auth": {:hex, :basic_auth, "2.2.4", "d8c748237870dd1df3bc5c0f1ab4f1fad6270c75472d7e62b19302ec59e92a79", [:mix], [{:plug, "~> 0.14 or ~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"bcrypt_elixir": {:hex, :bcrypt_elixir, "2.0.1", "1061e2114aaac554c12e5c1e4608bf4aadaca839f30d1b85224272facd5e6427", [:make, :mix], [{:comeonin, "~> 5.1", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
"certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm"},
"comeonin": {:hex, :comeonin, "5.1.1", "0abd6bae41acc01c369bb3eafe46399f301bf4e1bacebafdb89252bbb8a1a32d", [:mix], [], "hexpm"},
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"},
"cowboy": {:hex, :cowboy, "2.6.0", "dc1ff5354c89e36a3e3ef8d10433396dcff0dcbb1d4223b58c64c2d51a6d88d9", [:rebar3], [{:cowlib, "~> 2.7.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
"cowlib": {:hex, :cowlib, "2.7.0", "3ef16e77562f9855a2605900cedb15c1462d76fb1be6a32fc3ae91973ee543d2", [:rebar3], [], "hexpm"},
"db_connection": {:hex, :db_connection, "2.0.2", "440c05518b0bdca0469dafaf45403597430448c1281def14ef9ccaa41833ea1e", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"},
"decimal": {:hex, :decimal, "1.6.0", "bfd84d90ff966e1f5d4370bdd3943432d8f65f07d3bab48001aebd7030590dcc", [:mix], [], "hexpm"},
"dialyxir": {:hex, :dialyxir, "1.0.0-rc.6", "78e97d9c0ff1b5521dd68041193891aebebce52fc3b93463c0a6806874557d7d", [:mix], [{:erlex, "~> 0.2.1", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm"},
"ecto": {:hex, :ecto, "3.0.3", "018a3df0956636f84eb3033d807485a7d3dea8474f47b90da5cb8073444c4384", [:mix], [{:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"},
"ecto_sql": {:hex, :ecto_sql, "3.0.2", "0e04cbc183b91ea0085c502226befcd237a4ac31c204fd4be8d4db6676b5f10d", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.0.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.9.1", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.14.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.2.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
"elixir_make": {:hex, :elixir_make, "0.5.2", "96a28c79f5b8d34879cd95ebc04d2a0d678cfbbd3e74c43cb63a76adf0ee8054", [:mix], [], "hexpm"},
"erlex": {:hex, :erlex, "0.2.4", "23791959df45fe8f01f388c6f7eb733cc361668cbeedd801bf491c55a029917b", [:mix], [], "hexpm"},
"bcrypt_elixir": {:hex, :bcrypt_elixir, "2.0.1", "1061e2114aaac554c12e5c1e4608bf4aadaca839f30d1b85224272facd5e6427", [:make, :mix], [{:comeonin, "~> 5.1", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "64f174c76ea5edfcc471dfb7762280a20e29fe446baa02dc75c7d14251581e93"},
"certifi": {:hex, :certifi, "2.5.2", "b7cfeae9d2ed395695dd8201c57a2d019c0c43ecaf8b8bcb9320b40d6662f340", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "3b3b5f36493004ac3455966991eaf6e768ce9884693d9968055aeeeb1e575040"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
"comeonin": {:hex, :comeonin, "5.1.1", "0abd6bae41acc01c369bb3eafe46399f301bf4e1bacebafdb89252bbb8a1a32d", [:mix], [], "hexpm", "b77aef9eb7ec7a4c01cc3d0683332796052ab71067d858d5dacde967427de0a3"},
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"},
"cowboy": {:hex, :cowboy, "2.6.0", "dc1ff5354c89e36a3e3ef8d10433396dcff0dcbb1d4223b58c64c2d51a6d88d9", [:rebar3], [{:cowlib, "~> 2.7.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "ff7cf0fb7a2762f423d845468b32ed4edb5c7e0d78e6875b0628153239a7bb34"},
"cowlib": {:hex, :cowlib, "2.7.0", "3ef16e77562f9855a2605900cedb15c1462d76fb1be6a32fc3ae91973ee543d2", [:rebar3], [], "hexpm", "59f952d504b9921a6f53226fdb8b4a671bb694277c7ccf1ddeaadcdc6d43d88e"},
"db_connection": {:hex, :db_connection, "2.0.2", "440c05518b0bdca0469dafaf45403597430448c1281def14ef9ccaa41833ea1e", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "da1cfb8ba1e2b77b594305ea62cce8d8c64776199f0d9a849718c4f0fceb9ad2"},
"decimal": {:hex, :decimal, "1.6.0", "bfd84d90ff966e1f5d4370bdd3943432d8f65f07d3bab48001aebd7030590dcc", [:mix], [], "hexpm", "bbd124e240e3ff40f407d50fced3736049e72a73d547f69201484d3a624ab569"},
"dialyxir": {:hex, :dialyxir, "1.0.0-rc.6", "78e97d9c0ff1b5521dd68041193891aebebce52fc3b93463c0a6806874557d7d", [:mix], [{:erlex, "~> 0.2.1", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "49496d63267bc1a4614ffd5f67c45d9fc3ea62701a6797975bc98bc156d2763f"},
"ecto": {:hex, :ecto, "3.0.3", "018a3df0956636f84eb3033d807485a7d3dea8474f47b90da5cb8073444c4384", [:mix], [{:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "30b0956495d12b53dbabe7965bfb40e4cd5c49c2f0b051b23fb94ac2854006ea"},
"ecto_sql": {:hex, :ecto_sql, "3.0.2", "0e04cbc183b91ea0085c502226befcd237a4ac31c204fd4be8d4db6676b5f10d", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.0.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.9.1", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.14.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.2.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c047508d619a04ff193ea6e92f92701815fdee195be93bfd89bbee3025898b5e"},
"elixir_make": {:hex, :elixir_make, "0.5.2", "96a28c79f5b8d34879cd95ebc04d2a0d678cfbbd3e74c43cb63a76adf0ee8054", [:mix], [], "hexpm", "382eeea8e02dfe6c468f6729b6cf20fe5b14390671d38c7363e59621c7ab4efc"},
"erlex": {:hex, :erlex, "0.2.4", "23791959df45fe8f01f388c6f7eb733cc361668cbeedd801bf491c55a029917b", [:mix], [], "hexpm", "4a12ebc7cd8f24f2d0fce93d279fa34eb5068e0e885bb841d558c4d83c52c439"},
"feed_parser": {:git, "https://git.shadowfacts.net/shadowfacts/feed_parser.git", "8c42d4587328698e8d29d2ad562e478abb146f75", [branch: "master"]},
"fiet": {:git, "https://github.com/shadowfacts/fiet.git", "bf117bc30a6355a189d05a562127cfaf9e0187ae", [branch: "master"]},
"file_system": {:hex, :file_system, "0.2.6", "fd4dc3af89b9ab1dc8ccbcc214a0e60c41f34be251d9307920748a14bf41f1d3", [:mix], [], "hexpm"},
"floki": {:hex, :floki, "0.23.0", "956ab6dba828c96e732454809fb0bd8d43ce0979b75f34de6322e73d4c917829", [:mix], [{:html_entities, "~> 0.4.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm"},
"gettext": {:hex, :gettext, "0.17.0", "abe21542c831887a2b16f4c94556db9c421ab301aee417b7c4fbde7fbdbe01ec", [:mix], [], "hexpm"},
"hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"html_entities": {:hex, :html_entities, "0.4.0", "f2fee876858cf6aaa9db608820a3209e45a087c5177332799592142b50e89a6b", [:mix], [], "hexpm"},
"httpoison": {:hex, :httpoison, "1.6.2", "ace7c8d3a361cebccbed19c283c349b3d26991eff73a1eaaa8abae2e3c8089b6", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
"mime": {:hex, :mime, "1.3.0", "5e8d45a39e95c650900d03f897fbf99ae04f60ab1daa4a34c7a20a5151b7a5fe", [:mix], [], "hexpm"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"},
"file_system": {:hex, :file_system, "0.2.6", "fd4dc3af89b9ab1dc8ccbcc214a0e60c41f34be251d9307920748a14bf41f1d3", [:mix], [], "hexpm", "0d50da6b04c58e101a3793b1600f9a03b86e3a8057b192ac1766013d35706fa6"},
"floki": {:hex, :floki, "0.23.0", "956ab6dba828c96e732454809fb0bd8d43ce0979b75f34de6322e73d4c917829", [:mix], [{:html_entities, "~> 0.4.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "e680b5ef0b61ce02faa7137db8d1714903a5552be4c89fb57293b8770e7f49c2"},
"gettext": {:hex, :gettext, "0.17.0", "abe21542c831887a2b16f4c94556db9c421ab301aee417b7c4fbde7fbdbe01ec", [:mix], [], "hexpm", "e0b8598e802676c81e66b061a2148c37c03886b24a3ca86a1f98ed40693b94b3"},
"hackney": {:hex, :hackney, "1.16.0", "5096ac8e823e3a441477b2d187e30dd3fff1a82991a806b2003845ce72ce2d84", [:rebar3], [{:certifi, "2.5.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.1", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.0", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.6", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "3bf0bebbd5d3092a3543b783bf065165fa5d3ad4b899b836810e513064134e18"},
"html_entities": {:hex, :html_entities, "0.4.0", "f2fee876858cf6aaa9db608820a3209e45a087c5177332799592142b50e89a6b", [:mix], [], "hexpm", "3e3d7156a272950373ce5a4018b1490bea26676f8d6a7d409f6fac8568b8cb9a"},
"httpoison": {:hex, :httpoison, "1.6.2", "ace7c8d3a361cebccbed19c283c349b3d26991eff73a1eaaa8abae2e3c8089b6", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "aa2c74bd271af34239a3948779612f87df2422c2fdcfdbcec28d9c105f0773fe"},
"idna": {:hex, :idna, "6.0.1", "1d038fb2e7668ce41fbf681d2c45902e52b3cb9e9c77b55334353b222c2ee50c", [:rebar3], [{:unicode_util_compat, "0.5.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a02c8a1c4fd601215bb0b0324c8a6986749f807ce35f25449ec9e69758708122"},
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fdf843bca858203ae1de16da2ee206f53416bbda5dc8c9e78f43243de4bc3afe"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mime": {:hex, :mime, "1.3.0", "5e8d45a39e95c650900d03f897fbf99ae04f60ab1daa4a34c7a20a5151b7a5fe", [:mix], [], "hexpm", "5e839994289d60326aa86020c4fbd9c6938af188ecddab2579f07b66cd665328"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
"mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"},
"phoenix": {:hex, :phoenix, "1.4.0", "56fe9a809e0e735f3e3b9b31c1b749d4b436e466d8da627b8d82f90eaae714d2", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.0.0", "c43117a136e7399ea04ecaac73f8f23ee0ffe3e07acfcb8062fe5f4c9f0f6531", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"phoenix_html": {:hex, :phoenix_html, "2.12.0", "1fb3c2e48b4b66d75564d8d63df6d53655469216d6b553e7e14ced2b46f97622", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.2.0", "3bb31a9fbd40ffe8652e60c8660dffd72dd231efcdf49b744fb75b9ef7db5dd2", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.1", "6668d787e602981f24f17a5fbb69cc98f8ab085114ebfac6cc36e10a90c8e93c", [:mix], [], "hexpm"},
"plug": {:hex, :plug, "1.7.1", "8516d565fb84a6a8b2ca722e74e2cd25ca0fc9d64f364ec9dbec09d33eb78ccd", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}], "hexpm"},
"plug_cowboy": {:hex, :plug_cowboy, "2.0.0", "ab0c92728f2ba43c544cce85f0f220d8d30fc0c90eaa1e6203683ab039655062", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
"postgrex": {:hex, :postgrex, "0.14.0", "f3d6ffea1ca8a156e0633900a5338a3d17b00435227726baed8982718232b694", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
"ranch": {:hex, :ranch, "1.7.0", "9583f47160ca62af7f8d5db11454068eaa32b56eeadf984d4f46e61a076df5f2", [:rebar3], [], "hexpm"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"},
"phoenix": {:hex, :phoenix, "1.4.0", "56fe9a809e0e735f3e3b9b31c1b749d4b436e466d8da627b8d82f90eaae714d2", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "22da8f659cf13d3ba73b767f66b8c389113ddf0ef7b94225cc84e94b85eac90e"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.0.0", "c43117a136e7399ea04ecaac73f8f23ee0ffe3e07acfcb8062fe5f4c9f0f6531", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "fe15d9fee5b82f5e64800502011ffe530650d42e1710ae9b14bc4c9be38bf303"},
"phoenix_html": {:hex, :phoenix_html, "2.12.0", "1fb3c2e48b4b66d75564d8d63df6d53655469216d6b553e7e14ced2b46f97622", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "8bc079e2092f4447c8c75029b297cfc3256b8aa46dff02a16eaf1ade5eae96f6"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.2.0", "3bb31a9fbd40ffe8652e60c8660dffd72dd231efcdf49b744fb75b9ef7db5dd2", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "b61c87a23fabcd85ff369fb9c041d9c01787d210322749026f56a69a914b7503"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.1", "6668d787e602981f24f17a5fbb69cc98f8ab085114ebfac6cc36e10a90c8e93c", [:mix], [], "hexpm", "a3d890aaa3156d51056179dcaaadaf32b844f71656bb27c58756f2b97875c36c"},
"plug": {:hex, :plug, "1.7.1", "8516d565fb84a6a8b2ca722e74e2cd25ca0fc9d64f364ec9dbec09d33eb78ccd", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}], "hexpm", "daa5fee4209c12c3c48b05a96cf88c320b627c9575f987554dcdc1fdcdf2c15e"},
"plug_cowboy": {:hex, :plug_cowboy, "2.0.0", "ab0c92728f2ba43c544cce85f0f220d8d30fc0c90eaa1e6203683ab039655062", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "fa9087d93f4962d099b9c4246dbea97c2f2e6aab1029e8b8206e733a1274cecc"},
"plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm", "73c1682f0e414cfb5d9b95c8e8cd6ffcfdae699e3b05e1db744e58b7be857759"},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"},
"postgrex": {:hex, :postgrex, "0.14.0", "f3d6ffea1ca8a156e0633900a5338a3d17b00435227726baed8982718232b694", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "ee6d8bc31ec7064f87030b98e431cf7ef0537052339de289b575b0a8872f501e"},
"ranch": {:hex, :ranch, "1.7.0", "9583f47160ca62af7f8d5db11454068eaa32b56eeadf984d4f46e61a076df5f2", [:rebar3], [], "hexpm", "59f7501c3a56125b2fc5684c3048fac9d043c0bf4d173941b12ca927949af189"},
"readability": {:git, "https://github.com/shadowfacts/readability.git", "71fa17caaf8103ef213e2c7dde4b447a48669122", [branch: "master"]},
"saxy": {:hex, :saxy, "0.6.0", "cdb2f2fcd8133d1f3f8b0cf6a131ee1ca348dca613de266e9a239db850c4a093", [:mix], [], "hexpm"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm"},
"telemetry": {:hex, :telemetry, "0.2.0", "5b40caa3efe4deb30fb12d7cd8ed4f556f6d6bd15c374c2366772161311ce377", [:mix], [], "hexpm"},
"timex": {:hex, :timex, "3.6.1", "efdf56d0e67a6b956cc57774353b0329c8ab7726766a11547e529357ffdc1d56", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
"tzdata": {:hex, :tzdata, "1.0.1", "f6027a331af7d837471248e62733c6ebee86a72e57c613aa071ebb1f750fc71a", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"},
"xml_builder": {:hex, :xml_builder, "2.1.1", "2d6d665f09cf1319e3e1c46035755271b414d99ad8615d0bd6f337623e0c885b", [:mix], [], "hexpm"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
"telemetry": {:hex, :telemetry, "0.2.0", "5b40caa3efe4deb30fb12d7cd8ed4f556f6d6bd15c374c2366772161311ce377", [:mix], [], "hexpm", "4e9071b8d1795d0f1ae00584594c3faf430c88821b69e4bd09b02e7840231f32"},
"timex": {:hex, :timex, "3.6.1", "efdf56d0e67a6b956cc57774353b0329c8ab7726766a11547e529357ffdc1d56", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "f354efb2400dd7a80fd9eb6c8419068c4f632da4ac47f3d8822d6e33f08bc852"},
"tzdata": {:hex, :tzdata, "1.0.1", "f6027a331af7d837471248e62733c6ebee86a72e57c613aa071ebb1f750fc71a", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "cf1345dfbce6acdfd4e23cbb36e96e53d1981bc89181cd0b936f4f398f4c0b78"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.5.0", "8516502659002cec19e244ebd90d312183064be95025a319a6c7e89f4bccd65b", [:rebar3], [], "hexpm", "d48d002e15f5cc105a696cf2f1bbb3fc72b4b770a184d8420c8db20da2674b38"},
"xml_builder": {:hex, :xml_builder, "2.1.1", "2d6d665f09cf1319e3e1c46035755271b414d99ad8615d0bd6f337623e0c885b", [:mix], [], "hexpm", "214c16caa77e66bf0c6b74099a7059ee00de8fd07728d2a3dc32afe344a7452b"},
}

View File

@ -0,0 +1,10 @@
defmodule Frenzy.Repo.Migrations.FeedsAddRefreshFrequency do
use Ecto.Migration
def change do
alter table(:feeds) do
add :last_refreshed_at, :naive_datetime, default: nil
add :refresh_frequency, :integer, default: 30 * 60
end
end
end