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