diff --git a/app/controllers/families_controller.rb b/app/controllers/families_controller.rb index b0c47344..5ce52f56 100644 --- a/app/controllers/families_controller.rb +++ b/app/controllers/families_controller.rb @@ -77,57 +77,17 @@ class FamiliesController < ApplicationController end def update_location_sharing - enabled = ActiveModel::Type::Boolean.new.cast(params[:enabled]) - duration = params[:duration] + result = Families::UpdateLocationSharing.new( + user: current_user, + enabled: params[:enabled], + duration: params[:duration] + ).call - if current_user.update_family_location_sharing!(enabled, duration: duration) - response_data = { - success: true, - enabled: enabled, - duration: current_user.family_sharing_duration, - message: build_sharing_message(enabled, duration) - } - - if enabled && current_user.family_sharing_expires_at.present? - response_data[:expires_at] = current_user.family_sharing_expires_at.iso8601 - response_data[:expires_at_formatted] = current_user.family_sharing_expires_at.strftime('%b %d at %I:%M %p') - end - - render json: response_data - else - render json: { - success: false, - message: 'Failed to update location sharing setting' - }, status: :unprocessable_content - end - rescue => e - render json: { - success: false, - message: 'An error occurred while updating location sharing' - }, status: :internal_server_error + render json: result.payload, status: result.status end private - def build_sharing_message(enabled, duration) - return 'Location sharing disabled' unless enabled - - case duration - when '1h' - 'Location sharing enabled for 1 hour' - when '6h' - 'Location sharing enabled for 6 hours' - when '12h' - 'Location sharing enabled for 12 hours' - when '24h' - 'Location sharing enabled for 24 hours' - when 'permanent', nil - 'Location sharing enabled' - else - duration.to_i > 0 ? "Location sharing enabled for #{duration.to_i} hours" : 'Location sharing enabled' - end - end - def set_family @family = current_user.family redirect_to new_family_path, alert: 'You are not in a family' unless @family diff --git a/app/controllers/family/invitations_controller.rb b/app/controllers/family/invitations_controller.rb index 9bf53cd8..4d4f13e3 100644 --- a/app/controllers/family/invitations_controller.rb +++ b/app/controllers/family/invitations_controller.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class Family::InvitationsController < ApplicationController - before_action :authenticate_user!, except: %i[show accept] - before_action :ensure_family_feature_enabled!, except: %i[show accept] + before_action :authenticate_user!, except: %i[show] + before_action :ensure_family_feature_enabled!, except: %i[show] before_action :set_family, except: %i[show accept] before_action :set_invitation_by_id_and_family, only: %i[destroy] before_action :set_invitation_by_id, only: %i[accept] diff --git a/app/services/families/update_location_sharing.rb b/app/services/families/update_location_sharing.rb new file mode 100644 index 00000000..258f9d7e --- /dev/null +++ b/app/services/families/update_location_sharing.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +class Families::UpdateLocationSharing + Result = Struct.new(:success?, :payload, :status, keyword_init: true) + + def initialize(user:, enabled:, duration:) + @user = user + @enabled_param = enabled + @duration_param = duration + @boolean_caster = ActiveModel::Type::Boolean.new + end + + def call + if update_location_sharing + success_result + else + failure_result('Failed to update location sharing setting', :unprocessable_content) + end + rescue => error + Rails.logger.error("Failed to update family location sharing: #{error.message}") if defined?(Rails) + failure_result('An error occurred while updating location sharing', :internal_server_error) + end + + private + + attr_reader :user, :enabled_param, :duration_param, :boolean_caster + + def update_location_sharing + user.update_family_location_sharing!(enabled?, duration: duration_param) + end + + def enabled? + @enabled ||= boolean_caster.cast(enabled_param) + end + + def success_result + payload = { + success: true, + enabled: enabled?, + duration: user.family_sharing_duration, + message: build_sharing_message + } + + if enabled? && user.family_sharing_expires_at.present? + payload[:expires_at] = user.family_sharing_expires_at.iso8601 + payload[:expires_at_formatted] = user.family_sharing_expires_at.strftime('%b %d at %I:%M %p') + end + + Result.new(success?: true, payload: payload, status: :ok) + end + + def failure_result(message, status) + Result.new(success?: false, payload: { success: false, message: message }, status: status) + end + + def build_sharing_message + return 'Location sharing disabled' unless enabled? + + case duration_param + when '1h' then 'Location sharing enabled for 1 hour' + when '6h' then 'Location sharing enabled for 6 hours' + when '12h' then 'Location sharing enabled for 12 hours' + when '24h' then 'Location sharing enabled for 24 hours' + when 'permanent', nil then 'Location sharing enabled' + else + duration_param.to_i.positive? ? "Location sharing enabled for #{duration_param.to_i} hours" : 'Location sharing enabled' + end + end +end diff --git a/spec/services/families/update_location_sharing_spec.rb b/spec/services/families/update_location_sharing_spec.rb new file mode 100644 index 00000000..72243d5b --- /dev/null +++ b/spec/services/families/update_location_sharing_spec.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Families::UpdateLocationSharing do + include ActiveSupport::Testing::TimeHelpers + + describe '.call' do + subject(:call_service) do + described_class.new(user: user, enabled: enabled, duration: duration).call + end + + let(:duration) { '1h' } + + context 'when the user is in a family' do + let(:user) { create(:user) } + let!(:family_membership) { create(:family_membership, user: user) } + + context 'when enabling location sharing with a duration' do + let(:enabled) { true } + + around do |example| + travel_to(Time.zone.local(2024, 1, 1, 12, 0, 0)) { example.run } + end + + it 'returns a successful result with the expected payload' do + result = call_service + + expect(result).to be_success + expect(result.status).to eq(:ok) + expect(result.payload[:success]).to be true + expect(result.payload[:enabled]).to be true + expect(result.payload[:duration]).to eq('1h') + expect(result.payload[:message]).to eq('Location sharing enabled for 1 hour') + expect(result.payload[:expires_at]).to eq(1.hour.from_now.iso8601) + expect(result.payload[:expires_at_formatted]).to eq(1.hour.from_now.strftime('%b %d at %I:%M %p')) + end + end + + context 'when disabling location sharing' do + let(:enabled) { false } + let(:duration) { nil } + + it 'returns a successful result without expiration details' do + result = call_service + + expect(result).to be_success + expect(result.payload[:success]).to be true + expect(result.payload[:enabled]).to be false + expect(result.payload[:message]).to eq('Location sharing disabled') + expect(result.payload).not_to have_key(:expires_at) + expect(result.payload).not_to have_key(:expires_at_formatted) + end + end + + context 'when update raises an unexpected error' do + let(:enabled) { true } + + before do + allow(user).to receive(:update_family_location_sharing!).and_raise(StandardError, 'boom') + end + + it 'returns a failure result with internal server error status' do + result = call_service + + expect(result).not_to be_success + expect(result.status).to eq(:internal_server_error) + expect(result.payload[:success]).to be false + expect(result.payload[:message]).to eq('An error occurred while updating location sharing') + end + end + end + + context 'when the user is not in a family' do + let(:user) { create(:user) } + let(:enabled) { true } + + it 'returns a failure result with unprocessable content status' do + result = call_service + + expect(result).not_to be_success + expect(result.status).to eq(:unprocessable_content) + expect(result.payload[:success]).to be false + expect(result.payload[:message]).to eq('Failed to update location sharing setting') + end + end + end +end