Skip to content

Event Participation Module — MVP #237

@danielhe4rt

Description

@danielhe4rt

Problem Statement

A He4rt Developers organiza meetups presenciais, workshops e conferências (incluindo um meetup em breve e uma conferência de 2 dias no final do ano), mas não possui um sistema próprio para gerenciar o ciclo de participação dos membros. Hoje não há como:

  • Controlar inscrições com capacidade limitada e lista de espera
  • Verificar presença de quem realmente compareceu (check-in)
  • Recompensar participação com XP no sistema de gamificação existente
  • Aprovar candidatos a eventos com vagas limitadas (workshops)

Solution

Construir o módulo de participação em eventos (app-modules/events/) com lifecycle completo: enrollment → check-in → attendance → XP reward. O sistema suporta 3 métodos de entrada (RSVP, RSVP+check-in, Application com formulário dinâmico), 3 métodos de check-in (manual, código numérico, QR code), waitlist com promoção FIFO, e integração com o módulo de gamificação via domain events.

User Stories

  1. As a participant, I want to RSVP to a meetup with one click, so that I can confirm my presence quickly
  2. As a participant, I want to join a waitlist when an event is full, so that I can be promoted if someone cancels
  3. As a participant, I want to be automatically promoted from the waitlist when a slot opens, so that I don't miss the opportunity
  4. As a participant, I want to cancel my enrollment before the deadline, so that my slot is freed for others
  5. As a participant, I want to check in at a meetup by typing a numeric code, so that my presence is verified
  6. As a participant, I want to check in at a conference by showing my QR code badge, so that attendance is tracked per day
  7. As a participant, I want to receive XP when I confirm, check in, and complete attendance, so that my participation is rewarded in the gamification system
  8. As a participant, I want to fill an application form to apply for a workshop, so that the organizer can evaluate my candidacy
  9. As a participant, I want to be notified when my application is approved or rejected, so that I know my status
  10. As a participant, I want to check in on each day of a multi-day conference, so that my attendance per day is tracked
  11. As a participant, I want to use the same QR code across all event days, so that I only need one badge
  12. As a participant, I want to check in via Discord/Twitch bot by typing a command with the code, so that I can confirm presence in online events
  13. As a participant, I want to see my enrollment status for each event, so that I know where I stand
  14. As an organizer, I want to create events with configurable enrollment policies, so that each event has appropriate participation rules
  15. As an organizer, I want to set capacity limits with optional waitlist, so that I can control attendance
  16. As an organizer, I want to choose the enrollment method (RSVP, RSVP+check-in, Application), so that the entry flow matches the event type
  17. As an organizer, I want to choose the check-in method (manual, numeric code, QR code), so that presence verification fits the event format
  18. As an organizer, I want to define a dynamic application form (JSONB schema), so that I can collect relevant information per event
  19. As an organizer, I want to approve or reject applications with a reason, so that participants understand the decision
  20. As an organizer, I want to generate time-limited numeric codes bound to a specific date, so that check-in is controlled per day
  21. As an organizer, I want to manually check in participants, so that I can handle edge cases (late arrivals, code issues)
  22. As an organizer, I want to override a participant's status (e.g., no_show → attended), so that I can correct errors
  23. As an organizer, I want to set a cancellation deadline in hours before the event, so that I have predictability on attendance
  24. As an organizer, I want to configure XP rewards per event (RSVP, check-in, attendance), so that different events reward differently
  25. As an organizer, I want to set attendance_requirement (all_days, any_day, minimum_days), so that multi-day events have clear completion criteria
  26. As an organizer, I want to see all enrollments with their current status, so that I can manage the event
  27. As an organizer, I want a full audit trail of every status transition, so that I can investigate disputes
  28. As the system, I want to automatically mark checked_in → attended after the event ends, so that XP is awarded without manual intervention
  29. As the system, I want to automatically mark confirmed → no_show after the event ends, so that non-attendees are identified
  30. As the system, I want enrollment capacity checks to be atomic (lockForUpdate), so that overbooking is impossible under concurrency

Implementation Decisions

Architecture

  • Module: app-modules/events/ following existing modular architecture (internachi/modular)
  • Namespace: He4rt\Events\*
  • Panels: Admin panel = organizer, App panel = participant
  • Table prefix: All tables prefixed with events_ for clear identification in database

State Machine

  • Implemented as a PHP backed enum (EnrollmentStatusEnum) with canTransitionTo() method
  • NOT using spatie/laravel-model-states — transitions validated in Action classes explicitly
  • 8 states: pending, confirmed, waitlisted, checked_in, attended, cancelled, rejected, no_show
  • Terminal states: attended (success), cancelled, rejected, no_show
  • See ADR-0001

Schema (7 tables)

events — Core event record:

  • id, tenant_id, event_type (meetup/workshop/conference), active, slug, title, description, location, starts_at, ends_at, timestamps
  • No denormalized counters — derived by query

events_enrollment_policies — 1:1 configuration per event:

  • event_id (unique), enrollment_method, check_in_method, capacity (nullable = unlimited), has_waitlist, attendance_requirement (all_days/any_day/minimum_days), cancellation_deadline_hours, xp_reward_rsvp, xp_reward_checkin, xp_reward_attendance, application_form_schema (JSONB), timestamps

events_enrollments — Hub central (1 per user+event):

  • id, event_id, user_id, status (enum), waitlist_position (nullable), application_data (JSONB), rejection_reason, enrolled_at, confirmed_at, checked_in_at, attended_at, cancelled_at, is_public, timestamps
  • UNIQUE constraint on (event_id, user_id)

