diff --git a/app/controllers/achievements_controller.rb b/app/controllers/achievements_controller.rb index f5ba036..919e562 100644 --- a/app/controllers/achievements_controller.rb +++ b/app/controllers/achievements_controller.rb @@ -2,11 +2,12 @@ class AchievementsController < ApplicationController before_action :authenticate_user!, except: [:index, :show] def index - @achievements = Achievement.order(date: :desc) + # Scope achievements to the current user + @achievements = current_user.achievements.order(date: :desc) end def show - @achievement = Achievement.find(params[:id]) + @achievement = current_user.achievements.find(params[:id]) AchievementView.create( achievement: @achievement, user: @achievement.user, @@ -61,4 +62,4 @@ def destroy def achievement_params params.require(:achievement).permit(:title, :date, :description, :icon, :url) end -end \ No newline at end of file +end diff --git a/app/controllers/links_controller.rb b/app/controllers/links_controller.rb index 3cb26ae..4b2baf7 100644 --- a/app/controllers/links_controller.rb +++ b/app/controllers/links_controller.rb @@ -1,13 +1,14 @@ class LinksController < ApplicationController before_action :authenticate_user!, except: [:index, :show, :user_links, :track_click] - before_action :set_theme, only: [:user_links] # Add this line + before_action :set_theme, only: [:user_links] def index - @links = Link.order(:position) + # Scope links to the current user + @links = current_user.links.order(:position) end def show - @link = Link.find(params[:id]) + @link = current_user.links.find(params[:id]) end def new @@ -45,17 +46,17 @@ def destroy def user_links @user = User.find_by(username: params[:username]) return redirect_to root_path, alert: "User not found" if @user.nil? - - @links = @user.links.where(hidden: false, visible: true) - @hidden_links = @user.links.where(hidden: true) - @pinned_links = @user.links.where(pinned: true) + + @links = @user.links.visible + @hidden_links = @user.links.hidden + @pinned_links = @user.links.pinned @achievements = @user.achievements @user.tags = JSON.parse(@user.tags) if @user.tags.is_a?(String) - + # Add debugging Rails.logger.debug "Theme: #{@theme.inspect}" Rails.logger.debug "Hidden Links: #{@hidden_links.inspect}" - + # Render the appropriate template based on the theme case @theme when 'retro' @@ -90,7 +91,6 @@ def link_params end def set_theme - # Determine the theme either from a query parameter or route segment @theme = params[:theme] || 'default' end -end \ No newline at end of file +end diff --git a/app/controllers/users/registrations_controller.rb b/app/controllers/users/registrations_controller.rb index 0ffead2..5221a2e 100644 --- a/app/controllers/users/registrations_controller.rb +++ b/app/controllers/users/registrations_controller.rb @@ -1,10 +1,10 @@ class Users::RegistrationsController < Devise::RegistrationsController before_action :configure_permitted_parameters, if: :devise_controller? - before_action :check_signups_enabled, only: [:new, :create] + before_action :check_signups_enabled, only: [:create] def create - # Check if sign-ups are disabled - if !Rails.application.config.sign_ups_open + # Check if sign-ups are disabled and no valid invite code is provided + unless Rails.application.config.sign_ups_open || valid_invite_code?(params[:user][:invite_code]) redirect_to root_path, alert: "Sign-ups are currently disabled." return end @@ -60,15 +60,32 @@ def update end end + private + + def check_signups_enabled + # Check if sign-ups are disabled and no valid invite code is provided + if !Rails.application.config.sign_ups_open && (params[:user].blank? || !valid_invite_code?(params[:user][:invite_code])) + redirect_to root_path, alert: "Sign-ups are currently disabled." + end + end + + def valid_invite_code?(invite_code) + # List of valid invite codes + valid_codes = ["POWEROVERWHELMING", "SWORDFISH", "HUNTER2"] + + # Check if the provided invite code matches any of the valid codes, case-insensitive + valid_codes.any? { |code| code.casecmp(invite_code).zero? } + end + protected def configure_permitted_parameters - devise_parameter_sanitizer.permit(:sign_up, keys: [:email, :password, :password_confirmation, :username, :full_name, :tags, :avatar, :banner, :description, :banner_enabled, :avatar_border]) + devise_parameter_sanitizer.permit(:sign_up, keys: [:email, :password, :password_confirmation, :username, :full_name, :tags, :avatar, :banner, :description, :banner_enabled, :avatar_border, :invite_code]) devise_parameter_sanitizer.permit(:account_update, keys: [:email, :password, :password_confirmation, :username, :full_name, :tags, :avatar, :banner, :description, :banner_enabled, :avatar_border]) end def sign_up_params - params.require(:user).permit(:email, :password, :password_confirmation, :username, :full_name, :tags, :avatar, :banner, :description, :banner_enabled, :avatar_border).tap do |user_params| + params.require(:user).permit(:email, :password, :password_confirmation, :username, :full_name, :tags, :avatar, :banner, :description, :banner_enabled, :avatar_border, :invite_code).tap do |user_params| user_params[:tags] = user_params[:tags].split(',').map(&:strip).to_json if user_params[:tags].present? end end @@ -78,12 +95,4 @@ def account_update_params user_params[:tags] = user_params[:tags].split(',').map(&:strip).to_json if user_params[:tags].present? end end - - private - - def check_signups_enabled - unless Rails.application.config.sign_ups_open - redirect_to root_path, alert: "Sign-ups are currently disabled." - end - end end diff --git a/app/models/user.rb b/app/models/user.rb index 74b70ce..a98a9ee 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,5 +1,6 @@ # app/models/user.rb class User < ApplicationRecord + attr_accessor :invite_code devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable diff --git a/app/services/open_graph_image_generator.rb b/app/services/open_graph_image_generator.rb index 2f65da5..29aae56 100644 --- a/app/services/open_graph_image_generator.rb +++ b/app/services/open_graph_image_generator.rb @@ -54,10 +54,17 @@ def generate def download_image(url) tempfile = Tempfile.new(['avatar', '.jpg']) tempfile.binmode - URI.open(url) do |image| - tempfile.write(image.read) + begin + URI.open(url) do |image| + tempfile.write(image.read) + end + rescue OpenURI::HTTPError, Errno::ENOENT, SocketError => e + Rails.logger.error("Failed to download image from URL: #{url}. Error: #{e.message}. Using default image.") + # Use a default image if the download fails + default_image_path = Rails.root.join('app', 'assets', 'images', 'greg.jpg') + tempfile.write(File.read(default_image_path)) end tempfile.rewind MiniMagick::Image.open(tempfile.path) end -end \ No newline at end of file +end diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb index cc71baa..a477fdc 100644 --- a/app/views/devise/registrations/new.html.erb +++ b/app/views/devise/registrations/new.html.erb @@ -1,69 +1,80 @@

