13 KiB
13 KiB
Crew Feature Plan
Overview
The Crew system enables players to form groups of up to 30 members to collaborate, share strategies, and compete in Unite and Fight events. Crews provide a social layer to the application with hierarchical roles, shared content visibility, and performance tracking capabilities.
Business Requirements
Core Concepts
Crew Structure
- Size Limit: Maximum 30 members per crew
- Roles Hierarchy:
- Captain: The crew creator with full administrative privileges
- Subcaptains: Up to 3 members with elevated permissions
- Members: Regular crew participants with standard access
Key Features
- Crew Management: Creation, invitation, membership control
- Gamertags: 4-character tags displayed alongside member names
- Unite and Fight: Event participation and score tracking
- Crew Feed: Private content stream for crew-only parties, guides, and future posts
- Performance Analytics: Historical tracking and visualization of member contributions
User Stories
As a Captain
- I want to create a crew and invite players to join
- I want to appoint up to 3 subcaptains to help manage the crew
- I want to remove members who are inactive or problematic
- I want to set crew rules that all members can view
- I want to track member performance in Unite and Fight events
As a Subcaptain
- I want to help manage crew by inviting new members
- I want to update crew rules and information
- I want to record Unite and Fight scores for members
- I want to set gamertags for crew representation
As a Member
- I want to join a crew via invitation link
- I want to view crew rules and information
- I want to see my Unite and Fight performance history
- I want to choose whether to display my crew's gamertag
- I want to see crew-only parties and guides in the feed
As a System Administrator
- I want to add new Unite and Fight events when announced by Cygames
- I want to manage event dates and parameters
- I want to monitor crew activities for policy violations
Technical Design
Database Schema
crews
- id: uuid (primary key)
- name: string (unique, not null)
- captain_id: uuid (foreign key to users, not null)
- gamertag: string (4 characters, unique, can be null)
- rules: text
- member_count: integer (counter cache, default 1)
- created_at: timestamp
- updated_at: timestamp
Indexes:
- unique index on name
- unique index on gamertag (where not null)
- index on captain_id
- index on created_at
crew_memberships
- id: uuid (primary key)
- crew_id: uuid (foreign key to crews, not null)
- user_id: uuid (foreign key to users, not null)
- role: integer (0=member, 1=subcaptain, 2=captain)
- display_gamertag: boolean (default true)
- joined_at: timestamp (default now)
- created_at: timestamp
- updated_at: timestamp
Indexes:
- unique index on [crew_id, user_id]
- index on crew_id
- index on user_id
- index on role
- index on joined_at
crew_invitations
- id: uuid (primary key)
- crew_id: uuid (foreign key to crews, not null)
- invited_by_id: uuid (foreign key to users, not null)
- token: string (unique, not null)
- expires_at: timestamp (default 7 days from creation)
- used_at: timestamp (null)
- used_by_id: uuid (foreign key to users, null)
- created_at: timestamp
- updated_at: timestamp
Indexes:
- unique index on token
- index on crew_id
- index on expires_at
- index on [crew_id, used_at] (for tracking active invitations)
unite_and_fights
- id: uuid (primary key)
- name: string (not null)
- event_number: integer (sequential, unique)
- starts_at: timestamp (not null)
- ends_at: timestamp (not null)
- created_by_id: uuid (foreign key to users, not null)
- created_at: timestamp
- updated_at: timestamp
Indexes:
- unique index on event_number
- index on starts_at
- index on ends_at
- index on [starts_at, ends_at] (for finding active events)
unf_scores
- id: uuid (primary key)
- unite_and_fight_id: uuid (foreign key to unite_and_fights, not null)
- crew_id: uuid (foreign key to crews, not null)
- user_id: uuid (foreign key to users, not null)
- honors: bigint (not null, default 0)
- recorded_by_id: uuid (foreign key to users, not null)
- day_number: integer (1-7, not null)
- created_at: timestamp
- updated_at: timestamp
Indexes:
- unique index on [unite_and_fight_id, crew_id, user_id, day_number]
- index on unite_and_fight_id
- index on crew_id
- index on user_id
- index on [crew_id, unite_and_fight_id] (for crew performance queries)
- index on honors (for rankings)
crew_feeds (future table for reference)
- id: uuid (primary key)
- crew_id: uuid (foreign key to crews, not null)
- feedable_type: string (Party, Guide, Post, etc.)
- feedable_id: uuid (polymorphic reference)
- created_at: timestamp
Indexes:
- index on [crew_id, created_at] (for feed queries)
- index on [feedable_type, feedable_id]
Model Relationships
# User model additions
has_one :crew_membership, dependent: :destroy
has_one :crew, through: :crew_membership
has_many :captained_crews, class_name: 'Crew', foreign_key: :captain_id
has_many :crew_invitations_sent, class_name: 'CrewInvitation', foreign_key: :invited_by_id
has_many :unf_scores
has_many :recorded_unf_scores, class_name: 'UnfScore', foreign_key: :recorded_by_id
# Crew model
belongs_to :captain, class_name: 'User'
has_many :crew_memberships, dependent: :destroy
has_many :members, through: :crew_memberships, source: :user
has_many :crew_invitations, dependent: :destroy
has_many :unf_scores, dependent: :destroy
has_many :subcaptains, -> { where(crew_memberships: { role: 1 }) },
through: :crew_memberships, source: :user
# CrewMembership model
belongs_to :crew, counter_cache: :member_count
belongs_to :user
enum role: { member: 0, subcaptain: 1, captain: 2 }
# CrewInvitation model
belongs_to :crew
belongs_to :invited_by, class_name: 'User'
belongs_to :used_by, class_name: 'User', optional: true
# UniteAndFight model
has_many :unf_scores, dependent: :destroy
belongs_to :created_by, class_name: 'User'
# UnfScore model
belongs_to :unite_and_fight
belongs_to :crew
belongs_to :user
belongs_to :recorded_by, class_name: 'User'
API Design
Crew Management
Crew CRUD
POST /api/v1/crews
Body: { name, rules?, gamertag? }
Response: Created crew with captain membership
GET /api/v1/crews/:id
Response: Crew details with members list
PUT /api/v1/crews/:id
Body: { name?, rules?, gamertag? }
Response: Updated crew (captain/subcaptain only)
DELETE /api/v1/crews/:id
Response: Success (captain only, disbands crew)
GET /api/v1/crews/my
Response: Current user's crew with full details
Member Management
GET /api/v1/crews/:id/members
Response: Paginated list of crew members with roles
POST /api/v1/crews/:id/members/promote
Body: { user_id, role: "subcaptain" }
Response: Updated membership (captain only)
DELETE /api/v1/crews/:id/members/:user_id
Response: Success (captain only)
PUT /api/v1/crews/:id/members/me
Body: { display_gamertag }
Response: Updated own membership settings
Invitations
POST /api/v1/crews/:id/invitations
Response: { invitation_url, token, expires_at }
Note: Captain/subcaptain only
GET /api/v1/crews/:id/invitations
Response: List of pending invitations (captain/subcaptain)
DELETE /api/v1/crews/:id/invitations/:id
Response: Revoke invitation (captain/subcaptain)
POST /api/v1/crews/join
Body: { token }
Response: Joined crew details
Unite and Fight
Event Management (Admin)
GET /api/v1/unite_and_fights
Response: List of all UnF events
POST /api/v1/unite_and_fights
Body: { name, event_number, starts_at, ends_at }
Response: Created event (requires level 7+ permissions)
PUT /api/v1/unite_and_fights/:id
Body: { name?, starts_at?, ends_at? }
Response: Updated event (admin only)
Score Management
POST /api/v1/unf_scores
Body: { unite_and_fight_id, user_id, honors, day_number }
Response: Created/updated score (captain/subcaptain only)
GET /api/v1/crews/:crew_id/unf_scores
Query: unite_and_fight_id?, user_id?
Response: Scores for crew, optionally filtered
GET /api/v1/unf_scores/performance
Query: crew_id, user_id?, from_date?, to_date?
Response: Performance data for graphing
Crew Feed
GET /api/v1/crews/:id/feed
Query: page, limit, type?
Response: Paginated feed of crew-only content
Authorization & Permissions
Permission Matrix
| Action | Captain | Subcaptain | Member | Non-member |
|---|---|---|---|---|
| View crew info | ✓ | ✓ | ✓ | ✓ |
| View member list | ✓ | ✓ | ✓ | ✗ |
| Update crew info | ✓ | ✓ | ✗ | ✗ |
| Set gamertag | ✓ | ✓ | ✗ | ✗ |
| Invite members | ✓ | ✓ | ✗ | ✗ |
| Remove members | ✓ | ✗ | ✗ | ✗ |
| Promote to subcaptain | ✓ | ✗ | ✗ | ✗ |
| Record UnF scores | ✓ | ✓ | ✗ | ✗ |
| View UnF scores | ✓ | ✓ | ✓ | ✗ |
| View crew feed | ✓ | ✓ | ✓ | ✗ |
| Leave crew | ✗* | ✓ | ✓ | ✗ |
*Captain must transfer ownership or disband crew
Authorization Helpers
# app/models/user.rb
def captain_of?(crew)
crew.captain_id == id
end
def subcaptain_of?(crew)
crew_membership&.subcaptain? && crew_membership.crew_id == crew.id
end
def member_of?(crew)
crew_membership&.crew_id == crew.id
end
def can_manage_crew?(crew)
captain_of?(crew) || subcaptain_of?(crew)
end
def can_invite_to_crew?(crew)
can_manage_crew?(crew)
end
def can_remove_from_crew?(crew)
captain_of?(crew)
end
def can_record_unf_scores?(crew)
can_manage_crew?(crew)
end
Security Considerations
-
Invitation Security:
- Tokens are cryptographically secure random strings
- Automatic expiration after 7 days
- One-time use only
- Rate limiting on join attempts
-
Member Limits:
- Enforce 30 member maximum at database level
- Check before invitation acceptance
- Atomic operations for membership changes
-
Role Management:
- Only captain can promote/demote
- Maximum 3 subcaptains enforced
- Captain role transfer requires explicit action
-
Data Privacy:
- Crew-only content respects visibility settings
- UnF scores only visible to crew members
- Member list public, but details restricted
Performance Considerations
-
Caching:
- Cache crew member lists (5-minute TTL)
- Cache UnF leaderboards (1-hour TTL)
- Cache crew feed content
-
Database Optimization:
- Counter cache for member_count
- Composite indexes for common queries
- Partial indexes for active records
-
Query Optimization:
- Eager load associations
- Pagination for member lists and feeds
- Batch operations for UnF score updates
-
Background Jobs:
- Async invitation email sending
- Scheduled cleanup of expired invitations
- UnF score aggregation calculations
Implementation Phases
Phase 1: Core Crew System (Week 1-2)
- Database migrations and models
- Basic CRUD operations
- Captain and member roles
- Invitation system
Phase 2: Advanced Roles & Permissions (Week 3)
- Subcaptain functionality
- Permission system
- Gamertag management
- Crew rules
Phase 3: Unite and Fight Integration (Week 4-5)
- UnF event management
- Score recording system
- Performance queries
- Basic reporting
Phase 4: Feed & Analytics (Week 6)
- Crew feed implementation
- Integration with parties/guides
- Performance graphs
- Historical tracking
Phase 5: Polish & Optimization (Week 7)
- Performance tuning
- Caching layer
- Background jobs
- Admin tools
Success Metrics
- Adoption: 60% of active users join a crew within 3 months
- Engagement: Average crew has 15+ active members
- Performance: All crew operations complete within 200ms
- Reliability: 99.9% uptime for crew services
- UnF Participation: 80% score recording during events
Future Enhancements
- Crew Battles: Inter-crew competitions outside UnF
- Crew Chat: Real-time messaging system
- Crew Achievements: Badges and milestones
- Crew Resources: Shared guides and strategies library
- Crew Recruitment: Public crew discovery and application system
- Officer Roles: Additional permission tiers
- Crew Alliances: Multi-crew coordination
- Automated Scoring: API integration with game data (if available)
- Mobile Notifications: Push notifications for crew events
- Crew Statistics: Advanced analytics and insights
Risk Mitigation
- Toxic Behavior: Implement reporting system and moderation tools
- Inactive Crews: Automatic leadership transfer after inactivity
- Database Load: Implement read replicas for heavy queries
- Invitation Spam: Rate limiting and abuse detection
- Score Manipulation: Audit logs and validation rules