From e03c759bb78571a4d8b0bf3da763660a1ece3038 Mon Sep 17 00:00:00 2001 From: Daniel Kempkens Date: Thu, 18 May 2023 00:13:55 +0200 Subject: [PATCH] Initial commit --- .envrc | 2 + .formatter.exs | 8 + .gitignore | 39 +++++ LICENSE | 5 + README.md | 10 ++ config/config.exs | 6 + config/runtime.exs | 6 + flake.lock | 64 ++++++++ flake.nix | 47 ++++++ lib/bdfr_browser/application.ex | 21 +++ lib/bdfr_browser/http/plug.ex | 155 +++++++++++++++++++ mix.exs | 29 ++++ mix.lock | 16 ++ mix.nix | 194 ++++++++++++++++++++++++ priv/templates/http/_comment.eex | 23 +++ priv/templates/http/index.eex | 26 ++++ priv/templates/http/post.eex | 66 ++++++++ priv/templates/http/subreddit.eex | 26 ++++ priv/templates/http/subreddit_posts.eex | 33 ++++ test/bdfr_browser_test.exs | 8 + test/test_helper.exs | 1 + 21 files changed, 785 insertions(+) create mode 100644 .envrc create mode 100644 .formatter.exs create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 config/config.exs create mode 100644 config/runtime.exs create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 lib/bdfr_browser/application.ex create mode 100644 lib/bdfr_browser/http/plug.ex create mode 100644 mix.exs create mode 100644 mix.lock create mode 100644 mix.nix create mode 100644 priv/templates/http/_comment.eex create mode 100644 priv/templates/http/index.eex create mode 100644 priv/templates/http/post.eex create mode 100644 priv/templates/http/subreddit.eex create mode 100644 priv/templates/http/subreddit_posts.eex create mode 100644 test/bdfr_browser_test.exs create mode 100644 test/test_helper.exs diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..b3efbde --- /dev/null +++ b/.envrc @@ -0,0 +1,2 @@ +use flake +project elixir diff --git a/.formatter.exs b/.formatter.exs new file mode 100644 index 0000000..b717f1d --- /dev/null +++ b/.formatter.exs @@ -0,0 +1,8 @@ +[ + inputs: [ + "mix.exs", + "{config,lib,test}/**/*.{ex,exs}" + ], + line_length: 120, + import_deps: [:plug] +] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ce63943 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4957868 --- /dev/null +++ b/LICENSE @@ -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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..900c22e --- /dev/null +++ b/README.md @@ -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 diff --git a/config/config.exs b/config/config.exs new file mode 100644 index 0000000..05f2d03 --- /dev/null +++ b/config/config.exs @@ -0,0 +1,6 @@ +import Config + +config :logger, + backends: [], + handle_otp_reports: false, + handle_sasl_reports: false diff --git a/config/runtime.exs b/config/runtime.exs new file mode 100644 index 0000000..82a7c58 --- /dev/null +++ b/config/runtime.exs @@ -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")) diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..b8fa6a9 --- /dev/null +++ b/flake.lock @@ -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 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..bff589b --- /dev/null +++ b/flake.nix @@ -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; }; + }; + }; + }; +} diff --git a/lib/bdfr_browser/application.ex b/lib/bdfr_browser/application.ex new file mode 100644 index 0000000..7e20288 --- /dev/null +++ b/lib/bdfr_browser/application.ex @@ -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 diff --git a/lib/bdfr_browser/http/plug.ex b/lib/bdfr_browser/http/plug.ex new file mode 100644 index 0000000..dcba710 --- /dev/null +++ b/lib/bdfr_browser/http/plug.ex @@ -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 diff --git a/mix.exs b/mix.exs new file mode 100644 index 0000000..a328b08 --- /dev/null +++ b/mix.exs @@ -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 diff --git a/mix.lock b/mix.lock new file mode 100644 index 0000000..9376f51 --- /dev/null +++ b/mix.lock @@ -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"}, +} diff --git a/mix.nix b/mix.nix new file mode 100644 index 0000000..421aa20 --- /dev/null +++ b/mix.nix @@ -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 + diff --git a/priv/templates/http/_comment.eex b/priv/templates/http/_comment.eex new file mode 100644 index 0000000..cdb57ae --- /dev/null +++ b/priv/templates/http/_comment.eex @@ -0,0 +1,23 @@ +
+
+
+
+ <%= if comment.author == "AutoModerator" do %> +

