diff --git a/app/commands/auto_tagging/tag.rb b/app/commands/auto_tagging/tag.rb
new file mode 100644
index 0000000..23fca6f
--- /dev/null
+++ b/app/commands/auto_tagging/tag.rb
@@ -0,0 +1,31 @@
+module Adamantium
+ module Commands
+ module AutoTagging
+ class Tag
+ include Dry::Monads[:result]
+ include Deps["repos.post_repo", "repos.auto_tagging_repo"]
+
+ def call(auto_tag_id: nil)
+ auto_taggings = if auto_tag_id
+ auto_tagging_repo.find(auto_tag_id)
+ else
+ auto_tagging_repo.all
+ end
+
+ auto_taggings.each do |auto_tagging|
+ posts = auto_tagging.title_only? ?
+ post_repo.by_title(title_contains: auto_tagging.title_contains) :
+ post_repo.by_content(body_contains: auto_tagging.body_contains)
+
+ posts.each do |post|
+ post_repo.auto_tag_post(post_id: post.id,
+ tag_id: auto_tagging.tag_id)
+ end
+ end
+
+ Success()
+ end
+ end
+ end
+ end
+end
diff --git a/app/commands/posts/create_entry.rb b/app/commands/posts/create_entry.rb
index 6585992..b0cdb83 100644
--- a/app/commands/posts/create_entry.rb
+++ b/app/commands/posts/create_entry.rb
@@ -10,6 +10,7 @@ module Adamantium
syndicate: "commands.posts.syndicate",
send_to_dayone: "syndication.dayone",
send_webmentions: "commands.posts.send_webmentions",
+ auto_tag: "commands.auto_tagging.tag",
]
include Dry::Monads[:result]
@@ -20,6 +21,8 @@ module Adamantium
syndicate.call(created_post.id, post)
+ auto_tag.call()
+
# decorated_post = Decorators::Posts::Decorator.new(created_post)
# send_webmentions.call(post_content: attrs[:content], post_url: decorated_post.permalink)
diff --git a/app/entities/auto_tagging.rb b/app/entities/auto_tagging.rb
new file mode 100644
index 0000000..566feb9
--- /dev/null
+++ b/app/entities/auto_tagging.rb
@@ -0,0 +1,13 @@
+module Adamantium
+ module Entities
+ class AutoTagging < Dry::Struct
+ attribute? :title_contains, Types::Optional::String
+ attribute? :body_contains, Types::Optional::String
+ attribute :tag_id, Types::Coercible::Integer
+
+ def title_only?
+ !title_contains.empty?
+ end
+ end
+ end
+end
diff --git a/app/repos/auto_tagging_repo.rb b/app/repos/auto_tagging_repo.rb
new file mode 100644
index 0000000..8d776df
--- /dev/null
+++ b/app/repos/auto_tagging_repo.rb
@@ -0,0 +1,18 @@
+module Adamantium
+ module Repos
+ class AutoTaggingRepo < Adamantium::Repo[:auto_taggings]
+ def find(id)
+ auto_taggings
+ .where(id: id)
+ .map_to(Adamantium::Entities::AutoTagging)
+ .to_a
+ end
+
+ def all
+ auto_taggings
+ .map_to(Adamantium::Entities::AutoTagging)
+ .to_a
+ end
+ end
+ end
+end
diff --git a/app/repos/post_repo.rb b/app/repos/post_repo.rb
index 9c8d35a..2534caa 100644
--- a/app/repos/post_repo.rb
+++ b/app/repos/post_repo.rb
@@ -47,6 +47,38 @@ module Adamantium
end
end
+ def auto_tag_post(post_id:, tag_id:)
+
+ return if posts
+ .post_tags
+ .where(
+ post_id: post_id,
+ tag_id: tag_id
+ ).count > 0
+
+ posts
+ .post_tags
+ .changeset(:create, {
+ post_id: post_id,
+ tag_id: tag_id
+ })
+ .commit
+ end
+
+ def by_title(title_contains:)
+ posts
+ .where(post_type: "post")
+ .published
+ .where(Sequel.ilike(:name, "%#{title_contains}%")).to_a
+ end
+
+ def by_content(body_contains:)
+ posts
+ .where(post_type: "post")
+ .published
+ .where(Sequel.ilike(:content, "%#{body_contains}%")).to_a
+ end
+
def remove_tag(post_id:, tag:)
tag = posts.tags.where(label: tag).one
diff --git a/config/routes.rb b/config/routes.rb
index cb6eec7..672e7b2 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -60,6 +60,11 @@ module Adamantium
get "/tags", to: "tags.index"
delete "/tags/:id", to: "tags.delete"
+
+ get "/tags/auto_tagging", to: "auto_tagging.index"
+ get "/tags/auto_tagging/new", to: "auto_tagging.new"
+ post "/tags/auto_tagging", to: "auto_tagging.create"
+ delete "/tags/auto_taggings/:id", to: "auto_tagging.delete"
end
end
end
diff --git a/db/migrate/20230506081449_create_auto_taggings.rb b/db/migrate/20230506081449_create_auto_taggings.rb
new file mode 100644
index 0000000..e4af27d
--- /dev/null
+++ b/db/migrate/20230506081449_create_auto_taggings.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+ROM::SQL.migration do
+ change do
+ create_table(:auto_taggings) do
+ primary_key :id
+ column :title_contains, :text
+ column :body_contains, :text
+ foreign_key :tag_id, :tags, null: false
+ end
+ end
+end
diff --git a/lib/adamantium/persistence/relations/auto_taggings.rb b/lib/adamantium/persistence/relations/auto_taggings.rb
new file mode 100644
index 0000000..bb1e8a2
--- /dev/null
+++ b/lib/adamantium/persistence/relations/auto_taggings.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Adamantium
+ module Persistence
+ module Relations
+ class AutoTaggings < ROM::Relation[:sql]
+ schema :auto_taggings, infer: true do
+ associations do
+ belongs_to :tag
+ end
+ end
+
+ auto_struct(true)
+ end
+ end
+ end
+end
diff --git a/slices/admin/actions/auto_tagging/create.rb b/slices/admin/actions/auto_tagging/create.rb
new file mode 100644
index 0000000..dc1f732
--- /dev/null
+++ b/slices/admin/actions/auto_tagging/create.rb
@@ -0,0 +1,28 @@
+module Admin
+ module Actions
+ module AutoTagging
+ class Create < Action
+
+ include Deps["commands.auto_tagging.create",
+ "views.auto_tagging.index",
+ auto_tag: "commands.auto_tagging.tag"]
+
+ def handle(req, res)
+ title_contains = req.params[:title_contains]
+ body_contains = req.params[:body_contains]
+ tag_id = req.params[:tag_id]
+
+ result = create.(title_contains: title_contains,
+ body_contains: body_contains,
+ tag_id: tag_id)
+
+ if result.success?
+ auto_tag.(auto_tag_id: result.value!)
+ end
+
+ res.render index
+ end
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/slices/admin/actions/auto_tagging/delete.rb b/slices/admin/actions/auto_tagging/delete.rb
new file mode 100644
index 0000000..ca3240a
--- /dev/null
+++ b/slices/admin/actions/auto_tagging/delete.rb
@@ -0,0 +1,14 @@
+module Admin
+ module Actions
+ module AutoTagging
+ class Delete < Action
+
+ include Deps["repos.auto_tagging_repo"]
+
+ def handle(req, res)
+ auto_tagging_repo.delete(id: req.params[:id])
+ end
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/slices/admin/actions/auto_tagging/index.rb b/slices/admin/actions/auto_tagging/index.rb
new file mode 100644
index 0000000..d9efd8f
--- /dev/null
+++ b/slices/admin/actions/auto_tagging/index.rb
@@ -0,0 +1,14 @@
+module Admin
+ module Actions
+ module AutoTagging
+ class Index < Action
+
+ include Deps["views.auto_tagging.index"]
+
+ def handle(req, res)
+ res.render index
+ end
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/slices/admin/actions/auto_tagging/new.rb b/slices/admin/actions/auto_tagging/new.rb
new file mode 100644
index 0000000..940ec3f
--- /dev/null
+++ b/slices/admin/actions/auto_tagging/new.rb
@@ -0,0 +1,14 @@
+module Admin
+ module Actions
+ module AutoTagging
+ class New < Action
+
+ include Deps[new_view: "views.auto_tagging.new"]
+
+ def handle(req, res)
+ res.render new_view
+ end
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/slices/admin/commands/auto_tagging/create.rb b/slices/admin/commands/auto_tagging/create.rb
new file mode 100644
index 0000000..eff337d
--- /dev/null
+++ b/slices/admin/commands/auto_tagging/create.rb
@@ -0,0 +1,20 @@
+module Admin
+ module Commands
+ module AutoTagging
+ class Create
+ include Dry::Monads[:result]
+ include Deps["repos.auto_tagging_repo"]
+
+ def call(title_contains:, body_contains:, tag_id:)
+ Failure() if !title_contains.empty? && !body_contains.empty?
+
+ result = auto_tagging_repo.create(title_contains: title_contains,
+ body_contains: body_contains,
+ tag_id: tag_id)
+
+ Success(result.id)
+ end
+ end
+ end
+ end
+end
diff --git a/slices/admin/commands/auto_tagging/tag.rb b/slices/admin/commands/auto_tagging/tag.rb
new file mode 100644
index 0000000..f351e90
--- /dev/null
+++ b/slices/admin/commands/auto_tagging/tag.rb
@@ -0,0 +1,31 @@
+module Admin
+ module Commands
+ module AutoTagging
+ class Tag
+ include Dry::Monads[:result]
+ include Deps["repos.post_repo", "repos.auto_tagging_repo"]
+
+ def call(auto_tag_id: nil)
+ auto_taggings = if auto_tag_id
+ auto_tagging_repo.find(auto_tag_id)
+ else
+ auto_tagging_repo.all
+ end
+
+ auto_taggings.each do |auto_tagging|
+ posts = auto_tagging.title_only? ?
+ post_repo.by_title(title_contains: auto_tagging.title_contains) :
+ post_repo.by_content(body_contains: auto_tagging.body_contains)
+
+ posts.each do |post|
+ post_repo.tag_post(post_id: post.id,
+ tag_id: auto_tagging.tag_id)
+ end
+ end
+
+ Success()
+ end
+ end
+ end
+ end
+end
diff --git a/slices/admin/entities/auto_tagging.rb b/slices/admin/entities/auto_tagging.rb
new file mode 100644
index 0000000..f15b450
--- /dev/null
+++ b/slices/admin/entities/auto_tagging.rb
@@ -0,0 +1,22 @@
+module Admin
+ module Entities
+ class AutoTagging < Dry::Struct
+ attribute :id, Types::Coercible::Integer
+ attribute? :title_contains, Types::Optional::String
+ attribute? :body_contains, Types::Optional::String
+ attribute :tag_id, Types::Coercible::Integer
+
+ def title_only?
+ !title_contains.empty?
+ end
+
+ def term
+ title_only? ? title_contains : body_contains
+ end
+
+ class WithTag < AutoTagging
+ attribute :tag, Types::Tag
+ end
+ end
+ end
+end
diff --git a/slices/admin/repos/auto_tagging_repo.rb b/slices/admin/repos/auto_tagging_repo.rb
new file mode 100644
index 0000000..858e707
--- /dev/null
+++ b/slices/admin/repos/auto_tagging_repo.rb
@@ -0,0 +1,33 @@
+module Admin
+ module Repos
+ class AutoTaggingRepo < Adamantium::Repo[:auto_taggings]
+ commands :create
+
+ def find(id)
+ auto_taggings
+ .where(id: id)
+ .map_to(Admin::Entities::AutoTagging)
+ .to_a
+ end
+
+ def all
+ auto_taggings
+ .map_to(Admin::Entities::AutoTagging)
+ .to_a
+ end
+
+ def listing
+ auto_taggings
+ .combine(:tag)
+ .map_to(Admin::Entities::AutoTagging::WithTag)
+ .to_a
+ end
+
+ def delete(id:)
+ auto_taggings
+ .where(id: id)
+ .delete
+ end
+ end
+ end
+end
diff --git a/slices/admin/repos/post_repo.rb b/slices/admin/repos/post_repo.rb
new file mode 100644
index 0000000..c9b1aa5
--- /dev/null
+++ b/slices/admin/repos/post_repo.rb
@@ -0,0 +1,37 @@
+module Admin
+ module Repos
+ class PostRepo < Adamantium::Repo[:posts]
+ def tag_post(post_id:, tag_id:)
+
+ return if posts
+ .post_tags
+ .where(
+ post_id: post_id,
+ tag_id: tag_id
+ ).count > 0
+
+ posts
+ .post_tags
+ .changeset(:create, {
+ post_id: post_id,
+ tag_id: tag_id
+ })
+ .commit
+ end
+
+ def by_title(title_contains:)
+ posts
+ .where(post_type: "post")
+ .published
+ .where(Sequel.ilike(:name, "%#{title_contains}%")).to_a
+ end
+
+ def by_content(body_contains:)
+ posts
+ .where(post_type: "post")
+ .published
+ .where(Sequel.ilike(:content, "%#{body_contains}%")).to_a
+ 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
index 1cf7485..093a99f 100644
--- a/slices/admin/repos/post_tag_repo.rb
+++ b/slices/admin/repos/post_tag_repo.rb
@@ -5,7 +5,6 @@ module Admin
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
index e6a4530..86d6c0f 100644
--- a/slices/admin/repos/tag_repo.rb
+++ b/slices/admin/repos/tag_repo.rb
@@ -2,6 +2,11 @@ module Admin
module Repos
class TagRepo < Adamantium::Repo[:tags]
def list
+ tags
+ .order(Sequel.function(:lower, :label))
+ .to_a
+ end
+ def list_with_posts
tags
.combine(:posts)
.order(Sequel.function(:lower, :label))
diff --git a/slices/admin/templates/auto_tagging/index.html.slim b/slices/admin/templates/auto_tagging/index.html.slim
new file mode 100644
index 0000000..09ceb54
--- /dev/null
+++ b/slices/admin/templates/auto_tagging/index.html.slim
@@ -0,0 +1,10 @@
+div class="mb-12 prose dark:prose-invert max-w-prose mx-auto text-gray-800 dark:text-gray-200"
+ h1 Admin // Auto tagging
+
+div class="prose dark:prose-invert max-w-prose mx-auto"
+ - auto_taggings.each do |auto_tagging|
+ div id="auto-tag-#{auto_tagging.id}"
+ == "Tag post with #{auto_tagging.tag.label} when #{auto_tagging.title_only? ? "title" : "content"} contains #{auto_tagging.term}"
+ = " — "
+ button hx-delete="/admin/tags/auto_taggings/#{auto_tagging.id}" hx-target="#auto-tag-#{auto_tagging.id}" delete
+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/auto_tagging/new.html.slim b/slices/admin/templates/auto_tagging/new.html.slim
new file mode 100644
index 0000000..4ef2926
--- /dev/null
+++ b/slices/admin/templates/auto_tagging/new.html.slim
@@ -0,0 +1,23 @@
+div class="mb-12 prose dark:prose-invert max-w-prose mx-auto text-gray-800 dark:text-gray-200"
+ h1 Admin // Auto tagging
+
+div class="max-w-prose mx-auto"
+ form method="POST" action="/admin/tags/auto_tagging"
+ div class="mb-4"
+ label class="text-gray-800 dark:text-gray-200" for="title_contains" Title contains:
+ input class="text-gray-800 p-1" type="text" id="title_contains" name="title_contains"
+
+ div class="mb-4"
+ label class="text-gray-800 dark:text-gray-200" for="body_contains" ... or body contains:
+ input class="text-gray-800 p-1" type="text" id="body_contains" name="body_contains"
+
+ div class="mb-4"
+ label class="text-gray-800 dark:text-gray-200" for="tags" Tag with:
+ select class="text-gray-800" id="tags" name="tag_id"
+ - tags.each do |tag|
+ option value=tag.id
+ = tag.label
+ div class="mb-4"
+ button class="rounded bg-blue-100 hover:bg-blue-200 text-blue-600 px-2 hover:cursor-pointer" type="submit"
+ = "Create"
+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/types.rb b/slices/admin/types.rb
new file mode 100644
index 0000000..6363428
--- /dev/null
+++ b/slices/admin/types.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require "dry/types"
+
+module Admin
+ Types = Dry.Types
+
+ module Types
+ class Tag < Dry::Struct
+ attribute :label, Types::Coercible::String
+ end
+ end
+end
\ No newline at end of file
diff --git a/slices/admin/views/auto_tagging/index.rb b/slices/admin/views/auto_tagging/index.rb
new file mode 100644
index 0000000..6c374a3
--- /dev/null
+++ b/slices/admin/views/auto_tagging/index.rb
@@ -0,0 +1,14 @@
+module Admin
+ module Views
+ module AutoTagging
+ class Index < Admin::View
+
+ include Deps["repos.auto_tagging_repo"]
+
+ expose :auto_taggings do
+ auto_tagging_repo.listing
+ end
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/slices/admin/views/auto_tagging/new.rb b/slices/admin/views/auto_tagging/new.rb
new file mode 100644
index 0000000..920e474
--- /dev/null
+++ b/slices/admin/views/auto_tagging/new.rb
@@ -0,0 +1,14 @@
+module Admin
+ module Views
+ module AutoTagging
+ class New < Admin::View
+
+ include Deps["repos.tag_repo"]
+
+ expose :tags do
+ tag_repo.list.to_a
+ end
+ end
+ 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
index fa245c3..5c5f473 100644
--- a/slices/admin/views/tags/index.rb
+++ b/slices/admin/views/tags/index.rb
@@ -6,7 +6,7 @@ module Admin
include Deps["repos.tag_repo"]
expose :tags do
- tag_repo.list.to_a
+ tag_repo.list_with_posts.to_a
end
expose :unused_tags do |tags|