hensei-api/app/models/crew_invitation.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

64 lines
1.6 KiB
Ruby

# frozen_string_literal: true
class CrewInvitation < ApplicationRecord
belongs_to :crew
belongs_to :user
belongs_to :invited_by, class_name: 'User'
enum :status, { pending: 0, accepted: 1, rejected: 2, expired: 3 }
validates :user_id, uniqueness: {
scope: %i[crew_id status],
conditions: -> { where(status: :pending) },
message: 'already has a pending invitation to this crew'
}
validate :user_not_in_crew, on: :create
validate :inviter_is_officer
scope :active, -> { where(status: :pending).where('expires_at IS NULL OR expires_at > ?', Time.current) }
before_create :set_expiration
# Accept the invitation and create membership
def accept!
raise CrewErrors::InvitationExpiredError if expired? || (expires_at.present? && expires_at < Time.current)
raise CrewErrors::AlreadyInCrewError if user.reload.crew.present?
transaction do
update!(status: :accepted)
CrewMembership.create!(crew: crew, user: user, role: :member)
end
end
# Reject the invitation
def reject!
raise CrewErrors::InvitationExpiredError if expired?
update!(status: :rejected)
end
# Check if invitation is still valid
def active?
pending? && (expires_at.nil? || expires_at > Time.current)
end
private
def set_expiration
self.expires_at ||= 7.days.from_now
end
def user_not_in_crew
return unless user&.crew.present?
errors.add(:user, 'is already in a crew')
end
def inviter_is_officer
return unless invited_by.present?
return if invited_by.crew&.id == crew_id && invited_by.crew_officer?
errors.add(:invited_by, 'must be an officer of the crew')
end
end