Sign up

- <% if Rails.application.config.sign_ups_open %> - <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { class: "space-y-6" }) do |f| %> - <%= render "devise/shared/error_messages", resource: resource %> + <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { class: "space-y-6" }) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> -
- <%= f.label :email, class: 'block text-lime-200 font-semibold mb-2' %> - <%= f.email_field :email, autofocus: true, autocomplete: "email", class: 'block w-full px-4 py-2 border border-gray-700 rounded bg-gray-900 text-white focus:outline-none focus:border-lime-500' %> -
+
+ <%= f.label :email, class: 'block text-lime-200 font-semibold mb-2' %> + <%= f.email_field :email, autofocus: true, autocomplete: "email", class: 'block w-full px-4 py-2 border border-gray-700 rounded bg-gray-900 text-white focus:outline-none focus:border-lime-500' %> +
+ +
+ <%= f.label :password, class: 'block text-lime-200 font-semibold mb-2' %> + <% if @minimum_password_length %> + (<%= @minimum_password_length %> characters minimum) + <% end %> + <%= f.password_field :password, autocomplete: "new-password", class: 'block w-full px-4 py-2 border border-gray-700 rounded bg-gray-900 text-white focus:outline-none focus:border-lime-500' %> +
-
- <%= f.label :password, class: 'block text-lime-200 font-semibold mb-2' %> - <% if @minimum_password_length %> - (<%= @minimum_password_length %> characters minimum) - <% end %> - <%= f.password_field :password, autocomplete: "new-password", class: 'block w-full px-4 py-2 border border-gray-700 rounded bg-gray-900 text-white focus:outline-none focus:border-lime-500' %> -
+
+ <%= f.label :password_confirmation, class: 'block text-lime-200 font-semibold mb-2' %> + <%= f.password_field :password_confirmation, autocomplete: "new-password", class: 'block w-full px-4 py-2 border border-gray-700 rounded bg-gray-900 text-white focus:outline-none focus:border-lime-500' %> +
-
- <%= f.label :password_confirmation, class: 'block text-lime-200 font-semibold mb-2' %> - <%= f.password_field :password_confirmation, autocomplete: "new-password", class: 'block w-full px-4 py-2 border border-gray-700 rounded bg-gray-900 text-white focus:outline-none focus:border-lime-500' %> -
+
+ <%= f.label :username, class: 'block text-lime-200 font-semibold mb-2' %> + <%= f.text_field :username, class: 'block w-full px-4 py-2 border border-gray-700 rounded bg-gray-900 text-white focus:outline-none focus:border-lime-500' %> +
+ +
+ <%= f.label :full_name, class: 'block text-lime-200 font-semibold mb-2' %> + <%= f.text_field :full_name, class: 'block w-full px-4 py-2 border border-gray-700 rounded bg-gray-900 text-white focus:outline-none focus:border-lime-500' %> +
-
- <%= f.label :username, class: 'block text-lime-200 font-semibold mb-2' %> - <%= f.text_field :username, class: 'block w-full px-4 py-2 border border-gray-700 rounded bg-gray-900 text-white focus:outline-none focus:border-lime-500' %> -
+
+ <%= f.label :tags, class: 'block text-lime-200 font-semibold mb-2' %> + <%= f.text_field :tags, value: @user.parsed_tags.join(', '), class: 'block w-full px-4 py-2 border border-gray-700 rounded bg-gray-900 text-white focus:outline-none focus:border-lime-500' %> +
-
- <%= f.label :full_name, class: 'block text-lime-200 font-semibold mb-2' %> - <%= f.text_field :full_name, class: 'block w-full px-4 py-2 border border-gray-700 rounded bg-gray-900 text-white focus:outline-none focus:border-lime-500' %> -
+
+ <%= f.label :avatar, class: 'block text-lime-200 font-semibold mb-2' %> + <%= f.text_field :avatar, class: 'block w-full px-4 py-2 border border-gray-700 rounded bg-gray-900 text-white focus:outline-none focus:border-lime-500' %> +
-
- <%= f.label :tags, class: 'block text-lime-200 font-semibold mb-2' %> - <%= f.text_field :tags, value: @user.parsed_tags.join(', '), class: 'block w-full px-4 py-2 border border-gray-700 rounded bg-gray-900 text-white focus:outline-none focus:border-lime-500' %> -
+
+ <%= f.label :avatar_border, class: 'block text-lime-200 font-semibold mb-2' %> + <%= f.select :avatar_border, options_for_select([ + ['White', 'white'], + ['Black', 'black'], + ['None', 'none'], + ['Rainbow', 'rainbow'], + ['Rainbow Overlay', 'rainbow-overlay'] + ], @user.avatar_border), {}, class: 'block w-full px-4 py-2 border border-gray-700 rounded bg-gray-900 text-white focus:outline-none focus:border-lime-500' %> +
-
- <%= f.label :avatar, class: 'block text-lime-200 font-semibold mb-2' %> - <%= f.text_field :avatar, class: 'block w-full px-4 py-2 border border-gray-700 rounded bg-gray-900 text-white focus:outline-none focus:border-lime-500' %> -
+
+ <%= f.label :banner, class: 'block text-lime-200 font-semibold mb-2' %> + <%= f.text_field :banner, class: 'block w-full px-4 py-2 border border-gray-700 rounded bg-gray-900 text-white focus:outline-none focus:border-lime-500' %> +
-
- <%= f.label :banner, class: 'block text-lime-200 font-semibold mb-2' %> - <%= f.text_field :banner, class: 'block w-full px-4 py-2 border border-gray-700 rounded bg-gray-900 text-white focus:outline-none focus:border-lime-500' %> -
+
+ <%= f.label :description, class: 'block text-lime-200 font-semibold mb-2' %> + <%= f.text_area :description, rows: 3, class: 'block w-full px-4 py-2 border border-gray-700 rounded bg-gray-900 text-white focus:outline-none focus:border-lime-500' %> +
-
- <%= f.label :description, class: 'block text-lime-200 font-semibold mb-2' %> - <%= f.text_area :description, rows: 3, class: 'block w-full px-4 py-2 border border-gray-700 rounded bg-gray-900 text-white focus:outline-none focus:border-lime-500' %> -
+ +
+ <%= f.label :invite_code, "Invite Code", class: 'block text-lime-200 font-semibold mb-2' %> + <%= f.text_field :invite_code, class: 'block w-full px-4 py-2 border border-gray-700 rounded bg-gray-900 text-white focus:outline-none focus:border-lime-500' %> +
-
- <%= f.submit "Sign up", class: 'bg-lime-500 hover:bg-lime-600 text-white font-bold py-2 px-4 rounded transition duration-300 ease-in-out focus:outline-none' %> -
- <% end %> - <% else %> -
-

