hensei-api/app/models/user.rb
Justin Edmund b75a905e2e add crew invitations system
- create crew_invitations table with status enum
- add CrewInvitation model with accept/reject flow
- add CrewInvitationsController for send/accept/reject
- add invitation error classes
- add invitation routes nested under crews
- add pending invitations endpoint for current user
- 38 passing specs for model and controller
2025-12-03 23:06:07 -08:00

114 lines
No EOL
2.9 KiB
Ruby

# frozen_string_literal: true
class User < ApplicationRecord
before_save { self.email = email.downcase }
##### ActiveRecord Associations
has_many :parties, dependent: :destroy
has_many :favorites, dependent: :destroy
has_many :collection_characters, dependent: :destroy
has_many :collection_weapons, dependent: :destroy
has_many :collection_summons, dependent: :destroy
has_many :collection_job_accessories, dependent: :destroy
has_many :collection_artifacts, dependent: :destroy
# Crew associations
has_many :crew_memberships, dependent: :destroy
has_one :active_crew_membership, -> { where(retired: false) }, class_name: 'CrewMembership'
has_one :crew, through: :active_crew_membership
has_many :crew_invitations, dependent: :destroy
has_many :pending_crew_invitations, -> { where(status: :pending) }, class_name: 'CrewInvitation'
has_many :sent_crew_invitations, class_name: 'CrewInvitation', foreign_key: :invited_by_id, dependent: :nullify
##### ActiveRecord Validations
validates :username,
presence: true,
length: { minimum: 3, maximum: 26 }
validates :email,
presence: true,
uniqueness: true,
email: true
validates :password,
length: { minimum: 8 },
presence: true,
on: :create
validates :password,
length: { minimum: 8 },
on: :update,
if: :password_digest_changed?
validates :password_confirmation,
presence: true,
on: :create
validates :password_confirmation,
presence: true,
on: :update,
if: :password_digest_changed?
##### ActiveModel Security
has_secure_password
##### Enums
# Enum for collection privacy levels
enum :collection_privacy, {
everyone: 0,
crew_only: 1,
private_collection: 2
}, prefix: true
##### Instance Methods
def favorite_parties
favorites.map(&:party)
end
def admin?
role == 9
end
def blueprint
UserBlueprint
end
# Check if collection is viewable by another user
def collection_viewable_by?(viewer)
return true if self == viewer # Owners can always view their own collection
case collection_privacy
when 'everyone'
true
when 'crew_only'
viewer.present? && in_same_crew_as?(viewer)
when 'private_collection'
false
else
false
end
end
# Check if user is in same crew as another user
def in_same_crew_as?(other_user)
return false unless other_user.present?
return false unless crew.present? && other_user.crew.present?
crew.id == other_user.crew.id
end
# Get the user's crew role
def crew_role
active_crew_membership&.role
end
# Check if user is a crew officer (captain or vice captain)
def crew_officer?
crew_role.in?(%w[captain vice_captain])
end
# Check if user is a crew captain
def crew_captain?
crew_role == 'captain'
end
end