diff --git a/db/migrate/20240303091643_add_updated_at_to_pages.rb b/db/migrate/20240303091643_add_updated_at_to_pages.rb index 01938a9..c1eff94 100644 --- a/db/migrate/20240303091643_add_updated_at_to_pages.rb +++ b/db/migrate/20240303091643_add_updated_at_to_pages.rb @@ -12,7 +12,7 @@ ROM::SQL.migration do $$ language 'plpgsql'; SQL - alter_table :pages do + alter_table :pages do add_column :updated_at, :timestamp end @@ -25,7 +25,7 @@ ROM::SQL.migration do down do execute("DROP FUNCTION IF EXISTS update_updated_at_column();") - + run <<-SQL DROP TRIGGER IF EXISTS update_pages_updated_at ON pages; SQL diff --git a/db/migrate/20240304095229_add_programming_language_to_posts.rb b/db/migrate/20240304095229_add_programming_language_to_posts.rb new file mode 100644 index 0000000..5e68a45 --- /dev/null +++ b/db/migrate/20240304095229_add_programming_language_to_posts.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +ROM::SQL.migration do + change do + alter_table :posts do + add_column :programming_language, :text + end + end +end diff --git a/slices/main/decorators/statuses/decorator.rb b/slices/main/decorators/statuses/decorator.rb index 91155e7..e2b1d20 100644 --- a/slices/main/decorators/statuses/decorator.rb +++ b/slices/main/decorators/statuses/decorator.rb @@ -12,9 +12,8 @@ module Main class Decorator < Main::Decorators::Posts::Decorator def raw_content Sanitize.fragment(content, - elements: ["img", "p"], - attributes: {"img" => ["alt", "src", "title"]} - ) + elements: ["img", "p"], + attributes: {"img" => ["alt", "src", "title"]}) end end end diff --git a/slices/main/templates/posts/show.html.slim b/slices/main/templates/posts/show.html.slim index 7b08fb6..9220b3b 100644 --- a/slices/main/templates/posts/show.html.slim +++ b/slices/main/templates/posts/show.html.slim @@ -24,27 +24,32 @@ article class="h-entry" a class="dark:text-gray-400" href="/places" places - if post.photos? || post.videos? a class="dark:text-gray-400" href="/photos" photos - article class="mb-12 prose dark:prose-invert max-w-prose mx-auto text-gray-800 dark:text-gray-200 prose-a:text-blue-600 prose-a:no-underline hover:prose-a:underline prose-img:rounded prose-video:rounded" - div class="e-content prose-code:bg-pink-100 prose-code:text-pink-900" - == post.content + - if post.post_type == "code" + pre + code + == post.content + - else + article class="mb-12 prose dark:prose-invert max-w-prose mx-auto text-gray-800 dark:text-gray-200 prose-a:text-blue-600 prose-a:no-underline hover:prose-a:underline prose-img:rounded prose-video:rounded" + div class="e-content prose-code:bg-pink-100 prose-code:text-pink-900" + == post.content - - if post.photos? - - post.photos.each_with_index do |photo, idx| - figure id="photo-#{idx}" - img loading="lazy" class="u-photo shadow-solid shadow-pink-100 dark:shadow-pink-200 mb-4" src=photo["value"] alt=photo["alt"] - figcaption - = photo["alt"] - - if post.videos? - - post.videos.each_with_index do |video, index| - figure id="video-#{index}" - video loop=false muted=true controls=true - source type="video/mp4" src="#{video["value"]}" - figcaption= video["alt"] - a href="#" data-replay="video-#{index}" Replay + - if post.photos? + - post.photos.each_with_index do |photo, idx| + figure id="photo-#{idx}" + img loading="lazy" class="u-photo shadow-solid shadow-pink-100 dark:shadow-pink-200 mb-4" src=photo["value"] alt=photo["alt"] + figcaption + = photo["alt"] + - if post.videos? + - post.videos.each_with_index do |video, index| + figure id="video-#{index}" + video loop=false muted=true controls=true + source type="video/mp4" src="#{video["value"]}" + figcaption= video["alt"] + a href="#" data-replay="video-#{index}" Replay - - if post.location - img loading="lazy" class="shadow-solid shadow-pink-100 dark:shadow-pink-200 rounded mb-4" src=post.large_map + - if post.location + img loading="lazy" class="shadow-solid shadow-pink-100 dark:shadow-pink-200 rounded mb-4" src=post.large_map -if post.webmentions && post.webmentions.count > 0 div class="mt-12" h3 #{post.webmentions.count} Comment#{post.webmentions.count != 1 ? "s" : ""} diff --git a/slices/micropub/actions/posts/handle.rb b/slices/micropub/actions/posts/handle.rb index f1b28f2..b3035ab 100644 --- a/slices/micropub/actions/posts/handle.rb +++ b/slices/micropub/actions/posts/handle.rb @@ -34,7 +34,7 @@ module Micropub # create if req_entity && verify_scope(req: req, scope: :create) - Dry::Matcher::ResultMatcher.(create_entry.call(req_entity: req_entity)) do |m| + Dry::Matcher::ResultMatcher.call(create_entry.call(req_entity: req_entity)) do |m| m.success do |post| res.headers["Location"] = "#{settings.micropub_site_url}/#{post.value!.post_type}/#{post.value!.slug}" res.status = 201 diff --git a/slices/micropub/commands/posts/create_code_post.rb b/slices/micropub/commands/posts/create_code_post.rb new file mode 100644 index 0000000..c25b2ee --- /dev/null +++ b/slices/micropub/commands/posts/create_code_post.rb @@ -0,0 +1,35 @@ +require "dry/monads" + +module Micropub + module Commands + module Posts + class CreateCodePost < Adamantium::Command + include Deps["repos.post_repo", + renderer: "renderers.markdown", + syndicate: "commands.posts.syndicate", + ] + + include Dry::Monads[:result] + + def call(post) + post_params = prepare_params(params: post) + created_post = post_repo.create(post_params) + + # syndicate.call(created_post.id, post) + + # decorated_post = Decorators::Posts::Decorator.new(created_post) + + 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 diff --git a/slices/micropub/commands/posts/creation_resolver.rb b/slices/micropub/commands/posts/creation_resolver.rb index 6510573..5bcd069 100644 --- a/slices/micropub/commands/posts/creation_resolver.rb +++ b/slices/micropub/commands/posts/creation_resolver.rb @@ -7,10 +7,12 @@ module Micropub "validation.posts.bookmark_contract", "validation.posts.checkin_contract", "validation.posts.book_contract", + "validation.posts.code_contract", "commands.posts.create_post", "commands.posts.create_bookmark", "commands.posts.create_checkin", - "commands.posts.create_book_post" + "commands.posts.create_book_post", + "commands.posts.create_code_post" ] def call(entry_type:) @@ -21,6 +23,8 @@ module Micropub {command: create_checkin, validation: checkin_contract} in Entities::BookRequest {command: create_book_post, validation: book_contract} + in Entities::CodeRequest + {command: create_code_post, validation: code_contract} else {command: create_post, validation: post_contract} end diff --git a/slices/micropub/entities/code_request.rb b/slices/micropub/entities/code_request.rb new file mode 100644 index 0000000..443c660 --- /dev/null +++ b/slices/micropub/entities/code_request.rb @@ -0,0 +1,15 @@ +module Micropub + module Entities + class CodeRequest < 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 :programming_language, 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) + end + end +end diff --git a/slices/micropub/repos/post_repo.rb b/slices/micropub/repos/post_repo.rb index 7296541..7d820cd 100644 --- a/slices/micropub/repos/post_repo.rb +++ b/slices/micropub/repos/post_repo.rb @@ -13,7 +13,7 @@ module Micropub posts.transaction do new_post = posts.changeset(:create, post_attrs).commit - post_attrs[:category].each do |tag_name| + post_attrs[:category]&.each do |tag_name| next if tag_name == "" tag = posts.tags.where(label: tag_name).one || diff --git a/slices/micropub/request_parser.rb b/slices/micropub/request_parser.rb index e619c38..1e98548 100644 --- a/slices/micropub/request_parser.rb +++ b/slices/micropub/request_parser.rb @@ -16,6 +16,9 @@ module Micropub when :book book_params = parse_book_params(params) Entities::BookRequest.new(book_params) + when :code + code_params = parse_code_params(params) + Entities::CodeRequest.new(code_params) else req_params = parse_post_params(req_type, cont_type, params) Entities::PostRequest.new(req_params) @@ -28,6 +31,7 @@ module Micropub return :bookmark if params[:"bookmark-of"] return :book if params.dig(:properties, :"read-of") return :checkin if params.dig(:properties, :checkin) + return :code if params.dig(:properties, :programming_language) :post end @@ -43,6 +47,21 @@ module Micropub nil end + def parse_code_params(params) + new_params = {} + new_params[:h] = "entry" + new_params[:action] = params[:action] + new_params[:content] = params[:properties][:content].first + new_params[:slug] = SecureRandom.uuid + new_params[:programming_language] = params[:properties][:programming_language] + new_params[:published_at] = Time.now + new_params[:post_type] = :code + new_params[:name] = params[:properties][:name]&.first + new_params[:syndicate_to] = Array(params[:properties][:"mp-syndicate-to"]) || [] + + new_params + end + def parse_post_params(req_type, post_type, params) new_params = {} new_params[:h] = "entry" @@ -52,24 +71,24 @@ module Micropub publish_time = params[:published_at] || Time.now new_params = if req_type == :json - content = if params[:properties][:content] - if params[:properties][:content].is_a?(Array) && params[:properties][:content].first.is_a?(Hash) - params[:properties][:content].first[:html] - else - params[:properties][:content].first&.tr("\n", " ") - end - end - photos = if params[:properties][:photo].is_a?(Array) - params[:properties][:photo].map do |p| - {value: p, alt: ""} - end - elsif params[:properties][:photo].is_a?(Hash) - params[:properties][:photo] - elsif params[:properties][:photo] - {value: params[:properties][:photo], alt: ""} - else - [] - end + content = if params[:properties][:content] + if params[:properties][:content].is_a?(Array) && params[:properties][:content].first.is_a?(Hash) + params[:properties][:content].first[:html] + else + params[:properties][:content].first&.tr("\n", " ") + end + end + photos = if params[:properties][:photo].is_a?(Array) + params[:properties][:photo].map do |p| + {value: p, alt: ""} + end + elsif params[:properties][:photo].is_a?(Hash) + params[:properties][:photo] + elsif params[:properties][:photo] + {value: params[:properties][:photo], alt: ""} + else + [] + end new_params.merge({ published_at: (params[:"post-status"] == "draft") ? nil : publish_time, @@ -83,7 +102,7 @@ module Micropub }) else photos = if params[:photo].is_a?(String) - [{value: params[:photo], alt: ""}] + [{value: params[:photo], alt: ""}] elsif params[:photo].nil? [] else @@ -137,10 +156,10 @@ module Micropub new_params[:post_type] = :checkin location = if params.dig(:properties, :location) - params.dig(:properties, :location).first[:properties] - elsif checkin.dig(:properties, :latitude) && checkin.dig(:properties, :longitude) - {latitude: checkin.dig(:properties, :latitude), longitude: checkin.dig(:properties, :longitude)} - end + params.dig(:properties, :location).first[:properties] + elsif checkin.dig(:properties, :latitude) && checkin.dig(:properties, :longitude) + {latitude: checkin.dig(:properties, :latitude), longitude: checkin.dig(:properties, :longitude)} + end 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" diff --git a/slices/micropub/validation/posts/bookmark_contract.rb b/slices/micropub/validation/posts/bookmark_contract.rb index c8afe4d..b2d59ea 100644 --- a/slices/micropub/validation/posts/bookmark_contract.rb +++ b/slices/micropub/validation/posts/bookmark_contract.rb @@ -2,7 +2,6 @@ module Micropub module Validation module Posts class BookmarkContract < Dry::Validation::Contract - include Deps["repos.post_repo"] params do diff --git a/slices/micropub/validation/posts/code_contract.rb b/slices/micropub/validation/posts/code_contract.rb new file mode 100644 index 0000000..3f29249 --- /dev/null +++ b/slices/micropub/validation/posts/code_contract.rb @@ -0,0 +1,17 @@ +module Micropub + module Validation + module Posts + class CodeContract < Dry::Validation::Contract + params do + required(:name).maybe(:string) + required(:content).filled(:string) + required(:programming_language).filled(:string) + required(:published_at).maybe(:time) + required(:slug).filled(:string) + required(:post_type).value(included_in?: %w[code]) + required(:syndicate_to).array(:string) + end + end + end + end +end diff --git a/spec/requests/create_post_spec.rb b/spec/requests/create_post_spec.rb index 9a66952..040a93d 100644 --- a/spec/requests/create_post_spec.rb +++ b/spec/requests/create_post_spec.rb @@ -47,8 +47,8 @@ RSpec.describe "Post creation", :db, :requests do type: ["h-entry"], properties: { content: [{ - html: "
This post has bold and italic text.
" - }] + html: "This post has bold and italic text.
" + }] } } @@ -191,7 +191,7 @@ RSpec.describe "Post creation", :db, :requests do expect(last_response).to be_successful expect(post_repo.bookmark_listing.count).to eq 1 expect(post_repo.bookmark_listing.last.cached_content).to_not eq nil - + post "/micropub", params expect(last_response).to_not be_successful expect(post_repo.bookmark_listing.count).to eq 1