From fbfcc455718341cf30a7f1dfbf1ea0b69384bf06 Mon Sep 17 00:00:00 2001
From: Jeremy Smith
Date: Fri, 20 Mar 2026 15:43:18 -0400
Subject: [PATCH] Add voting
---
app/controllers/admin_controller.rb | 2 +-
app/controllers/pages_controller.rb | 7 +++++-
app/controllers/votes_controller.rb | 26 ++++++++++++++++++++++
app/models/submission.rb | 1 +
app/models/user.rb | 1 +
app/models/vote.rb | 15 +++++++++++++
app/views/admin/index.html.erb | 1 +
app/views/layouts/application.html.erb | 4 +++-
app/views/pages/home.html.erb | 21 +++++++++++++++++
config/routes.rb | 4 +++-
db/migrate/20260320191401_create_votes.rb | 12 ++++++++++
db/schema.rb | 14 +++++++++++-
public/apple-touch-icon.png | Bin 0 -> 1417 bytes
public/favicon.ico | Bin 0 -> 5430 bytes
public/icon-192.png | Bin 0 -> 1423 bytes
public/icon-512.png | Bin 0 -> 3446 bytes
public/icon.png | Bin 4166 -> 3446 bytes
public/icon.svg | 4 +---
public/manifest.webmanifest | 6 +++++
19 files changed, 110 insertions(+), 8 deletions(-)
create mode 100644 app/controllers/votes_controller.rb
create mode 100644 app/models/vote.rb
create mode 100644 db/migrate/20260320191401_create_votes.rb
create mode 100644 public/apple-touch-icon.png
create mode 100644 public/favicon.ico
create mode 100644 public/icon-192.png
create mode 100644 public/icon-512.png
create mode 100644 public/manifest.webmanifest
diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb
index 70c0386..64627e7 100644
--- a/app/controllers/admin_controller.rb
+++ b/app/controllers/admin_controller.rb
@@ -2,7 +2,7 @@ class AdminController < ApplicationController
before_action :require_admin
def index
- @submissions = Submission.includes(:user).order(created_at: :desc)
+ @submissions = Submission.includes(:user, :votes).order(created_at: :desc)
end
def toggle_discard
diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb
index 51c1789..2d8011b 100644
--- a/app/controllers/pages_controller.rb
+++ b/app/controllers/pages_controller.rb
@@ -1,5 +1,10 @@
class PagesController < ApplicationController
def home
- @submissions = Submission.kept.order("RANDOM()")
+ @submissions = Submission.kept.includes(:votes).sort_by { |s| -s.votes.size }
+ if Current.user
+ @voted_submission_ids = Current.user.votes.where(submission_id: @submissions.map(&:id)).pluck(:submission_id).to_set
+ else
+ @voted_submission_ids = Set.new
+ end
end
end
diff --git a/app/controllers/votes_controller.rb b/app/controllers/votes_controller.rb
new file mode 100644
index 0000000..b713001
--- /dev/null
+++ b/app/controllers/votes_controller.rb
@@ -0,0 +1,26 @@
+class VotesController < ApplicationController
+ before_action :require_authentication
+ before_action :set_submission
+
+ def create
+ vote = @submission.votes.new(user: Current.user)
+
+ if vote.save
+ redirect_back fallback_location: root_path
+ else
+ redirect_back fallback_location: root_path, alert: vote.errors.full_messages.first
+ end
+ end
+
+ def destroy
+ vote = @submission.votes.find_by(user: Current.user)
+ vote&.destroy
+ redirect_back fallback_location: root_path
+ end
+
+ private
+
+ def set_submission
+ @submission = Submission.find(params[:submission_id])
+ end
+end
diff --git a/app/models/submission.rb b/app/models/submission.rb
index 149beab..d507e7a 100644
--- a/app/models/submission.rb
+++ b/app/models/submission.rb
@@ -1,5 +1,6 @@
class Submission < ApplicationRecord
belongs_to :user
+ has_many :votes
validates :line1, :line2, :line3, presence: true
diff --git a/app/models/user.rb b/app/models/user.rb
index 8fb17a2..032a7dc 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1,5 +1,6 @@
class User < ApplicationRecord
has_one :submission
+ has_many :votes
def self.find_or_create_from_omniauth(auth)
find_or_create_by(github_uid: auth.uid) do |user|
diff --git a/app/models/vote.rb b/app/models/vote.rb
new file mode 100644
index 0000000..d31cb3b
--- /dev/null
+++ b/app/models/vote.rb
@@ -0,0 +1,15 @@
+class Vote < ApplicationRecord
+ belongs_to :user
+ belongs_to :submission
+
+ validates :user_id, uniqueness: { scope: :submission_id }
+ validate :not_own_submission
+
+ private
+
+ def not_own_submission
+ if submission && user_id == submission.user_id
+ errors.add(:base, "You cannot vote on your own submission")
+ end
+ end
+end
diff --git a/app/views/admin/index.html.erb b/app/views/admin/index.html.erb
index 45c793d..438e03c 100644
--- a/app/views/admin/index.html.erb
+++ b/app/views/admin/index.html.erb
@@ -11,6 +11,7 @@
<%= submission.user.github_username %>
(<%= submission.user.github_email %>)
· <%= time_ago_in_words(submission.created_at) %> ago
+ · <%= submission.votes.size %> vote<%= submission.votes.size == 1 ? "" : "s" %>
<% if submission.discarded? %>
— discarded
<% end %>
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index 44402aa..1f9de27 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -13,9 +13,11 @@
<%= yield :head %>
+
-
+
+
<%= stylesheet_link_tag :app, "data-turbo-track": "reload" %>
<%= javascript_include_tag "application", "data-turbo-track": "reload", type: "module" %>
diff --git a/app/views/pages/home.html.erb b/app/views/pages/home.html.erb
index f5dd016..c231be9 100644
--- a/app/views/pages/home.html.erb
+++ b/app/views/pages/home.html.erb
@@ -56,6 +56,27 @@
<%= submission.line2 %>
<%= submission.line3 %>
+
+
+
+ <%= submission.votes.size %> vote<%= submission.votes.size == 1 ? "" : "s" %>
+
+
+
+ <% if Current.user && submission.user_id != Current.user.id %>
+ <% if @voted_submission_ids.include?(submission.id) %>
+ <%= button_to submission_vote_path(submission), method: :delete, class: "inline-flex items-center gap-1 text-sm px-3 py-1 rounded-full cursor-pointer bg-gray-400 bg-linear-to-t from-gray-500 to-gray-400 text-white" do %>
+ Voted
+ <% end %>
+ <% else %>
+ <%= button_to submission_vote_path(submission), method: :post, class: "inline-flex items-center gap-1 text-sm px-3 py-1 rounded-full cursor-pointer bg-[#C41C1C] bg-linear-to-t from-[#C41C1C] to-[#DD423E] text-white" do %>
+ ⬆ Vote
+ <% end %>
+ <% end %>
+ <% end %>
+
+
+
<% end %>
diff --git a/config/routes.rb b/config/routes.rb
index 7c153bb..aeb1c5a 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -5,7 +5,9 @@
get "auth/failure", to: "sessions#failure"
delete "sign_out", to: "sessions#destroy"
- resources :submissions, only: [ :new, :create, :update ]
+ resources :submissions, only: [ :new, :create, :update ] do
+ resource :vote, only: [ :create, :destroy ]
+ end
get "admin", to: "admin#index"
post "admin/:id/toggle_discard", to: "admin#toggle_discard", as: :admin_toggle_discard
diff --git a/db/migrate/20260320191401_create_votes.rb b/db/migrate/20260320191401_create_votes.rb
new file mode 100644
index 0000000..4a6a555
--- /dev/null
+++ b/db/migrate/20260320191401_create_votes.rb
@@ -0,0 +1,12 @@
+class CreateVotes < ActiveRecord::Migration[8.1]
+ def change
+ create_table :votes do |t|
+ t.references :user, null: false, foreign_key: true
+ t.references :submission, null: false, foreign_key: true
+
+ t.timestamps
+ end
+
+ add_index :votes, [ :user_id, :submission_id ], unique: true
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 9db965d..50f3162 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[8.1].define(version: 2026_03_15_175159) do
+ActiveRecord::Schema[8.1].define(version: 2026_03_20_191401) do
create_table "submissions", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "discarded_at"
@@ -33,5 +33,17 @@
t.index ["github_uid"], name: "index_users_on_github_uid", unique: true
end
+ create_table "votes", force: :cascade do |t|
+ t.datetime "created_at", null: false
+ t.integer "submission_id", null: false
+ t.datetime "updated_at", null: false
+ t.integer "user_id", null: false
+ t.index ["submission_id"], name: "index_votes_on_submission_id"
+ t.index ["user_id", "submission_id"], name: "index_votes_on_user_id_and_submission_id", unique: true
+ t.index ["user_id"], name: "index_votes_on_user_id"
+ end
+
add_foreign_key "submissions", "users"
+ add_foreign_key "votes", "submissions"
+ add_foreign_key "votes", "users"
end
diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..dec3dd04b81437b51fbc52148292fed49468dd86
GIT binary patch
literal 1417
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
literal 0
HcmV?d00001
diff --git a/public/icon-512.png b/public/icon-512.png
new file mode 100644
index 0000000000000000000000000000000000000000..704c12db917f74012a16f21a1e4fae0531715d44
GIT binary patch
literal 3446
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
BBBTHS
literal 0
HcmV?d00001
diff --git a/public/icon.png b/public/icon.png
index c4c9dbfbbd2f7c1421ffd5727188146213abbcef..704c12db917f74012a16f21a1e4fae0531715d44 100644
GIT binary patch
literal 3446
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
BBBTHS
literal 4166
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
diff --git a/public/icon.svg b/public/icon.svg
index 04b34bf..de7ffc4 100644
--- a/public/icon.svg
+++ b/public/icon.svg
@@ -1,3 +1 @@
-
+
\ No newline at end of file
diff --git a/public/manifest.webmanifest b/public/manifest.webmanifest
new file mode 100644
index 0000000..a893e5b
--- /dev/null
+++ b/public/manifest.webmanifest
@@ -0,0 +1,6 @@
+{
+ "icons": [
+ { "src": "/icon-192.png", "type": "image/png", "sizes": "192x192" },
+ { "src": "/icon-512.png", "type": "image/png", "sizes": "512x512" }
+ ]
+}