events_enrollment_transitions — Append-only audit trail:

  • id, enrollment_id, from_status, to_status, triggered_by (system/user/admin), actor_user_id (nullable), reason (nullable), created_at
  • Written explicitly by Actions, NOT by database triggers (ADR-0003)

events_check_ins — 1:N per enrollment (one per day):

  • id, enrollment_id, method (manual/numeric_code/qr_code), payload (JSONB), event_date, checked_in_at, timestamps
  • Multi-day events: one check-in record per day attended (ADR-0004)
  • Event days derived from events.starts_at/ends_at date range — no separate days table

events_check_in_codes — Organizer-generated numeric codes:

  • id, event_id, code, event_date, valid_from, valid_until, max_uses (nullable), uses_count, timestamps
  • Bound to specific date — one code per day

events_qr_tokens — One per enrollment, reusable across days:

  • id, enrollment_id, token (unique), expires_at (nullable), timestamps

XP Integration

  • Events module publishes domain events: EnrollmentConfirmed, ParticipantCheckedIn, ParticipantAttended
  • Gamification module listens and calls IncrementExperience on Character
  • XP amounts come from events_enrollment_policies fields, passed as payload in domain events
  • No XP ledger in Events module — auditability via events_enrollment_transitions
  • Idempotency responsibility on Gamification listener
  • See ADR-0002

Bot Integration

  • Discord/Twitch bot dispatches a domain event (e.g., CheckInRequested) with user identifier and code
  • Events module has a listener that validates the code and processes the check-in
  • Bot is pure transport — all business logic in Events module
  • Bot must listen for response event (CheckInProcessed) to reply to user in chat
  • Consistent with existing Bot Discord → Moderation pattern in CONTEXT-MAP
  • See ADR-0005

Enrollment Flow — Concurrency

  • Capacity check uses DB::transaction with lockForUpdate() on the enrollment policy
  • Count confirmed/checked_in/attended enrollments → compare against capacity → insert or waitlist
  • Waitlist promotion on cancellation: FIFO (ordered by enrolled_at), atomic update + notification dispatch

Scheduled Job

  • Runs after event.ends_at passes
  • Batch transitions: checked_in → attended (if attendance_requirement met), confirmed → no_show
  • Dispatches XP domain events for newly attended enrollments
  • Single job handles both transitions per event

Application Form

  • Dynamic form schema stored as JSONB in events_enrollment_policies.application_form_schema
  • Filament form builder for organizer to define questions
  • Participant responses stored in events_enrollments.application_data as JSONB
  • Organizer reviews in Admin panel with approve/reject actions

Event Types

enum EventTypeEnum: string {
    case Meetup = 'meetup';
    case Workshop = 'workshop';
    case Conference = 'conference';
}

Testing Decisions

  • Good tests: test external behavior through Actions and domain events, not internal implementation. Assert state transitions, database state, and dispatched events.
  • Test location: app-modules/events/tests/Feature/ and app-modules/events/tests/Unit/
  • Prior art: existing tests in app-modules/moderation/tests/ and app-modules/bot-discord/tests/
  • What to test:
    • Each Action (EnrollUser, CancelEnrollment, CheckIn, ApproveApplication, RejectApplication, PromoteFromWaitlist) — happy path + edge cases
    • State machine transitions — valid and invalid
    • Concurrency: capacity enforcement under race conditions
    • Waitlist promotion FIFO ordering
    • Multi-day check-in with attendance_requirement validation
    • Scheduled job (attended + no_show marking)
    • Domain event dispatch (assert events are fired, don't test Gamification's listener here)
    • Cancellation deadline enforcement
    • Numeric code validation (expiry, max_uses, wrong date)
    • QR token validation (expiry, reuse across days)
  • Factories: create factories for EventModel, EventEnrollmentPolicy, EventEnrollment, EventCheckIn, EventCheckInCode, EventQrToken
  • Use Pest with php artisan make:test --pest
  • ActingAs: all Filament panel tests require $this->actingAs(User::factory()->create())

Out of Scope

  • Networking between participants (profiles, connections, discovery)
  • Referral / invite links with tracking
  • Magic link check-in method
  • Geolocation check-in method
  • Sponsors association with events
  • Timeline / activity feed display
  • Call for Papers / Submissions (CFP)
  • Agenda / schedule display
  • Paid events / payment integration
  • No-show penalties affecting future eligibility (data tracked, consequences deferred)
  • Event days as explicit table (derived from date range)
  • Denormalized counters on events table

Further Notes

  • The events module was previously gutted (all tables dropped in migration 2026_05_16_195205). This PRD rebuilds the module from scratch with the new participation-focused architecture.
  • The previous module had: events, events_talks, events_attendees, sponsors, events_sponsors, events_agenda, event_submission_speakers. The new module replaces this with a fundamentally different schema focused on enrollment lifecycle.
  • Existing ADRs document key architectural decisions: see app-modules/events/docs/adr/0001-0005.
  • CONTEXT-MAP.md at repo root has been updated to include Events and Gamification bounded contexts with dependency diagram.
  • app-modules/events/CONTEXT.md contains the full domain glossary and state machine reference.
  • Future phases (post-MVP): magic_link/geolocation check-in, CFP/submissions, agenda display, networking, referral, payment integration, no-show penalties.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestready-for-agentFully specified, ready for an AFK agent

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions