From ec26e425c679ce0d5a4d2687d512b608cd438fb0 Mon Sep 17 00:00:00 2001 From: Dylan Knutson Date: Mon, 30 Dec 2024 01:39:21 +0000 Subject: [PATCH] Add FA Cookies management functionality - Introduced methods for managing FurAffinity cookies in the GlobalStatesController, including `fa_cookies`, `edit_fa_cookies`, and `update_fa_cookies`. - Added a new policy for managing FA cookies, restricting access to admin users. - Created views for displaying and editing FA cookies, enhancing user interaction. - Updated routes to include paths for FA cookies management. - Added comprehensive tests for the new functionality in the GlobalStatesController spec. --- app/controllers/global_states_controller.rb | 54 ++++++ app/policies/global_state_policy.rb | 12 ++ .../global_states/edit_fa_cookies.html.erb | 62 +++++++ app/views/global_states/fa_cookies.html.erb | 49 +++++ app/views/global_states/index.html.erb | 173 ++++++++---------- config/routes.rb | 8 +- .../global_states_controller_spec.rb | 147 +++++++++++++++ 7 files changed, 406 insertions(+), 99 deletions(-) create mode 100644 app/views/global_states/edit_fa_cookies.html.erb create mode 100644 app/views/global_states/fa_cookies.html.erb diff --git a/app/controllers/global_states_controller.rb b/app/controllers/global_states_controller.rb index 1398287a..5b037392 100644 --- a/app/controllers/global_states_controller.rb +++ b/app/controllers/global_states_controller.rb @@ -2,6 +2,12 @@ class GlobalStatesController < ApplicationController before_action :set_global_state, only: %i[edit update destroy] after_action :verify_authorized + FA_COOKIE_KEYS = %w[ + furaffinity-cookie-a + furaffinity-cookie-b + furaffinity-cookie-oaid + ].freeze + def index authorize GlobalState @global_states = policy_scope(GlobalState).order(:key) @@ -44,6 +50,50 @@ class GlobalStatesController < ApplicationController notice: "Global state was successfully deleted." end + def fa_cookies + authorize GlobalState + @fa_cookies = + FA_COOKIE_KEYS.map do |key| + GlobalState.find_by(key: key) || + GlobalState.new(key: key, value_type: :string) + end + end + + def edit_fa_cookies + authorize GlobalState + @fa_cookies = + FA_COOKIE_KEYS.map do |key| + GlobalState.find_by(key: key) || + GlobalState.new(key: key, value_type: :string) + end + end + + def update_fa_cookies + authorize GlobalState + + begin + ActiveRecord::Base.transaction do + fa_cookies_params.each do |key, value| + state = GlobalState.find_or_initialize_by(key: key) + state.value = value + state.value_type = :string + state.save! + end + end + + redirect_to fa_cookies_global_states_path, + notice: "FA cookies were successfully updated." + rescue ActiveRecord::RecordInvalid => e + @fa_cookies = + FA_COOKIE_KEYS.map do |key| + GlobalState.find_by(key: key) || + GlobalState.new(key: key, value_type: :string) + end + flash.now[:alert] = "Error updating FA cookies: #{e.message}" + render :edit_fa_cookies, status: :unprocessable_entity + end + end + private def set_global_state @@ -53,4 +103,8 @@ class GlobalStatesController < ApplicationController def global_state_params params.require(:global_state).permit(:key, :value, :value_type) end + + def fa_cookies_params + params.require(:fa_cookies).permit(*FA_COOKIE_KEYS) + end end diff --git a/app/policies/global_state_policy.rb b/app/policies/global_state_policy.rb index 98e89292..e9b5204b 100644 --- a/app/policies/global_state_policy.rb +++ b/app/policies/global_state_policy.rb @@ -27,6 +27,18 @@ class GlobalStatePolicy < ApplicationPolicy update? end + def fa_cookies? + user.admin? + end + + def edit_fa_cookies? + user.admin? + end + + def update_fa_cookies? + user.admin? + end + class Scope < Scope def resolve user.admin? ? scope.all : scope.none diff --git a/app/views/global_states/edit_fa_cookies.html.erb b/app/views/global_states/edit_fa_cookies.html.erb new file mode 100644 index 00000000..75b9a9a3 --- /dev/null +++ b/app/views/global_states/edit_fa_cookies.html.erb @@ -0,0 +1,62 @@ +
+
+
+

+ Edit FurAffinity Cookies +

+

+ Update the cookie values used for FurAffinity authentication. +

