Refactor micropub specific things out to a slice
This commit is contained in:
7
slices/micropub/action.rb
Normal file
7
slices/micropub/action.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
# auto_register: false
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Micropub
|
||||
class Action < Adamantium::Action
|
||||
end
|
||||
end
|
0
slices/micropub/actions/.keep
Normal file
0
slices/micropub/actions/.keep
Normal file
29
slices/micropub/actions/media/create.rb
Normal file
29
slices/micropub/actions/media/create.rb
Normal file
@@ -0,0 +1,29 @@
|
||||
module Micropub
|
||||
module Actions
|
||||
module Media
|
||||
class Create < Action
|
||||
include Deps["commands.media.upload"]
|
||||
before :authenticate!
|
||||
|
||||
def handle(req, res)
|
||||
data = req.params[:file]
|
||||
|
||||
halt 401 unless verify_scope(req: req, scope: :create) || verify_scope(req: req, scope: :media)
|
||||
|
||||
upload_result = upload.call(file: data)
|
||||
|
||||
res.status = 422 if upload_result.failure?
|
||||
|
||||
if upload_result.success?
|
||||
res.status = 201
|
||||
res.headers["Location"] = upload_result.value!
|
||||
res.headers["HX-Refresh"] = true
|
||||
res.body = {
|
||||
url: upload_result.value!
|
||||
}.to_json
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
29
slices/micropub/actions/media/show.rb
Normal file
29
slices/micropub/actions/media/show.rb
Normal file
@@ -0,0 +1,29 @@
|
||||
module Micropub
|
||||
module Actions
|
||||
module Media
|
||||
class Show < Action
|
||||
include Deps["settings"]
|
||||
|
||||
def handle(req, res)
|
||||
res.body = if req.params[:q] == "source"
|
||||
{
|
||||
items: media_url(req.params[:file])
|
||||
}.to_json
|
||||
else
|
||||
"Micropub media endpoint"
|
||||
end
|
||||
|
||||
res.status = 200
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def media_url(filename)
|
||||
pathname = Time.now.strftime("%m-%Y")
|
||||
|
||||
File.join(settings.micropub_site_url, "/media/", "/#{pathname}/", filename).to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
79
slices/micropub/actions/posts/handle.rb
Normal file
79
slices/micropub/actions/posts/handle.rb
Normal file
@@ -0,0 +1,79 @@
|
||||
module Micropub
|
||||
module Actions
|
||||
module Posts
|
||||
class Handle < Action
|
||||
before :authenticate!
|
||||
|
||||
include Deps[
|
||||
"settings",
|
||||
"post_utilities.slugify",
|
||||
"repos.post_repo",
|
||||
post_param_parser: "param_parser.micropub_post",
|
||||
create_resolver: "commands.posts.creation_resolver",
|
||||
delete_post: "commands.posts.delete",
|
||||
undelete_post: "commands.posts.undelete",
|
||||
update_post: "commands.posts.update",
|
||||
add_post_syndication_source: "commands.posts.add_syndication_source"
|
||||
]
|
||||
|
||||
def handle(req, res)
|
||||
req_entity = post_param_parser.call(params: req.params.to_h)
|
||||
action = req.params[:action]
|
||||
|
||||
# delete, undelete, update
|
||||
if action
|
||||
perform_action(req: req, res: res, action: action)
|
||||
elsif req_entity # create
|
||||
create_entry(req: req, res: res, req_entity: req_entity)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_entry(req:, res:, req_entity:)
|
||||
halt 401 unless verify_scope(req: req, scope: :create)
|
||||
|
||||
command, contract = create_resolver.call(entry_type: req_entity).values_at(:command, :validation)
|
||||
post_params = prepare_params(req_entity.to_h)
|
||||
validation = contract.call(post_params)
|
||||
|
||||
if validation.success?
|
||||
command.call(validation.to_h).bind do |post|
|
||||
res.status = 201
|
||||
res.headers["Location"] = "#{settings.micropub_site_url}/#{post.post_type}/#{post.slug}"
|
||||
end
|
||||
else
|
||||
res.body = {error: validation.errors.to_h}.to_json
|
||||
res.status = 422
|
||||
end
|
||||
end
|
||||
|
||||
def perform_action(req:, res:, action:)
|
||||
operation, permission_check = resolve_operation(action)
|
||||
|
||||
halt 401 unless permission_check.call(req)
|
||||
|
||||
operation.call(params: req.params.to_h)
|
||||
res.status = 200
|
||||
end
|
||||
|
||||
def prepare_params(post_params)
|
||||
post = post_params.to_h
|
||||
post[:slug] = post[:slug].empty? ? slugify.call(text: post[:name], checker: post_repo.method(:slug_exists?)) : post[:slug]
|
||||
post
|
||||
end
|
||||
|
||||
def resolve_operation(action)
|
||||
case action
|
||||
when "delete"
|
||||
[delete_post, ->(req) { verify_scope(req: req, scope: :delete) }]
|
||||
when "undelete"
|
||||
[undelete_post, ->(req) { verify_scope(req: req, scope: :undelete) }]
|
||||
when "update"
|
||||
[update_post, ->(req) { verify_scope(req: req, scope: :update) }]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
69
slices/micropub/actions/site/config.rb
Normal file
69
slices/micropub/actions/site/config.rb
Normal file
@@ -0,0 +1,69 @@
|
||||
module Micropub
|
||||
module Actions
|
||||
module Site
|
||||
class Config < Action
|
||||
include Deps["settings", "queries.posts.microformat_post"]
|
||||
before :authenticate!
|
||||
|
||||
def handle(req, res)
|
||||
if req.params[:q] == "config"
|
||||
res.status = 200
|
||||
res.content_type = "application/json"
|
||||
res.body = {
|
||||
"media-endpoint" => settings.micropub_media_endpoint,
|
||||
"destination" => [
|
||||
{uid: settings.micropub_site_id, name: settings.micropub_site_name}
|
||||
],
|
||||
"post-types" => [
|
||||
{type: "note", name: "Note", properties: %w[content photo category]},
|
||||
{type: "article", name: "Article", properties: %w[name content category]},
|
||||
{type: "photo", name: "Photo", properties: %w[name photo content category]},
|
||||
{type: "video", name: "Video", properties: %w[name video content category]},
|
||||
{type: "bookmark", name: "Bookmark", properties: %w[name content category]}
|
||||
],
|
||||
"syndicate-to" => [
|
||||
{
|
||||
uid: "https://social.dnitza.com",
|
||||
name: "Mastodon"
|
||||
},
|
||||
{
|
||||
uid: "https://pinboard.in",
|
||||
name: "Pinboard"
|
||||
},
|
||||
{
|
||||
uid: "https://bsky.app",
|
||||
name: "Blue Sky"
|
||||
}
|
||||
]
|
||||
}.to_json
|
||||
elsif req.params[:q] == "syndicate-to"
|
||||
res.status = 200
|
||||
res.content_type = "Application/JSON"
|
||||
res.body = {
|
||||
"syndicate-to" => [
|
||||
{
|
||||
uid: "https://social.dnitza.com",
|
||||
name: "Mastodon"
|
||||
},
|
||||
{
|
||||
uid: "https://pinboard.in",
|
||||
name: "Pinboard"
|
||||
},
|
||||
{
|
||||
uid: "https://bsky.app",
|
||||
name: "Blue Sky"
|
||||
}
|
||||
]
|
||||
}.to_json
|
||||
elsif req.params[:q] == "source"
|
||||
res.status = 200
|
||||
res.content_type = "Application/JSON"
|
||||
res.body = microformat_post.call(url: req.params[:url], properties: req.params[:properties]).to_json
|
||||
else
|
||||
res.redirect_to "/"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
32
slices/micropub/actions/webmentions/create.rb
Normal file
32
slices/micropub/actions/webmentions/create.rb
Normal file
@@ -0,0 +1,32 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Micropub
|
||||
module Actions
|
||||
module Webmentions
|
||||
class Create < Adamantium::Action
|
||||
include Deps["repos.webmentions_repo",
|
||||
"repos.post_repo",
|
||||
webmention_parser: "param_parser.webmention"
|
||||
]
|
||||
|
||||
def handle(req, res)
|
||||
webmention = webmention_parser.call(params: req.params)
|
||||
case webmention
|
||||
in Success[:reply, reply]
|
||||
slug = req.params[:"in-reply-to"].split("/").last
|
||||
post = post_repo.fetch!(slug)
|
||||
|
||||
reply[:post_id] = post.id
|
||||
|
||||
webmentions_repo.create(reply)
|
||||
res.status = 201
|
||||
in Failure(:invalid_request)
|
||||
res.status = 429
|
||||
in Failure(:not_implemented)
|
||||
res.status = 429
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
5
slices/micropub/assets/css/app.css
Normal file
5
slices/micropub/assets/css/app.css
Normal file
@@ -0,0 +1,5 @@
|
||||
body {
|
||||
background-color: #fff;
|
||||
color: #000;
|
||||
font-family: sans-serif;
|
||||
}
|
1
slices/micropub/assets/js/app.js
Normal file
1
slices/micropub/assets/js/app.js
Normal file
@@ -0,0 +1 @@
|
||||
import "../css/app.css";
|
31
slices/micropub/commands/auto_tagging/tag.rb
Normal file
31
slices/micropub/commands/auto_tagging/tag.rb
Normal file
@@ -0,0 +1,31 @@
|
||||
module Micropub
|
||||
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
|
103
slices/micropub/commands/media/upload.rb
Normal file
103
slices/micropub/commands/media/upload.rb
Normal file
@@ -0,0 +1,103 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "securerandom"
|
||||
require "dry/monads"
|
||||
require "filemagic"
|
||||
require "image_processing/vips"
|
||||
require "open3"
|
||||
|
||||
module Micropub
|
||||
module Commands
|
||||
module Media
|
||||
class Upload < Adamantium::Command
|
||||
include Deps["settings"]
|
||||
include Dry::Monads[:result]
|
||||
|
||||
IMAGE_TYPES = %i[jpeg jpg png].freeze
|
||||
VIDEO_TYPES = %i[gif iso].freeze
|
||||
VALID_UPLOAD_TYPES = IMAGE_TYPES + VIDEO_TYPES
|
||||
|
||||
def call(file:)
|
||||
mime = FileMagic.new
|
||||
type = mime.file(file[:tempfile].path, true).to_sym
|
||||
|
||||
return Failure(:invalid_file_type) unless VALID_UPLOAD_TYPES.include? type
|
||||
|
||||
result = save_image(file: file) if IMAGE_TYPES.include? type
|
||||
result = save_video(file: file, type: type) if VIDEO_TYPES.include? type
|
||||
|
||||
if result.success?
|
||||
Success(result.value!)
|
||||
else
|
||||
Failure()
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def pathname
|
||||
Time.now.strftime("%m-%Y")
|
||||
end
|
||||
|
||||
def uuid
|
||||
SecureRandom.uuid
|
||||
end
|
||||
|
||||
def save_video(file:, type:)
|
||||
fullsize_filename = "#{uuid}.mp4"
|
||||
|
||||
dirname = File.join("public", "media", pathname)
|
||||
|
||||
unless File.directory?(dirname)
|
||||
FileUtils.mkdir_p(dirname)
|
||||
end
|
||||
|
||||
begin
|
||||
case type
|
||||
when :gif
|
||||
Open3.popen3("ffmpeg -i #{file[:tempfile].path} -movflags faststart -pix_fmt yuv420p -vf 'scale=trunc(iw/2)*2:trunc(ih/2)*2' #{File.join(dirname, fullsize_filename)}")
|
||||
when :iso
|
||||
Open3.popen3("ffmpeg -i #{file[:tempfile].path} -vcodec libx264 -crf 28 #{File.join(dirname, fullsize_filename)}")
|
||||
end
|
||||
rescue Errno::ENOENT, NoMethodError => e
|
||||
return Failure(e.message)
|
||||
end
|
||||
|
||||
upload_path = File.join(settings.micropub_site_url, "/media/", "/#{pathname}/", fullsize_filename).to_s
|
||||
Success(upload_path)
|
||||
end
|
||||
|
||||
def save_image(file:)
|
||||
fullsize_filename = "#{uuid}#{File.extname(file[:filename])}"
|
||||
thumbnail_filename = "#{uuid}-small#{File.extname(file[:filename])}"
|
||||
|
||||
dirname = File.join("public", "media", pathname)
|
||||
|
||||
fullsize_pipeline = ImageProcessing::Vips.source(file[:tempfile])
|
||||
.resize_to_limit(1024, nil)
|
||||
.saver(quality: 100)
|
||||
.call(save: false)
|
||||
|
||||
thumbnail_pipeline = ImageProcessing::Vips.source(file[:tempfile])
|
||||
.resize_to_limit(300, 300, crop: :attention)
|
||||
.saver(quality: 100)
|
||||
.call(save: false)
|
||||
|
||||
unless File.directory?(dirname)
|
||||
FileUtils.mkdir_p(dirname)
|
||||
end
|
||||
|
||||
begin
|
||||
fullsize_pipeline.write_to_file(File.join(dirname, fullsize_filename))
|
||||
thumbnail_pipeline.write_to_file(File.join(dirname, thumbnail_filename))
|
||||
rescue Errno::ENOENT, NoMethodError => e
|
||||
return Failure(e.message)
|
||||
end
|
||||
|
||||
upload_path = File.join(settings.micropub_site_url, "/media/", "/#{pathname}/", fullsize_filename).to_s
|
||||
Success(upload_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
18
slices/micropub/commands/posts/add_syndication_source.rb
Normal file
18
slices/micropub/commands/posts/add_syndication_source.rb
Normal file
@@ -0,0 +1,18 @@
|
||||
module Micropub
|
||||
module Commands
|
||||
module Posts
|
||||
class AddSyndicationSource
|
||||
include Deps["repos.post_repo"]
|
||||
|
||||
def call(post_id, source, url)
|
||||
post = post_repo.find!(post_id).to_h
|
||||
syndication_sources = post[:syndication_sources] || {}
|
||||
syndication_sources[source] = url
|
||||
post[:syndication_sources] = syndication_sources
|
||||
|
||||
post_repo.update(post_id, post)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
19
slices/micropub/commands/posts/create_book_post.rb
Normal file
19
slices/micropub/commands/posts/create_book_post.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
require "dry/monads"
|
||||
|
||||
module Micropub
|
||||
module Commands
|
||||
module Posts
|
||||
class CreateBookPost < Adamantium::Command
|
||||
include Deps["repos.post_repo"]
|
||||
|
||||
include Dry::Monads[:result]
|
||||
|
||||
def call(post)
|
||||
created_post = post_repo.create(post)
|
||||
|
||||
Success(created_post)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
32
slices/micropub/commands/posts/create_bookmark.rb
Normal file
32
slices/micropub/commands/posts/create_bookmark.rb
Normal file
@@ -0,0 +1,32 @@
|
||||
require "dry/monads"
|
||||
|
||||
module Micropub
|
||||
module Commands
|
||||
module Posts
|
||||
class CreateBookmark < Adamantium::Command
|
||||
include Deps["repos.post_repo",
|
||||
"post_utilities.page_cacher",
|
||||
syndicate: "commands.posts.syndicate",
|
||||
raindrop: "syndication.raindrop",
|
||||
]
|
||||
|
||||
include Dry::Monads[:result]
|
||||
|
||||
def call(bookmark)
|
||||
created_bookmark = post_repo.create(bookmark)
|
||||
|
||||
syndicate.call(created_bookmark.id, bookmark)
|
||||
raindrop.call(post: created_bookmark)
|
||||
|
||||
if bookmark[:cache]
|
||||
page_cacher.call(url: created_bookmark.url) do |content|
|
||||
post_repo.update(created_bookmark.id, cached_content: content)
|
||||
end
|
||||
end
|
||||
|
||||
Success(created_bookmark)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
42
slices/micropub/commands/posts/create_checkin.rb
Normal file
42
slices/micropub/commands/posts/create_checkin.rb
Normal file
@@ -0,0 +1,42 @@
|
||||
require "dry/monads"
|
||||
|
||||
module Micropub
|
||||
module Commands
|
||||
module Posts
|
||||
class CreateCheckin < Adamantium::Command
|
||||
include Deps["repos.post_repo",
|
||||
"post_utilities.slugify",
|
||||
"logger",
|
||||
renderer: "renderers.markdown",
|
||||
add_post_syndication_source: "commands.posts.add_syndication_source"
|
||||
]
|
||||
|
||||
include Dry::Monads[:result]
|
||||
|
||||
def call(post)
|
||||
syndication_sources = post.delete(:syndication_sources)
|
||||
post_params = prepare_params(params: post)
|
||||
created_post = post_repo.create(post_params)
|
||||
|
||||
syndication_sources.each do |url|
|
||||
add_post_syndication_source.call(created_post.id, :swarm, url)
|
||||
end
|
||||
|
||||
# decorated_post = Decorators::Posts::Decorator.new(created_post)
|
||||
|
||||
# send_webmentions.call(post_content: attrs[:content], post_url: decorated_post.permalink)
|
||||
|
||||
Success(created_post)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def prepare_params(params:)
|
||||
attrs = params.to_h
|
||||
attrs[:content] = renderer.call(content: attrs[:content]) if attrs[:content]
|
||||
attrs
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
43
slices/micropub/commands/posts/create_entry.rb
Normal file
43
slices/micropub/commands/posts/create_entry.rb
Normal file
@@ -0,0 +1,43 @@
|
||||
require "dry/monads"
|
||||
|
||||
module Micropub
|
||||
module Commands
|
||||
module Posts
|
||||
class CreateEntry < Adamantium::Command
|
||||
include Deps["repos.post_repo",
|
||||
"post_utilities.slugify",
|
||||
renderer: "renderers.markdown",
|
||||
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]
|
||||
|
||||
def call(post)
|
||||
post_params = prepare_params(params: post)
|
||||
created_post = post_repo.create(post_params)
|
||||
|
||||
auto_tag.call
|
||||
|
||||
syndicate.call(created_post.id, post)
|
||||
|
||||
decorated_post = Decorators::Posts::Decorator.new(created_post)
|
||||
|
||||
send_webmentions.call(post_content: created_post.content, post_url: decorated_post.permalink)
|
||||
|
||||
Success(created_post)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def prepare_params(params:)
|
||||
attrs = params.to_h
|
||||
attrs[:content] = renderer.call(content: attrs[:content])
|
||||
attrs
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
31
slices/micropub/commands/posts/creation_resolver.rb
Normal file
31
slices/micropub/commands/posts/creation_resolver.rb
Normal file
@@ -0,0 +1,31 @@
|
||||
module Micropub
|
||||
module Commands
|
||||
module Posts
|
||||
class CreationResolver
|
||||
include Deps[
|
||||
"validation.posts.post_contract",
|
||||
"validation.posts.bookmark_contract",
|
||||
"validation.posts.checkin_contract",
|
||||
"validation.posts.book_contract",
|
||||
"commands.posts.create_entry",
|
||||
"commands.posts.create_bookmark",
|
||||
"commands.posts.create_checkin",
|
||||
"commands.posts.create_book_post"
|
||||
]
|
||||
|
||||
def call(entry_type:)
|
||||
case entry_type
|
||||
in Entities::BookmarkRequest
|
||||
{command: create_bookmark, validation: bookmark_contract}
|
||||
in Entities::CheckinRequest
|
||||
{command: create_checkin, validation: checkin_contract}
|
||||
in Entities::BookRequest
|
||||
{command: create_book_post, validation: book_contract}
|
||||
else
|
||||
{command: create_entry, validation: post_contract}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
14
slices/micropub/commands/posts/delete.rb
Normal file
14
slices/micropub/commands/posts/delete.rb
Normal file
@@ -0,0 +1,14 @@
|
||||
module Micropub
|
||||
module Commands
|
||||
module Posts
|
||||
class Delete < Adamantium::Command
|
||||
include Deps["repos.post_repo"]
|
||||
def call(params:)
|
||||
slug = URI(params[:url]).path.split("/").last
|
||||
|
||||
post_repo.delete!(slug)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
18
slices/micropub/commands/posts/send_webmentions.rb
Normal file
18
slices/micropub/commands/posts/send_webmentions.rb
Normal file
@@ -0,0 +1,18 @@
|
||||
require "httparty"
|
||||
require "que"
|
||||
|
||||
module Micropub
|
||||
module Commands
|
||||
module Posts
|
||||
class SendWebmentions
|
||||
include Deps["settings", "post_utilities.link_finder"]
|
||||
|
||||
def call(post_content:, post_url:)
|
||||
Que.connection = Adamantium::Container["persistence.db"]
|
||||
|
||||
Adamantium::Jobs::SendWebMentions.enqueue(post_content: post_content, post_url: post_url)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
51
slices/micropub/commands/posts/syndicate.rb
Normal file
51
slices/micropub/commands/posts/syndicate.rb
Normal file
@@ -0,0 +1,51 @@
|
||||
require "dry/monads"
|
||||
require "dry/monads/do"
|
||||
|
||||
module Micropub
|
||||
module Commands
|
||||
module Posts
|
||||
class Syndicate
|
||||
include Dry::Monads[:result]
|
||||
include Dry::Monads::Do.for(:call)
|
||||
|
||||
include Deps["settings",
|
||||
"syndication.mastodon",
|
||||
"syndication.blue_sky",
|
||||
add_post_syndication_source: "commands.posts.add_syndication_source",
|
||||
send_to_dayone: "syndication.dayone",
|
||||
]
|
||||
|
||||
def call(post_id, post)
|
||||
syndicate_to = syndication_targets(post[:syndicate_to])
|
||||
|
||||
if syndicate_to.include? :mastodon
|
||||
res = mastodon.call(post: post)
|
||||
|
||||
add_post_syndication_source.call(post_id, :mastodon, res.value!) if res.success?
|
||||
end
|
||||
|
||||
if syndicate_to.include? :blue_sky
|
||||
res = blue_sky.call(post: post)
|
||||
|
||||
add_post_syndication_source.call(post_id, :blue_sky, res.value!) if res.success?
|
||||
end
|
||||
|
||||
if post[:category].include? "weekly"
|
||||
send_to_dayone.call(name: post[:name], content: post[:content])
|
||||
end
|
||||
|
||||
Success()
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def syndication_targets(syndicate_to)
|
||||
targets = []
|
||||
targets << :mastodon if syndicate_to.any? { |url| settings.mastodon_server.match(/#{url}/) }
|
||||
targets << :blue_sky if syndicate_to.any? { |url| settings.blue_sky_url.match(/#{url}/) }
|
||||
targets
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
14
slices/micropub/commands/posts/undelete.rb
Normal file
14
slices/micropub/commands/posts/undelete.rb
Normal file
@@ -0,0 +1,14 @@
|
||||
module Micropub
|
||||
module Commands
|
||||
module Posts
|
||||
class Undelete < Adamantium::Command
|
||||
include Deps["repos.post_repo"]
|
||||
def call(params:)
|
||||
slug = URI(params[:url]).path.split("/").last
|
||||
|
||||
post_repo.restore!(slug)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
72
slices/micropub/commands/posts/update.rb
Normal file
72
slices/micropub/commands/posts/update.rb
Normal file
@@ -0,0 +1,72 @@
|
||||
module Micropub
|
||||
module Commands
|
||||
module Posts
|
||||
class Update < Adamantium::Command
|
||||
include Deps[
|
||||
"repos.post_repo",
|
||||
"renderers.markdown",
|
||||
"commands.posts.add_syndication_source"
|
||||
]
|
||||
|
||||
def call(params:)
|
||||
slug = URI(params[:url]).path.split("/").last
|
||||
post = post_repo.fetch!(slug)
|
||||
|
||||
if params.key? :replace
|
||||
content = params[:replace].delete(:content).first
|
||||
name = params[:replace].delete(:name)
|
||||
|
||||
attrs_to_replace = {}
|
||||
attrs_to_replace[:name] = name if name
|
||||
attrs_to_replace[:content] = markdown.call(content: content) if content
|
||||
|
||||
post_repo.update(post.id, attrs_to_replace)
|
||||
end
|
||||
|
||||
if params.key? :add
|
||||
attrs_to_add = {}
|
||||
|
||||
syndication = params[:add].delete(:syndication)&.first
|
||||
tags = params[:add].delete(:category)
|
||||
content = params[:add].delete(:content)&.first
|
||||
name = params[:add].delete(:name)
|
||||
|
||||
attrs_to_add[:name] = name if post.name.empty?
|
||||
attrs_to_add[:content] = markdown.call(content: content) if post.content.empty?
|
||||
|
||||
params[:add].keys.each_with_object(attrs_to_add) do |attr, memo|
|
||||
memo[attr] = params[:add][attr].first if post.fetch(attr, nil).nil?
|
||||
end
|
||||
|
||||
post_repo.update(post.id, attrs_to_add) unless attrs_to_add.empty?
|
||||
|
||||
post_repo.tag_post(post_id: post.id, tags: tags) if tags && !tags.empty?
|
||||
add_syndication_source.call(post.id, "", syndication) if syndication && !syndication.empty?
|
||||
end
|
||||
|
||||
if params.key? :delete
|
||||
if params[:delete].is_a? Hash
|
||||
tags = params[:delete][:category]
|
||||
tags&.each do |tag|
|
||||
post_repo.remove_tag(post_id: post.id, tag: tag)
|
||||
end
|
||||
elsif params[:delete].is_a? Array
|
||||
if params[:delete].delete("category")
|
||||
post.tags.each do |tag|
|
||||
post_repo.remove_tag(post_id: post.id, tag: tag.label)
|
||||
end
|
||||
end
|
||||
|
||||
attrs = {}
|
||||
params[:delete].each do |attr|
|
||||
attrs[attr.to_sym] = nil
|
||||
end
|
||||
|
||||
post_repo.update(post.id, attrs) unless attrs.empty?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
18
slices/micropub/commands/workouts/create.rb
Normal file
18
slices/micropub/commands/workouts/create.rb
Normal file
@@ -0,0 +1,18 @@
|
||||
require "securerandom"
|
||||
require "dry/monads"
|
||||
require "filemagic"
|
||||
|
||||
module Adamantium
|
||||
module Commands
|
||||
module Workouts
|
||||
class Create < Command
|
||||
include Deps["repos.workout_repo"]
|
||||
include Dry::Monads[:result]
|
||||
|
||||
def call(svg:, distance:, duration:)
|
||||
workout_repo.create(path: svg, distance: distance, duration: duration, published_at: Time.now)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
16
slices/micropub/config/routes.rb
Normal file
16
slices/micropub/config/routes.rb
Normal 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
|
167
slices/micropub/decorators/posts/decorator.rb
Normal file
167
slices/micropub/decorators/posts/decorator.rb
Normal 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
|
22
slices/micropub/entities/auto_tagging.rb
Normal file
22
slices/micropub/entities/auto_tagging.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
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
|
||||
|
||||
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
|
15
slices/micropub/entities/book_request.rb
Normal file
15
slices/micropub/entities/book_request.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
module Micropub
|
||||
module Entities
|
||||
class BookRequest < Dry::Struct
|
||||
attribute :h, Types::Coercible::String
|
||||
attribute :content, Types::Coercible::String
|
||||
attribute :book_status, Types::Coercible::String
|
||||
attribute :name, Types::Coercible::String
|
||||
attribute :book_author, Types::Coercible::String
|
||||
attribute :slug, Types::Coercible::String
|
||||
attribute :category, Types::Array.of(Types::Coercible::String)
|
||||
attribute :published_at, Types::Nominal::DateTime
|
||||
attribute :post_type, Types::Coercible::String
|
||||
end
|
||||
end
|
||||
end
|
19
slices/micropub/entities/bookmark_request.rb
Normal file
19
slices/micropub/entities/bookmark_request.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
module Micropub
|
||||
module Entities
|
||||
class BookmarkRequest < Dry::Struct
|
||||
attribute :h, Types::Coercible::String
|
||||
attribute :action, Types::Coercible::String.optional
|
||||
attribute :name, Types::Coercible::String
|
||||
attribute :cache, Types::Params::Bool.optional
|
||||
attribute :content, Types::Coercible::String.optional
|
||||
attribute :url, Types::Coercible::String
|
||||
attribute :slug, Types::Coercible::String
|
||||
attribute :category, Types::Array.of(Types::Coercible::String)
|
||||
attribute :published_at, Types::Nominal::DateTime.optional
|
||||
attribute :post_type, Types::Coercible::String
|
||||
attribute :syndicate_to, Types::Array.of(Types::Coercible::String)
|
||||
attribute :photos, Types::Array.of(Types::Hash)
|
||||
attribute :location, Types::Coercible::String.optional
|
||||
end
|
||||
end
|
||||
end
|
17
slices/micropub/entities/checkin_request.rb
Normal file
17
slices/micropub/entities/checkin_request.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
module Micropub
|
||||
module Entities
|
||||
class CheckinRequest < Dry::Struct
|
||||
attribute :h, Types::Coercible::String
|
||||
attribute :name, Types::Coercible::String.optional
|
||||
attribute :content, Types::Coercible::String.optional
|
||||
attribute :slug, Types::Coercible::String
|
||||
attribute :url, Types::Coercible::String
|
||||
attribute :category, Types::Array.of(Types::Coercible::String)
|
||||
attribute :published_at, Types::Nominal::DateTime.optional
|
||||
attribute :post_type, Types::Coercible::String
|
||||
attribute :syndication_sources, Types::Array.of(Types::Coercible::String)
|
||||
attribute :photos, Types::Array.of(Types::Hash)
|
||||
attribute :location, Types::Coercible::String
|
||||
end
|
||||
end
|
||||
end
|
17
slices/micropub/entities/post_request.rb
Normal file
17
slices/micropub/entities/post_request.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
module Micropub
|
||||
module Entities
|
||||
class PostRequest < Dry::Struct
|
||||
attribute :h, Types::Coercible::String
|
||||
attribute :action, Types::Coercible::String.optional
|
||||
attribute :name, Types::Coercible::String.optional
|
||||
attribute :content, Types::Coercible::String
|
||||
attribute :slug, Types::Coercible::String
|
||||
attribute :category, Types::Array.of(Types::Coercible::String)
|
||||
attribute :published_at, Types::Nominal::DateTime.optional
|
||||
attribute :post_type, Types::Coercible::String
|
||||
attribute :syndicate_to, Types::Array.of(Types::Coercible::String)
|
||||
attribute :photos, Types::Array.of(Types::Hash)
|
||||
attribute :location, Types::Coercible::String.optional
|
||||
end
|
||||
end
|
||||
end
|
37
slices/micropub/queries/posts/microformat_post.rb
Normal file
37
slices/micropub/queries/posts/microformat_post.rb
Normal file
@@ -0,0 +1,37 @@
|
||||
require "reverse_markdown"
|
||||
|
||||
module Micropub
|
||||
module Queries
|
||||
module Posts
|
||||
class MicroformatPost
|
||||
include Deps["repos.post_repo"]
|
||||
|
||||
def call(url:, properties:)
|
||||
slug = URI(url).path.split("/").last
|
||||
|
||||
post = post_repo.fetch_unpublished!(slug)
|
||||
markdown_content = ReverseMarkdown.convert(post.content, unknown_tags: :pass_through, github_flavored: true).to_s
|
||||
|
||||
if properties.nil? || properties.empty?
|
||||
return {
|
||||
type: ["h-entry"],
|
||||
properties: {
|
||||
published: [post.published_at],
|
||||
content: [markdown_content],
|
||||
photo: post.photos,
|
||||
category: post.tags.map { |t| t.label.to_s }
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
result = {properties: {}}
|
||||
result[:properties][:published] = [post.published_at] if properties.include? "published"
|
||||
result[:properties][:content] = [markdown_content] if properties.include? "content"
|
||||
result[:properties][:category] = post.tags.map { |t| t.label.to_s } if properties.include? "category"
|
||||
result[:properties][:photos] = post.photos if properties.include? "photos"
|
||||
result
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
18
slices/micropub/repos/auto_tagging_repo.rb
Normal file
18
slices/micropub/repos/auto_tagging_repo.rb
Normal file
@@ -0,0 +1,18 @@
|
||||
module Micropub
|
||||
module Repos
|
||||
class AutoTaggingRepo < Adamantium::Repo[:auto_taggings]
|
||||
def find(id)
|
||||
auto_taggings
|
||||
.where(id: id)
|
||||
.map_to(Micropub::Entities::AutoTagging)
|
||||
.to_a
|
||||
end
|
||||
|
||||
def all
|
||||
auto_taggings
|
||||
.map_to(Micropub::Entities::AutoTagging)
|
||||
.to_a
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
7
slices/micropub/repos/movie_repo.rb
Normal file
7
slices/micropub/repos/movie_repo.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
module Micropub
|
||||
module Repos
|
||||
class MovieRepo < Adamantium::Repo[:movies]
|
||||
commands :create
|
||||
end
|
||||
end
|
||||
end
|
15
slices/micropub/repos/podcast_repo.rb
Normal file
15
slices/micropub/repos/podcast_repo.rb
Normal 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
|
112
slices/micropub/repos/post_repo.rb
Normal file
112
slices/micropub/repos/post_repo.rb
Normal 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
|
7
slices/micropub/repos/webmentions_repo.rb
Normal file
7
slices/micropub/repos/webmentions_repo.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
module Micropub
|
||||
module Repos
|
||||
class WebmentionsRepo < Adamantium::Repo[:webmentions]
|
||||
commands :create
|
||||
end
|
||||
end
|
||||
end
|
7
slices/micropub/repos/workout_repo.rb
Normal file
7
slices/micropub/repos/workout_repo.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
module Micropub
|
||||
module Repos
|
||||
class WorkoutRepo < Adamantium::Repo[:workouts]
|
||||
commands :create, update: :by_pk
|
||||
end
|
||||
end
|
||||
end
|
143
slices/micropub/request_parser.rb
Normal file
143
slices/micropub/request_parser.rb
Normal file
@@ -0,0 +1,143 @@
|
||||
module Micropub
|
||||
class RequestParser
|
||||
def call(params:)
|
||||
return nil if params.key?(:action)
|
||||
|
||||
cont_type = content_type(params)
|
||||
req_type = request_type(params)
|
||||
|
||||
case cont_type
|
||||
when :bookmark
|
||||
req_params = parse_post_params(req_type, cont_type, params)
|
||||
Entities::BookmarkRequest.new(req_params)
|
||||
when :checkin
|
||||
checkin_params = parse_checkin_params(params)
|
||||
Entities::CheckinRequest.new(checkin_params)
|
||||
when :book
|
||||
book_params = parse_book_params(params)
|
||||
Entities::BookRequest.new(book_params)
|
||||
else
|
||||
req_params = parse_post_params(req_type, cont_type, params)
|
||||
Entities::PostRequest.new(req_params)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def content_type(params)
|
||||
return :bookmark if params[:"bookmark-of"]
|
||||
return :book if params.dig(:properties, :"read-of")
|
||||
return :checkin if params.dig(:properties, :checkin)
|
||||
:post
|
||||
end
|
||||
|
||||
def request_type(params)
|
||||
if params[:h] == "entry"
|
||||
return :form
|
||||
end
|
||||
|
||||
if params[:type]&.include?("h-entry")
|
||||
return :json
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def parse_post_params(req_type, post_type, params)
|
||||
new_params = {}
|
||||
new_params[:h] = "entry"
|
||||
new_params[:post_type] = post_type
|
||||
new_params[:action] = params[:action]
|
||||
|
||||
publish_time = params[:published_at] || Time.now
|
||||
|
||||
new_params = if req_type == :json
|
||||
new_params.merge({
|
||||
published_at: (params[:"post-status"] == "draft") ? nil : publish_time,
|
||||
category: params[:properties][:category] || [],
|
||||
name: params[:properties][:name]&.first,
|
||||
content: params[:properties][:content]&.first&.tr("\n", " "),
|
||||
slug: params[:slug] || params[:"mp-slug"],
|
||||
syndicate_to: Array(params[:properties][:"mp-syndicate-to"]) || [],
|
||||
photos: params[:properties][:photo] || [],
|
||||
location: params[:properties][:location]
|
||||
})
|
||||
else
|
||||
photos = if params[:photo].is_a?(String)
|
||||
{value: params[:photo], alt: ""}
|
||||
elsif params[:photo].nil?
|
||||
[]
|
||||
else
|
||||
params[:photo]
|
||||
end
|
||||
|
||||
content = if params[:content]
|
||||
if params[:content].is_a?(Hash) && params[:content][:html]
|
||||
params[:content][:html]
|
||||
else
|
||||
params[:content]
|
||||
end
|
||||
end
|
||||
|
||||
new_params.merge({
|
||||
syndicate_to: Array(params[:"mp-syndicate-to"]) || [],
|
||||
name: params[:name],
|
||||
slug: params[:slug] || params[:"mp-slug"],
|
||||
published_at: (params[:"post-status"] == "draft") ? nil : publish_time,
|
||||
category: (if params[:category].is_a?(Array)
|
||||
params[:category]
|
||||
elsif params[:category].is_a?(String)
|
||||
params[:category].split(",")
|
||||
else
|
||||
[]
|
||||
end),
|
||||
photos: photos,
|
||||
location: params[:location],
|
||||
content: content
|
||||
})
|
||||
end
|
||||
new_params[:url] = params[:"bookmark-of"]
|
||||
new_params[:cache] = params[:cache] || false
|
||||
|
||||
new_params
|
||||
end
|
||||
|
||||
def parse_checkin_params(params)
|
||||
new_params = {}
|
||||
|
||||
checkin = params.dig(:properties, :checkin).first
|
||||
new_params[:h] = "entry"
|
||||
new_params[:syndication_sources] = params.dig(:properties, :syndication)
|
||||
new_params[:name] = checkin.dig(:properties, :name).first
|
||||
new_params[:content] = params.dig(:properties, :content)&.first || ""
|
||||
new_params[:url] = checkin.dig(:properties, :url)&.first
|
||||
new_params[:slug] = SecureRandom.uuid
|
||||
new_params[:category] = params.dig(:properties, :category)
|
||||
published = DateTime.parse(params.dig(:properties, :published)&.first).new_offset(0)
|
||||
new_params[:published_at] = published.to_s
|
||||
new_params[:post_type] = :checkin
|
||||
location = params.dig(:properties, :location).first[:properties]
|
||||
new_params[:photos] = params.dig(:properties, :photo)&.map { |p| {value: p, alt: new_params[:name]} } || []
|
||||
new_params[:location] = "geo:#{location.dig(:latitude).first},#{location.dig(:longitude).first};u=0"
|
||||
new_params
|
||||
end
|
||||
|
||||
def parse_book_params(params)
|
||||
new_params = {}
|
||||
new_params[:post_type] = "book"
|
||||
|
||||
entry = params[:properties]
|
||||
new_params[:h] = "entry"
|
||||
new_params[:content] = entry[:summary].first
|
||||
new_params[:book_status] = entry[:"read-status"].first
|
||||
|
||||
book = params.dig(:properties, :"read-of").first[:properties]
|
||||
new_params[:name] = book[:name].first
|
||||
new_params[:book_author] = book[:author].first
|
||||
new_params[:slug] = book[:uid].first
|
||||
new_params[:category] = []
|
||||
new_params[:published_at] = Time.now
|
||||
new_params
|
||||
end
|
||||
end
|
||||
end
|
0
slices/micropub/templates/.keep
Normal file
0
slices/micropub/templates/.keep
Normal file
0
slices/micropub/templates/layouts/.keep
Normal file
0
slices/micropub/templates/layouts/.keep
Normal file
14
slices/micropub/templates/layouts/app.html.erb
Normal file
14
slices/micropub/templates/layouts/app.html.erb
Normal 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
13
slices/micropub/types.rb
Normal 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
|
18
slices/micropub/validation/posts/book_contract.rb
Normal file
18
slices/micropub/validation/posts/book_contract.rb
Normal file
@@ -0,0 +1,18 @@
|
||||
module Micropub
|
||||
module Validation
|
||||
module Posts
|
||||
class BookContract < Dry::Validation::Contract
|
||||
params do
|
||||
required(:name).maybe(:string)
|
||||
required(:content).filled(:string)
|
||||
required(:category).array(:string)
|
||||
required(:published_at).maybe(:time)
|
||||
required(:slug).filled(:string)
|
||||
required(:post_type).value(included_in?: %w[book])
|
||||
required(:book_author).maybe(:string)
|
||||
required(:book_status).value(included_in?: %w[to-read reading read finished])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
20
slices/micropub/validation/posts/bookmark_contract.rb
Normal file
20
slices/micropub/validation/posts/bookmark_contract.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
module Micropub
|
||||
module Validation
|
||||
module Posts
|
||||
class BookmarkContract < Dry::Validation::Contract
|
||||
params do
|
||||
required(:name).filled(:string)
|
||||
required(:content).maybe(:string)
|
||||
required(:category).array(:string)
|
||||
required(:published_at).maybe(:time)
|
||||
required(:url).filled(:string)
|
||||
required(:slug).filled(:string)
|
||||
required(:post_type).value(included_in?: %w[bookmark])
|
||||
required(:syndicate_to).array(:string)
|
||||
required(:photos).array(:hash)
|
||||
required(:cache).filled(:bool)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
19
slices/micropub/validation/posts/checkin_contract.rb
Normal file
19
slices/micropub/validation/posts/checkin_contract.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
module Micropub
|
||||
module Validation
|
||||
module Posts
|
||||
class CheckinContract < Dry::Validation::Contract
|
||||
params do
|
||||
required(:name).maybe(:string)
|
||||
required(:content).maybe(:string)
|
||||
required(:category).array(:string)
|
||||
required(:published_at).maybe(:time)
|
||||
required(:slug).filled(:string)
|
||||
required(:post_type).value(included_in?: %w[checkin])
|
||||
required(:syndication_sources).array(:string)
|
||||
required(:photos).array(:hash)
|
||||
required(:location).maybe(:string)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
19
slices/micropub/validation/posts/post_contract.rb
Normal file
19
slices/micropub/validation/posts/post_contract.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
module Micropub
|
||||
module Validation
|
||||
module Posts
|
||||
class PostContract < Dry::Validation::Contract
|
||||
params do
|
||||
required(:name).maybe(:string)
|
||||
required(:content).filled(:string)
|
||||
required(:category).array(:string)
|
||||
required(:published_at).maybe(:time)
|
||||
required(:slug).filled(:string)
|
||||
required(:post_type).value(included_in?: %w[post])
|
||||
required(:syndicate_to).array(:string)
|
||||
required(:photos).array(:hash)
|
||||
required(:location).maybe(:string)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
7
slices/micropub/view.rb
Normal file
7
slices/micropub/view.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
# auto_register: false
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Micropub
|
||||
class View < Adamantium::View
|
||||
end
|
||||
end
|
0
slices/micropub/views/.keep
Normal file
0
slices/micropub/views/.keep
Normal file
10
slices/micropub/views/helpers.rb
Normal file
10
slices/micropub/views/helpers.rb
Normal 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
|
Reference in New Issue
Block a user