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
- As a participant, I want to RSVP to a meetup with one click, so that I can confirm my presence quickly
- As a participant, I want to join a waitlist when an event is full, so that I can be promoted if someone cancels
- As a participant, I want to be automatically promoted from the waitlist when a slot opens, so that I don't miss the opportunity
- As a participant, I want to cancel my enrollment before the deadline, so that my slot is freed for others
- As a participant, I want to check in at a meetup by typing a numeric code, so that my presence is verified
- As a participant, I want to check in at a conference by showing my QR code badge, so that attendance is tracked per day
- 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
- As a participant, I want to fill an application form to apply for a workshop, so that the organizer can evaluate my candidacy
- As a participant, I want to be notified when my application is approved or rejected, so that I know my status
- As a participant, I want to check in on each day of a multi-day conference, so that my attendance per day is tracked
- As a participant, I want to use the same QR code across all event days, so that I only need one badge
- 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
- As a participant, I want to see my enrollment status for each event, so that I know where I stand
- As an organizer, I want to create events with configurable enrollment policies, so that each event has appropriate participation rules
- As an organizer, I want to set capacity limits with optional waitlist, so that I can control attendance
- As an organizer, I want to choose the enrollment method (RSVP, RSVP+check-in, Application), so that the entry flow matches the event type
- As an organizer, I want to choose the check-in method (manual, numeric code, QR code), so that presence verification fits the event format
- As an organizer, I want to define a dynamic application form (JSONB schema), so that I can collect relevant information per event
- As an organizer, I want to approve or reject applications with a reason, so that participants understand the decision
- As an organizer, I want to generate time-limited numeric codes bound to a specific date, so that check-in is controlled per day
- As an organizer, I want to manually check in participants, so that I can handle edge cases (late arrivals, code issues)
- As an organizer, I want to override a participant's status (e.g., no_show → attended), so that I can correct errors
- As an organizer, I want to set a cancellation deadline in hours before the event, so that I have predictability on attendance
- As an organizer, I want to configure XP rewards per event (RSVP, check-in, attendance), so that different events reward differently
- As an organizer, I want to set attendance_requirement (all_days, any_day, minimum_days), so that multi-day events have clear completion criteria
- As an organizer, I want to see all enrollments with their current status, so that I can manage the event
- As an organizer, I want a full audit trail of every status transition, so that I can investigate disputes
- As the system, I want to automatically mark checked_in → attended after the event ends, so that XP is awarded without manual intervention
- As the system, I want to automatically mark confirmed → no_show after the event ends, so that non-attendees are identified
- 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.
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:
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
Implementation Decisions
Architecture
app-modules/events/following existing modular architecture (internachi/modular)He4rt\Events\*events_for clear identification in databaseState Machine
EnrollmentStatusEnum) withcanTransitionTo()methodspatie/laravel-model-states— transitions validated in Action classes explicitlypending,confirmed,waitlisted,checked_in,attended,cancelled,rejected,no_showattended(success),cancelled,rejected,no_showSchema (7 tables)
events— Core event record:id,tenant_id,event_type(meetup/workshop/conference),active,slug,title,description,location,starts_at,ends_at,timestampsevents_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),timestampsevents_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(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_atevents_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,timestampsevents.starts_at/ends_atdate range — no separate days tableevents_check_in_codes— Organizer-generated numeric codes:id,event_id,code,event_date,valid_from,valid_until,max_uses(nullable),uses_count,timestampsevents_qr_tokens— One per enrollment, reusable across days:id,enrollment_id,token(unique),expires_at(nullable),timestampsXP Integration
EnrollmentConfirmed,ParticipantCheckedIn,ParticipantAttendedIncrementExperienceon Characterevents_enrollment_policiesfields, passed as payload in domain eventsevents_enrollment_transitionsBot Integration
CheckInRequested) with user identifier and codeCheckInProcessed) to reply to user in chatEnrollment Flow — Concurrency
DB::transactionwithlockForUpdate()on the enrollment policyenrolled_at), atomic update + notification dispatchScheduled Job
event.ends_atpasseschecked_in → attended(if attendance_requirement met),confirmed → no_showattendedenrollmentsApplication Form
events_enrollment_policies.application_form_schemaevents_enrollments.application_dataas JSONBEvent Types
Testing Decisions
app-modules/events/tests/Feature/andapp-modules/events/tests/Unit/app-modules/moderation/tests/andapp-modules/bot-discord/tests/php artisan make:test --pest$this->actingAs(User::factory()->create())Out of Scope
Further Notes
2026_05_16_195205). This PRD rebuilds the module from scratch with the new participation-focused architecture.app-modules/events/docs/adr/0001-0005.CONTEXT-MAP.mdat repo root has been updated to include Events and Gamification bounded contexts with dependency diagram.app-modules/events/CONTEXT.mdcontains the full domain glossary and state machine reference.