+
+
+ <%= link_to "Back to Cookies", + fa_cookies_global_states_path, + class: + "bg-slate-500 hover:bg-slate-700 text-white font-bold py-2 px-4 rounded" %> +
+
+ +
+
+ <%= form_tag fa_cookies_global_states_path, method: :patch do %> + + + + + + + + + <% @fa_cookies.each do |cookie| %> + + + + + <% end %> + +
+ Cookie + + Value +
+ <%= cookie.key.sub("furaffinity-cookie-", "").upcase %> + + <%= text_field_tag "fa_cookies[#{cookie.key}]", + cookie.value, + class: + "block w-full rounded-md border-slate-300 shadow-sm focus:border-sky-500 focus:ring-sky-500 sm:text-sm" %> +
+ +
+ <%= link_to "Cancel", + fa_cookies_global_states_path, + class: + "rounded-md border border-slate-300 bg-white py-2 px-4 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50 focus:outline-none focus:ring-2 focus:ring-sky-500 focus:ring-offset-2" %> + <%= submit_tag "Save", + class: + "inline-flex justify-center rounded-md border border-transparent bg-sky-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-sky-700 focus:outline-none focus:ring-2 focus:ring-sky-500 focus:ring-offset-2" %> +
+ <% end %> +
+
+
diff --git a/app/views/global_states/fa_cookies.html.erb b/app/views/global_states/fa_cookies.html.erb new file mode 100644 index 00000000..5ebc43f5 --- /dev/null +++ b/app/views/global_states/fa_cookies.html.erb @@ -0,0 +1,49 @@ +
+
+
+

FurAffinity Cookies

+

+ Manage cookies for FurAffinity authentication. +

+
+
+ <%= link_to "Back to Global States", + global_states_path, + class: + "bg-slate-500 hover:bg-slate-700 text-white font-bold py-2 px-4 rounded mr-3" %> + <%= link_to "Edit Cookies", + fa_cookies_edit_global_states_path, + class: + "bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" %> +
+
+ +
+
+ + + + + + + + + <% @fa_cookies.each do |cookie| %> + + + + + <% end %> + +
+ Cookie + + Value +
+ <%= cookie.key.sub("furaffinity-cookie-", "").upcase %> + + <%= cookie.value.present? ? cookie.value : "(not set)" %> +
+
+
+
diff --git a/app/views/global_states/index.html.erb b/app/views/global_states/index.html.erb index 28507801..5c4b786c 100644 --- a/app/views/global_states/index.html.erb +++ b/app/views/global_states/index.html.erb @@ -3,111 +3,88 @@

Global States

- A list of all global states in the application. + A list of all global states in the system.

