diff --git a/app/controllers/api/v1/families_controller.rb b/app/controllers/api/v1/families_controller.rb index 0afb9f51..3cd93894 100644 --- a/app/controllers/api/v1/families_controller.rb +++ b/app/controllers/api/v1/families_controller.rb @@ -16,12 +16,6 @@ class Api::V1::FamiliesController < ApiController private - def ensure_family_feature_enabled! - return if DawarichSettings.family_feature_enabled? - - render json: { error: 'Family feature is not enabled' }, status: :forbidden - end - def ensure_user_in_family! return if current_api_user.in_family? diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 7b48a558..515aebef 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -56,6 +56,12 @@ class ApplicationController < ActionController::Base end end + def ensure_family_feature_enabled! + return if DawarichSettings.family_feature_enabled? + + render json: { error: 'Family feature is not enabled' }, status: :forbidden + end + private def set_self_hosted_status diff --git a/app/controllers/families_controller.rb b/app/controllers/families_controller.rb index 59dd9e9f..b0c47344 100644 --- a/app/controllers/families_controller.rb +++ b/app/controllers/families_controller.rb @@ -36,14 +36,12 @@ class FamiliesController < ApplicationController else @family = Family.new(family_params) - # Handle validation errors if service.errors.any? service.errors.each do |error| @family.errors.add(error.attribute, error.message) end end - # Handle service-level errors if service.error_message.present? @family.errors.add(:base, service.error_message) end @@ -130,12 +128,6 @@ class FamiliesController < ApplicationController end end - def ensure_family_feature_enabled! - unless DawarichSettings.family_feature_enabled? - redirect_to root_path, alert: 'Family feature is not available' - 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 0dccb414..7e4108ca 100644 --- a/app/controllers/family/invitations_controller.rb +++ b/app/controllers/family/invitations_controller.rb @@ -92,12 +92,6 @@ class Family::InvitationsController < ApplicationController private - def ensure_family_feature_enabled! - unless DawarichSettings.family_feature_enabled? - redirect_to root_path, alert: 'Family feature is not available' - end - end - def set_family @family = current_user.family diff --git a/app/controllers/family/memberships_controller.rb b/app/controllers/family/memberships_controller.rb index 3577ee91..65784f07 100644 --- a/app/controllers/family/memberships_controller.rb +++ b/app/controllers/family/memberships_controller.rb @@ -14,10 +14,8 @@ class Family::MembershipsController < ApplicationController if service.call if member_user == current_user - # User removed themselves redirect_to new_family_path, notice: 'You have left the family' else - # Owner removed another member redirect_to family_path, notice: "#{member_user.email} has been removed from the family" end else @@ -27,12 +25,6 @@ class Family::MembershipsController < ApplicationController private - def ensure_family_feature_enabled! - unless DawarichSettings.family_feature_enabled? - redirect_to root_path, alert: 'Family feature is not available' - end - end - def set_family @family = current_user.family diff --git a/app/helpers/families_helper.rb b/app/helpers/families_helper.rb deleted file mode 100644 index f2852e5f..00000000 --- a/app/helpers/families_helper.rb +++ /dev/null @@ -1,84 +0,0 @@ -# frozen_string_literal: true - -module FamiliesHelper - def family_member_role_badge(membership) - case membership.role - when 'owner' - content_tag :span, 'Owner', class: 'badge badge-primary badge-sm' - when 'member' - content_tag :span, 'Member', class: 'badge badge-secondary badge-sm' - else - content_tag :span, membership.role.humanize, class: 'badge badge-ghost badge-sm' - end - end - - def family_invitation_status_badge(invitation) - case invitation.status - when 'pending' - content_tag :span, 'Pending', class: 'badge badge-warning badge-sm' - when 'accepted' - content_tag :span, 'Accepted', class: 'badge badge-success badge-sm' - when 'expired' - content_tag :span, 'Expired', class: 'badge badge-error badge-sm' - when 'cancelled' - content_tag :span, 'Cancelled', class: 'badge badge-ghost badge-sm' - else - content_tag :span, invitation.status.humanize, class: 'badge badge-ghost badge-sm' - end - end - - def family_capacity_warning(family) - return unless family.members.count >= Family::MAX_MEMBERS - 1 - - content_tag :div, class: 'alert alert-warning mt-2' do - content_tag :div do - if family.members.count >= Family::MAX_MEMBERS - 'This family has reached the maximum number of members.' - else - "This family is almost full (#{family.members.count}/#{Family::MAX_MEMBERS} members)." - end - end - end - end - - def invitation_expiry_warning(invitation) - return unless invitation.pending? - - time_left = invitation.expires_at - Time.current - return unless time_left < 24.hours - - warning_class = time_left < 1.hour ? 'alert-error' : 'alert-warning' - - content_tag :div, class: "alert #{warning_class} mt-2" do - content_tag :div do - if time_left < 1.hour - 'This invitation expires in less than 1 hour!' - else - "This invitation expires in #{time_ago_in_words(invitation.expires_at)}." - end - end - end - end - - def family_member_location_status(member) - # This would integrate with location sharing when implemented - content_tag :span, class: 'text-sm text-gray-500' do - 'Location sharing not implemented yet' - end - end - - def family_creation_benefits - content_tag :div, class: 'bg-base-200 p-4 rounded-lg' do - content_tag :h3, 'Family Features:', class: 'font-semibold mb-2' do - concat content_tag(:h3, 'Family Features:', class: 'font-semibold mb-2') - concat content_tag(:ul, class: 'list-disc list-inside space-y-1 text-sm') do - concat content_tag(:li, "Share your current location with up to #{Family::MAX_MEMBERS - 1} family members") - concat content_tag(:li, 'See where your family members are right now') - concat content_tag(:li, 'Control your privacy with sharing toggles') - concat content_tag(:li, 'Invite members by email') - concat content_tag(:li, 'Secure and private - only family members can see your location') - end - end - end - end -end \ No newline at end of file diff --git a/app/jobs/family_invitations_cleanup_job.rb b/app/jobs/family/invitations/cleanup_job.rb similarity index 88% rename from app/jobs/family_invitations_cleanup_job.rb rename to app/jobs/family/invitations/cleanup_job.rb index 0e4e0191..b55d39c7 100644 --- a/app/jobs/family_invitations_cleanup_job.rb +++ b/app/jobs/family/invitations/cleanup_job.rb @@ -6,14 +6,12 @@ class FamilyInvitationsCleanupJob < ApplicationJob def perform Rails.logger.info 'Starting family invitations cleanup' - # Update expired invitations expired_count = FamilyInvitation.where(status: :pending) .where('expires_at < ?', Time.current) .update_all(status: :expired) Rails.logger.info "Updated #{expired_count} expired family invitations" - # Delete old expired/cancelled invitations (older than 30 days) cleanup_threshold = 30.days.ago deleted_count = FamilyInvitation.where(status: [:expired, :cancelled]) .where('updated_at < ?', cleanup_threshold) @@ -23,4 +21,4 @@ class FamilyInvitationsCleanupJob < ApplicationJob Rails.logger.info 'Family invitations cleanup completed' end -end \ No newline at end of file +end diff --git a/app/models/family.rb b/app/models/family.rb index 2baaef41..ab12339d 100644 --- a/app/models/family.rb +++ b/app/models/family.rb @@ -10,12 +10,10 @@ class Family < ApplicationRecord MAX_MEMBERS = 5 - # Optimized scopes for better performance scope :with_members, -> { includes(:members, :family_memberships) } scope :with_pending_invitations, -> { includes(family_invitations: :invited_by) } def can_add_members? - # Check if family can accept more members (including pending invitations) (member_count + pending_invitations_count) < MAX_MEMBERS end @@ -28,13 +26,11 @@ class Family < ApplicationRecord end def owners - # Get family owners efficiently members.joins(:family_membership) .where(family_memberships: { role: :owner }) end def owner - # Get the primary owner (creator) efficiently @owner ||= creator end @@ -46,7 +42,6 @@ class Family < ApplicationRecord family_invitations.active.includes(:invited_by) end - # Clear cached counters when members change def clear_member_cache! @member_count = nil @pending_invitations_count = nil diff --git a/app/models/family_invitation.rb b/app/models/family_invitation.rb index 1e0d65b2..2f197280 100644 --- a/app/models/family_invitation.rb +++ b/app/models/family_invitation.rb @@ -8,8 +8,7 @@ class FamilyInvitation < ApplicationRecord validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP } validates :token, presence: true, uniqueness: true - validates :expires_at, presence: true - validates :status, presence: true + validates :expires_at, :status, presence: true enum :status, { pending: 0, accepted: 1, expired: 2, cancelled: 3 } @@ -17,7 +16,6 @@ class FamilyInvitation < ApplicationRecord before_validation :generate_token, :set_expiry, on: :create - # Clear family cache when invitation status changes or is created/destroyed after_create :clear_family_cache after_update :clear_family_cache, if: :saved_change_to_status? after_destroy :clear_family_cache diff --git a/app/models/family_membership.rb b/app/models/family_membership.rb index a8a03dfa..711e1ef1 100644 --- a/app/models/family_membership.rb +++ b/app/models/family_membership.rb @@ -4,12 +4,11 @@ class FamilyMembership < ApplicationRecord belongs_to :family belongs_to :user - validates :user_id, presence: true, uniqueness: true # One family per user + validates :user_id, presence: true, uniqueness: true validates :role, presence: true enum :role, { owner: 0, member: 1 } - # Clear family cache when membership changes after_create :clear_family_cache after_update :clear_family_cache after_destroy :clear_family_cache diff --git a/app/models/user.rb b/app/models/user.rb index 8a7bf0dd..b8076f32 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -4,7 +4,7 @@ class User < ApplicationRecord # rubocop:disable Metrics/ClassLength devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable, :trackable - has_many :points, dependent: :destroy + has_many :points, dependent: :destroy, counter_cache: true has_many :imports, dependent: :destroy has_many :stats, dependent: :destroy has_many :exports, dependent: :destroy diff --git a/config/schedule.yml b/config/schedule.yml index cb0c94e7..ae920927 100644 --- a/config/schedule.yml +++ b/config/schedule.yml @@ -44,3 +44,8 @@ nightly_reverse_geocoding_job: cron: "15 1 * * *" # every day at 01:15 class: "Points::NightlyReverseGeocodingJob" queue: reverse_geocoding + +nightly_family_invitations_cleanup_job: + cron: "30 2 * * *" # every day at 02:30 + class: "Family::Invitations::CleanupJob" + queue: family diff --git a/config/sidekiq.yml b/config/sidekiq.yml index 780bbc1c..07588984 100644 --- a/config/sidekiq.yml +++ b/config/sidekiq.yml @@ -5,6 +5,7 @@ - points - default - mailers + - family - imports - exports - stats