diff options
-rw-r--r-- | Gemfile | 2 | ||||
-rw-r--r-- | Gemfile.lock | 31 | ||||
-rw-r--r-- | app/controllers/blogs_controller.rb | 34 | ||||
-rw-r--r-- | app/models/concerns/votable.rb | 8 | ||||
-rw-r--r-- | app/models/vote.rb | 1 | ||||
-rw-r--r-- | app/views/blogs/_blog.html.haml | 2 | ||||
-rw-r--r-- | app/views/blogs/show.html.haml | 1 | ||||
-rw-r--r-- | app/views/layouts/application.html.haml | 2 | ||||
-rw-r--r-- | config/routes.rb | 1 | ||||
-rw-r--r-- | db/migrate/20241207200746_add_like_fields_to_vote.rb | 8 | ||||
-rw-r--r-- | db/schema.rb | 4 |
11 files changed, 91 insertions, 3 deletions
diff --git a/Gemfile b/Gemfile index d0f9528..d6ca5e9 100644 --- a/Gemfile +++ b/Gemfile | |||
@@ -85,3 +85,5 @@ gem "image_processing", ">= 1.2" | |||
85 | gem "meta-tags" | 85 | gem "meta-tags" |
86 | gem 'rails_autolink' | 86 | gem 'rails_autolink' |
87 | gem 'whenever', "~> 1.0.0", require: false | 87 | gem 'whenever', "~> 1.0.0", require: false |
88 | gem "webmention" | ||
89 | 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 | |||
137 | railties (>= 4.1.0) | 137 | railties (>= 4.1.0) |
138 | responders | 138 | responders |
139 | warden (~> 1.2.3) | 139 | warden (~> 1.2.3) |
140 | domain_name (0.6.20240107) | ||
140 | drb (2.2.0) | 141 | drb (2.2.0) |
141 | ruby2_keywords | 142 | ruby2_keywords |
142 | ed25519 (1.3.0) | 143 | ed25519 (1.3.0) |
@@ -145,6 +146,9 @@ GEM | |||
145 | erubi (1.12.0) | 146 | erubi (1.12.0) |
146 | execjs (2.9.1) | 147 | execjs (2.9.1) |
147 | ffi (1.16.3) | 148 | ffi (1.16.3) |
149 | ffi-compiler (1.3.2) | ||
150 | ffi (>= 1.15.5) | ||
151 | rake | ||
148 | globalid (1.2.1) | 152 | globalid (1.2.1) |
149 | activesupport (>= 6.1) | 153 | activesupport (>= 6.1) |
150 | haml (6.2.3) | 154 | haml (6.2.3) |
@@ -152,11 +156,24 @@ GEM | |||
152 | thor | 156 | thor |
153 | tilt | 157 | tilt |
154 | highline (2.1.0) | 158 | highline (2.1.0) |
159 | http (5.2.0) | ||
160 | addressable (~> 2.8) | ||
161 | base64 (~> 0.1) | ||
162 | http-cookie (~> 1.0) | ||
163 | http-form_data (~> 2.2) | ||
164 | llhttp-ffi (~> 0.5.0) | ||
165 | http-cookie (1.0.8) | ||
166 | domain_name (~> 0.5) | ||
167 | http-form_data (2.3.0) | ||
155 | i18n (1.14.1) | 168 | i18n (1.14.1) |
156 | concurrent-ruby (~> 1.0) | 169 | concurrent-ruby (~> 1.0) |
157 | image_processing (1.12.2) | 170 | image_processing (1.12.2) |
158 | mini_magick (>= 4.9.5, < 5) | 171 | mini_magick (>= 4.9.5, < 5) |
159 | ruby-vips (>= 2.0.17, < 3) | 172 | ruby-vips (>= 2.0.17, < 3) |
173 | indieweb-endpoints (8.0.0) | ||
174 | http (~> 5.0) | ||
175 | link-header-parser (~> 5.0) | ||
176 | nokogiri (>= 1.13) | ||
160 | io-console (0.6.0) | 177 | io-console (0.6.0) |
161 | irb (1.8.3) | 178 | irb (1.8.3) |
162 | rdoc | 179 | rdoc |
@@ -172,10 +189,15 @@ GEM | |||
172 | railties (>= 3.2.16) | 189 | railties (>= 3.2.16) |
173 | js-routes (2.2.7) | 190 | js-routes (2.2.7) |
174 | railties (>= 4) | 191 | railties (>= 4) |
192 | json (2.9.0) | ||
175 | libv8-node (18.16.0.0) | 193 | libv8-node (18.16.0.0) |
194 | link-header-parser (5.1.1) | ||
176 | listen (3.0.8) | 195 | listen (3.0.8) |
177 | rb-fsevent (~> 0.9, >= 0.9.4) | 196 | rb-fsevent (~> 0.9, >= 0.9.4) |
178 | rb-inotify (~> 0.9, >= 0.9.7) | 197 | rb-inotify (~> 0.9, >= 0.9.7) |
198 | llhttp-ffi (0.5.0) | ||
199 | ffi-compiler (~> 1.0) | ||
200 | rake (~> 13.0) | ||
179 | loofah (2.21.4) | 201 | loofah (2.21.4) |
180 | crass (~> 1.0.2) | 202 | crass (~> 1.0.2) |
181 | nokogiri (>= 1.12.0) | 203 | nokogiri (>= 1.12.0) |
@@ -187,6 +209,9 @@ GEM | |||
187 | marcel (1.0.2) | 209 | marcel (1.0.2) |
188 | meta-tags (2.19.0) | 210 | meta-tags (2.19.0) |
189 | actionpack (>= 3.2.0, < 7.2) | 211 | actionpack (>= 3.2.0, < 7.2) |
212 | microformats (4.5.0) | ||
213 | json | ||
214 | nokogiri | ||
190 | mime-types (3.5.1) | 215 | mime-types (3.5.1) |
191 | mime-types-data (~> 3.2015) | 216 | mime-types-data (~> 3.2015) |
192 | mime-types-data (3.2023.1003) | 217 | mime-types-data (3.2023.1003) |
@@ -339,6 +364,10 @@ GEM | |||
339 | activemodel (>= 6.0.0) | 364 | activemodel (>= 6.0.0) |
340 | bindex (>= 0.4.0) | 365 | bindex (>= 0.4.0) |
341 | railties (>= 6.0.0) | 366 | railties (>= 6.0.0) |
367 | webmention (7.0.0) | ||
368 | http (~> 5.0) | ||
369 | indieweb-endpoints (~> 8.0) | ||
370 | nokogiri (>= 1.13) | ||
342 | webrick (1.8.1) | 371 | webrick (1.8.1) |
343 | websocket (1.2.10) | 372 | websocket (1.2.10) |
344 | websocket-driver (0.7.6) | 373 | websocket-driver (0.7.6) |
@@ -380,6 +409,7 @@ DEPENDENCIES | |||
380 | js-routes | 409 | js-routes |
381 | listen (>= 3.0.5, < 3.2) | 410 | listen (>= 3.0.5, < 3.2) |
382 | meta-tags | 411 | meta-tags |
412 | microformats (~> 4.0, >= 4.2.1) | ||
383 | mini_racer | 413 | mini_racer |
384 | mysql2 | 414 | mysql2 |
385 | normalize-rails | 415 | normalize-rails |
@@ -398,6 +428,7 @@ DEPENDENCIES | |||
398 | turbolinks (~> 5) | 428 | turbolinks (~> 5) |
399 | tzinfo-data | 429 | tzinfo-data |
400 | web-console (>= 3.3.0) | 430 | web-console (>= 3.3.0) |
431 | webmention | ||
401 | webrick (~> 1.7) | 432 | webrick (~> 1.7) |
402 | whenever (~> 1.0.0) | 433 | whenever (~> 1.0.0) |
403 | will_paginate (~> 4.0) | 434 | 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 @@ | |||
1 | require 'microformats' | ||
1 | require 'redcarpet/render_strip' | 2 | require 'redcarpet/render_strip' |
3 | require 'webmention' | ||
2 | 4 | ||
3 | class BlogsController < ApplicationController | 5 | class BlogsController < ApplicationController |
6 | skip_before_action :verify_authenticity_token, only: [:webmention] | ||
4 | 7 | ||
5 | def summary | 8 | def summary |
6 | @blogs = Blog.where(published: true).order(published_at: :desc).paginate(page: params[:page], per_page: 10) | 9 | @blogs = Blog.where(published: true).order(published_at: :desc).paginate(page: params[:page], per_page: 10) |
@@ -89,4 +92,35 @@ class BlogsController < ApplicationController | |||
89 | end | 92 | end |
90 | end | 93 | end |
91 | 94 | ||
95 | def webmention | ||
96 | @blog = Blog.find_by_slug(params[:slug]) | ||
97 | |||
98 | raise ActiveRecord::RecordNotFound unless @blog | ||
99 | raise ActiveRecord::RecordNotFound unless @blog.published | ||
100 | |||
101 | permalink = url_for(@blog) | ||
102 | |||
103 | target = params[:target] | ||
104 | unless target == permalink | ||
105 | render json: { error: "Incorrect target for webmention endpoint (#{target} != #{permalink})." } | ||
106 | return | ||
107 | end | ||
108 | |||
109 | source = params[:source] | ||
110 | verification = Webmention.verify_webmention(source, target) | ||
111 | unless verification.verified? | ||
112 | render json: { error: "Webmention could not be verified." } | ||
113 | return | ||
114 | end | ||
115 | |||
116 | response = Webmention::Request.get(source) | ||
117 | parsed = Microformats.parse(response.body.to_s) | ||
118 | |||
119 | if parsed.entry.properties.to_hash.include?("like-of") and parsed.entry.like_of(:all).map(&:to_s).include? permalink | ||
120 | @blog.like!(parsed.entry.author.url, parsed.entry.author.name) | ||
121 | end | ||
122 | |||
123 | head :ok | ||
124 | end | ||
125 | |||
92 | end | 126 | 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 | |||
39 | save! | 39 | save! |
40 | end | 40 | end |
41 | end | 41 | end |
42 | |||
43 | def like!(url, name) | ||
44 | return false unless votes.where(liker_url: url).empty? | ||
45 | |||
46 | votes.create(liker_url: url, liker_name: name, upvote: 1).save | ||
47 | self.upvotes += 1 | ||
48 | save! | ||
49 | end | ||
42 | end | 50 | end |
43 | end | 51 | 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 | |||
2 | belongs_to :votable, polymorphic: true | 2 | belongs_to :votable, polymorphic: true |
3 | 3 | ||
4 | validates :upvote, presence: true, inclusion: { in: [0, 1] } | 4 | validates :upvote, presence: true, inclusion: { in: [0, 1] } |
5 | validates :ip, presence: true | ||
6 | end | 5 | 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 @@ | |||
19 | %cite.bubble.blog-cite | 19 | %cite.bubble.blog-cite |
20 | %span.p-author.h-card | 20 | %span.p-author.h-card |
21 | %strong.p-name= blog.user.login.capitalize | 21 | %strong.p-name= blog.user.login.capitalize |
22 | %data.u-url{ value: "/" } | 22 | %data.u-url{ value: root_url } |
23 | on | 23 | on |
24 | %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") | 24 | %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") |
25 | .post-vote{ id: "blog-vote-section-#{blog.id}" } | 25 | .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 @@ | |||
1 | - title @blog.title | 1 | - title @blog.title |
2 | - content_for :webmention_endpoint, webmention_blog_url(@blog) | ||
2 | - unless @prev.nil? | 3 | - unless @prev.nil? |
3 | .back-post= link_to "← #{@prev.title}", @prev | 4 | .back-post= link_to "← #{@prev.title}", @prev |
4 | - unless @next.nil? | 5 | - 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 @@ | |||
6 | = stylesheet_link_tag 'main', media: 'all', 'data-turbolinks-track': 'reload' | 6 | = stylesheet_link_tag 'main', media: 'all', 'data-turbolinks-track': 'reload' |
7 | = javascript_include_tag 'application', 'data-turbolinks-track': 'reload' | 7 | = javascript_include_tag 'application', 'data-turbolinks-track': 'reload' |
8 | = display_meta_tags og: { site_name: "Four Island" } | 8 | = display_meta_tags og: { site_name: "Four Island" } |
9 | - if content_for?(:webmention_endpoint) | ||
10 | %link{rel: "webmention", href: yield(:webmention_endpoint)} | ||
9 | %body#main-body | 11 | %body#main-body |
10 | - if flash[:alert] | 12 | - if flash[:alert] |
11 | %div#flash.flash-alert= flash[:alert] | 13 | %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 | |||
51 | member do | 51 | member do |
52 | post 'upvote' | 52 | post 'upvote' |
53 | post 'downvote' | 53 | post 'downvote' |
54 | post 'webmention' | ||
54 | 55 | ||
55 | resources :comments, only: [:create] | 56 | resources :comments, only: [:create] |
56 | end | 57 | 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 @@ | |||
1 | class AddLikeFieldsToVote < ActiveRecord::Migration[7.1] | ||
2 | def change | ||
3 | change_table :votes do |t| | ||
4 | t.string :liker_url | ||
5 | t.string :liker_name | ||
6 | end | ||
7 | end | ||
8 | 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 @@ | |||
10 | # | 10 | # |
11 | # It's strongly recommended that you check this file into your version control system. | 11 | # It's strongly recommended that you check this file into your version control system. |
12 | 12 | ||
13 | ActiveRecord::Schema[7.1].define(version: 2024_03_20_145033) do | 13 | ActiveRecord::Schema[7.1].define(version: 2024_12_07_200746) do |
14 | create_table "active_storage_attachments", force: :cascade do |t| | 14 | create_table "active_storage_attachments", force: :cascade do |t| |
15 | t.string "name", null: false | 15 | t.string "name", null: false |
16 | t.string "record_type", null: false | 16 | t.string "record_type", null: false |
@@ -421,6 +421,8 @@ ActiveRecord::Schema[7.1].define(version: 2024_03_20_145033) do | |||
421 | t.string "ip" | 421 | t.string "ip" |
422 | t.datetime "created_at", null: false | 422 | t.datetime "created_at", null: false |
423 | t.datetime "updated_at", null: false | 423 | t.datetime "updated_at", null: false |
424 | t.string "liker_url" | ||
425 | t.string "liker_name" | ||
424 | t.index ["votable_type", "votable_id"], name: "index_votes_on_votable" | 426 | t.index ["votable_type", "votable_id"], name: "index_votes_on_votable" |
425 | end | 427 | end |
426 | 428 | ||