diff --git a/.gitignore b/.gitignore index 0907e95..40bc87f 100644 --- a/.gitignore +++ b/.gitignore @@ -40,7 +40,7 @@ npm-debug.log /.elixir_ls # blog content repo folder -/priv/content/* +/priv/content/ # dev TODO.md diff --git a/lib/home73k/application.ex b/lib/home73k/application.ex index ecc3a44..ea69fe1 100644 --- a/lib/home73k/application.ex +++ b/lib/home73k/application.ex @@ -12,9 +12,10 @@ defmodule Home73k.Application do # Start the PubSub system {Phoenix.PubSub, name: Home73k.PubSub}, # Start the Endpoint (http/https) - Home73kWeb.Endpoint + Home73kWeb.Endpoint, # Start a worker by calling: Home73k.Worker.start_link(arg) # {Home73k.Worker, arg} + Home73k.Blog ] # See https://hexdocs.pm/elixir/Supervisor.html diff --git a/lib/home73k/blog.ex b/lib/home73k/blog.ex new file mode 100644 index 0000000..8f25f1c --- /dev/null +++ b/lib/home73k/blog.ex @@ -0,0 +1,116 @@ +defmodule Home73k.Blog do + use GenServer + + # + # Setup + # + Application.ensure_all_started(:earmark) + + @repo Home73k.Repo.get() + + # + # Client + # + + def start_link(_opts) do + GenServer.start_link(__MODULE__, %{posts: [], tags: [], files: []}, name: __MODULE__) + end + + def get_posts() do + GenServer.call(__MODULE__, :get_posts) + end + + def get_files() do + GenServer.call(__MODULE__, :get_files) + end + + # def push(pid, element) do + # GenServer.cast(pid, {:push, element}) + # end + + # def pop(pid) do + # GenServer.call(pid, :pop) + # end + + # def put(server, key, value) do + # GenServer.cast(server, {:put, key, value}) + # end + + # def get(server, key) do + # GenServer.call(server, {:get, key}) + # end + + # + # Server + # + + @impl true + def init(state) do + repo_all_paths = @repo.path |> Path.join("**/*.*") |> Path.wildcard() + repo_post_paths = repo_all_paths |> Enum.filter(fn f -> String.ends_with?(f, ".md") end) + repo_file_paths = repo_all_paths |> Enum.filter(fn f -> !String.ends_with?(f, ".md") end) + {:ok, %{state | posts: repo_post_paths, files: repo_file_paths}} + end + + @impl true + def handle_call(:get_posts, _from, %{posts: posts} = state) do + {:reply, posts, state} + end + + @impl true + def handle_call(:get_files, _from, %{files: files} = state) do + {:reply, files, state} + end + + # @impl true + # def handle_call(:pop, _from, [head | tail]) do + # {:reply, head, tail} + # end + + # @impl true + # def handle_cast({:push, element}, state) do + # {:noreply, [element | state]} + # end + + # def handle_cast({:put, key, value}, state) do + # {:noreply, Map.put(state, key, value)} + # end + + # def handle_call({:get, key}, _from, state) do + # {:reply, Map.fetch!(state, key), state} + # end + + # alias Home73k.Blog.Post + + # @posts_dir Application.compile_env(:home73k, :blog_posts_dir, "posts") + + # posts_paths = + # @posts_dir + # |> Path.join("**/*.md") + # |> Path.wildcard() + # |> Enum.sort() + + # posts = + # for post_path <- posts_paths do + # @external_resource Path.relative_to_cwd(post_path) + # Post.parse!(post_path) + # end + + # @posts Enum.sort_by(posts, & &1.date, {:desc, Date}) + + # @tags posts |> Enum.flat_map(& &1.tags) |> Enum.uniq() |> Enum.sort() + + # def list_posts, do: @posts + # def list_tags, do: @tags + + # defmodule NotFoundError do + # defexception [:message, plug_status: 404] + # end + + # def get_posts_by_tag!(tag) do + # case Enum.filter(list_posts(), &(tag in &1.tags)) do + # [] -> raise NotFoundError, "posts with tag=#{tag} not found" + # posts -> posts + # end + # end +end diff --git a/lib/home73k/repo.ex b/lib/home73k/repo.ex new file mode 100644 index 0000000..23bd676 --- /dev/null +++ b/lib/home73k/repo.ex @@ -0,0 +1,43 @@ +defmodule Home73k.Repo do + @repo_url Application.compile_env(:home73k, [:content_repo, :url], nil) + @content_path Application.compile_env(:home73k, [:content_repo, :path], "./priv/content") + |> Path.expand() + + ###################################################################### + # REPO SETUP + ###################################################################### + def get do + @content_path |> File.exists?() |> init_repo() + end + + def content_path, do: @content_path + + # If content path is absent, clone repo if url is present + defp init_repo(false) do + if @repo_url do + {:ok, repo} = Git.clone([@repo_url, @content_path]) + repo + else + nil + end + end + + # If content path exists, check for .git child and pull or return nil + defp init_repo(true) do + if git_data_dir_ok?() do + Git.new(@content_path) + else + nil + end + end + + defp git_data_dir_ok? do + git_data_dir = Path.join(@content_path, ".git") + File.exists?(git_data_dir) && File.dir?(git_data_dir) + end + + ###################################################################### + # REPO OPERATIONS + ###################################################################### + def update(repo), do: Git.pull(repo) +end diff --git a/lib/home73k_web/controllers/blog_file_controller.ex b/lib/home73k_web/controllers/blog_file_controller.ex new file mode 100644 index 0000000..dc9b910 --- /dev/null +++ b/lib/home73k_web/controllers/blog_file_controller.ex @@ -0,0 +1,30 @@ +defmodule Home73kWeb.BlogFileController do + use Home73kWeb, :controller + + @moduledoc """ + This controller handles path requests that didn't match an existing route, + including the main Plug.Static for the / root path. + + To handle this situation, we'll check if the requested resource exists as + a file under the blog content repo folder, by querying the Blog genserve. + + If it exists, we'll redirect to the blog content static path under /_ + Otherwise, we'll return 404 not found. + """ + + alias Home73k.Repo + alias Home73k.Blog + + def index(conn, _params) do + # What would be the content path of this requested resource? + content_path = Repo.content_path() |> Path.join(conn.request_path) + + # Check if it exists in the Blog's known files + Blog.get_files() + |> Enum.member?(content_path) + |> case do + true -> redirect(conn, to: "/_#{conn.request_path}") + false -> send_resp(conn, 404, "not found") + end + end +end diff --git a/lib/home73k_web/endpoint.ex b/lib/home73k_web/endpoint.ex index 3e9a938..1559f7c 100644 --- a/lib/home73k_web/endpoint.ex +++ b/lib/home73k_web/endpoint.ex @@ -26,6 +26,18 @@ defmodule Home73kWeb.Endpoint do gzip: true, only: ~w(css fonts images js favicon.ico robots.txt DF185CEE29A3D443_public_key.asc) + # Blog static path handler + if File.dir?(Home73k.Repo.content_path()) do + plug Plug.Static, + at: "/_", + from: Home73k.Repo.content_path(), + gzip: false, + only: + Home73k.Repo.content_path() + |> File.ls!() + |> Enum.filter(fn f -> !String.starts_with?(f, ".") end) + end + # Code reloading can be explicitly enabled under the # :code_reloader configuration of your endpoint. if code_reloading? do diff --git a/lib/home73k_web/router.ex b/lib/home73k_web/router.ex index a2bd4cc..ef374be 100644 --- a/lib/home73k_web/router.ex +++ b/lib/home73k_web/router.ex @@ -43,4 +43,9 @@ defmodule Home73kWeb.Router do live_dashboard "/dashboard", metrics: Home73kWeb.Telemetry end end + + # Wildcard path for handling Blog Files from repo + scope "/", Home73kWeb do + get "/*path", BlogFileController, :index + end end