From c4bd903e7446cb3ece04b34280da382453fbcb90 Mon Sep 17 00:00:00 2001 From: Daniel Nitsikopoulos Date: Sat, 6 May 2023 17:11:54 +1000 Subject: [PATCH] Add admin area --- app/templates/layouts/app.html.slim | 3 ++ config/app.rb | 7 ++- config/routes.rb | 15 +++++- config/settings.rb | 3 ++ lib/adamantium/middleware/basic_auth.rb | 16 ++++++ lib/adamantium/persistence/relations/tags.rb | 4 +- public/assets/index.js | 14 +++++ slices/admin/action.rb | 7 +++ slices/admin/actions/.keep | 0 slices/admin/actions/index.rb | 12 +++++ slices/admin/actions/tags/delete.rb | 17 ++++++ slices/admin/actions/tags/index.rb | 14 +++++ slices/admin/repos/post_tag_repo.rb | 11 ++++ slices/admin/repos/tag_repo.rb | 16 ++++++ slices/admin/templates/index.html.slim | 12 +++++ slices/admin/templates/layouts/app.html.slim | 56 ++++++++++++++++++++ slices/admin/templates/shared/_tag.html.slim | 4 ++ slices/admin/templates/tags/index.html.slim | 17 ++++++ slices/admin/view.rb | 6 +++ slices/admin/views/index.rb | 7 +++ slices/admin/views/tags/index.rb | 22 ++++++++ 21 files changed, 258 insertions(+), 5 deletions(-) create mode 100644 slices/admin/action.rb create mode 100644 slices/admin/actions/.keep create mode 100644 slices/admin/actions/index.rb create mode 100644 slices/admin/actions/tags/delete.rb create mode 100644 slices/admin/actions/tags/index.rb create mode 100644 slices/admin/repos/post_tag_repo.rb create mode 100644 slices/admin/repos/tag_repo.rb create mode 100644 slices/admin/templates/index.html.slim create mode 100644 slices/admin/templates/layouts/app.html.slim create mode 100644 slices/admin/templates/shared/_tag.html.slim create mode 100644 slices/admin/templates/tags/index.html.slim create mode 100644 slices/admin/view.rb create mode 100644 slices/admin/views/index.rb create mode 100644 slices/admin/views/tags/index.rb diff --git a/app/templates/layouts/app.html.slim b/app/templates/layouts/app.html.slim index 3fcb1bb..87e3303 100644 --- a/app/templates/layouts/app.html.slim +++ b/app/templates/layouts/app.html.slim @@ -29,6 +29,9 @@ html script src="https://unpkg.com/htmx.org@1.8.4" integrity="sha384-wg5Y/JwF7VxGk4zLsJEcAojRtlVp1FKKdGy1qN+OMtdq72WRvX/EdRdqg/LOhYeV" crossorigin="anonymous" + script src="https://api.mapbox.com/mapbox-gl-js/v2.9.1/mapbox-gl.js" + link href="https://api.mapbox.com/mapbox-gl-js/v2.9.1/mapbox-gl.css" rel="stylesheet" + - if Hanami.app.settings.micropub_pub_key link rel="pgpkey" href="/key" body class="bg-white dark:bg-black selection:bg-blue-100 selection:text-blue-900 dark:selection:bg-blue-600 dark:selection:text-blue-100" diff --git a/config/app.rb b/config/app.rb index 69917d4..fe9dde7 100644 --- a/config/app.rb +++ b/config/app.rb @@ -6,10 +6,13 @@ module Adamantium class App < Hanami::App config.actions.content_security_policy[:script_src] += " https://gist.github.com" config.actions.content_security_policy[:script_src] += " *.dnitza.com" + config.actions.content_security_policy[:script_src] += " https://api.mapbox.com/mapbox-gl-js/v2.9.1/mapbox-gl.js" config.actions.content_security_policy[:media_src] += " https://dnitza.com" - config.actions.content_security_policy[:script_src] += " https://unpkg.com/htmx.org@1.8.4" - config.actions.content_security_policy[:connect_src] += " https://stats.dnitza.com/api/event" + config.actions.content_security_policy[:script_src] += " https://unpkg.com/htmx.org@1.8.4 https://unpkg.com/htmx.org@1.9.2" + config.actions.content_security_policy[:connect_src] += " https://stats.dnitza.com/api/event https://*.mapbox.com" config.actions.content_security_policy[:frame_src] += " https://embed.music.apple.com" + config.actions.content_security_policy[:style_src] += " https://api.mapbox.com/mapbox-gl-js/v2.9.1/mapbox-gl.css" + config.actions.content_security_policy[:child_src] = " blob:" config.logger.level = :debug config.logger.stream = "log/hanami.log" diff --git a/config/routes.rb b/config/routes.rb index ab813a7..cb6eec7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,12 +1,18 @@ # frozen_string_literal: true require "hanami/middleware/body_parser" -require "adamantium/middleware/process_params" +require "adamantium/middleware/basic_auth" module Adamantium class Routes < Hanami::Routes use Hanami::Middleware::BodyParser, [:form, :json] # use Adamantium::Middleware::ProcessParams + if Hanami.app.settings.basic_auth_username && Hanami.app.settings.basic_auth_password + use Adamantium::Middleware::BasicAuth do |username, password| + username == Hanami.app.settings.basic_auth_username && + password == Hanami.app.settings.basic_auth_password + end + end scope "micropub" do get "/", to: "site.config" @@ -48,5 +54,12 @@ module Adamantium redirect "deploying-a-hanami-app-to-fly-io", to: "/post/deploying-a-hanami-20-app-to-flyio" redirect "deploying-a-hanami-app-to-fly-io/", to: "/post/deploying-a-hanami-20-app-to-flyio" + + slice :admin, at: "/admin" do + get "/", to: "index" + + get "/tags", to: "tags.index" + delete "/tags/:id", to: "tags.delete" + end end end diff --git a/config/settings.rb b/config/settings.rb index 40a2771..da19029 100644 --- a/config/settings.rb +++ b/config/settings.rb @@ -43,5 +43,8 @@ module Adamantium setting :mastodon_server, default: nil setting :raindrop_api_key, default: nil + + setting :basic_auth_username, default: nil + setting :basic_auth_password, default: nil end end diff --git a/lib/adamantium/middleware/basic_auth.rb b/lib/adamantium/middleware/basic_auth.rb index e69de29..9cbc178 100644 --- a/lib/adamantium/middleware/basic_auth.rb +++ b/lib/adamantium/middleware/basic_auth.rb @@ -0,0 +1,16 @@ +module Adamantium + module Middleware + class BasicAuth < Rack::Auth::Basic + def call(env) + request = Rack::Request.new(env) + if request.path.match(/^\/admin*/) + # Execute basic authentication + super(env) + else + # Pass basic authentication + @app.call(env) + end + end + end + end +end diff --git a/lib/adamantium/persistence/relations/tags.rb b/lib/adamantium/persistence/relations/tags.rb index 3b84a1d..d3bcb05 100644 --- a/lib/adamantium/persistence/relations/tags.rb +++ b/lib/adamantium/persistence/relations/tags.rb @@ -6,8 +6,8 @@ module Adamantium class Tags < ROM::Relation[:sql] schema :tags, infer: true do associations do - belongs_to :post_tag - belongs_to :post, through: :post_tag + has_many :post_tags + has_many :posts, through: :post_tags end end diff --git a/public/assets/index.js b/public/assets/index.js index f76507c..ee3b8a5 100644 --- a/public/assets/index.js +++ b/public/assets/index.js @@ -5,5 +5,19 @@ const oldDtime = Date.parse(time.dateTime); time.innerHTML = new Date(oldDtime).toLocaleDateString(navigator.language, { weekday:"long", year:"numeric", month:"short", day:"numeric"}); }); + + // mapboxgl.accessToken = 'pk.eyJ1IjoiZG5pdHphIiwiYSI6ImNsZWIyY3ZzaTE0cjUzdm4xdnZ6czRlYjUifQ.FRETOXYRID6T2IoB7qqRLg'; + // var map = new mapboxgl.Map({ + // container: 'map', + // style: 'mapbox://styles/mapbox/streets-v11' + // }); + // const mapContainer = document.getElementById("map"); + // const markers = JSON.parse(mapContainer.dataset["markers"]); + // for (var i = 0; i < markers.length; i++) { + // const marker = markers[i]; + // new mapboxgl.Marker() + // .setLngLat(marker) + // .addTo(map); + // } }); })(); diff --git a/slices/admin/action.rb b/slices/admin/action.rb new file mode 100644 index 0000000..4524c3d --- /dev/null +++ b/slices/admin/action.rb @@ -0,0 +1,7 @@ +# auto_register: false +# frozen_string_literal: true + +module Admin + class Action < Adamantium::Action + end +end diff --git a/slices/admin/actions/.keep b/slices/admin/actions/.keep new file mode 100644 index 0000000..e69de29 diff --git a/slices/admin/actions/index.rb b/slices/admin/actions/index.rb new file mode 100644 index 0000000..deff24f --- /dev/null +++ b/slices/admin/actions/index.rb @@ -0,0 +1,12 @@ +module Admin + module Actions + class Index < Action + + include Deps["views.index"] + + def handle(req, res) + res.render index + end + end + end +end \ No newline at end of file diff --git a/slices/admin/actions/tags/delete.rb b/slices/admin/actions/tags/delete.rb new file mode 100644 index 0000000..994f95c --- /dev/null +++ b/slices/admin/actions/tags/delete.rb @@ -0,0 +1,17 @@ +module Admin + module Actions + module Tags + class Delete < Action + + include Deps["repos.post_tag_repo", "repos.tag_repo"] + + def handle(req, res) + tag_id = req.params[:id] + + post_tag_repo.delete(tag_id: tag_id) + tag_repo.delete(tag_id: tag_id) + end + end + end + end +end \ No newline at end of file diff --git a/slices/admin/actions/tags/index.rb b/slices/admin/actions/tags/index.rb new file mode 100644 index 0000000..f9f31a9 --- /dev/null +++ b/slices/admin/actions/tags/index.rb @@ -0,0 +1,14 @@ +module Admin + module Actions + module Tags + class Index < Action + + include Deps["views.tags.index"] + + def handle(req, res) + res.render index + end + end + end + end +end \ No newline at end of file diff --git a/slices/admin/repos/post_tag_repo.rb b/slices/admin/repos/post_tag_repo.rb new file mode 100644 index 0000000..1cf7485 --- /dev/null +++ b/slices/admin/repos/post_tag_repo.rb @@ -0,0 +1,11 @@ +module Admin + module Repos + class PostTagRepo < Adamantium::Repo[:post_tags] + + def delete(tag_id:) + post_tags.where(tag_id: tag_id).delete + end + + end + end +end diff --git a/slices/admin/repos/tag_repo.rb b/slices/admin/repos/tag_repo.rb new file mode 100644 index 0000000..e6a4530 --- /dev/null +++ b/slices/admin/repos/tag_repo.rb @@ -0,0 +1,16 @@ +module Admin + module Repos + class TagRepo < Adamantium::Repo[:tags] + def list + tags + .combine(:posts) + .order(Sequel.function(:lower, :label)) + .to_a + end + + def delete(tag_id:) + tags.by_pk(tag_id).delete + end + end + end +end diff --git a/slices/admin/templates/index.html.slim b/slices/admin/templates/index.html.slim new file mode 100644 index 0000000..bf7dc02 --- /dev/null +++ b/slices/admin/templates/index.html.slim @@ -0,0 +1,12 @@ +div class="mb-12 prose dark:prose-invert max-w-prose mx-auto text-gray-800 dark:text-gray-200" + h1 Admin + +div class="max-w-prose mx-auto" + ul + li + a href="/admin/tags" Tags + +div class="max-w-screen-md mx-auto border-t-4 border-solid border-gray-400 dark:border-gray-600" + + + diff --git a/slices/admin/templates/layouts/app.html.slim b/slices/admin/templates/layouts/app.html.slim new file mode 100644 index 0000000..55e62ab --- /dev/null +++ b/slices/admin/templates/layouts/app.html.slim @@ -0,0 +1,56 @@ +html + head + meta charest="utf-8" + + meta name="viewport" content="width=device-width, initial-scale=1.0" + + meta name="theme-color" content="rgb(37, 99, 235)" + + title Daniel Nitsikopoulos + + link rel="authorization_endpoint" href=Hanami.app.settings.micropub_authorization_endpoint + link rel="token_endpoint" href=Hanami.app.settings.micropub_token_endpoint + link rel="micropub" href="#{URI.join(Hanami.app.settings.micropub_site_url, "micropub")}" + + link rel="webmention" href=Hanami.app.settings.webmention_url + link rel="pingback" href=Hanami.app.settings.pingback_url + link rel="feed" type="text/html" href="/posts" + link rel="feed alternate" type="application/rss+xml" href="/feeds/rss" + link rel="feed alternate" type="application/rss+xml" href="/feeds/statuses_rss" + + link rel="me" href=Hanami.app.settings.mastodon_url + link rel="me" href=Hanami.app.settings.github_url + + link rel="stylesheet" href="/assets/index.css" + link rel="icon" type="image/x-icon" href="/assets/favicon.ico" + + script data-domain="dnitza.com" src="https://stats.dnitza.com/js/script.js" defer="true" + script src="/assets/index.js" + + script src="https://unpkg.com/htmx.org@1.9.2" integrity="sha384-L6OqL9pRWyyFU3+/bjdSri+iIphTN/bvYyM37tICVyOJkWZLpP2vGn6VUEXgzg6h" crossorigin="anonymous" + + - if Hanami.app.settings.micropub_pub_key + link rel="pgpkey" href="/key" + body class="bg-white dark:bg-black selection:bg-blue-100 selection:text-blue-900 dark:selection:bg-blue-600 dark:selection:text-blue-100" + main class="pb-8 px-4 pt-4 md:pt-8" + header class="mb-12 max-w-screen-md mx-auto" + div class="flex items-center mb-8 md:mb-12 text-lg md:text-xl text-gray-400 dark:text-gray-600" + div class="flex-none mx-auto md:flex-auto md:mx-0" + div class="h-card flex items-center" + img class="u-photo w-6 h6 md:w-10 md:h-10 rounded-full mr-1.5" src="/assets/memoji.png" + a href="/" rel="me" class="u-url u-uid" + h1 class="p-name uppercase text-sm md:text-sm text-gray-400 dark:text-gray-400" = Hanami.app.settings.site_name + nav class="space-x-1 text-sm md:text-sm uppercase md:block" + a class="p-1 rounded text-gray-400 hover:bg-red-100 hover:text-red-400 dark:hover:bg-red-200 #{link_active?('about') ? 'text-red-600 dark:text-red-400' : ''}" href="/admin" Admin + span class="text-gray-400 dark:text-gray-600" + = "/" + a class="p-1 rounded text-gray-400 hover:bg-red-100 hover:text-red-400 dark:hover:bg-red-200 #{link_active?('about') ? 'text-red-600 dark:text-red-400' : ''}" href="/admin/tags" Tags + span class="text-gray-400 dark:text-gray-600" + = "/" + == yield + div class="px-4 max-w-screen-md mx-auto pb-10" + p class="float-left text-gray-200 dark:text-gray-600" Β© 2023 Daniel Nitsikopoulos. All rights reserved. + p class="float-right text-gray-200 dark:text-gray-600" + a href="https://xn--sr8hvo.ws/%F0%9F%8D%93%E2%9E%97%F0%9F%8E%B0/previous" ← + a href="https://xn--sr8hvo.ws" πŸ•ΈπŸ’ + a href="https://xn--sr8hvo.ws/%F0%9F%8D%93%E2%9E%97%F0%9F%8E%B0/next"   → diff --git a/slices/admin/templates/shared/_tag.html.slim b/slices/admin/templates/shared/_tag.html.slim new file mode 100644 index 0000000..73415c2 --- /dev/null +++ b/slices/admin/templates/shared/_tag.html.slim @@ -0,0 +1,4 @@ +li id="tag-#{tag.id}" + = "#{tag.label} (#{tag.posts.count})" + = " β€” " + button hx-delete="/admin/tags/#{tag.id}" hx-target="#tag-#{tag.id}" delete \ No newline at end of file diff --git a/slices/admin/templates/tags/index.html.slim b/slices/admin/templates/tags/index.html.slim new file mode 100644 index 0000000..a2e9184 --- /dev/null +++ b/slices/admin/templates/tags/index.html.slim @@ -0,0 +1,17 @@ +div class="mb-12 prose dark:prose-invert max-w-prose mx-auto text-gray-800 dark:text-gray-200" + h1 Admin // Tags + +div class="max-w-prose mx-auto" + ul + - unused_tags.each do |tag| + == render "shared/tag", tag: tag + li class="py-2" + hr + - used_tags.each do |tag| + == render "shared/tag", tag: tag + + +div class="max-w-screen-md mx-auto border-t-4 border-solid border-gray-400 dark:border-gray-600" + + + diff --git a/slices/admin/view.rb b/slices/admin/view.rb new file mode 100644 index 0000000..7bba4e5 --- /dev/null +++ b/slices/admin/view.rb @@ -0,0 +1,6 @@ +module Admin + class View < Adamantium::View + config.layouts_dir = "layouts" + config.paths = "slices/admin/templates" + end +end \ No newline at end of file diff --git a/slices/admin/views/index.rb b/slices/admin/views/index.rb new file mode 100644 index 0000000..e0a49ac --- /dev/null +++ b/slices/admin/views/index.rb @@ -0,0 +1,7 @@ +module Admin + module Views + class Index < Admin::View + + end + end +end \ No newline at end of file diff --git a/slices/admin/views/tags/index.rb b/slices/admin/views/tags/index.rb new file mode 100644 index 0000000..fa245c3 --- /dev/null +++ b/slices/admin/views/tags/index.rb @@ -0,0 +1,22 @@ +module Admin + module Views + module Tags + class Index < Admin::View + + include Deps["repos.tag_repo"] + + expose :tags do + tag_repo.list.to_a + end + + expose :unused_tags do |tags| + tags.partition {|t| t.posts.count == 0}.first + end + + expose :used_tags do |tags| + tags.partition {|t| t.posts.count == 0}.last + end + end + end + end +end \ No newline at end of file