# CC Soccer - Architecture Decision Log

Major design decisions and their rationale.

Last Updated: January 1, 2025

---

## Decision 1: Custom Registration Entity (Dec 18, 2024)

**Decision:** Build custom Registration entity instead of extending contrib Registration module.

**Context:**
- Initially planned to extend `drupal/registration` module (per REQUIREMENTS document)
- During implementation discovered module expects "host entity" pattern (registrations attached to events/nodes)
- Module requires `entity_type_id` and `entity_id` fields to link to host entities
- Our architecture needs: direct entity references (`season` XOR `tournament`)

**Alternatives Considered:**

1. **Fight contrib module** - Add custom fields, work around host entity pattern
   - ❌ Complex, fragile integration
   - ❌ Redundant fields (our season ref + module's entity_id)
   - ❌ Unclear data model

2. **Use module's pattern** - Store season/tournament via entity_type_id/entity_id
   - ❌ Queries become complex: `WHERE entity_type_id='season' AND entity_id=X`
   - ❌ Less clear than `WHERE season=X`
   - ❌ Still need Commerce Registration contrib module

3. **Build custom entity** - Full control, clean architecture
   - âœ… Clear data model: direct entity references
   - âœ… Simple queries: `WHERE season=X`
   - âœ… Full control over fields, validation, business logic
   - âœ… No dependency on contrib maintenance
   - ⚠️ Must write Commerce integration (~100 lines)
   - ⚠️ Lose contrib features (none we needed anyway)

**Decision:** Build custom `ccsoccer_registration` entity.

**Rationale:**
- Clean, maintainable code worth the extra ~100 lines for Commerce hooks
- Contrib module designed for different use case (event registration)
- Our needs are specific: season/tournament workflows with complex fields
- Better long-term: no fighting module assumptions

**Implementation:**
- **Entity:** `Drupal\ccsoccer\Entity\Registration`
- **Table:** `ccsoccer_registration`
- **Fields:** 25+ custom fields for season/tournament workflows
- **Namespace:** `Drupal\ccsoccer\Entity`

**Status:** ✅ Complete - entity created, tested, working

---

## Decision 2: Tournament as Separate Entity (Dec 17, 2024)

**Decision:** Create Tournament as separate entity, not a Season type.

**Context:**
- Tournaments have fundamentally different workflows than seasons
- Different capacity tracking (max_teams vs max_players)
- Different team formation (captain-led vs admin-generated)
- Different registration flows (deposit, no waitlist, no credits)

**Decision:** Separate `tournament` entity alongside `season` entity.

**Rationale:**
- Avoids conditional logic everywhere: "if season vs tournament then..."
- Cleaner queries and relationships
- Matches actual business workflow (managed separately)
- Each entity has only relevant fields

**Status:** ✅ Complete

---

## Decision 3: Extend User Entity (Dec 16, 2024)

**Decision:** Add fields to core User entity, not create separate Player entity.

**Context:**
- Need to store player attributes: skill level, jersey size, goalie preference, etc.
- Could create separate "Player" entity OR add fields to User

**Decision:** Extend Drupal core User entity with custom fields.

**Rationale:**
- Attributes belong to the person, not a role
- Simpler data model (one entity, not two)
- User roles handle permissions (Player, Captain, Board Member, Admin)
- No need for separate player management

**Implementation:**
- Added via `hook_install()` in `ccsoccer.install`
- Fields: first_name, last_name, phone, dob, gender, jersey_size, skill_level, prefers_goalie, credits_balance, player_picture, etc.

**Status:** ✅ Complete

---

## Decision 4: Groups as Logical Collections, Not Entity (Dec 17, 2024)

**Decision:** Implement groups via `group_id` field on Registration, not separate Group entity.

**Context:**
- Players want to register together (friends, family)
- Need way to keep groups together during team formation

**Decision:** Use simple `group_id` string field on Registration entity.

**Rationale:**
- Simpler than separate entity + junction tables
- Query: `SELECT * FROM registration WHERE group_id = 'smith-fall-2025'`
- No additional entity management needed
- Groups are temporary (only exist during registration/team formation)

**Implementation:**
- `group_id` field format: `{username}-{season-id}`
- Only created when invitations actually sent
- Team formation algorithm keeps groups together

**Status:** ✅ Complete (field implemented)

---

## Decision 5: Unified Group Management (Jan 1, 2025)

**Decision:** Use single interface for both season groups AND tournament team management, rather than separate captain-specific UI.

**Context:**
- Initially planned separate "captain team management" UI for tournaments
- Season groups and tournament teams have nearly identical workflows:
  - Send invitations to players
  - Accept/decline invitations
  - View roster
  - Remove members
  - Send notifications to team/group

**Alternatives Considered:**

1. **Separate interfaces** - `/my-group/{registration}` for seasons, `/tournament/{tournament}/team/{team}/manage` for tournaments
   - ❌ Code duplication (two controllers, two templates, two route sets)
   - ❌ Inconsistent UX (players learn two different interfaces)
   - ❌ More maintenance burden
   - âœ… Slightly clearer URL structure

2. **Unified interface** - `/my-group/{registration}` for both
   - âœ… Single controller, single template, single route set
   - âœ… Consistent UX (players learn once)
   - âœ… Less code to maintain
   - âœ… Template adapts based on context (is_tournament flag)
   - ⚠️ URL doesn't explicitly say "tournament"

**Decision:** Unified interface at `/my-group/{registration}`.

**Rationale:**
- Core functionality is identical between season groups and tournament teams
- Template can easily adapt (show "Team" vs "Group", "Captain" vs "Manager")
- Access control via `CaptainAccessChecker` handles captain-specific permissions
- Simpler codebase = fewer bugs
- Players benefit from consistent interface

**Implementation:**
- **Controller:** `GroupController.php` handles both contexts
- **Template:** `ccsoccer-group-manage.html.twig` with `is_tournament` flag
- **Access:** `CaptainAccessChecker` enforces tournament captain permissions
- **Routes:** Single set at `/my-group/{registration}/*`

**Status:** ✅ Complete - working for both seasons and tournaments

---

## Decision 6: Authentication Strategy — reCAPTCHA Now, Passkeys Post-Launch (Mar 2026)

**Decision:** Launch with standard password login + reCAPTCHA on registration. Add WebAuthn/passkey support as an optional post-launch enhancement.

**Context:**
- Site has ~200 migrated users from D7 who expect standard username/password login
- Registration form needs bot protection before going live
- Passkeys (WebAuthn/FIDO2) are a meaningful UX improvement but not launch-critical
- Player base is recreational soccer — mix of tech-savvy and non-tech users

**Launch Configuration:**
- Standard Drupal password login
- `drupal/recaptcha` module enabled on user registration form (Google reCAPTCHA v2 or v3)
- reCAPTCHA site key/secret managed in `settings.local.php` per environment (not committed)

**Post-Launch Enhancement:**
- Add `drupal/wa` (WebAuthn module) to support Touch ID, Face ID, Windows Hello, YubiKey
- Passkeys are *optional* — users self-select based on device support and comfort level
- Password login remains as fallback for all users
- Passkeys sit alongside password login, not replacing it

**Rationale:**
- Migrated users need a familiar login experience at launch
- reCAPTCHA is proven, simple, and already partially configured from D7
- Passkeys are still unfamiliar to many users; forcing them would create support burden
- Self-selection model: tech-forward players use passkeys, others use password — both work
- `drupal/wa` is not yet under Drupal security advisory policy — appropriate to defer to post-launch

**Pre-Launch Checklist Items Added:**
- [ ] Enable `drupal/recaptcha`, configure site key/secret in production `settings.local.php`
- [ ] Attach reCAPTCHA to user registration form
- [ ] Test registration flow with reCAPTCHA enabled

**Future Decisions:**
- Which reCAPTCHA version (v2 checkbox vs v3 invisible)?
- Whether to gate passkey setup behind a "Manage Passkeys" tab on user profile
- Whether to require passkeys for board member accounts (higher security tier)

**Status:** 🔲 reCAPTCHA — pre-launch task. Passkeys — post-launch enhancement.

---

## Future Decisions

Document additional architectural decisions here as they're made:
- Commerce checkout flow customization
- Team balancing algorithm approach
- Schedule generation algorithm approach
- Notification system architecture
- Credit calculation rules
- Waitlist management approach

---

**Note:** This log helps future developers (and future us) understand WHY decisions were made, not just WHAT was built.
