Skip to content

Commit 2f60e93

Browse files
committed
Add event invitation flow diagrams and documentation
- Created sequence diagram for event access via invitation token. - Added flowchart for event invitations process, detailing organizer and invitee interactions. - Developed entity-relationship diagram for event invitations schema. - Introduced end-user documentation on event invitations and RSVP processes. - Updated documentation assessment with the latest date. - Enhanced platform organizers' README with links to privacy and invitation tokens section. - Expanded host management guide to include details on privacy settings and invitation tokens. - Updated table of contents to include new sections on event invitations and RSVP management.
1 parent 9989c4d commit 2f60e93

17 files changed

+475
-1
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Event Invitations Management (Organizers & Hosts)
2+
3+
This guide covers how organizers and event hosts invite people to events, manage invitation delivery, and track RSVP outcomes.
4+
5+
## Permissions & Access
6+
7+
- You must be the event creator, a platform manager, or a host representative for the event to manage invitations.
8+
- Host representative status is determined by the `EventPolicy` (`event_host_member?`).
9+
10+
## Inviting Members vs External Emails
11+
12+
From the event page’s “Invitations” panel:
13+
14+
- Invite Member:
15+
- Use the “Invite Member” tab to select an existing person.
16+
- The selector uses `available_people` to show members not already invited and with an email.
17+
- Locale defaults from the person if set.
18+
- Invite by Email:
19+
- Use the “Invite by Email” tab to invite someone via email address.
20+
- Set a preferred locale for the invitation.
21+
22+
## Delivery & Throttling
23+
24+
- Member invitations send a Noticed notification (in-app) and may send an email.
25+
- Email invitations send via `EventInvitationsMailer` only.
26+
- To prevent spam, repeated sends are throttled if the last send was within 15 minutes.
27+
- You can resend an invitation from the invitations table when appropriate.
28+
29+
## Managing Invitations
30+
31+
- View all invitations in the “Invitations” section of the event page.
32+
- Status lifecycle: `pending``accepted` or `declined`.
33+
- Duplicate protection prevents inviting the same person/email again while pending/accepted.
34+
- You can delete invitations that are no longer needed.
35+
36+
## Private Platform Access via Token
37+
38+
- Invitation links include a token that grants access to the specific event on private platforms.
39+
- Invitees who aren’t signed in will be prompted to sign in or register; the token is saved to complete the response after authentication.
40+
41+
## RSVP Effects
42+
43+
- Accepting an event invitation automatically:
44+
- Ensures the invitee is a member of the host community (standard member role).
45+
- Sets RSVP to “Going” and creates a calendar entry for the invitee.
46+
47+
## Best Practices
48+
49+
- Use “Invite Member” when possible (richer delivery + better tracking).
50+
- Stagger resends: avoid sending the same invite within 15 minutes.
51+
- Monitor the “Attendees” section for Going/Interested counts to plan capacity.
52+
- Include clear descriptions and schedule times so RSVP is available.
53+
54+
## Troubleshooting
55+
56+
- Duplicate errors: someone was already invited or accepted — review the invitations table first.
57+
- No results in “Select Person”: the person may lack an email address; ask them to add one to their profile.
58+
- Invitee can’t open link on private platform: confirm the token matches the correct event and is not expired.
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
# Event Invitations & Attendance
2+
3+
This document provides an end-to-end, in-depth reference for event invitations and attendance (RSVP) in the Better Together Community Engine. It covers data models, controller flows, access control, invitation token handling, email and in-app notifications, RSVP life cycle, and how these pieces interact with platform privacy.
4+
5+
## Overview
6+
7+
- Event invitations allow organizers and hosts to invite existing members or external emails to a specific event.
8+
- Invitations support secure token links for review, acceptance, or decline — including first-time registration flows.
9+
- Acceptance automatically ensures community membership and sets RSVP to “going”, creating a calendar entry.
10+
- Attendance (RSVP) supports two statuses: “interested” and “going”, with cancellation removing the attendance record and calendar entry.
11+
- Invitation tokens permit access to otherwise private content for the specific invited event while preserving platform privacy.
12+
13+
## Core Models
14+
15+
- `BetterTogether::Invitation`: Polymorphic base model with `invitable`, `inviter`, optional `invitee`, `role`, `status` (string enum: pending, accepted, declined), `token` (secure), validity window, and timestamps.
16+
- `BetterTogether::EventInvitation < Invitation`: Invitation specialization for events.
17+
- Status values: `pending`, `accepted`, `declined` (string enum).
18+
- Validates presence/inclusion of `locale` and requires one of `invitee` or `invitee_email`.
19+
- Prevents duplicate invitations for a given event for either the same `invitee` or the same `invitee_email` while status is in `pending` or `accepted`.
20+
- Methods:
21+
- `event`: alias to `invitable`.
22+
- `url_for_review`: event URL with `invitation_token` param to support review page and access.
23+
- `for_existing_user?` / `for_email?`: invitation mode helpers.
24+
- `accept!(invitee_person:)`: sets status, ensures community membership, creates/updates `EventAttendance` to `going`.
25+
- `decline!`: sets status to `declined`.
26+
- `BetterTogether::EventAttendance`: RSVP record for a `person` and `event` with string enum statuses: `interested`, `going`.
27+
- Constraints:
28+
- Unique per person/event.
29+
- Event must be scheduled (no RSVP on drafts).
30+
- Side effects:
31+
- On `going`, creates a `CalendarEntry` in the person’s primary calendar.
32+
- On status change away from `going` or destroy, removes the calendar entry.
33+
34+
## Data Model Diagram (Invitations + Attendance)
35+
36+
```mermaid
37+
%% See separate Mermaid source file for editing: docs/diagrams/source/events_invitations_schema_erd.mmd
38+
```
39+
40+
**Diagram Files:**
41+
- 📊 Mermaid Source: ../diagrams/source/events_invitations_schema_erd.mmd
42+
- 🖼️ PNG Export: ../diagrams/exports/png/events_invitations_schema_erd.png
43+
- 🎯 SVG Export: ../diagrams/exports/svg/events_invitations_schema_erd.svg
44+
45+
## Invitation Creation & Delivery
46+
47+
Event organizers and host representatives create invitations from the event page (Invitations panel). Two modes are supported:
48+
49+
- Invite existing member:
50+
- Provide `invitee_id` (a `BetterTogether::Person`), which auto-fills `invitee_email` and `locale` from the person record.
51+
- Delivery: Noticed notification to the user (`EventInvitationNotifier`) and optional email via `EventInvitationsMailer`.
52+
- Invite by email:
53+
- Provide `invitee_email` and target `locale`.
54+
- Delivery: Email sent to the external address via `EventInvitationsMailer`.
55+
56+
Simple throttling prevents resending an invitation more than once within 15 minutes (`last_sent` timestamp check). Resend is supported for pending invitations.
57+
58+
### Controller Endpoints (Organizer/Host)
59+
60+
- `POST /events/:event_id/invitations` (create):
61+
- Authorization: `EventInvitationPolicy#create?` (organizer/host scope).
62+
- Parameters: `invitee_id` or `invitee_email`, optional `valid_from`, `valid_until`, `locale`, `role_id`.
63+
- Behavior: builds `EventInvitation`, sets `status: 'pending'`, `inviter`, and default `valid_from`.
64+
- Delivery: Noticed/email depending on invitation type; updates `last_sent`.
65+
66+
- `PUT /events/:event_id/invitations/:id/resend` (resend):
67+
- Authorization: same as create; respects resend throttling.
68+
69+
- `DELETE /events/:event_id/invitations/:id` (destroy):
70+
- Authorization: allowed for organizers/hosts.
71+
72+
- `GET /events/:event_id/invitations/available_people` (AJAX):
73+
- Returns up to 20 non-invited people with an email address; supports search term.
74+
- Uses policy scope over `Person` and joins on user/contact email.
75+
76+
## Public Invitation Review & Response
77+
78+
Public review and response routes are token-based:
79+
80+
- `GET /invitations/:token``InvitationsController#show`
81+
- `POST /invitations/:token/accept``#accept`
82+
- `POST /invitations/:token/decline``#decline`
83+
84+
Behavior:
85+
- Finds `Invitation.pending.not_expired` by token; returns 404 if missing.
86+
- For `accept`:
87+
- Requires authentication; if not signed in, stores token in session and redirects to sign-in/registration based on `invitee_email` lookup.
88+
- If `invitee` is bound, enforces that the logged-in person matches; otherwise, binds the invitation to the current person.
89+
- Calls `accept!` if available (for `EventInvitation`) or sets status to `accepted`.
90+
- Redirects to the invitable (event) after acceptance.
91+
- For `decline`:
92+
- Calls `decline!` (or sets status) and redirects to event if available, else home.
93+
94+
## Access Control & Privacy with Invitation Tokens
95+
96+
Invitation tokens grant limited access only to the specific event to which they belong. The platform may still require login for other content.
97+
98+
Key elements:
99+
100+
- `EventsController#check_platform_privacy` augmentation:
101+
- For private platforms and unauthenticated users, extracts an `invitation_token` from params/session.
102+
- Validates token for an `EventInvitation` that matches the requested event.
103+
- On valid, stores token and locale in session and allows access to the event page.
104+
- On invalid/expired, redirects to sign-in.
105+
106+
- `EventPolicy#show?`:
107+
- Permits if event is public and scheduled, or if creator/manager/host member, or if the current person holds an invitation, or if there is a valid invitation token.
108+
109+
- `ApplicationPolicy::Scope` for events:
110+
- Includes events visible through valid `invitation_token` in the scope.
111+
112+
### Token Access Flow Diagram
113+
114+
```mermaid
115+
%% See separate Mermaid source file for editing: docs/diagrams/source/events_access_via_invitation_token.mmd
116+
```
117+
118+
**Diagram Files:**
119+
- 📊 Mermaid Source: ../diagrams/source/events_access_via_invitation_token.mmd
120+
- 🖼️ PNG Export: ../diagrams/exports/png/events_access_via_invitation_token.png
121+
- 🎯 SVG Export: ../diagrams/exports/svg/events_access_via_invitation_token.svg
122+
123+
## Notifications
124+
125+
- `BetterTogether::EventInvitationNotifier` (Noticed):
126+
- Channels: ActionCable (in-app) and Email (`EventInvitationsMailer`).
127+
- Email uses parameterized mailer with `invitation` and `invitable` context.
128+
- Message includes localized title/body and the invitation review URL.
129+
130+
- `BetterTogether::EventInvitationsMailer`:
131+
- Sends to `invitee_email` using the invitation’s `locale`.
132+
- Subject includes event name with localized fallback.
133+
134+
## RSVP (Attendance)
135+
136+
The RSVP system is centered on `EventAttendance` and is managed through member routes on the `Event` resource:
137+
138+
- `POST /events/:id/rsvp_interested`
139+
- `POST /events/:id/rsvp_going`
140+
- `DELETE /events/:id/rsvp_cancel`
141+
142+
Constraints and behavior:
143+
- Only available when the event is scheduled (has `starts_at`).
144+
- Requires authentication for all RSVP actions.
145+
- Authorization uses `EventPolicy#show?` followed by `EventAttendancePolicy` for create/update.
146+
- On `going`, a calendar entry is created; on cancel or switching away from `going`, the entry is removed.
147+
148+
Existing diagrams cover the RSVP journey and reminder scheduling:
149+
- RSVP Flow: ../diagrams/source/events_rsvp_flow.mmd
150+
- Reminder Timeline: ../diagrams/source/events_reminders_timeline.mmd
151+
152+
## Organizer UI
153+
154+
- Invitations Panel on Event Show:
155+
- Tabs include Attendees and Invitations.
156+
- Invite by Member (`invitee_id`) or by Email (`invitee_email`).
157+
- View invitations table with type (member/email), status, resend/delete actions.
158+
- Search available people endpoint to prevent re-inviting and to filter by email presence.
159+
160+
## Security & Validation
161+
162+
- String enums: all statuses are strings for human-readable DB contents.
163+
- Duplicate protection: event invitation uniqueness enforced for both `invitee` and `invitee_email` while status is pending/accepted.
164+
- Privacy: invitation tokens scoped to a single event; do not grant broad platform access.
165+
- Session token storage: invitation token and locale persisted for acceptance and consistent access; tokens expire per validity window.
166+
- Safe dynamic resolution: no unsafe constantization of user input; controllers use allow-lists for host assignment and standard strong parameters.
167+
- Authorization: `Pundit` policies on events, attendances, and invitations.
168+
169+
## Performance Considerations
170+
171+
- Organizer views preload invitations, invitees, and inviters to minimize N+1 queries.
172+
- Event show preloads hosts, categories, attendances, translations, and cover image attachment.
173+
- RSVP and invitation actions redirect back to the event to keep interaction snappy.
174+
- Noticed notifications and email delivery run asynchronously.
175+
176+
## Troubleshooting
177+
178+
- Invitation token shows 404 on private platform:
179+
- Ensure the token matches the requested event and is still pending/not expired.
180+
- Verify session is storing `event_invitation_token` and that controller privacy check is triggered.
181+
182+
- Invitee required error:
183+
- Provide either `invitee_id` (member) or `invitee_email` (external) — one must be present.
184+
185+
- Duplicate invitation error:
186+
- An outstanding pending/accepted invitation already exists for that person or email.
187+
188+
- RSVP not available:
189+
- Event must be scheduled (`starts_at` present). Draft events do not accept RSVPs.
190+
191+
- Calendar entry not created:
192+
- Calendar entries are only created for `going` status; ensure the person has a primary calendar.
193+
194+
## Related Files (Code Pointers)
195+
196+
- Models:
197+
- `app/models/better_together/invitation.rb`
198+
- `app/models/better_together/event_invitation.rb`
199+
- `app/models/better_together/event_attendance.rb`
200+
201+
- Controllers:
202+
- `app/controllers/better_together/events/invitations_controller.rb`
203+
- `app/controllers/better_together/invitations_controller.rb`
204+
- `app/controllers/better_together/events_controller.rb`
205+
206+
- Policies:
207+
- `app/policies/better_together/event_policy.rb`
208+
- `app/policies/better_together/event_invitation_policy.rb`
209+
- `app/policies/better_together/event_attendance_policy.rb`
210+
211+
- Notifications & Mailers:
212+
- `app/notifiers/better_together/event_invitation_notifier.rb`
213+
- `app/mailers/better_together/event_invitations_mailer.rb`
214+
215+
## Process Flow: Event Invitations
216+
217+
```mermaid
218+
%% See separate Mermaid source file for editing: docs/diagrams/source/events_invitations_flow.mmd
219+
```
220+
221+
**Diagram Files:**
222+
- 📊 Mermaid Source: ../diagrams/source/events_invitations_flow.mmd
223+
- 🖼️ PNG Export: ../diagrams/exports/png/events_invitations_flow.png
224+
- 🎯 SVG Export: ../diagrams/exports/svg/events_invitations_flow.svg
225+

docs/developers/systems/events_system.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
This document explains the Event model, how events are created and displayed, how visibility works, how calendars fit in, the comprehensive notification system for event reminders and updates, and the event hosting system.
44

5+
See also: [Event Invitations & Attendance](./event_invitations_and_attendance.md) for invitation tokens, delivery, and RSVP lifecycle details.
6+
57
## Database Schema
68

79
The Events & Calendars domain consists of five primary tables plus standard shared tables (translations, ActionText, etc.). All Better Together tables are created via `create_bt_table`, which adds `id: :uuid`, `lock_version`, and `timestamps` automatically.
69.4 KB
Loading
81.4 KB
Loading
146 KB
Loading

docs/diagrams/exports/svg/events_access_via_invitation_token.svg

Lines changed: 1 addition & 0 deletions
Loading

docs/diagrams/exports/svg/events_invitations_flow.svg

Lines changed: 1 addition & 0 deletions
Loading

docs/diagrams/exports/svg/events_invitations_schema_erd.svg

Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
sequenceDiagram
2+
participant U as Unauthenticated User
3+
participant EC as EventsController
4+
participant P as EventPolicy/Scope
5+
participant DB as DB
6+
7+
U->>EC: GET /:locale/bt/events/:id?invitation_token=TOKEN
8+
Note right of EC: check_platform_privacy override
9+
EC->>DB: Find Event by slug
10+
EC->>DB: Find EventInvitation by token
11+
alt invitation present & pending & not expired
12+
EC->>EC: store token + locale in session
13+
EC->>P: authorize show? (valid_invitation_token?)
14+
P-->>EC: permitted
15+
EC-->>U: render event show
16+
else invalid/expired token
17+
EC-->>U: redirect to sign-in
18+
end
19+

0 commit comments

Comments
 (0)