diff --git a/lib/chubi/content.ex b/lib/chubi/content.ex index 11b8533..67605ae 100644 --- a/lib/chubi/content.ex +++ b/lib/chubi/content.ex @@ -211,7 +211,7 @@ defmodule Chubi.Content do end defp parse_content(%{"content" => content} = params) do - format = Application.get_env(:chubi, :post_format) || "html" + format = params["format"] || Application.get_env(:chubi, :post_format) || "markdown" parser = Keyword.get(@content_parser_map, String.to_atom(format)) attrs = parser.parse(content, params) Map.merge(params, attrs) diff --git a/lib/chubi/content_file_store.ex b/lib/chubi/content_file_store.ex new file mode 100644 index 0000000..3af5cba --- /dev/null +++ b/lib/chubi/content_file_store.ex @@ -0,0 +1,121 @@ +defmodule Chubi.ContentFileStore do + alias Chubi.Content.Post + alias Chubi.Content.Page + @content_dir "./priv/content" + + def content_directory(suffix \\ "") do + @content_dir + |> Path.join(suffix) + |> Path.absname() + end + + def ensure_directory(path) do + if File.dir?(path) do + path + else + File.mkdir_p!(path) + path + end + end + + defp directory(post) do + case post do + %Post{} -> + ensure_directory(content_directory("posts")) + + %Page{} -> + ensure_directory(content_directory("pages")) + + _ -> + nil + end + end + + defp extension(post) do + case post.format do + "markdown" -> "md" + _ -> "json" + end + end + + defp content(%Post{} = post) do + case post.format do + "markdown" -> + post.content + + _ -> + post + |> Map.drop([:__meta__, :__struct__]) + |> Map.merge(%{ + categories: Enum.map(post.categories, & &1.name), + tags: Enum.map(post.tags, & &1.name) + }) + |> Jason.encode!() + end + end + + defp content(%Page{} = post) do + case post.format do + "markdown" -> + post.content + + _ -> + post + |> Map.drop([:__meta__, :__struct__]) + |> Jason.encode!() + end + end + + defp file_name(post) do + date = Timex.format!(post.date || post.inserted_at, "{YYYY}-{0M}-{0D}") + "#{date}-#{post.slug}.#{extension(post)}" + end + + defp file_path(post) do + Path.join(directory(post), file_name(post)) + end + + def write(post) do + File.write(file_path(post), content(post), [:write]) + end + + def write_mem(post) do + {file_name(post), content(post)} + end + + def delete(post) do + File.rm(file_path(post)) + end + + def read_all_posts() do + ensure_directory(content_directory("/posts")) + |> read_directory + end + + def read_all_pages() do + ensure_directory(content_directory("/pages")) + |> read_directory + end + + def read_directory(path) do + with {:ok, files} <- File.ls(path) do + Enum.map(files, fn file -> + Path.join(path, file) + |> read_file + end) + |> Enum.filter(&(not is_nil(&1))) + else + _ -> [] + end + end + + def read_file(file_path) do + with {:ok, content} <- File.read(file_path) do + case Path.extname(file_path) do + ".md" -> %{"content" => content, "format" => "markdown"} + ".json" -> Jason.decode!(content) + _ -> nil + end + end + end +end diff --git a/lib/chubi_web.ex b/lib/chubi_web.ex index e619b37..2bc91d6 100644 --- a/lib/chubi_web.ex +++ b/lib/chubi_web.ex @@ -57,7 +57,7 @@ defmodule ChubiWeb do import Plug.Conn import ChubiWeb.Gettext - alias ChubiWeb.Router.Helpers, as: Routes + alias ChubiWeb.AdminRouter.Helpers, as: Routes end end @@ -86,7 +86,7 @@ defmodule ChubiWeb do import ChubiWeb.Admin.InputHelpers import ChubiWeb.Admin.ViewHelpers import ChubiWeb.Gettext - alias ChubiWeb.Router.Helpers, as: Routes + alias ChubiWeb.AdminRouter.Helpers, as: Routes end end diff --git a/lib/chubi_web/admin_router.ex b/lib/chubi_web/admin_router.ex new file mode 100644 index 0000000..28c683f --- /dev/null +++ b/lib/chubi_web/admin_router.ex @@ -0,0 +1,46 @@ +defmodule ChubiWeb.AdminRouter do + use ChubiWeb, :router + + pipeline :browser do + plug(:accepts, ["html"]) + plug(:fetch_session) + plug(:fetch_flash) + plug(:protect_from_forgery) + plug(:put_secure_browser_headers) + end + + pipeline :admin do + plug(:put_layout, {ChubiWeb.Admin.LayoutView, "app.html"}) + plug(BasicAuth, use_config: {:chubi, :auth_config}) + plug(ChubiWeb.Plugs.PutSiteSetting) + plug(ChubiWeb.Plugs.LoadSiteParams) + end + + pipeline :preview do + plug(BasicAuth, use_config: {:chubi, :auth_config}) + end + + pipeline :app do + plug(ChubiWeb.Plugs.PutSiteSetting) + plug(ChubiWeb.Plugs.LoadSiteParams) + # put this at end of pipeline + plug(ChubiWeb.Plugs.PutSiteLayout) + end + + scope "/", ChubiWeb.Admin, path: "/admin" do + pipe_through([:browser, :admin]) + get("/", PostController, :index) + resources("/tags", TagController) + resources("/categories", CategoryController) + resources("/posts", PostController) + resources("/pages", PageController) + + post("/upload", UploadController, :create) + delete("/upload", UploadController, :delete) + + get("/settings/set_theme", SettingController, :set_theme) + get("/settings/export", SettingController, :export_content) + get("/settings/select_import", SettingController, :select_import) + post("/settings/import", SettingController, :import_content) + end +end diff --git a/lib/chubi_web/controllers/admin/category_controller.ex b/lib/chubi_web/controllers/admin/category_controller.ex index 2ecbf67..a8a7bec 100644 --- a/lib/chubi_web/controllers/admin/category_controller.ex +++ b/lib/chubi_web/controllers/admin/category_controller.ex @@ -19,7 +19,7 @@ defmodule ChubiWeb.Admin.CategoryController do {:ok, category} -> conn |> put_flash(:info, "Category created successfully.") - |> redirect(to: Routes.admin_category_path(conn, :show, category)) + |> redirect(to: Routes.category_path(conn, :show, category)) {:error, %Ecto.Changeset{} = changeset} -> render(conn, "new.html", changeset: changeset) @@ -44,7 +44,7 @@ defmodule ChubiWeb.Admin.CategoryController do {:ok, category} -> conn |> put_flash(:info, "Category updated successfully.") - |> redirect(to: Routes.admin_category_path(conn, :show, category)) + |> redirect(to: Routes.category_path(conn, :show, category)) {:error, %Ecto.Changeset{} = changeset} -> render(conn, "edit.html", category: category, changeset: changeset) @@ -57,6 +57,6 @@ defmodule ChubiWeb.Admin.CategoryController do conn |> put_flash(:info, "Category deleted successfully.") - |> redirect(to: Routes.admin_category_path(conn, :index)) + |> redirect(to: Routes.category_path(conn, :index)) end end diff --git a/lib/chubi_web/controllers/admin/page_controller.ex b/lib/chubi_web/controllers/admin/page_controller.ex index 6824758..4602130 100644 --- a/lib/chubi_web/controllers/admin/page_controller.ex +++ b/lib/chubi_web/controllers/admin/page_controller.ex @@ -27,7 +27,7 @@ defmodule ChubiWeb.Admin.PageController do {:ok, page} -> conn |> put_flash(:info, "Page created successfully.") - |> redirect(to: Routes.admin_page_path(conn, :show, page)) + |> redirect(to: Routes.page_path(conn, :show, page)) {:error, %Ecto.Changeset{} = changeset} -> render(conn, "new.html", changeset: changeset) @@ -54,7 +54,7 @@ defmodule ChubiWeb.Admin.PageController do {:ok, page} -> conn |> put_flash(:info, "Page updated successfully.") - |> redirect(to: Routes.admin_page_path(conn, :show, page)) + |> redirect(to: Routes.page_path(conn, :show, page)) {:error, %Ecto.Changeset{} = changeset} -> render(conn, "edit.html", page: page, changeset: changeset) @@ -67,6 +67,6 @@ defmodule ChubiWeb.Admin.PageController do conn |> put_flash(:info, "Page deleted successfully.") - |> redirect(to: Routes.admin_page_path(conn, :index)) + |> redirect(to: Routes.page_path(conn, :index)) end end diff --git a/lib/chubi_web/controllers/admin/post_controller.ex b/lib/chubi_web/controllers/admin/post_controller.ex index a8cf3a8..7977ed6 100644 --- a/lib/chubi_web/controllers/admin/post_controller.ex +++ b/lib/chubi_web/controllers/admin/post_controller.ex @@ -37,7 +37,7 @@ defmodule ChubiWeb.Admin.PostController do {:ok, post} -> conn |> put_flash(:info, "Post created successfully.") - |> redirect(to: Routes.admin_post_path(conn, :show, post)) + |> redirect(to: Routes.post_path(conn, :show, post)) {:error, %Ecto.Changeset{} = changeset} -> conn @@ -75,7 +75,7 @@ defmodule ChubiWeb.Admin.PostController do {:ok, post} -> conn |> put_flash(:info, "Post updated successfully.") - |> redirect(to: Routes.admin_post_path(conn, :show, post)) + |> redirect(to: Routes.post_path(conn, :show, post)) {:error, %Ecto.Changeset{} = changeset} -> conn @@ -90,7 +90,7 @@ defmodule ChubiWeb.Admin.PostController do conn |> put_flash(:info, "Post deleted successfully.") - |> redirect(to: Routes.admin_post_path(conn, :index)) + |> redirect(to: Routes.post_path(conn, :index)) end defp put_meta(conn) do diff --git a/lib/chubi_web/controllers/admin/setting_controller.ex b/lib/chubi_web/controllers/admin/setting_controller.ex index f7cce3f..9e4b79f 100644 --- a/lib/chubi_web/controllers/admin/setting_controller.ex +++ b/lib/chubi_web/controllers/admin/setting_controller.ex @@ -1,9 +1,86 @@ defmodule ChubiWeb.Admin.SettingController do - use ChubiWeb, :controller + use ChubiWeb, :admin_controller + alias Chubi.Content def set_theme(conn, %{"theme" => theme}) do conn |> put_session(:theme, theme) - |> redirect(to: Routes.admin_post_path(conn, :index)) + |> redirect(to: Routes.post_path(conn, :index)) + end + + def export_content(conn, _params) do + posts = + Content.list_posts() + |> Enum.map(&Chubi.ContentFileStore.write_mem(&1)) + |> Enum.map(fn {file_name, content} -> + {String.to_charlist(Path.join("posts/", file_name)), content} + end) + + pages = + Content.list_pages() + |> Enum.map(&Chubi.ContentFileStore.write_mem(&1)) + |> Enum.map(fn {file_name, content} -> + {String.to_charlist(Path.join("pages/", file_name)), content} + end) + + with {:ok, {filename, data}} = :zip.create("blog-content.zip", posts ++ pages, [:memory]) do + # download file + conn + |> Plug.Conn.put_resp_content_type("application/octet-stream") + |> Plug.Conn.put_resp_header( + "content-disposition", + "attachment; filename=\"#{filename}\"" + ) + |> Plug.Conn.send_resp(:ok, data) + else + _ -> + conn + |> put_flash(:error, gettext("Cannot generate template")) + |> redirect(to: Routes.post_path(conn, :index)) + end + end + + def select_import(conn, _) do + render(conn, "select_import.html") + end + + def import_content(conn, %{"file" => %{path: path}}) do + with {:ok, files} <- :zip.extract(String.to_charlist(path), [:memory]) do + rs = + Enum.map(files, fn {file_path, content} -> + file_path = + to_string(file_path) + |> IO.inspect() + + attrs = + case Path.extname(file_path) do + ".md" -> + %{"content" => content, "format" => "markdown"} + + _ext -> + Jason.decode!(content) + end + + if String.starts_with?(file_path, "posts") do + Content.create_post(attrs) + else + Content.create_page(attrs) + end + end) + + success = Enum.filter(rs, &(elem(&1, 0) == :ok)) + + conn + |> put_flash( + :info, + gettext("%{success}/%{total} is imported", %{success: length(success), total: length(rs)}) + ) + |> redirect(to: Routes.post_path(conn, :index)) + else + err -> + conn + |> put_flash(:error, gettext("Cannot import content")) + |> redirect(to: Routes.setting_path(conn, :select_import)) + end end end diff --git a/lib/chubi_web/controllers/admin/tag_controller.ex b/lib/chubi_web/controllers/admin/tag_controller.ex index 004270e..236d20f 100644 --- a/lib/chubi_web/controllers/admin/tag_controller.ex +++ b/lib/chubi_web/controllers/admin/tag_controller.ex @@ -19,7 +19,7 @@ defmodule ChubiWeb.Admin.TagController do {:ok, tag} -> conn |> put_flash(:info, "Tag created successfully.") - |> redirect(to: Routes.admin_tag_path(conn, :show, tag)) + |> redirect(to: Routes.tag_path(conn, :show, tag)) {:error, %Ecto.Changeset{} = changeset} -> render(conn, "new.html", changeset: changeset) @@ -44,7 +44,7 @@ defmodule ChubiWeb.Admin.TagController do {:ok, tag} -> conn |> put_flash(:info, "Tag updated successfully.") - |> redirect(to: Routes.admin_tag_path(conn, :show, tag)) + |> redirect(to: Routes.tag_path(conn, :show, tag)) {:error, %Ecto.Changeset{} = changeset} -> render(conn, "edit.html", tag: tag, changeset: changeset) @@ -57,6 +57,6 @@ defmodule ChubiWeb.Admin.TagController do conn |> put_flash(:info, "Tag deleted successfully.") - |> redirect(to: Routes.admin_tag_path(conn, :index)) + |> redirect(to: Routes.tag_path(conn, :index)) end end diff --git a/lib/chubi_web/router.ex b/lib/chubi_web/router.ex index 753d373..4291497 100644 --- a/lib/chubi_web/router.ex +++ b/lib/chubi_web/router.ex @@ -46,17 +46,5 @@ defmodule ChubiWeb.Router do get("/pages/:id", PreviewController, :show_page) end - scope "/admin", ChubiWeb.Admin, as: "admin" do - pipe_through([:browser, :admin]) - get("/", PostController, :index) - resources("/tags", TagController) - resources("/categories", CategoryController) - resources("/posts", PostController) - resources("/pages", PageController) - - post("/upload", UploadController, :create) - delete("/upload", UploadController, :delete) - - get("/settings/set_theme", SettingController, :set_theme) - end + forward("/admin", ChubiWeb.AdminRouter) end diff --git a/lib/chubi_web/templates/admin/category/edit.html.eex b/lib/chubi_web/templates/admin/category/edit.html.eex index e6c5b47..e025e6f 100644 --- a/lib/chubi_web/templates/admin/category/edit.html.eex +++ b/lib/chubi_web/templates/admin/category/edit.html.eex @@ -1,5 +1,5 @@