Initial commit
This commit is contained in:
commit
e03c759bb7
21 changed files with 785 additions and 0 deletions
2
.envrc
Normal file
2
.envrc
Normal file
|
@ -0,0 +1,2 @@
|
|||
use flake
|
||||
project elixir
|
8
.formatter.exs
Normal file
8
.formatter.exs
Normal file
|
@ -0,0 +1,8 @@
|
|||
[
|
||||
inputs: [
|
||||
"mix.exs",
|
||||
"{config,lib,test}/**/*.{ex,exs}"
|
||||
],
|
||||
line_length: 120,
|
||||
import_deps: [:plug]
|
||||
]
|
39
.gitignore
vendored
Normal file
39
.gitignore
vendored
Normal file
|
@ -0,0 +1,39 @@
|
|||
# The directory Mix will write compiled artifacts to.
|
||||
/_build
|
||||
|
||||
# If you run "mix test --cover", coverage assets end up here.
|
||||
/cover
|
||||
|
||||
# The directory Mix downloads your dependencies sources to.
|
||||
/deps
|
||||
|
||||
# Where 3rd-party dependencies like ExDoc output generated docs.
|
||||
/doc
|
||||
|
||||
# If the VM crashes, it generates a dump, let's ignore it too.
|
||||
erl_crash.dump
|
||||
|
||||
# Also ignore archive artifacts (built via "mix archive.build").
|
||||
*.ez
|
||||
|
||||
# nix
|
||||
/.direnv
|
||||
/.elixir_ls
|
||||
|
||||
# Exclude releases
|
||||
/rel/*
|
||||
!/rel/config.exs
|
||||
!/rel/commands/
|
||||
!/rel/plugins/
|
||||
!/rel/vm.args.eex
|
||||
!/rel/env.sh.eex
|
||||
!/rel/env.bat.eex
|
||||
.deliver/releases/
|
||||
/result
|
||||
|
||||
.DS_Store
|
||||
*.orig
|
||||
/tmp
|
||||
/tags*
|
||||
/xref_graph.dot
|
||||
/xref_graph.png
|
5
LICENSE
Normal file
5
LICENSE
Normal file
|
@ -0,0 +1,5 @@
|
|||
Copyright 2023 Daniel Kempkens
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
10
README.md
Normal file
10
README.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
# BdfrBrowser
|
||||
|
||||
Maybe some else finds this useful.
|
||||
|
||||
It's a very crude and quickly thrown together browser for [BDFR](https://github.com/aliparlakci/bulk-downloader-for-reddit) dumps.
|
||||
|
||||
## Known Issues
|
||||
|
||||
- Directories with many posts (per month) are slow to load
|
||||
- The HTML templates are very much a work in progress
|
6
config/config.exs
Normal file
6
config/config.exs
Normal file
|
@ -0,0 +1,6 @@
|
|||
import Config
|
||||
|
||||
config :logger,
|
||||
backends: [],
|
||||
handle_otp_reports: false,
|
||||
handle_sasl_reports: false
|
6
config/runtime.exs
Normal file
6
config/runtime.exs
Normal file
|
@ -0,0 +1,6 @@
|
|||
import Config
|
||||
|
||||
config :bdfr_browser,
|
||||
base_directory: System.get_env("BDFR_BROWSER_BASE_DIRECTORY", "/nonexistant"),
|
||||
http_ip: to_charlist(System.get_env("BDFR_BROWSER_HTTP_IP", "127.0.0.1")),
|
||||
http_port: String.to_integer(System.get_env("BDFR_BROWSER_HTTP_PORT", "4040"))
|
64
flake.lock
Normal file
64
flake.lock
Normal file
|
@ -0,0 +1,64 @@
|
|||
{
|
||||
"nodes": {
|
||||
"flake-parts": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1683560683,
|
||||
"narHash": "sha256-XAygPMN5Xnk/W2c1aW0jyEa6lfMDZWlQgiNtmHXytPc=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "006c75898cf814ef9497252b022e91c946ba8e17",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1684242266,
|
||||
"narHash": "sha256-uaCQ2k1bmojHKjWQngvnnnxQJMY8zi1zq527HdWgQf8=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "7e0743a5aea1dc755d4b761daf75b20aa486fdad",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"dir": "lib",
|
||||
"lastModified": 1682879489,
|
||||
"narHash": "sha256-sASwo8gBt7JDnOOstnps90K1wxmVfyhsTPPNTGBPjjg=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "da45bf6ec7bbcc5d1e14d3795c025199f28e0de0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"dir": "lib",
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-parts": "flake-parts",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
47
flake.nix
Normal file
47
flake.nix
Normal file
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
description = "bdfr-browser development environment";
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||
};
|
||||
|
||||
outputs = inputs@{ flake-parts, ... }:
|
||||
flake-parts.lib.mkFlake { inherit inputs; } {
|
||||
systems = [ "aarch64-darwin" "x86_64-linux" "aarch64-linux" ];
|
||||
|
||||
perSystem = { pkgs, lib, self', ... }:
|
||||
let
|
||||
pname = "bdfr-browser";
|
||||
version = "0.0.1";
|
||||
|
||||
erlang = pkgs.beam.interpreters.erlangR25;
|
||||
beamPackages = pkgs.beam.packagesWith erlang;
|
||||
elixir = beamPackages.elixir_1_14;
|
||||
|
||||
inherit (pkgs.stdenv) isDarwin;
|
||||
in
|
||||
{
|
||||
devShells.default = pkgs.mkShell {
|
||||
packages = (with pkgs; [
|
||||
erlang
|
||||
elixir
|
||||
|
||||
beamPackages.elixir-ls
|
||||
mix2nix
|
||||
]) ++ lib.optionals isDarwin (with pkgs.darwin.apple_sdk.frameworks; [
|
||||
CoreFoundation
|
||||
CoreServices
|
||||
]);
|
||||
|
||||
ERL_INCLUDE_PATH = "${erlang}/lib/erlang/usr/include";
|
||||
};
|
||||
|
||||
packages.default = beamPackages.mixRelease {
|
||||
inherit pname version;
|
||||
|
||||
src = ./.;
|
||||
mixNixDeps = import ./mix.nix { inherit lib beamPackages; };
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
21
lib/bdfr_browser/application.ex
Normal file
21
lib/bdfr_browser/application.ex
Normal file
|
@ -0,0 +1,21 @@
|
|||
defmodule BdfrBrowser.Application do
|
||||
# See https://hexdocs.pm/elixir/Application.html
|
||||
# for more information on OTP Applications
|
||||
@moduledoc false
|
||||
|
||||
use Application
|
||||
|
||||
@impl true
|
||||
def start(_type, _args) do
|
||||
{:ok, http_ip} = :inet.parse_address(Application.fetch_env!(:bdfr_browser, :http_ip))
|
||||
http_port = Application.fetch_env!(:bdfr_browser, :http_port)
|
||||
|
||||
children = [
|
||||
{Plug.Cowboy, scheme: :http, plug: BdfrBrowser.HTTP.Plug, options: [ip: http_ip, port: http_port]},
|
||||
:systemd.ready()
|
||||
]
|
||||
|
||||
opts = [strategy: :one_for_one, name: BdfrBrowser.Supervisor]
|
||||
Supervisor.start_link(children, opts)
|
||||
end
|
||||
end
|
155
lib/bdfr_browser/http/plug.ex
Normal file
155
lib/bdfr_browser/http/plug.ex
Normal file
|
@ -0,0 +1,155 @@
|
|||
defmodule BdfrBrowser.HTTP.Plug do
|
||||
use Plug.Router
|
||||
|
||||
plug :match
|
||||
plug :dispatch
|
||||
|
||||
get "/" do
|
||||
tpl_params = [
|
||||
subreddits: list_folders(sort: :asc)
|
||||
]
|
||||
|
||||
tpl_file = Application.app_dir(:bdfr_browser, "priv/templates/http/index.eex")
|
||||
content = EEx.eval_file(tpl_file, tpl_params)
|
||||
|
||||
conn
|
||||
|> put_resp_header("content-type", "text/html; charset=utf-8")
|
||||
|> send_resp(200, content)
|
||||
end
|
||||
|
||||
get "/r/:subreddit" do
|
||||
tpl_params = [
|
||||
subreddit: subreddit,
|
||||
dates: list_folders(paths: [subreddit])
|
||||
]
|
||||
|
||||
tpl_file = Application.app_dir(:bdfr_browser, "priv/templates/http/subreddit.eex")
|
||||
content = EEx.eval_file(tpl_file, tpl_params)
|
||||
|
||||
conn
|
||||
|> put_resp_header("content-type", "text/html; charset=utf-8")
|
||||
|> send_resp(200, content)
|
||||
end
|
||||
|
||||
get "/r/:subreddit/:date" do
|
||||
tpl_params = [
|
||||
subreddit: subreddit,
|
||||
date: date,
|
||||
posts: read_posts(paths: [subreddit, date], ext: ".json")
|
||||
]
|
||||
|
||||
tpl_file = Application.app_dir(:bdfr_browser, "priv/templates/http/subreddit_posts.eex")
|
||||
content = EEx.eval_file(tpl_file, tpl_params)
|
||||
|
||||
conn
|
||||
|> put_resp_header("content-type", "text/html; charset=utf-8")
|
||||
|> send_resp(200, content)
|
||||
end
|
||||
|
||||
get "/r/:subreddit/:date/:post" do
|
||||
tpl_params = [
|
||||
subreddit: subreddit,
|
||||
date: date,
|
||||
post: read_post(post, paths: [subreddit, date]),
|
||||
media: post_media(post, paths: [subreddit, date]),
|
||||
comment_template: Application.app_dir(:bdfr_browser, "priv/templates/http/_comment.eex")
|
||||
]
|
||||
|
||||
tpl_file = Application.app_dir(:bdfr_browser, "priv/templates/http/post.eex")
|
||||
content = EEx.eval_file(tpl_file, tpl_params)
|
||||
|
||||
conn
|
||||
|> put_resp_header("content-type", "text/html; charset=utf-8")
|
||||
|> send_resp(200, content)
|
||||
end
|
||||
|
||||
get "/media/*path" do
|
||||
base_directory = Application.fetch_env!(:bdfr_browser, :base_directory)
|
||||
joined_path = Path.join(path)
|
||||
{:ok, media} = [base_directory, joined_path] |> Path.join() |> File.read()
|
||||
|
||||
conn
|
||||
|> put_resp_header("content-type", mime_from_ext(joined_path))
|
||||
|> send_resp(200, media)
|
||||
end
|
||||
|
||||
match _ do
|
||||
send_resp(conn, 404, "Not Found")
|
||||
end
|
||||
|
||||
# Helper
|
||||
|
||||
defp list_folders(args) do
|
||||
paths = Keyword.get(args, :paths, [])
|
||||
extname = Keyword.get(args, :ext, "")
|
||||
sort = Keyword.get(args, :sort, :desc)
|
||||
base_directory = Application.fetch_env!(:bdfr_browser, :base_directory)
|
||||
|
||||
[base_directory | paths]
|
||||
|> Path.join()
|
||||
|> File.ls!()
|
||||
|> Enum.filter(fn s -> not String.starts_with?(s, ".") and Path.extname(s) == extname end)
|
||||
|> Enum.sort_by(&String.downcase/1, sort)
|
||||
end
|
||||
|
||||
defp read_posts(args) do
|
||||
posts = list_folders(args)
|
||||
sort = Keyword.get(args, :sort, :desc)
|
||||
|
||||
base_directory = Application.fetch_env!(:bdfr_browser, :base_directory)
|
||||
post_dir = Path.join([base_directory | Keyword.fetch!(args, :paths)])
|
||||
|
||||
compact_posts =
|
||||
for post <- posts do
|
||||
{:ok, content} = [post_dir, post] |> Path.join() |> File.read!() |> Jason.decode()
|
||||
|
||||
%{
|
||||
title: content["title"],
|
||||
author: content["author"],
|
||||
num_comments: content["num_comments"],
|
||||
created_utc: content["created_utc"],
|
||||
filename: Path.basename(post, ".json")
|
||||
}
|
||||
end
|
||||
|
||||
Enum.sort_by(compact_posts, fn p -> p.created_utc end, sort)
|
||||
end
|
||||
|
||||
defp read_post(post, args) do
|
||||
base_directory = Application.fetch_env!(:bdfr_browser, :base_directory)
|
||||
post_dir = Path.join([base_directory | Keyword.fetch!(args, :paths)])
|
||||
post_file = "#{post}.json"
|
||||
|
||||
{:ok, content} = [post_dir, post_file] |> Path.join() |> File.read!() |> Jason.decode(keys: :atoms)
|
||||
content
|
||||
end
|
||||
|
||||
defp post_media(post, args) do
|
||||
base_directory = Application.fetch_env!(:bdfr_browser, :base_directory)
|
||||
post_dir = Path.join([base_directory | Keyword.fetch!(args, :paths)])
|
||||
post_img = "#{post}*.{jpg,JPG,jpeg,JPEG,png,PNG,gif,GIF}"
|
||||
post_vid = "#{post}*.{mp4,MP4}"
|
||||
|
||||
%{
|
||||
images: [post_dir, post_img] |> Path.join() |> Path.wildcard() |> Enum.map(&media_path/1),
|
||||
videos: [post_dir, post_vid] |> Path.join() |> Path.wildcard() |> Enum.map(&media_path/1)
|
||||
}
|
||||
end
|
||||
|
||||
defp media_path(full_path) do
|
||||
base_directory = Application.fetch_env!(:bdfr_browser, :base_directory)
|
||||
String.replace(full_path, "#{base_directory}/", "/media/")
|
||||
end
|
||||
|
||||
defp mime_from_ext(path) do
|
||||
normalized_path = String.downcase(path)
|
||||
|
||||
case Path.extname(normalized_path) do
|
||||
".jpg" -> "image/jpeg"
|
||||
".jpeg" -> "image/jpeg"
|
||||
".png" -> "image/png"
|
||||
".gif" -> "image/gif"
|
||||
".mp4" -> "video/mp4"
|
||||
end
|
||||
end
|
||||
end
|
29
mix.exs
Normal file
29
mix.exs
Normal file
|
@ -0,0 +1,29 @@
|
|||
defmodule BdfrBrowser.MixProject do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[
|
||||
app: :bdfr_browser,
|
||||
version: "0.1.0",
|
||||
elixir: "~> 1.14",
|
||||
start_permanent: Mix.env() == :prod,
|
||||
deps: deps()
|
||||
]
|
||||
end
|
||||
|
||||
def application do
|
||||
[
|
||||
extra_applications: [:logger, :eex],
|
||||
mod: {BdfrBrowser.Application, []}
|
||||
]
|
||||
end
|
||||
|
||||
defp deps do
|
||||
[
|
||||
{:plug_cowboy, "~> 2.6"},
|
||||
{:jason, "~> 1.4"},
|
||||
{:earmark, "~> 1.4"},
|
||||
{:systemd, "~> 0.6"}
|
||||
]
|
||||
end
|
||||
end
|
16
mix.lock
Normal file
16
mix.lock
Normal file
|
@ -0,0 +1,16 @@
|
|||
%{
|
||||
"cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"},
|
||||
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
|
||||
"cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"},
|
||||
"earmark": {:hex, :earmark, "1.4.38", "ba8fda946c259c6e8f6759d3647d448e9216e2c0afed8c6ae7f8ce1f7072a497", [:mix], [{:earmark_parser, "~> 1.4.32", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "f938e30de4167e7d8f3bf588b01dc041138278dda1e5a13fb9ec89b43dd5ec7f"},
|
||||
"earmark_parser": {:hex, :earmark_parser, "1.4.32", "fa739a0ecfa34493de19426681b23f6814573faee95dfd4b4aafe15a7b5b32c6", [:mix], [], "hexpm", "b8b0dd77d60373e77a3d7e8afa598f325e49e8663a51bcc2b88ef41838cca755"},
|
||||
"enough": {:hex, :enough, "0.1.0", "0254710c52d324e2dadde54cb56fbb80a792c2eb285669b8379efd0752bf89f0", [:rebar3], [], "hexpm", "0460c7abda5f5e0ea592b12bc6976b8a5c4b96e42f332059cd396525374bf9a1"},
|
||||
"jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
|
||||
"mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"},
|
||||
"plug": {:hex, :plug, "1.14.2", "cff7d4ec45b4ae176a227acd94a7ab536d9b37b942c8e8fa6dfc0fff98ff4d80", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "842fc50187e13cf4ac3b253d47d9474ed6c296a8732752835ce4a86acdf68d13"},
|
||||
"plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"},
|
||||
"plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"},
|
||||
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
|
||||
"systemd": {:hex, :systemd, "0.6.2", "aaa24f1e3e6cb978c45369768b1abd766a0dbff637ed61254ca64797bcec9963", [:rebar3], [{:enough, "~> 0.1.0", [hex: :enough, repo: "hexpm", optional: false]}], "hexpm", "5062b911800c1ab05157c7bf9a9fbe23dd24c58891c87fd12d2e3ed8fc1708b8"},
|
||||
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
|
||||
}
|
194
mix.nix
Normal file
194
mix.nix
Normal file
|
@ -0,0 +1,194 @@
|
|||
{ lib, beamPackages, overrides ? (x: y: {}) }:
|
||||
|
||||
let
|
||||
buildRebar3 = lib.makeOverridable beamPackages.buildRebar3;
|
||||
buildMix = lib.makeOverridable beamPackages.buildMix;
|
||||
buildErlangMk = lib.makeOverridable beamPackages.buildErlangMk;
|
||||
|
||||
self = packages // (overrides self packages);
|
||||
|
||||
packages = with beamPackages; with self; {
|
||||
cowboy = buildErlangMk rec {
|
||||
name = "cowboy";
|
||||
version = "2.10.0";
|
||||
|
||||
src = fetchHex {
|
||||
pkg = "${name}";
|
||||
version = "${version}";
|
||||
sha256 = "0sqxqjdykxc2ai9cvkc0xjwkvr80z98wzlqlrd1z3iiw32vwrz9s";
|
||||
};
|
||||
|
||||
beamDeps = [ cowlib ranch ];
|
||||
};
|
||||
|
||||
cowboy_telemetry = buildRebar3 rec {
|
||||
name = "cowboy_telemetry";
|
||||
version = "0.4.0";
|
||||
|
||||
src = fetchHex {
|
||||
pkg = "${name}";
|
||||
version = "${version}";
|
||||
sha256 = "1pn90is3k9dq64wbijvzkqb6ldfqvwiqi7ymc8dx6ra5xv0vm63x";
|
||||
};
|
||||
|
||||
beamDeps = [ cowboy telemetry ];
|
||||
};
|
||||
|
||||
cowlib = buildRebar3 rec {
|
||||
name = "cowlib";
|
||||
version = "2.12.1";
|
||||
|
||||
src = fetchHex {
|
||||
pkg = "${name}";
|
||||
version = "${version}";
|
||||
sha256 = "1c4dgi8canscyjgddp22mjc69znvwy44wk3r7jrl2wvs6vv76fqn";
|
||||
};
|
||||
|
||||
beamDeps = [];
|
||||
};
|
||||
|
||||
earmark = buildMix rec {
|
||||
name = "earmark";
|
||||
version = "1.4.38";
|
||||
|
||||
src = fetchHex {
|
||||
pkg = "${name}";
|
||||
version = "${version}";
|
||||
sha256 = "0zzcslyv92gcp4zs3rd1vmw844s1q0fv127m7f7pszhnwh6y6f7r";
|
||||
};
|
||||
|
||||
beamDeps = [ earmark_parser ];
|
||||
};
|
||||
|
||||
earmark_parser = buildMix rec {
|
||||
name = "earmark_parser";
|
||||
version = "1.4.32";
|
||||
|
||||
src = fetchHex {
|
||||
pkg = "${name}";
|
||||
version = "${version}";
|
||||
sha256 = "0md7rhw1ix4fp31bql9scvl4jpijixczm2ky7mxffwq3srvxvc5q";
|
||||
};
|
||||
|
||||
beamDeps = [];
|
||||
};
|
||||
|
||||
enough = buildRebar3 rec {
|
||||
name = "enough";
|
||||
version = "0.1.0";
|
||||
|
||||
src = fetchHex {
|
||||
pkg = "${name}";
|
||||
version = "${version}";
|
||||
sha256 = "18gr9cvjar9rrmcj0crgwjb4np4adfbwcaxijajhwpjzvamwfq04";
|
||||
};
|
||||
|
||||
beamDeps = [];
|
||||
};
|
||||
|
||||
jason = buildMix rec {
|
||||
name = "jason";
|
||||
version = "1.4.0";
|
||||
|
||||
src = fetchHex {
|
||||
pkg = "${name}";
|
||||
version = "${version}";
|
||||
sha256 = "0891p2yrg3ri04p302cxfww3fi16pvvw1kh4r91zg85jhl87k8vr";
|
||||
};
|
||||
|
||||
beamDeps = [];
|
||||
};
|
||||
|
||||
mime = buildMix rec {
|
||||
name = "mime";
|
||||
version = "2.0.3";
|
||||
|
||||
src = fetchHex {
|
||||
pkg = "${name}";
|
||||
version = "${version}";
|
||||
sha256 = "0szzdfalafpawjrrwbrplhkgxjv8837mlxbkpbn5xlj4vgq0p8r7";
|
||||
};
|
||||
|
||||
beamDeps = [];
|
||||
};
|
||||
|
||||
plug = buildMix rec {
|
||||
name = "plug";
|
||||
version = "1.14.2";
|
||||
|
||||
src = fetchHex {
|
||||
pkg = "${name}";
|
||||
version = "${version}";
|
||||
sha256 = "04wdyv6nma74bj1m49vkm2bc5mjf8zclfg957fng8g71hw0wabw4";
|
||||
};
|
||||
|
||||
beamDeps = [ mime plug_crypto telemetry ];
|
||||
};
|
||||
|
||||
plug_cowboy = buildMix rec {
|
||||
name = "plug_cowboy";
|
||||
version = "2.6.1";
|
||||
|
||||
src = fetchHex {
|
||||
pkg = "${name}";
|
||||
version = "${version}";
|
||||
sha256 = "04v6xc4v741dr2y38j66fmcc4xc037dnaxzkj2vih6j53yif2dny";
|
||||
};
|
||||
|
||||
beamDeps = [ cowboy cowboy_telemetry plug ];
|
||||
};
|
||||
|
||||
plug_crypto = buildMix rec {
|
||||
name = "plug_crypto";
|
||||
version = "1.2.5";
|
||||
|
||||
src = fetchHex {
|
||||
pkg = "${name}";
|
||||
version = "${version}";
|
||||
sha256 = "0hnqgzc3zas7j7wycgnkkdhaji5farkqccy2n4p1gqj5ccfrlm16";
|
||||
};
|
||||
|
||||
beamDeps = [];
|
||||
};
|
||||
|
||||
ranch = buildRebar3 rec {
|
||||
name = "ranch";
|
||||
version = "1.8.0";
|
||||
|
||||
src = fetchHex {
|
||||
pkg = "${name}";
|
||||
version = "${version}";
|
||||
sha256 = "1rfz5ld54pkd2w25jadyznia2vb7aw9bclck21fizargd39wzys9";
|
||||
};
|
||||
|
||||
beamDeps = [];
|
||||
};
|
||||
|
||||
systemd = buildRebar3 rec {
|
||||
name = "systemd";
|
||||
version = "0.6.2";
|
||||
|
||||
src = fetchHex {
|
||||
pkg = "${name}";
|
||||
version = "${version}";
|
||||
sha256 = "1f082zydhgif5p8pzj4ii32j9p93psgrmgy7ax8v06hch08vjqjh";
|
||||
};
|
||||
|
||||
beamDeps = [ enough ];
|
||||
};
|
||||
|
||||
telemetry = buildRebar3 rec {
|
||||
name = "telemetry";
|
||||
version = "1.2.1";
|
||||
|
||||
src = fetchHex {
|
||||
pkg = "${name}";
|
||||
version = "${version}";
|
||||
sha256 = "1mgyx9zw92g6w8fp9pblm3b0bghwxwwcbslrixq23ipzisfwxnfs";
|
||||
};
|
||||
|
||||
beamDeps = [];
|
||||
};
|
||||
};
|
||||
in self
|
||||
|
23
priv/templates/http/_comment.eex
Normal file
23
priv/templates/http/_comment.eex
Normal file
|
@ -0,0 +1,23 @@
|
|||
<div style="padding-left: <%= level * 5%>px;">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<blockquote class="blockquote mb-0">
|
||||
<%= if comment.author == "AutoModerator" do %>
|
||||
<p><small>Hidden AutoModerator comment</small></p>
|
||||
<% else %>
|
||||
<%= Earmark.as_html!(comment.body) %>
|
||||
<% end %>
|
||||
|
||||
<footer class="blockquote-footer">
|
||||
<%= comment.author %>,
|
||||
<small><%= trunc(comment.created_utc) |> DateTime.from_unix!() |> DateTime.to_iso8601() %></small>
|
||||
</footer>
|
||||
</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
|
||||
<%= for reply <- comment.replies do %>
|
||||
<%= EEx.eval_file(comment_template, comment: reply, level: level + 1, comment_template: comment_template) %>
|
||||
<% end %>
|
26
priv/templates/http/index.eex
Normal file
26
priv/templates/http/index.eex
Normal file
|
@ -0,0 +1,26 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
<title>BDFR Browser</title>
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-KK94CHFLLe+nY2dmCWGMq91rCGa5gtU4mk92HdvYe+M/SXH301p5ILy+dN9+nJOZ" crossorigin="anonymous">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ENjdO4Dr2bkBIFxQpeoTz1HIcje39Wm4jDKdf19U8gI4ddQ3GYNS7NTKfAdVQSZe" crossorigin="anonymous"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<h2>Archived Subreddits</h2>
|
||||
|
||||
<div class="row text-center">
|
||||
<div class="d-grid gap-2 col-12 mx-auto">
|
||||
<%= for subreddit <- subreddits do %>
|
||||
<a class="btn btn-outline-secondary btn-lg" href="/r/<%= subreddit %>" role="button"><%= subreddit %></a>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
66
priv/templates/http/post.eex
Normal file
66
priv/templates/http/post.eex
Normal file
|
@ -0,0 +1,66 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
<title>BDFR Browser</title>
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-KK94CHFLLe+nY2dmCWGMq91rCGa5gtU4mk92HdvYe+M/SXH301p5ILy+dN9+nJOZ" crossorigin="anonymous">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ENjdO4Dr2bkBIFxQpeoTz1HIcje39Wm4jDKdf19U8gI4ddQ3GYNS7NTKfAdVQSZe" crossorigin="anonymous"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<h2><a href="<%= post.url %>"><%= post.title %></a></h2>
|
||||
|
||||
<p>
|
||||
<small><a href="https://reddit.com/user/<%= post.author %>"><%= post.author %></a></small>
|
||||
-
|
||||
<small><%= trunc(post.created_utc) |> DateTime.from_unix!() |> DateTime.to_iso8601() %></small>
|
||||
-
|
||||
<a href="https://reddit.com<%= post.permalink %>">Open reddit</a>
|
||||
</p>
|
||||
|
||||
<%= unless Enum.empty?(media.images) do %>
|
||||
<div id="carouselImages" class="carousel slide">
|
||||
<div class="carousel-inner">
|
||||
<%= for {img, i} <- Enum.with_index(media.images) do %>
|
||||
<div class="carousel-item <%= if i == 0, do: "active" %>">
|
||||
<img src="<%= img %>" class="d-block w-100">
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<button class="carousel-control-prev" type="button" data-bs-target="#carouselImages" data-bs-slide="prev">
|
||||
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
|
||||
<span class="visually-hidden">Previous</span>
|
||||
</button>
|
||||
<button class="carousel-control-next" type="button" data-bs-target="#carouselImages" data-bs-slide="next">
|
||||
<span class="carousel-control-next-icon" aria-hidden="true"></span>
|
||||
<span class="visually-hidden">Next</span>
|
||||
</button>
|
||||
</div>
|
||||
<br>
|
||||
<% end %>
|
||||
|
||||
<%= unless Enum.empty?(media.videos) do %>
|
||||
<div class="row">
|
||||
<%= for video <- media.videos do %>
|
||||
<video controls>
|
||||
<source src="<%= video %>" type="video/mp4">
|
||||
</video>
|
||||
<% end %>
|
||||
</div>
|
||||
<br>
|
||||
<% end %>
|
||||
|
||||
<%= for comment <- post.comments do %>
|
||||
<div class="row">
|
||||
<%= EEx.eval_file(comment_template, comment: comment, level: 0, comment_template: comment_template) %>
|
||||
</div>
|
||||
<br>
|
||||
<% end %>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
26
priv/templates/http/subreddit.eex
Normal file
26
priv/templates/http/subreddit.eex
Normal file
|
@ -0,0 +1,26 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
<title>BDFR Browser</title>
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-KK94CHFLLe+nY2dmCWGMq91rCGa5gtU4mk92HdvYe+M/SXH301p5ILy+dN9+nJOZ" crossorigin="anonymous">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ENjdO4Dr2bkBIFxQpeoTz1HIcje39Wm4jDKdf19U8gI4ddQ3GYNS7NTKfAdVQSZe" crossorigin="anonymous"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<h2><%= subreddit %></h2>
|
||||
|
||||
<div class="row text-center">
|
||||
<div class="d-grid gap-2 col-12 mx-auto">
|
||||
<%= for date <- dates do %>
|
||||
<a class="btn btn-outline-secondary btn-lg" href="/r/<%= subreddit %>/<%= date %>" role="button"><%= date %></a>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
33
priv/templates/http/subreddit_posts.eex
Normal file
33
priv/templates/http/subreddit_posts.eex
Normal file
|
@ -0,0 +1,33 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
<title>BDFR Browser</title>
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-KK94CHFLLe+nY2dmCWGMq91rCGa5gtU4mk92HdvYe+M/SXH301p5ILy+dN9+nJOZ" crossorigin="anonymous">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ENjdO4Dr2bkBIFxQpeoTz1HIcje39Wm4jDKdf19U8gI4ddQ3GYNS7NTKfAdVQSZe" crossorigin="anonymous"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<h2><%= subreddit %></h2>
|
||||
|
||||
<div class="row text-center">
|
||||
<div class="d-grid gap-2 col-12 mx-auto">
|
||||
<%= for post <- posts do %>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><a href="/r/<%= subreddit %>/<%= date %>/<%= post.filename %>"><%= post.title %></a></h5>
|
||||
<h6 class="card-subtitle mb-2 text-body-secondary">
|
||||
<%= post.num_comments %> comment(s) - <%= trunc(post.created_utc) |> DateTime.from_unix!() |> DateTime.to_iso8601() %>
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
8
test/bdfr_browser_test.exs
Normal file
8
test/bdfr_browser_test.exs
Normal file
|
@ -0,0 +1,8 @@
|
|||
defmodule BdfrBrowserTest do
|
||||
use ExUnit.Case
|
||||
doctest BdfrBrowser
|
||||
|
||||
test "greets the world" do
|
||||
assert BdfrBrowser.hello() == :world
|
||||
end
|
||||
end
|
1
test/test_helper.exs
Normal file
1
test/test_helper.exs
Normal file
|
@ -0,0 +1 @@
|
|||
ExUnit.start()
|
Reference in a new issue