Refactor micropub specific things out to a slice

This commit is contained in:
2023-11-15 18:55:57 +11:00
parent 730ecb9ea4
commit 5b133363b3
63 changed files with 468 additions and 174 deletions

0
app/commands/.keep Normal file
View File

0
app/entities/.keep Normal file
View File

View File

@@ -3,86 +3,6 @@ module Adamantium
class PostRepo < Adamantium::Repo[:posts]
Sequel.extension :pg_json
Sequel.extension :pg_json_ops
commands update: :by_pk
def create(post_attrs)
posts.transaction do
new_post = posts.changeset(:create, post_attrs).commit
post_attrs[:category].each do |tag_name|
next if tag_name == ""
tag = posts.tags.where(label: tag_name).one ||
posts
.tags
.changeset(:create, {label: tag_name, slug: tag_name.downcase.strip.tr(" ", "-").gsub(/[^\w-]/, "")})
.commit
posts.post_tags.changeset(:create, {
post_id: new_post.id,
tag_id: tag[:id]
})
.commit
end
new_post
end
end
def tag_post(post_id:, tags:)
tags.each do |tag_name|
next if tag_name == ""
tag = posts.tags.where(label: tag_name).one ||
posts
.tags
.changeset(:create, {label: tag_name, slug: tag_name.downcase.strip.tr(" ", "-").gsub(/[^\w-]/, "")})
.commit
posts.post_tags.changeset(:create, {
post_id: post_id,
tag_id: tag[:id]
})
.commit
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
posts.post_tags.where(post_id: post_id, tag_id: tag[:id]).changeset(:delete).commit if tag
end
def by_year(year:)
posts
@@ -250,35 +170,12 @@ module Adamantium
.one!
end
def fetch_unpublished!(slug)
posts
.combine(:tags)
.where(slug: slug)
.one!
end
def find!(id)
posts
.by_pk(id)
.one!
end
def slug_exists?(slug)
!!posts
.where(slug: slug)
.one
end
def delete!(slug)
delete_post = posts.where(slug: slug).command(:update)
delete_post.call(published_at: nil)
end
def restore!(slug)
delete_post = posts.where(slug: slug).command(:update)
delete_post.call(published_at: Time.now)
end
def post_years
posts
.where(post_type: "post", location: nil)

View File

@@ -23,9 +23,16 @@ module Adamantium
config.logger.stream = "log/hanami.log"
config.shared_app_component_keys += [
"post_utilities.slugify",
"syndication.dayone",
"syndication.mastodon",
"syndication.blue_sky",
"syndication.raindrop",
"renderers.markdown",
"post_utilities.link_finder"
"post_utilities.link_finder",
"param_parser.micropub_post",
"param_parser.webmention",
"post_utilities.page_cacher"
]
end
end

View File

@@ -2,7 +2,7 @@
Hanami.app.register_provider :param_parser, namespace: true do
start do
register "micropub_post", Adamantium::MicropubRequestParser.new
register "micropub_post", Micropub::RequestParser.new
register "webmention", Adamantium::WebmentionRequestParser.new
end
end

View File

@@ -9,14 +9,7 @@ module Adamantium
use Hanami::Middleware::BodyParser, [:form, :json]
# use Adamantium::Middleware::ProcessParams
scope "micropub" do
get "/", to: "site.config"
post "/", to: "posts.handle"
post "/media", to: "media.create"
get "/media", to: "media.show"
post "/webmentions", to: "webmentions.create"
end
slice :micropub, at: "/micropub"
get "/", to: "site.home"
get "/post/top_tracks/:slug", to: "posts.top_tracks"

View File

@@ -0,0 +1,7 @@
# auto_register: false
# frozen_string_literal: true
module Micropub
class Action < Adamantium::Action
end
end

View File

View File

@@ -1,4 +1,4 @@
module Adamantium
module Micropub
module Actions
module Media
class Create < Action

View File

@@ -1,4 +1,4 @@
module Adamantium
module Micropub
module Actions
module Media
class Show < Action

