Skip to content

Commit 64930f0

Browse files
committed
Add registration backbone for events, payments, and activity
1 parent 35b0637 commit 64930f0

File tree

6 files changed

+1265
-6
lines changed

6 files changed

+1265
-6
lines changed

README.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ Create a single source of truth for operational data that keeps the team synchro
99
- Operations planning for seasons, events, innhopps, manifests, and landing zones.
1010
- Participant roster and event assignments, including crew roles per manifest.
1111
- Logistics tracking for transports, vehicles, accommodations, meals, and ad-hoc logistics items.
12+
- Registration backbone for participant-event lifecycle tracking, payment obligations, and activity history.
1213
- Safety and compliance fields on events/innhopps (NOTAM, risk assessment, safety precautions).
1314
- RBAC-backed login sessions (OIDC) with seeded roles for core operational duties.
1415

1516
## Not yet implemented
16-
- Payments, waivers, certifications, and health declarations.
17-
- Automated notifications, check-in flows, or public driver route pages.
17+
- Public registration pages, automated notifications, and campaign email sending.
18+
- Payment provider integrations, waivers, certifications, and health declarations.
19+
- Check-in flows or public driver route pages.
1820
- Analytics dashboards beyond raw data access.
1921

2022
## User roles
@@ -34,13 +36,14 @@ RBAC ensures each user only sees the modules and actions needed for their duties
3436

3537
## Workflows
3638
1. **Event planning**: Create seasons/events, define innhopps with landing and safety details, and publish manifests.
37-
2. **Participant coordination**: Maintain the roster, attach participants to events, and assign crew roles per manifest.
39+
2. **Participant coordination**: Maintain the roster, attach participants to events, assign crew roles per manifest, and track registrations with status, notes, and payment records.
3840
3. **Logistics tracking**: Capture transports, vehicles, accommodations, meals, and other logistics items per event.
3941

4042
## What you can do today
4143
- Build seasons and events, including detailed innhopp plans with landing areas, NOTAM notes, risk mitigation, and hospital/boat coverage metadata.
4244
- Register airfields, attach them to events, and manage manifests with capacity, staff slots, and participant assignments.
4345
- Maintain a participant roster (roles, experience, contacts), add people to events/manifests, and track crew roles such as Jump Master/Leader, Ground Crew, Driver, and Packer.
46+
- Create event registrations for participants, record deposit/balance payment entries, track registration status, and append internal activity notes.
4447
- Coordinate transports, vehicles, accommodations, meals, and other logistics items per operation.
4548
- Authenticate via OIDC (authorization code flow), persist sessions in secure cookies, and enforce role-based permissions seeded on startup (Admin, Staff, Jump Master, Jump Leader, Ground Crew, Driver, Packer, Participant). Set `DEV_ALLOW_ALL=true` to bypass auth locally.
4649
- Use the frontend pages for login, events, manifests, participants, logistics, seasons, innhopp details, and airfield details (see `frontend/src/pages/` and `frontend/src/components/Layout.tsx` for the routes).
@@ -109,5 +112,5 @@ Frontend environment variables:
109112
| `VITE_GOOGLE_MAPS_API_KEY` | Google Maps API key for route duration features | placeholder value |
110113

111114
## API and UI references
112-
- Backend endpoints for seasons, events, innhopps, manifests, airfields, participants, crew assignments, logistics transports, and auth are documented in `backend/README.md`.
115+
- Backend endpoints for seasons, events, innhopps, manifests, airfields, participants, registrations, crew assignments, logistics transports, and auth are documented in `backend/README.md`.
113116
- Frontend routes include `/login`, `/events`, `/events/:eventId`, `/events/:eventId/innhopps/:innhoppId`, `/manifests`, `/manifests/:manifestId`, `/participants`, `/participants/:participantId`, `/logistics`, `/airfields/:airfieldId`, plus creation flows for seasons, events, manifests, and participants.

backend/README.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ This Go service provides the core REST API for the Innhopp Central platform. It
55
## Features
66

