Add auto tagger for posts

This commit is contained in:
2023-05-06 19:58:59 +10:00
parent c4bd903e74
commit 9f8359d782
25 changed files with 424 additions and 2 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -5,7 +5,6 @@ module Admin
def delete(tag_id:)
post_tags.where(tag_id: tag_id).delete
end
end
end
end

View File

@@ -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))

View File

@@ -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 <strong>#{auto_tagging.tag.label}</strong> when <strong>#{auto_tagging.title_only? ? "title" : "content"}</strong> contains <strong>#{auto_tagging.term}</strong>"
= " — "
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"

View File

@@ -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:&nbsp;
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:&nbsp;
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:&nbsp;
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"

13
slices/admin/types.rb Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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|