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" } + ] +}