-
- <%= link_to new_global_state_path, +
+ <%= link_to "Manage FA Cookies", + fa_cookies_global_states_path, class: - "inline-flex items-center justify-center rounded-md border border-transparent bg-sky-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-sky-700 focus:outline-none focus:ring-2 focus:ring-sky-500 focus:ring-offset-2 sm:w-auto" do %> - - New Global State - <% end %> + "bg-slate-500 hover:bg-slate-700 text-white font-bold py-2 px-4 rounded" %> + <%= link_to "New Global State", + new_global_state_path, + class: + "bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" %>
-
-
-
-
- - - - + + + <% end %> + +
+
+ + + + + + + + + + + <% @global_states.each do |global_state| %> + + + - - - - - - - <% @global_states.each do |global_state| %> - - - - - - - - <% end %> - -
+ Key + + Value Type + + Value + Actions
+ <%= global_state.key %> + + <% pill_color = + case global_state.value_type + when "string" + "bg-sky-100 text-sky-700" + when "counter" + "bg-emerald-100 text-emerald-700" + when "duration" + "bg-purple-100 text-purple-700" + when "password" + "bg-rose-100 text-rose-700" + end %> + - Key - - - Type - - Value - - Last Updated - - Actions -
- <%= global_state.key %> - - inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium" - > - <%= global_state.value_type.titleize %> - - - <%= global_state.display_value %> - - <%= time_ago_in_words(global_state.updated_at) %> ago - - <%= link_to "Edit", - edit_global_state_path(global_state), - class: "text-sky-600 hover:text-sky-900 mr-3" %> - <%= button_to "Delete", - global_state_path(global_state), - method: :delete, - class: "text-red-600 hover:text-red-900 inline", - form: { - class: "inline", - data: { - turbo_confirm: "Are you sure?", - }, - } %> -
-
- + <%= global_state.value_type.humanize %> + + +
+ <%= global_state.display_value %> + + <% if policy(global_state).edit? %> + <%= link_to "Edit", + edit_global_state_path(global_state), + class: "text-blue-600 hover:text-blue-900 mr-3" %> + <% end %> + <% if policy(global_state).destroy? %> + <%= button_to "Delete", + global_state_path(global_state), + method: :delete, + class: "text-red-600 hover:text-red-900 inline", + form: { + class: "inline", + data: { + turbo_confirm: "Are you sure?", + }, + } %> + <% end %> +
diff --git a/config/routes.rb b/config/routes.rb index a026d922..824c33d4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -89,5 +89,11 @@ Rails.application.routes.draw do end end - resources :global_states, path: "state" + resources :global_states, path: "state" do + collection do + get "fa-cookies", to: "global_states#fa_cookies" + get "fa-cookies/edit", to: "global_states#edit_fa_cookies" + patch "fa-cookies", to: "global_states#update_fa_cookies" + end + end end diff --git a/spec/controllers/global_states_controller_spec.rb b/spec/controllers/global_states_controller_spec.rb index 738a9fa9..08756dcc 100644 --- a/spec/controllers/global_states_controller_spec.rb +++ b/spec/controllers/global_states_controller_spec.rb @@ -103,4 +103,151 @@ RSpec.describe GlobalStatesController, type: :controller do expect(response).to redirect_to(global_states_path) end end + + describe "FA Cookies Management" do + let(:fa_cookie_keys) do + %w[furaffinity-cookie-a furaffinity-cookie-b furaffinity-cookie-oaid] + end + + describe "GET #fa_cookies" do + context "when no FA cookie states exist" do + before { GlobalState.delete_all } + + it "returns a success response" do + get :fa_cookies + expect(response).to be_successful + end + + it "assigns new GlobalState objects for all required cookies" do + get :fa_cookies + assigned_cookies = assigns(:fa_cookies) + + expect(assigned_cookies.size).to eq(3) + expect(assigned_cookies).to all(be_new_record) + expect(assigned_cookies.map(&:key)).to match_array(fa_cookie_keys) + expect(assigned_cookies.map(&:value_type).uniq).to eq(["string"]) + end + end + + context "when FA cookie states exist" do + it "assigns all FA cookie states" do + cookie_states = + fa_cookie_keys.map do |key| + create( + :global_state, + key: key, + value: "test", + value_type: :string, + ) + end + + get :fa_cookies + expect(assigns(:fa_cookies)).to match_array(cookie_states) + end + end + end + + describe "GET #edit_fa_cookies" do + context "when no FA cookie states exist" do + before { GlobalState.delete_all } + + it "returns a success response" do + get :edit_fa_cookies + expect(response).to be_successful + end + + it "assigns new GlobalState objects for all required cookies" do + get :edit_fa_cookies + assigned_cookies = assigns(:fa_cookies) + + expect(assigned_cookies.size).to eq(3) + expect(assigned_cookies).to all(be_new_record) + expect(assigned_cookies.map(&:key)).to match_array(fa_cookie_keys) + expect(assigned_cookies.map(&:value_type).uniq).to eq(["string"]) + end + end + + context "when FA cookie states exist" do + it "assigns all FA cookie states" do + cookie_states = + fa_cookie_keys.map do |key| + create( + :global_state, + key: key, + value: "test", + value_type: :string, + ) + end + + get :edit_fa_cookies + expect(assigns(:fa_cookies)).to match_array(cookie_states) + end + end + end + + describe "PATCH #update_fa_cookies" do + context "when no FA cookie states exist" do + before { GlobalState.delete_all } + + let(:new_values) { fa_cookie_keys.index_with { |_key| "new_value" } } + + it "creates all cookie states" do + expect { + patch :update_fa_cookies, params: { fa_cookies: new_values } + }.to change(GlobalState, :count).by(3) + + fa_cookie_keys.each do |key| + state = GlobalState.find_by(key: key) + expect(state).to be_present + expect(state.value).to eq("new_value") + expect(state.value_type).to eq("string") + end + end + + it "redirects to the fa_cookies page" do + patch :update_fa_cookies, params: { fa_cookies: new_values } + expect(response).to redirect_to(fa_cookies_global_states_path) + end + end + + context "when FA cookie states exist" do + let(:new_values) { fa_cookie_keys.index_with { |_key| "new_value" } } + + it "updates existing records" do + existing_states = + fa_cookie_keys.map do |key| + create( + :global_state, + key: key, + value: "old_value", + value_type: :string, + ) + end + + patch :update_fa_cookies, params: { fa_cookies: new_values } + + existing_states.each do |state| + expect(state.reload.value).to eq("new_value") + end + end + end + + context "with invalid params" do + let(:invalid_values) { { "furaffinity-cookie-a" => "" } } + + it "returns unprocessable entity status" do + patch :update_fa_cookies, params: { fa_cookies: invalid_values } + expect(response).to have_http_status(:unprocessable_entity) + end + + it "assigns @fa_cookies with all required keys" do + patch :update_fa_cookies, params: { fa_cookies: invalid_values } + assigned_cookies = assigns(:fa_cookies) + + expect(assigned_cookies).to be_present + expect(assigned_cookies.map(&:key)).to match_array(fa_cookie_keys) + end + end + end + end end