77
- Modular HTTP routing for authentication, event operations, participant management, crew RBAC, and logistics.
8-
- Automatic bootstrapping of the core PostgreSQL schema for seasons, events, manifests, participant profiles, crew assignments, and gear assets.
9-
- JSON APIs for managing seasons/events/manifests, participant records, crew assignments, and gear tracking.
8+
- Automatic bootstrapping of the core PostgreSQL schema for seasons, events, manifests, participant profiles, registrations, payment records, crew assignments, and gear assets.
9+
- JSON APIs for managing seasons/events/manifests, participant records, registration lifecycles, payment/activity records, crew assignments, and gear tracking.
1010
- Health check endpoint for uptime monitoring and Chi middleware for structured logging and request tracing.
1111

1212
## Requirements
@@ -35,6 +35,9 @@ On startup the server creates these tables if they do not already exist:
3535
- `event_innhopps` – ordered jump sequences planned within an event.
3636
- `manifests` – scheduled aircraft loads for an event.
3737
- `participant_profiles` – canonical roster of all flyers and staff.
38+
- `event_registrations` – participant-to-event lifecycle records with deadlines, notes, and ownership.
39+
- `registration_payments` – ledger entries for deposit, balance, refund, and manual adjustments per registration.
40+
- `registration_activity` – internal timeline entries attached to a registration.
3841
- `crew_assignments` – role assignments for a participant on a manifest.
3942
- `gear_assets` – tracked gear inventory with inspection status.
4043

@@ -94,6 +97,14 @@ On startup the server creates these tables if they do not already exist:
9497
| GET | `/api/events/manifests/{id}` | Retrieve a manifest |
9598
| GET | `/api/participants/profiles` | List participant profiles |
9699
| POST | `/api/participants/profiles` | Create a participant profile |
100+
| GET | `/api/registrations/events/{eventID}` | List registrations for an event |
101+
| POST | `/api/registrations/events/{eventID}` | Create a registration for an event |
102+
| GET | `/api/registrations/{registrationID}` | Retrieve one registration with payments and activity |
103+
| PUT | `/api/registrations/{registrationID}` | Update registration metadata |
104+
| POST | `/api/registrations/{registrationID}/status` | Transition a registration status |
105+
| POST | `/api/registrations/{registrationID}/payments` | Create a payment ledger row |
106+
| PUT | `/api/registrations/payments/{paymentID}` | Update a payment ledger row |
107+
| POST | `/api/registrations/{registrationID}/activity` | Append an internal activity entry |
97108
| GET | `/api/rbac/crew-assignments` | List crew assignments |
98109
| POST | `/api/rbac/crew-assignments` | Create a crew assignment |
99110
| GET | `/api/logistics/gear-assets` | List gear assets |
@@ -104,6 +115,7 @@ On startup the server creates these tables if they do not already exist:
104115
- All timestamps in request payloads must be RFC3339 strings except for season dates which use `YYYY-MM-DD`.
105116
- Endpoints respond with JSON and enforce strict payload validation (unknown fields are rejected).
106117
- Foreign key constraints ensure referenced seasons, events, manifests, and participants must already exist.
118+
- The registration backbone enforces one active registration per participant per event; cancelled or expired registrations can be recreated.
107119

108120
## Testing
109121

backend/main.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"github.com/innhopp/central/backend/logistics"
2020
"github.com/innhopp/central/backend/participants"
2121
"github.com/innhopp/central/backend/rbac"
22+
"github.com/innhopp/central/backend/registrations"
2223
)
2324

