defmodule BdfrBrowser.HTTP.Plug do use Plug.Router alias BdfrBrowser.{Chat, Comment, Importer, Message, Repo, Post, Subreddit} plug :match plug :dispatch get "/" do config_file = Application.fetch_env!(:bdfr_browser, :config_file) archived_subreddits = Enum.sort_by(config_file["archived_subreddits"], &String.downcase/1) archived_users = Enum.sort_by(config_file["archived_users"], &String.downcase/1) simulated_multireddits = Enum.sort_by(config_file["simulated_multireddits"], fn {t, _} -> String.downcase(t) end) saved_searches = Enum.sort_by(config_file["saved_searches"], &String.downcase/1) tpl_args = [ subreddits: archived_subreddits, users: archived_users, multireddits: simulated_multireddits, searches: saved_searches ] content = render_template("index", tpl_args) conn |> put_resp_header("content-type", "text/html; charset=utf-8") |> send_resp(200, content) end get "/all/subreddits" do config_file = Application.fetch_env!(:bdfr_browser, :config_file) archived_subreddits = MapSet.new(config_file["archived_subreddits"]) tpl_args = [subreddits: Subreddit.names() |> Repo.all(), archived_subreddits: archived_subreddits] content = render_template("all_subreddits", tpl_args) conn |> put_resp_header("content-type", "text/html; charset=utf-8") |> send_resp(200, content) end get "/r/:subreddit" do subreddit_record = Repo.get_by(Subreddit, name: subreddit) tpl_args = [ subreddit: subreddit, dates: subreddit_record |> Post.date_listing() |> Repo.all(), statistics: subreddit |> Subreddit.statistics() |> Repo.all() ] content = render_template("subreddit", tpl_args) conn |> put_resp_header("content-type", "text/html; charset=utf-8") |> send_resp(200, content) end get "/r/:subreddit/:date" do subreddit_record = Repo.get_by(Subreddit, name: subreddit) tpl_args = [ subreddit: subreddit, date: date, posts: subreddit_record |> Post.during_month(date) |> Repo.all() ] content = render_template("subreddit_posts", tpl_args) conn |> put_resp_header("content-type", "text/html; charset=utf-8") |> send_resp(200, content) end get "/r/:subreddit/:date/:id" do post_record = id |> Post.get_full() |> Repo.one() tpl_args = [ subreddit: subreddit, date: date, post: post_record, media: post_media(post_record.filename, paths: [subreddit, date]), comment_template: Application.app_dir(:bdfr_browser, "priv/templates/http/_comment.eex") ] content = render_template("post", tpl_args) conn |> put_resp_header("content-type", "text/html; charset=utf-8") |> send_resp(200, content) end get "/m/:subreddits" do subreddit_names = String.split(subreddits, "+") subreddit_records = Subreddit.multiple_names(subreddit_names) |> Repo.all() tpl_args = [ subreddit: subreddit_names, dates: subreddit_records |> Post.date_listing() |> Repo.all(), statistics: subreddit_names |> Subreddit.statistics() |> Repo.all() ] content = render_template("subreddit", tpl_args) conn |> put_resp_header("content-type", "text/html; charset=utf-8") |> send_resp(200, content) end get "/m/:subreddits/:date" do subreddit_names = String.split(subreddits, "+") subreddit_records = Subreddit.multiple_names(subreddit_names) |> Repo.all() tpl_args = [ subreddit: subreddit_names, date: date, posts: subreddit_records |> Post.during_month(date) |> Repo.all() ] content = render_template("subreddit_posts", tpl_args) conn |> put_resp_header("content-type", "text/html; charset=utf-8") |> send_resp(200, content) end get "/s/:subreddits/most_comments" do subreddit_names = String.split(subreddits, "+") subreddit_records = Subreddit.multiple_names(subreddit_names) |> Repo.all() {:ok, range_start} = Date.from_iso8601("1970-01-01") range_end = Date.utc_today() tpl_args = [ subreddit: subreddit_names, date: "Most Comments", posts: subreddit_records |> Post.during_range(range_start, range_end, :most_comments) |> Post.with_comments(1) |> Repo.all() ] content = render_template("subreddit_posts", tpl_args) conn |> put_resp_header("content-type", "text/html; charset=utf-8") |> send_resp(200, content) end get "/chats" do tpl_args = [chats: Chat.listing() |> Repo.all()] content = render_template("chats", tpl_args) conn |> put_resp_header("content-type", "text/html; charset=utf-8") |> send_resp(200, content) end get "/chats/:id" do chat_record = Repo.get(Chat, id) tpl_args = [ chat: chat_record, messages: chat_record |> Message.listing() |> Repo.all() ] content = render_template("chat", tpl_args) conn |> put_resp_header("content-type", "text/html; charset=utf-8") |> send_resp(200, content) end get "/user/:name" do tpl_args = [ name: name, posts: name |> Post.by_author() |> Repo.all(), comments: name |> Comment.by_author() |> Repo.all(), chats: name |> Chat.by_author() |> Repo.all() ] content = render_template("user", tpl_args) conn |> put_resp_header("content-type", "text/html; charset=utf-8") |> send_resp(200, content) end get "/search" do conn = Plug.Conn.fetch_query_params(conn) params = conn.query_params search = params["search"] subreddits = params["subreddit"] tpl_args = [ search: search, posts: search |> Post.search(subreddits) |> Repo.all(), comments: search |> Comment.search(subreddits) |> Repo.all() ] content = render_template("search", tpl_args) conn |> put_resp_header("content-type", "text/html; charset=utf-8") |> send_resp(200, content) end get "/static/*path" do url_path = Path.join(path) file_path = Application.app_dir(:bdfr_browser, Path.join("priv/static", url_path)) if File.exists?(file_path) do {:ok, file} = File.read(file_path) conn |> put_resp_header("content-type", mime_from_ext(file_path)) |> send_resp(200, file) else send_resp(conn, 404, "Not Found") end end get "/media/*path" do base_directory = Application.fetch_env!(:bdfr_browser, :base_directory) joined_path = Path.join(path) file_path = Path.join([base_directory, joined_path]) if File.exists?(file_path) do {:ok, media} = File.read(file_path) conn |> put_resp_header("content-type", mime_from_ext(file_path)) |> send_resp(200, media) else send_resp(conn, 404, "Not Found") end end get "/chat_media/*path" do chat_directory = Application.fetch_env!(:bdfr_browser, :chat_directory) joined_path = Path.join(path) file_path = Path.join([chat_directory, "images", joined_path]) if File.exists?(file_path) do {:ok, media} = File.read(file_path) conn |> put_resp_header("content-type", mime_from_ext(file_path)) |> send_resp(200, media) else send_resp(conn, 404, "Not Found") end end post "/_import" do :ok = Importer.background_import() send_resp(conn, 200, "IMPORTING") end post "/_import_changes" do :ok = Importer.background_import_changes() send_resp(conn, 200, "IMPORTING CHANGES") end post "/_reload" do config_file = YamlElixir.read_from_file!(System.fetch_env!("BDFR_BROWSER_CONFIG_FILE")) :ok = Application.put_env(:bdfr_browser, :config_file, config_file) send_resp(conn, 200, "RELOAD") end post "/_cleanup" do _ = Importer.cleanup_messages() send_resp(conn, 200, "CLEANED UP") end get "/_ping" do send_resp(conn, 200, "PONG") end match _ do send_resp(conn, 404, "Not Found") end # Helper defp render_template(name, args) do tpl_file = Application.app_dir(:bdfr_browser, "priv/templates/http/application.eex") embedded_tpl = Application.app_dir(:bdfr_browser, "priv/templates/http/#{name}.eex") EEx.eval_file(tpl_file, embedded_template: embedded_tpl, embedded_args: args) 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,webp,WEBP}" post_vid = "#{post}*.{mp4,MP4,webm,WEBM}" %{ images: post_media_for_type(post_dir, post_img), videos: post_media_for_type(post_dir, post_vid) } end defp post_media_for_type(post_dir, post_type) do [post_dir, post_type] |> Path.join() |> Path.wildcard() |> Enum.map(&media_path/1) |> Enum.sort() end defp media_path(full_path) do base_directory = Application.fetch_env!(:bdfr_browser, :base_directory) full_path |> String.replace("#{base_directory}/", "/media/") |> String.split("/") |> Enum.map(fn p -> URI.encode(p, &URI.char_unreserved?/1) end) |> Enum.join("/") 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" ".webp" -> "image/webp" ".webm" -> "video/webm" ".js" -> "text/javascript" ".css" -> "text/css" ".svg" -> "image/svg+xml" ".woff" -> "font/woff" ".woff2" -> "font/woff2" end end end