Sign-ups are currently disabled.

+
+ <%= f.submit "Sign up", class: 'bg-lime-500 hover:bg-lime-600 text-white font-bold py-2 px-4 rounded transition duration-300 ease-in-out focus:outline-none' %>
<% end %> +
-
- <%= render "devise/shared/links" %> -
+
+ <%= render "devise/shared/links" %>
diff --git a/app/views/devise/shared/_links.html.erb b/app/views/devise/shared/_links.html.erb index 8cef40a..a435cde 100644 --- a/app/views/devise/shared/_links.html.erb +++ b/app/views/devise/shared/_links.html.erb @@ -2,10 +2,8 @@ <%= link_to "Log in", new_session_path(resource_name), class: "text-lime-300 hover:text-lime-100 transition duration-300 ease-in-out" %>
<% end %> -<% if Rails.application.config.sign_ups_open %> - <%- if devise_mapping.registerable? && controller_name != 'registrations' %> - <%= link_to "Sign up", new_registration_path(resource_name), class: "text-lime-300 hover:text-lime-100 transition duration-300 ease-in-out" %>
- <% end %> +<% if devise_mapping.registerable? && controller_name != 'registrations' %> + <%= link_to "Sign up", new_registration_path(resource_name), class: "text-lime-300 hover:text-lime-100 transition duration-300 ease-in-out" %>
<% end %> <%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> @@ -24,4 +22,4 @@ <%- resource_class.omniauth_providers.each do |provider| %> <%= button_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), data: { turbo: false }, class: "mt-2 bg-gray-600 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded transition duration-300 ease-in-out focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-opacity-50" %>
<% end %> -<% end %> \ No newline at end of file +<% end %> diff --git a/config/routes.rb b/config/routes.rb index f5ccce9..cb4eb19 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,6 @@ Rails.application.routes.draw do + # Static and more specific routes first + get 'analytics/index' # Devise routes for user registration @@ -19,10 +21,10 @@ # Root route root to: 'pages#home' - # Static routes should be defined before dynamic routes + # Routes for achievements resources :achievements, only: [:index, :show, :new, :create, :edit, :update, :destroy] - # Routes for links with standard RESTful actions - move these above the dynamic route + # Routes for links with standard RESTful actions resources :links do member do get :track_click diff --git a/spec/controllers/achievements_controller_spec.rb b/spec/controllers/achievements_controller_spec.rb index d4fa78f..df439e2 100644 --- a/spec/controllers/achievements_controller_spec.rb +++ b/spec/controllers/achievements_controller_spec.rb @@ -5,6 +5,10 @@ let(:user) { create(:user) } let(:achievement) { create(:achievement, user: user) } + before do + sign_in user # Sign in the user before each test + end + describe "GET #index" do it "returns a success response" do get :index @@ -38,7 +42,6 @@ describe "GET #new" do it "returns a success response" do - sign_in user get :new expect(response).to be_successful end @@ -47,7 +50,6 @@ describe "POST #create" do context "with valid params" do it "creates a new Achievement" do - sign_in user expect { post :create, params: { achievement: attributes_for(:achievement) } }.to change(Achievement, :count).by(1) @@ -60,7 +62,6 @@ let(:new_attributes) { { title: "New Title" } } it "updates the requested achievement" do - sign_in user put :update, params: { id: achievement.to_param, achievement: new_attributes } achievement.reload expect(achievement.title).to eq("New Title") @@ -70,11 +71,10 @@ describe "DELETE #destroy" do it "destroys the requested achievement" do - sign_in user achievement # ensure achievement is created before the expect block expect { delete :destroy, params: { id: achievement.to_param } }.to change(Achievement, :count).by(-1) end end -end \ No newline at end of file +end diff --git a/spec/controllers/links_controller_spec.rb b/spec/controllers/links_controller_spec.rb index ea77088..aaeaef1 100644 --- a/spec/controllers/links_controller_spec.rb +++ b/spec/controllers/links_controller_spec.rb @@ -5,6 +5,10 @@ let(:user) { create(:user) } let(:link) { create(:link, user: user) } + before do + sign_in user # Sign in the user before each test + end + describe "GET #index" do it "returns a success response" do get :index @@ -21,7 +25,6 @@ describe "GET #new" do it "returns a success response" do - sign_in user get :new expect(response).to be_successful end @@ -30,7 +33,6 @@ describe "POST #create" do context "with valid params" do it "creates a new Link" do - sign_in user expect { post :create, params: { link: attributes_for(:link) } }.to change(Link, :count).by(1) @@ -43,7 +45,6 @@ let(:new_attributes) { { title: "New Title" } } it "updates the requested link" do - sign_in user put :update, params: { id: link.to_param, link: new_attributes } link.reload expect(link.title).to eq("New Title") @@ -53,7 +54,6 @@ describe "DELETE #destroy" do it "destroys the requested link" do - sign_in user link # ensure link is created before the expect block expect { delete :destroy, params: { id: link.to_param } diff --git a/spec/controllers/users/registrations_controller_spec.rb b/spec/controllers/users/registrations_controller_spec.rb index 371caa5..d58e0ce 100644 --- a/spec/controllers/users/registrations_controller_spec.rb +++ b/spec/controllers/users/registrations_controller_spec.rb @@ -8,7 +8,7 @@ describe "POST #create" do let(:valid_attributes) { { email: "test@example.com", password: "password", password_confirmation: "password", - username: "testuser", full_name: "Test User", tags: "tag1,tag2", avatar_border: "white" } + username: "testuser", full_name: "Test User", tags: "tag1,tag2", avatar_border: "white", invite_code: "POWEROVERWHELMING" } } context "when sign-ups are enabled" do @@ -35,13 +35,24 @@ allow(Rails.application.config).to receive(:sign_ups_open).and_return(false) end - it "does not create a new User and redirects to root path" do + it "does not create a new User without a valid invite code and redirects to root path" do + invalid_attributes = valid_attributes.merge(invite_code: "INVALIDCODE") expect { - post :create, params: { user: valid_attributes } + post :create, params: { user: invalid_attributes } }.not_to change(User, :count) expect(response).to redirect_to(root_path) expect(flash[:alert]).to eq("Sign-ups are currently disabled.") end + + it "creates a new User with a valid invite code" do + expect(controller).to receive(:after_sign_up_path_for).with(instance_of(User)).and_return("/path/to/redirect") + + expect { + post :create, params: { user: valid_attributes } + }.to change(User, :count).by(1) + + expect(response).to redirect_to("/path/to/redirect") + end end end