2425
func main() {
@@ -120,6 +121,7 @@ func main() {
120121
router.Mount("/api/auth", authHandler.Routes())
121122
router.Mount("/api/events", events.NewHandler(pool).Routes(enforcer))
122123
router.Mount("/api/participants", participants.NewHandler(pool).Routes(enforcer))
124+
router.Mount("/api/registrations", registrations.NewHandler(pool).Routes(enforcer))
123125
router.Mount("/api/rbac", rbac.NewHandler(pool).Routes(enforcer))
124126
router.Mount("/api/logistics", logistics.NewHandler(pool).Routes(enforcer))
125127
router.Mount("/api/innhopps", innhopps.NewHandler(pool).Routes(enforcer))
@@ -509,6 +511,84 @@ func ensureSchema(ctx context.Context, pool *pgxpool.Pool) error {
509511
UNIQUE (account_id, role_name)
510512
)`,
511513
`ALTER TABLE participant_profiles ADD COLUMN IF NOT EXISTS account_id INTEGER UNIQUE REFERENCES accounts(id) ON DELETE SET NULL`,
514+
`CREATE TABLE IF NOT EXISTS event_registrations (
515+
id SERIAL PRIMARY KEY,
516+
event_id INTEGER NOT NULL REFERENCES events(id) ON DELETE CASCADE,
517+
participant_id INTEGER NOT NULL REFERENCES participant_profiles(id) ON DELETE CASCADE,
518+
status TEXT NOT NULL DEFAULT 'deposit_pending',
519+
source TEXT,
520+
registered_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
521+
deposit_due_at TIMESTAMPTZ,
522+
deposit_paid_at TIMESTAMPTZ,
523+
balance_due_at TIMESTAMPTZ,
524+
balance_paid_at TIMESTAMPTZ,
525+
cancelled_at TIMESTAMPTZ,
526+
expired_at TIMESTAMPTZ,
527+
waitlist_position INTEGER,
528+
staff_owner_account_id INTEGER REFERENCES accounts(id) ON DELETE SET NULL,
529+
tags TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[],
530+
internal_notes TEXT,
531+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
532+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
533+
)`,
534+
`ALTER TABLE event_registrations ADD COLUMN IF NOT EXISTS status TEXT NOT NULL DEFAULT 'deposit_pending'`,
535+
`ALTER TABLE event_registrations ADD COLUMN IF NOT EXISTS source TEXT`,
536+
`ALTER TABLE event_registrations ADD COLUMN IF NOT EXISTS registered_at TIMESTAMPTZ NOT NULL DEFAULT NOW()`,
537+
`ALTER TABLE event_registrations ADD COLUMN IF NOT EXISTS deposit_due_at TIMESTAMPTZ`,
538+
`ALTER TABLE event_registrations ADD COLUMN IF NOT EXISTS deposit_paid_at TIMESTAMPTZ`,
539+
`ALTER TABLE event_registrations ADD COLUMN IF NOT EXISTS balance_due_at TIMESTAMPTZ`,
540+
`ALTER TABLE event_registrations ADD COLUMN IF NOT EXISTS balance_paid_at TIMESTAMPTZ`,
541+
`ALTER TABLE event_registrations ADD COLUMN IF NOT EXISTS cancelled_at TIMESTAMPTZ`,
542+
`ALTER TABLE event_registrations ADD COLUMN IF NOT EXISTS expired_at TIMESTAMPTZ`,
543+
`ALTER TABLE event_registrations ADD COLUMN IF NOT EXISTS waitlist_position INTEGER`,
544+
`ALTER TABLE event_registrations ADD COLUMN IF NOT EXISTS staff_owner_account_id INTEGER REFERENCES accounts(id) ON DELETE SET NULL`,
545+
`ALTER TABLE event_registrations ADD COLUMN IF NOT EXISTS tags TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[]`,
546+
`ALTER TABLE event_registrations ADD COLUMN IF NOT EXISTS internal_notes TEXT`,
547+
`ALTER TABLE event_registrations ADD COLUMN IF NOT EXISTS created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()`,
548+
`ALTER TABLE event_registrations ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()`,
549+
`CREATE UNIQUE INDEX IF NOT EXISTS event_registrations_active_participant_idx
550+
ON event_registrations (event_id, participant_id)
551+
WHERE cancelled_at IS NULL AND expired_at IS NULL`,
552+
`CREATE TABLE IF NOT EXISTS registration_payments (
553+
id SERIAL PRIMARY KEY,
554+
registration_id INTEGER NOT NULL REFERENCES event_registrations(id) ON DELETE CASCADE,
555+
kind TEXT NOT NULL,
556+
amount NUMERIC(12,2) NOT NULL DEFAULT 0,
557+
currency TEXT NOT NULL DEFAULT 'EUR',
558+
status TEXT NOT NULL DEFAULT 'pending',
559+
due_at TIMESTAMPTZ,
560+
paid_at TIMESTAMPTZ,
561+
provider TEXT,
562+
provider_ref TEXT,
563+
recorded_by_account_id INTEGER REFERENCES accounts(id) ON DELETE SET NULL,
564+
notes TEXT,
565+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
566+
)`,
567+
`ALTER TABLE registration_payments ADD COLUMN IF NOT EXISTS kind TEXT NOT NULL DEFAULT 'deposit'`,
568+
`ALTER TABLE registration_payments ADD COLUMN IF NOT EXISTS amount NUMERIC(12,2) NOT NULL DEFAULT 0`,
569+
`ALTER TABLE registration_payments ADD COLUMN IF NOT EXISTS currency TEXT NOT NULL DEFAULT 'EUR'`,
570+
`ALTER TABLE registration_payments ADD COLUMN IF NOT EXISTS status TEXT NOT NULL DEFAULT 'pending'`,
571+
`ALTER TABLE registration_payments ADD COLUMN IF NOT EXISTS due_at TIMESTAMPTZ`,
572+
`ALTER TABLE registration_payments ADD COLUMN IF NOT EXISTS paid_at TIMESTAMPTZ`,
573+
`ALTER TABLE registration_payments ADD COLUMN IF NOT EXISTS provider TEXT`,
574+
`ALTER TABLE registration_payments ADD COLUMN IF NOT EXISTS provider_ref TEXT`,
575+
`ALTER TABLE registration_payments ADD COLUMN IF NOT EXISTS recorded_by_account_id INTEGER REFERENCES accounts(id) ON DELETE SET NULL`,
576+
`ALTER TABLE registration_payments ADD COLUMN IF NOT EXISTS notes TEXT`,
577+
`ALTER TABLE registration_payments ADD COLUMN IF NOT EXISTS created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()`,
578+
`CREATE TABLE IF NOT EXISTS registration_activity (
579+
id SERIAL PRIMARY KEY,
580+
registration_id INTEGER NOT NULL REFERENCES event_registrations(id) ON DELETE CASCADE,
581+
type TEXT NOT NULL,
582+
summary TEXT NOT NULL,
583+
payload JSONB NOT NULL DEFAULT '{}'::jsonb,
584+
created_by_account_id INTEGER REFERENCES accounts(id) ON DELETE SET NULL,
585+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
586+
)`,
587+
`ALTER TABLE registration_activity ADD COLUMN IF NOT EXISTS type TEXT NOT NULL DEFAULT 'note'`,
588+
`ALTER TABLE registration_activity ADD COLUMN IF NOT EXISTS summary TEXT NOT NULL DEFAULT ''`,
589+
`ALTER TABLE registration_activity ADD COLUMN IF NOT EXISTS payload JSONB NOT NULL DEFAULT '{}'::jsonb`,
590+
`ALTER TABLE registration_activity ADD COLUMN IF NOT EXISTS created_by_account_id INTEGER REFERENCES accounts(id) ON DELETE SET NULL`,
591+
`ALTER TABLE registration_activity ADD COLUMN IF NOT EXISTS created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()`,
512592
}
513593

514594
for _, stmt := range stmts {

backend/rbac/roles.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ const (
2222
PermissionManageSeasons Permission = "seasons:manage"
2323
PermissionViewEvents Permission = "events:view"
2424
PermissionManageEvents Permission = "events:manage"
25+
PermissionViewRegistrations Permission = "registrations:view"
26+
PermissionManageRegistrations Permission = "registrations:manage"
2527
PermissionViewManifests Permission = "manifests:view"
2628
PermissionManageManifests Permission = "manifests:manage"
2729
PermissionViewParticipants Permission = "participants:view"
@@ -65,6 +67,14 @@ var RoleMatrix = map[Permission][]Role{
6567
RoleAdmin,
6668
RoleStaff,
6769
},
70+
PermissionViewRegistrations: {
71+
RoleAdmin,
72+
RoleStaff,
73+
},
74+
PermissionManageRegistrations: {
75+
RoleAdmin,
76+
RoleStaff,
77+
},
6878
PermissionViewManifests: {
6979
RoleAdmin,
7080
RoleStaff,

0 commit comments

Comments
 (0)