Hidden AutoModerator comment

+ <% else %> + <%= Earmark.as_html!(comment.body) %> + <% end %> + +
+ <%= comment.author %>, + <%= trunc(comment.created_utc) |> DateTime.from_unix!() |> DateTime.to_iso8601() %> +
+
+
+
+
+
+ +<%= for reply <- comment.replies do %> + <%= EEx.eval_file(comment_template, comment: reply, level: level + 1, comment_template: comment_template) %> +<% end %> diff --git a/priv/templates/http/index.eex b/priv/templates/http/index.eex new file mode 100644 index 0000000..41d9daf --- /dev/null +++ b/priv/templates/http/index.eex @@ -0,0 +1,26 @@ + + + + + + + BDFR Browser + + + + + + +
+

Archived Subreddits

+ +
+
+ <%= for subreddit <- subreddits do %> + <%= subreddit %> + <% end %> +
+
+
+ + diff --git a/priv/templates/http/post.eex b/priv/templates/http/post.eex new file mode 100644 index 0000000..65611ab --- /dev/null +++ b/priv/templates/http/post.eex @@ -0,0 +1,66 @@ + + + + + + + BDFR Browser + + + + + + +
+

<%= post.title %>

+ +

+ <%= post.author %> + - + <%= trunc(post.created_utc) |> DateTime.from_unix!() |> DateTime.to_iso8601() %> + - + Open reddit +

+ + <%= unless Enum.empty?(media.images) do %> + +
+ <% end %> + + <%= unless Enum.empty?(media.videos) do %> +
+ <%= for video <- media.videos do %> + + <% end %> +
+
+ <% end %> + + <%= for comment <- post.comments do %> +
+ <%= EEx.eval_file(comment_template, comment: comment, level: 0, comment_template: comment_template) %> +
+
+ <% end %> +
+ + diff --git a/priv/templates/http/subreddit.eex b/priv/templates/http/subreddit.eex new file mode 100644 index 0000000..06a9b1f --- /dev/null +++ b/priv/templates/http/subreddit.eex @@ -0,0 +1,26 @@ + + + + + + + BDFR Browser + + + + + + +
+

<%= subreddit %>

+ +
+
+ <%= for date <- dates do %> + <%= date %> + <% end %> +
+
+
+ + diff --git a/priv/templates/http/subreddit_posts.eex b/priv/templates/http/subreddit_posts.eex new file mode 100644 index 0000000..e94be50 --- /dev/null +++ b/priv/templates/http/subreddit_posts.eex @@ -0,0 +1,33 @@ + + + + + + + BDFR Browser + + + + + + +
+

<%= subreddit %>

+ +
+
+ <%= for post <- posts do %> +
+
+
<%= post.title %>
+
+ <%= post.num_comments %> comment(s) - <%= trunc(post.created_utc) |> DateTime.from_unix!() |> DateTime.to_iso8601() %> +
+
+
+ <% end %> +
+
+
+ + diff --git a/test/bdfr_browser_test.exs b/test/bdfr_browser_test.exs new file mode 100644 index 0000000..af7cc12 --- /dev/null +++ b/test/bdfr_browser_test.exs @@ -0,0 +1,8 @@ +defmodule BdfrBrowserTest do + use ExUnit.Case + doctest BdfrBrowser + + test "greets the world" do + assert BdfrBrowser.hello() == :world + end +end diff --git a/test/test_helper.exs b/test/test_helper.exs new file mode 100644 index 0000000..869559e --- /dev/null +++ b/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start()