From a996f3bd05fc480247fd112f23fa3e67f7d5d7b5 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Sat, 7 Dec 2024 15:54:39 -0500 Subject: Added support for liking blog posts via webmention --- Gemfile | 2 ++ Gemfile.lock | 31 ++++++++++++++++++++ app/controllers/blogs_controller.rb | 34 ++++++++++++++++++++++ app/models/concerns/votable.rb | 8 +++++ app/models/vote.rb | 1 - app/views/blogs/_blog.html.haml | 2 +- app/views/blogs/show.html.haml | 1 + app/views/layouts/application.html.haml | 2 ++ config/routes.rb | 1 + .../20241207200746_add_like_fields_to_vote.rb | 8 +++++ db/schema.rb | 4 ++- 11 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 db/migrate/20241207200746_add_like_fields_to_vote.rb diff --git a/Gemfile b/Gemfile index d0f9528..d6ca5e9 100644 --- a/Gemfile +++ b/Gemfile @@ -85,3 +85,5 @@ gem "image_processing", ">= 1.2" gem "meta-tags" gem 'rails_autolink' gem 'whenever', "~> 1.0.0", require: false +gem "webmention" +gem 'microformats', '~> 4.0', '>= 4.2.1' diff --git a/Gemfile.lock b/Gemfile.lock index a3408d7..cc60005 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -137,6 +137,7 @@ GEM railties (>= 4.1.0) responders warden (~> 1.2.3) + domain_name (0.6.20240107) drb (2.2.0) ruby2_keywords ed25519 (1.3.0) @@ -145,6 +146,9 @@ GEM erubi (1.12.0) execjs (2.9.1) ffi (1.16.3) + ffi-compiler (1.3.2) + ffi (>= 1.15.5) + rake globalid (1.2.1) activesupport (>= 6.1) haml (6.2.3) @@ -152,11 +156,24 @@ GEM thor tilt highline (2.1.0) + http (5.2.0) + addressable (~> 2.8) + base64 (~> 0.1) + http-cookie (~> 1.0) + http-form_data (~> 2.2) + llhttp-ffi (~> 0.5.0) + http-cookie (1.0.8) + domain_name (~> 0.5) + http-form_data (2.3.0) i18n (1.14.1) concurrent-ruby (~> 1.0) image_processing (1.12.2) mini_magick (>= 4.9.5, < 5) ruby-vips (>= 2.0.17, < 3) + indieweb-endpoints (8.0.0) + http (~> 5.0) + link-header-parser (~> 5.0) + nokogiri (>= 1.13) io-console (0.6.0) irb (1.8.3) rdoc @@ -172,10 +189,15 @@ GEM railties (>= 3.2.16) js-routes (2.2.7) railties (>= 4) + json (2.9.0) libv8-node (18.16.0.0) + link-header-parser (5.1.1) listen (3.0.8) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) + llhttp-ffi (0.5.0) + ffi-compiler (~> 1.0) + rake (~> 13.0) loofah (2.21.4) crass (~> 1.0.2) nokogiri (>= 1.12.0) @@ -187,6 +209,9 @@ GEM marcel (1.0.2) meta-tags (2.19.0) actionpack (>= 3.2.0, < 7.2) + microformats (4.5.0) + json + nokogiri mime-types (3.5.1) mime-types-data (~> 3.2015) mime-types-data (3.2023.1003) @@ -339,6 +364,10 @@ GEM activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) + webmention (7.0.0) + http (~> 5.0) + indieweb-endpoints (~> 8.0) + nokogiri (>= 1.13) webrick (1.8.1) websocket (1.2.10) websocket-driver (0.7.6) @@ -380,6 +409,7 @@ DEPENDENCIES js-routes listen (>= 3.0.5, < 3.2) meta-tags + microformats (~> 4.0, >= 4.2.1) mini_racer mysql2 normalize-rails @@ -398,6 +428,7 @@ DEPENDENCIES turbolinks (~> 5) tzinfo-data web-console (>= 3.3.0) + webmention webrick (~> 1.7) whenever (~> 1.0.0) will_paginate (~> 4.0) diff --git a/app/controllers/blogs_controller.rb b/app/controllers/blogs_controller.rb index 033d6bb..4bbbc28 100644 --- a/app/controllers/blogs_controller.rb +++ b/app/controllers/blogs_controller.rb @@ -1,6 +1,9 @@ +require 'microformats' require 'redcarpet/render_strip' +require 'webmention' class BlogsController < ApplicationController + skip_before_action :verify_authenticity_token, only: [:webmention] def summary @blogs = Blog.where(published: true).order(published_at: :desc).paginate(page: params[:page], per_page: 10) @@ -89,4 +92,35 @@ class BlogsController < ApplicationController end end + def webmention + @blog = Blog.find_by_slug(params[:slug]) + + raise ActiveRecord::RecordNotFound unless @blog + raise ActiveRecord::RecordNotFound unless @blog.published + + permalink = url_for(@blog) + + target = params[:target] + unless target == permalink + render json: { error: "Incorrect target for webmention endpoint (#{target} != #{permalink})." } + return + end + + source = params[:source] + verification = Webmention.verify_webmention(source, target) + unless verification.verified? + render json: { error: "Webmention could not be verified." } + return + end + + response = Webmention::Request.get(source) + parsed = Microformats.parse(response.body.to_s) + + if parsed.entry.properties.to_hash.include?("like-of") and parsed.entry.like_of(:all).map(&:to_s).include? permalink + @blog.like!(parsed.entry.author.url, parsed.entry.author.name) + end + + head :ok + end + end diff --git a/app/models/concerns/votable.rb b/app/models/concerns/votable.rb index ba6e6d5..40b3a2a 100644 --- a/app/models/concerns/votable.rb +++ b/app/models/concerns/votable.rb @@ -39,5 +39,13 @@ module Votable save! end end + + def like!(url, name) + return false unless votes.where(liker_url: url).empty? + + votes.create(liker_url: url, liker_name: name, upvote: 1).save + self.upvotes += 1 + save! + end end end diff --git a/app/models/vote.rb b/app/models/vote.rb index e2d8386..fced5bd 100644 --- a/app/models/vote.rb +++ b/app/models/vote.rb @@ -2,5 +2,4 @@ class Vote < ApplicationRecord belongs_to :votable, polymorphic: true validates :upvote, presence: true, inclusion: { in: [0, 1] } - validates :ip, presence: true end diff --git a/app/views/blogs/_blog.html.haml b/app/views/blogs/_blog.html.haml index d0b81d1..878b1a2 100644 --- a/app/views/blogs/_blog.html.haml +++ b/app/views/blogs/_blog.html.haml @@ -19,7 +19,7 @@ %cite.bubble.blog-cite %span.p-author.h-card %strong.p-name= blog.user.login.capitalize - %data.u-url{ value: "/" } + %data.u-url{ value: root_url } on %time.dt-published{ datetime: blog.visible_date.strftime("%Y-%m-%dT%H:%M:%SZ%z") }= blog.visible_date.strftime("%B #{blog.visible_date.day.ordinalize}, %Y at %-I:%M:%S%P") .post-vote{ id: "blog-vote-section-#{blog.id}" } diff --git a/app/views/blogs/show.html.haml b/app/views/blogs/show.html.haml index 7558257..c849143 100644 --- a/app/views/blogs/show.html.haml +++ b/app/views/blogs/show.html.haml @@ -1,4 +1,5 @@ - title @blog.title +- content_for :webmention_endpoint, webmention_blog_url(@blog) - unless @prev.nil? .back-post= link_to "← #{@prev.title}", @prev - unless @next.nil? diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 69cd6ba..ec37f73 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -6,6 +6,8 @@ = stylesheet_link_tag 'main', media: 'all', 'data-turbolinks-track': 'reload' = javascript_include_tag 'application', 'data-turbolinks-track': 'reload' = display_meta_tags og: { site_name: "Four Island" } + - if content_for?(:webmention_endpoint) + %link{rel: "webmention", href: yield(:webmention_endpoint)} %body#main-body - if flash[:alert] %div#flash.flash-alert= flash[:alert] diff --git a/config/routes.rb b/config/routes.rb index 6f7b7de..21e1517 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -51,6 +51,7 @@ Rails.application.routes.draw do member do post 'upvote' post 'downvote' + post 'webmention' resources :comments, only: [:create] end diff --git a/db/migrate/20241207200746_add_like_fields_to_vote.rb b/db/migrate/20241207200746_add_like_fields_to_vote.rb new file mode 100644 index 0000000..eae2816 --- /dev/null +++ b/db/migrate/20241207200746_add_like_fields_to_vote.rb @@ -0,0 +1,8 @@ +class AddLikeFieldsToVote < ActiveRecord::Migration[7.1] + def change + change_table :votes do |t| + t.string :liker_url + t.string :liker_name + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 9cfdf57..b01ed43 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_03_20_145033) do +ActiveRecord::Schema[7.1].define(version: 2024_12_07_200746) do create_table "active_storage_attachments", force: :cascade do |t| t.string "name", null: false t.string "record_type", null: false @@ -421,6 +421,8 @@ ActiveRecord::Schema[7.1].define(version: 2024_03_20_145033) do t.string "ip" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.string "liker_url" + t.string "liker_name" t.index ["votable_type", "votable_id"], name: "index_votes_on_votable" end -- cgit 1.4.1