View File

@@ -1,4 +1,4 @@
module Adamantium
module Micropub
module Actions
module Posts
class Handle < Action

View File

@@ -1,8 +1,8 @@
module Adamantium
module Micropub
module Actions
module Site
class Config < Action
include Deps["settings", "views.site.home", "queries.posts.microformat_post"]
include Deps["settings", "queries.posts.microformat_post"]
before :authenticate!
def handle(req, res)
@@ -60,7 +60,7 @@ module Adamantium
res.content_type = "Application/JSON"
res.body = microformat_post.call(url: req.params[:url], properties: req.params[:properties]).to_json
else
res.render home
res.redirect_to "/"
end
end
end

View File

@@ -1,6 +1,6 @@
# frozen_string_literal: true
module Adamantium
module Micropub
module Actions
module Webmentions
class Create < Adamantium::Action

View File

@@ -0,0 +1,5 @@
body {
background-color: #fff;
color: #000;
font-family: sans-serif;
}

View File

@@ -0,0 +1 @@
import "../css/app.css";

View File

@@ -1,4 +1,4 @@
module Adamantium
module Micropub
module Commands
module AutoTagging
class Tag

View File

@@ -6,10 +6,10 @@ require "filemagic"
require "image_processing/vips"
require "open3"
module Adamantium
module Micropub
module Commands
module Media
class Upload < Command
class Upload < Adamantium::Command
include Deps["settings"]
include Dry::Monads[:result]

View File

@@ -1,4 +1,4 @@
module Adamantium
module Micropub
module Commands
module Posts
class AddSyndicationSource

View File

@@ -1,9 +1,9 @@
require "dry/monads"
module Adamantium
module Micropub
module Commands
module Posts
class CreateBookPost < Command
class CreateBookPost < Adamantium::Command
include Deps["repos.post_repo"]
include Dry::Monads[:result]

View File

