Summary of Changes:
Phase 1-3 (Domain/Infrastructure/Application):
- ThrowEventType enum with Strike, Circle, Bell, Gutter flags
- ClubGif, ClubGifRating, GifSubmissionToken entities
- EF configurations with proper indexes
- ClubGifRepository with weighted random selection
- MediaStorageService for file storage (wwwroot/club-media/{LoginName}/gifs/)
- ClubGifService with rating system and auto-disable at -5 threshold
Phase 4-5 (Admin UI & Playback):
- Admin page at /club/{ClubId}/admin/gifs with Upload, Import, Edit, Token management
- QR code generation for submission tokens
- GifPlayer component with overlay, rating buttons, and dismiss
- Fluxor state management (GifState, actions, reducers, effects)
Phase 6 (Anonymous Submission):
- Public page at /gif/submit/{Token} with EmptyLayout
- Token validation and file upload
Phase 7 (Game Integration + Migration):
- Database migration AddClubGifFeature applied
- GameEffects.TriggerGifForEventsAsync triggers GIFs on special throws
- SignalR broadcast via BroadcastGifTriggeredAsync to all clients
- DayDetails.razor includes GifPlayer and handles SignalR GIF events
Key Features:
- GIFs play on Strike (alle 9), Circle (Kranz), Bell (Glocke), Gutter (Rinne)
- Weighted random selection based on ratings
- Real-time sync across all connected clients via SignalR
- Thumbs up/down rating with auto-disable at -5
- Max file size: 20MB, Video duration: 15s
- Supports GIF, MP4, WebM formats
- Extend Game entity with GameType, Status, StartedAt, CompletedAt, RowVersion
- Add GameConfiguration with RowVersion for optimistic concurrency
- Create IGamePersistenceService interface
- Implement GamePersistenceService with CRUD operations
- Create GameStateSerializationDto for JSON serialization
- Extend GameEffects with full persistence lifecycle:
- HandleStartGame: Creates game in DB
- HandleEndGame: Updates status to Completed/Aborted
- HandleLoadActiveGame: Recovery from page reload
- HandleRecordThrow: Debounced save (500ms)
- HandleSaveGameState: Explicit save with concurrency check
- Add migration ExtendGameEntity
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- ITriggerRepository interface + TriggerRepository implementation
- ITriggerService interface + TriggerService implementation
- TriggerDto, ExpenseTriggerLinkDto, FireTriggerDto
- FireTriggerAsync creates PersonExpenses with multiplier
- DI registration for both services
- Fix test mock for IClubRepository
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- IEmailService in Domain/Interfaces for membership notifications
- StubEmailService in Infrastructure/Services logs instead of sending
- TODO comments for future SMTP implementation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- ClubInvitation: Token, ExpiresAt, MaxUses, UsedCount
- Unique index on Token
- DbSet + EF Configuration
- Migration applied
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Summary of all fixes
3 files changed:
1. src/Koogle.Application/Services/UserService.cs:140-171
- GetByIdentityUserIdAsync now includes .Include(p => p.Clubs) and maps ClubMemberships
2. src/Koogle.Web/Store/AuthState/AuthEffects.cs:53-73
- Merges club-specific roles from ClubMemberships into AuthState roles
3. src/Koogle.Infrastructure/Security/ClubRoleRequirement.cs:17-114
- Changed ClubRoleHandler to extend AuthorizationHandler<ClubRoleRequirement> (no resource)
- Reads current_club_id from claims to determine club context
- Added ClubRoleResourceHandler for resource-based auth (explicit clubId)
4. src/Koogle.Infrastructure/DependencyInjection.cs:72
- Registered ClubRoleResourceHandler
The [Authorize(Policy = "ClubViewer")] attribute now uses current_club_id claim set during login to check club roles.