@@ -1,9 +1,9 @@
require "dry/monads"
module Adamantium
module Micropub
module Commands
module Posts
class CreateBookmark < Command
class CreateBookmark < Adamantium::Command
include Deps["repos.post_repo",
"post_utilities.page_cacher",
syndicate: "commands.posts.syndicate",

View File

@@ -1,9 +1,9 @@
require "dry/monads"
module Adamantium
module Micropub
module Commands
module Posts
class CreateCheckin < Command
class CreateCheckin < Adamantium::Command
include Deps["repos.post_repo",
"post_utilities.slugify",
"logger",

View File

@@ -1,9 +1,9 @@
require "dry/monads"
module Adamantium
module Micropub
module Commands
module Posts
class CreateEntry < Command
class CreateEntry < Adamantium::Command
include Deps["repos.post_repo",
"post_utilities.slugify",
renderer: "renderers.markdown",

View File

@@ -1,4 +1,4 @@
module Adamantium
module Micropub
module Commands
module Posts
class CreationResolver

View File

@@ -1,7 +1,7 @@
module Adamantium
module Micropub
module Commands
module Posts
class Delete < Command
class Delete < Adamantium::Command
include Deps["repos.post_repo"]
def call(params:)
slug = URI(params[:url]).path.split("/").last

View File

@@ -1,7 +1,7 @@
require "httparty"
require "que"
module Adamantium
module Micropub
module Commands
module Posts
class SendWebmentions

View File

@@ -1,7 +1,7 @@
require "dry/monads"
require "dry/monads/do"
module Adamantium
module Micropub
module Commands
module Posts
class Syndicate

View File

@@ -1,7 +1,7 @@
module Adamantium
module Micropub
module Commands
module Posts
class Undelete < Command
class Undelete < Adamantium::Command
include Deps["repos.post_repo"]
def call(params:)
slug = URI(params[:url]).path.split("/").last

View File

@@ -1,7 +1,7 @@
module Adamantium
module Micropub
module Commands
module Posts
class Update < Command
class Update < Adamantium::Command
include Deps[
"repos.post_repo",
"renderers.markdown",

View File

@@ -0,0 +1,16 @@
# frozen_string_literal: true
require "hanami/middleware/body_parser"
module Micropub
class Routes < Hanami::Routes
use Hanami::Middleware::BodyParser, [:form, :json]
get "/", to: "site.config"
post "/", to: "posts.handle"
post "/media", to: "media.create"
get "/media", to: "media.show"
post "/webmentions", to: "webmentions.create"
end
end

View File

@@ -0,0 +1,167 @@
# frozen_string_literal: false
# auto_register: false
require "rexml/parsers/pullparser"
require "sanitize"
module Micropub
module Decorators
module Posts
class Decorator < SimpleDelegator
def syndicated?
!syndication_sources.empty?
end
def syndicated_to
syndication_sources.map do |source, url|
{
location: source,
url: url
}
end
end
def photos?
__getobj__.photos.count { |p| !p["value"].end_with?("mp4") } > 0
end
def photos
__getobj__.photos.select { |p| !p["value"].end_with?("mp4") }
end
def videos?
__getobj__.photos.count { |p| p["value"].end_with?("mp4") } > 0
end
def videos
__getobj__.photos.select { |p| p["value"].end_with?("mp4") }
end
def prefix_emoji
if name
""
elsif photos? && content == ""
"📷"
else
"💬"
end
end
def display_title
title = name
"#{prefix_emoji} #{title}"
end
def display_published_at
published_at.strftime("%e %B, %Y")
end
def machine_published_at
published_at.rfc2822
end
def feed_content
photos? ? "<div>#{photos.map { |p| "<img src='#{p["value"]}'/>" }.join("")} #{content}</div>" : content
end
def raw_content
Sanitize.fragment(content)
end
def excerpt
name ? truncate_html(content, 240, true) : content
end
def permalink
"#{Hanami.app.settings.micropub_site_url}/post/#{slug}"
end
def lat
geo[0]
end
def lon
geo[1]
end
def small_map
"https://api.mapbox.com/styles/v1/dnitza/cleb2o734000k01pbifls5620/static/pin-s+555555(#{lon},#{lat})/#{lon},#{lat},14,0/200x100@2x?access_token=pk.eyJ1IjoiZG5pdHphIiwiYSI6ImNsZWIzOHFmazBkODIzdm9kZHgxdDF4ajQifQ.mSneE-1SKeju8AOz5gp4BQ"
end
def large_map
"https://api.mapbox.com/styles/v1/dnitza/cleb2o734000k01pbifls5620/static/pin-s+555555(#{lon},#{lat})/#{lon},#{lat},14,0/620x310@2x?access_token=pk.eyJ1IjoiZG5pdHphIiwiYSI6ImNsZWIzOHFmazBkODIzdm9kZHgxdDF4ajQifQ.mSneE-1SKeju8AOz5gp4BQ"
end
def template_type
:post
end
def posted_in
if name.nil?
:statuses
elsif post_type.to_sym == :book
:bookshelf
elsif location.nil?
:posts
else
:places
end
end
def trips
__getobj__.trips
end
private
# e.g. geo:-37.75188,144.90417;u=35
def geo
loc = location.split(":")[1]
p = loc.split(";")[0]
p.split(",")
end
def truncate_html(content, len = 30, at_end = nil)
return content if content.to_s.length <= len
p = REXML::Parsers::PullParser.new(content)
tags = []
new_len = len
results = ""
while p.has_next? && new_len > 0
p_e = p.pull
case p_e.event_type
when :start_element
tags.push p_e[0]
results << "<#{tags.last}#{attrs_to_s(p_e[1])}>"
when :end_element
results << "</#{tags.pop}>"
when :text
results << p_e[0][0..new_len]
new_len -= p_e[0].length
else
results << "<!-- #{p_e.inspect} -->"
end
end
if at_end
results << "..."
end
tags.reverse_each do |tag|
results << "</#{tag}>"
end
results
end
def attrs_to_s(attrs)
if attrs.empty?
""
else
" " + attrs.to_a.map { |attr| %(#{attr[0]}="#{attr[1]}") }.join(" ")
end
end
end
end
end
end

View File

@@ -1,6 +1,7 @@
module Adamantium
module Micropub
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
@@ -8,6 +9,14 @@ module Adamantium
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

@@ -1,4 +1,4 @@
module Adamantium
module Micropub
module Entities
class BookRequest < Dry::Struct
attribute :h, Types::Coercible::String

View File

@@ -1,4 +1,4 @@
module Adamantium
module Micropub
module Entities
class BookmarkRequest < Dry::Struct
attribute :h, Types::Coercible::String

View File

@@ -1,4 +1,4 @@
module Adamantium
module Micropub
module Entities
class CheckinRequest < Dry::Struct
attribute :h, Types::Coercible::String

View File

@@ -1,4 +1,4 @@
module Adamantium
module Micropub
module Entities
class PostRequest < Dry::Struct
attribute :h, Types::Coercible::String

View File

@@ -1,6 +1,6 @@
require "reverse_markdown"
module Adamantium
module Micropub
module Queries
module Posts
class MicroformatPost

View File

@@ -1,16 +1,16 @@
module Adamantium
module Micropub
module Repos
class AutoTaggingRepo < Adamantium::Repo[:auto_taggings]
def find(id)
auto_taggings
.where(id: id)
.map_to(Adamantium::Entities::AutoTagging)
.map_to(Micropub::Entities::AutoTagging)
.to_a
end
def all
auto_taggings
.map_to(Adamantium::Entities::AutoTagging)
.map_to(Micropub::Entities::AutoTagging)
.to_a
end
end

View File

@@ -0,0 +1,7 @@
module Micropub
module Repos
class MovieRepo < Adamantium::Repo[:movies]
commands :create
end
end
end

View File

@@ -0,0 +1,15 @@
module Micropub
module Repos
class PodcastRepo < Adamantium::Repo[:podcasts]
commands :create
def listing
podcasts.order(:name).to_a
end
def delete_all
podcasts.delete
end
end
end
end

View File

@@ -0,0 +1,112 @@
module Micropub
module Repos
class PostRepo < Adamantium::Repo[:posts]
commands update: :by_pk
def remove_tag(post_id:, tag:)
tag = posts.tags.where(label: tag).one
posts.post_tags.where(post_id: post_id, tag_id: tag[:id]).changeset(:delete).commit if tag
end
def create(post_attrs)
posts.transaction do
new_post = posts.changeset(:create, post_attrs).commit
post_attrs[:category].each do |tag_name|
next if tag_name == ""
tag = posts.tags.where(label: tag_name).one ||
posts
.tags
.changeset(:create, {label: tag_name, slug: tag_name.downcase.strip.tr(" ", "-").gsub(/[^\w-]/, "")})
.commit
posts.post_tags.changeset(:create, {
post_id: new_post.id,
tag_id: tag[:id]
})
.commit
end
new_post
end
end
def slug_exists?(slug)
!!posts
.where(slug: slug)
.one
end
def find!(id)
posts
.by_pk(id)
.one!
end
def fetch!(slug)
posts
.published
.combine(:tags, :trips, :webmentions)
.node(:webmentions) { |webmention|
webmention.where(type: "reply")
}
.where(slug: slug)
.one!
end
def fetch_unpublished!(slug)
posts
.combine(:tags)
.where(slug: slug)
.one!
end
def tag_post(post_id:, tags:)
tags.each do |tag_name|
next if tag_name == ""
tag = posts.tags.where(label: tag_name).one ||
posts
.tags
.changeset(:create, {label: tag_name, slug: tag_name.downcase.strip.tr(" ", "-").gsub(/[^\w-]/, "")})
.commit
posts.post_tags.changeset(:create, {
post_id: post_id,
tag_id: tag[:id]
})
.commit
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 delete!(slug)
delete_post = posts.where(slug: slug).command(:update)
delete_post.call(published_at: nil)
end
def restore!(slug)
delete_post = posts.where(slug: slug).command(:update)
delete_post.call(published_at: Time.now)
end
end
end
end

View File

@@ -0,0 +1,7 @@
module Micropub
module Repos
class WebmentionsRepo < Adamantium::Repo[:webmentions]
commands :create
end
end
end

View File

@@ -0,0 +1,7 @@
module Micropub
module Repos
class WorkoutRepo < Adamantium::Repo[:workouts]
commands :create, update: :by_pk
end
end
end

View File

@@ -1,5 +1,5 @@
module Adamantium
class MicropubRequestParser
module Micropub
class RequestParser
def call(params:)
return nil if params.key?(:action)

View File

View File

View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Adamantium - Micropub</title>
<%= favicon_tag %>
<%= stylesheet_tag "micropub/app" %>
</head>
<body>
<%= yield %>
<%= javascript_tag "micropub/app" %>
</body>
</html>

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

@@ -0,0 +1,13 @@
# frozen_string_literal: true
require "dry/types"
module Micropub
Types = Dry.Types
module Types
class Tag < Dry::Struct
attribute :label, Types::Coercible::String
end
end
end

View File

@@ -1,4 +1,4 @@
module Adamantium
module Micropub
module Validation
module Posts
class BookContract < Dry::Validation::Contract

View File

@@ -1,4 +1,4 @@
module Adamantium
module Micropub
module Validation
module Posts
class BookmarkContract < Dry::Validation::Contract

View File

@@ -1,4 +1,4 @@
module Adamantium
module Micropub
module Validation
module Posts
class CheckinContract < Dry::Validation::Contract

View File

@@ -1,4 +1,4 @@
module Adamantium
module Micropub
module Validation
module Posts
class PostContract < Dry::Validation::Contract

7
slices/micropub/view.rb Normal file
View File

@@ -0,0 +1,7 @@
# auto_register: false
# frozen_string_literal: true
module Micropub
class View < Adamantium::View
end
end

View File

View File

@@ -0,0 +1,10 @@
# auto_register: false
# frozen_string_literal: true
module Micropub
module Views
module Helpers
# Add your view helpers here
end
end
end

View File

@@ -1,4 +1,4 @@
RSpec.describe Adamantium::MicropubRequestParser do
RSpec.describe Micropub::RequestParser do
subject { described_class.new }
context "json request" do
@@ -19,7 +19,7 @@ RSpec.describe Adamantium::MicropubRequestParser do
it "parses the params in to the expected shape" do
Timecop.freeze do
result = subject.call(params: params)
expect(result).to be_a Adamantium::Entities::PostRequest
expect(result).to be_a Micropub::Entities::PostRequest
end
end
end
@@ -38,7 +38,7 @@ RSpec.describe Adamantium::MicropubRequestParser do
it "parses the params in to the expected shape" do
Timecop.freeze do
result = subject.call(params: params)
expect(result).to be_a Adamantium::Entities::PostRequest
expect(result).to be_a Micropub::Entities::PostRequest
end
end
end
@@ -140,7 +140,7 @@ RSpec.describe Adamantium::MicropubRequestParser do
it "parses the request" do
Timecop.freeze do
result = subject.call(params: params)
expect(result).to be_a Adamantium::Entities::CheckinRequest
expect(result).to be_a Micropub::Entities::CheckinRequest
end
end
end

View File

@@ -3,7 +3,7 @@
require "dry/monads"
require "base64"
RSpec.describe Adamantium::Commands::Media::Upload do
RSpec.describe Micropub::Commands::Media::Upload do
subject { described_class.new }
it "saves a file and returns its URL" do

View File

@@ -1,11 +1,11 @@
require "spec_helper"
RSpec.describe Adamantium::Commands::Posts::AddSyndicationSource, :db do
RSpec.describe Micropub::Commands::Posts::AddSyndicationSource, :db do
subject { described_class.new }
describe "setting a syndication source" do
let(:post) { Test::Factory[:post] }
let(:repo) { Adamantium::Container["repos.post_repo"] }
let(:repo) { Micropub::Container["repos.post_repo"] }
context "when no sources exist" do
it "sets a new source" do

View File

@@ -1,7 +1,7 @@
require "spec_helper"
RSpec.describe Adamantium::Commands::Posts::Delete, :db do
let(:post_repo) { spy(Adamantium::Repos::PostRepo) }
RSpec.describe Micropub::Commands::Posts::Delete, :db do
let(:post_repo) { spy(Micropub::Repos::PostRepo) }
let(:subject) { described_class.new(post_repo: post_repo) }
it "deletes a post" do

View File

@@ -1,14 +1,14 @@
require "spec_helper"
require "dry/monads"
RSpec.describe Adamantium::Commands::Posts::Syndicate do
RSpec.describe Micropub::Commands::Posts::Syndicate do
include Dry::Monads[:result]
let(:settings) { double("settings", mastodon_server: "https://mastodon.example/@tester", blue_sky_url: "https://bluesky.app") }
let(:mastodon_client) { double("Adamantium::Client::Mastodon") }
let(:mastodon_syndicator) { Adamantium::Syndication::Mastodon.new(mastodon_client: mastodon_client) }
let(:post) { {url: "example.com", syndicate_to: ["https://mastodon.example", "https://pinboard.in"], category: []} }
let(:add_post_syndication_source) { spy(Adamantium::Commands::Posts::AddSyndicationSource) }
let(:add_post_syndication_source) { spy(Micropub::Commands::Posts::AddSyndicationSource) }
subject {
described_class.new(mastodon: mastodon_syndicator,

View File

@@ -1,7 +1,7 @@
require "spec_helper"
RSpec.describe Adamantium::Commands::Posts::Undelete, :db do
let(:post_repo) { spy(Adamantium::Repos::PostRepo) }
RSpec.describe Micropub::Commands::Posts::Undelete, :db do
let(:post_repo) { spy(Micropub::Repos::PostRepo) }
let(:subject) { described_class.new(post_repo: post_repo) }
it "deletes a post" do

View File

@@ -1,6 +1,6 @@
require "spec_helper"
RSpec.describe Adamantium::Commands::Posts::Update, :db do
RSpec.describe Micropub::Commands::Posts::Update, :db do
# Adding: add a property that didn't previously exist
# If there are any existing values for this property, they are not changed, the new values are added.
# If the property does not exist already, it is created.
@@ -10,7 +10,7 @@ RSpec.describe Adamantium::Commands::Posts::Update, :db do
describe "add" do
let(:post) { Test::Factory[:post] }
let(:repo) { Adamantium::Container["repos.post_repo"] }
let(:repo) { Micropub::Container["repos.post_repo"] }
let(:params) {
{
@@ -37,7 +37,7 @@ RSpec.describe Adamantium::Commands::Posts::Update, :db do
# Replacing: Replace all values of the property. If the property does not exist already, it is created.
describe "replace" do
let(:post) { Test::Factory[:post] }
let(:repo) { Adamantium::Container["repos.post_repo"] }
let(:repo) { Micropub::Container["repos.post_repo"] }
let(:params) {
{
@@ -62,7 +62,7 @@ RSpec.describe Adamantium::Commands::Posts::Update, :db do
describe "remove" do
let(:post1) { Test::Factory[:post] }
let(:post2) { Test::Factory[:post] }
let(:repo) { Adamantium::Container["repos.post_repo"] }
let(:repo) { Micropub::Container["repos.post_repo"] }
let(:complete_params) {
{

View File

@@ -9,7 +9,7 @@ RSpec.describe Adamantium::Syndication::Mastodon do
describe "syndication to mastodon" do
let(:post) {
Adamantium::Entities::PostRequest.new(
Micropub::Entities::PostRequest.new(
h: "h-type",
action: nil,
name: "My Post",