Campaign Keeper is a lightweight campaign journal for tabletop RPGs. It helps a DM keep session notes, recap links, NPCs, players, locations, open threads, and post-session feedback in one place.
- Next.js 16
- React 19
- Firebase Auth
- Firestore via
firebase-admin - Private S3 bucket for NPC and player portraits
- Tailwind + shadcn/ui primitives
- Magic-link sign-in with Firebase Auth
- Campaign dashboard with sessions, NPCs, players, locations, and open threads
- Public player recap links with rotatable/disableable share tokens
- DM-only notes and reflections kept separate from player-safe recap content
- NPC and player portraits stored in private S3 and served through authenticated app routes
- Session feedback form on shared recap pages
Client-side Firebase config:
NEXT_PUBLIC_FIREBASE_API_KEY=
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=
NEXT_PUBLIC_FIREBASE_PROJECT_ID=
NEXT_PUBLIC_APP_URL=http://localhost:3000
NEXT_PUBLIC_FIREBASE_EMULATOR=falseServer-side config:
FIREBASE_SERVICE_ACCOUNT=
CAMPAIGN_KEEPER_AWS_REGION=
CAMPAIGN_KEEPER_AWS_ACCESS_KEY_ID=
CAMPAIGN_KEEPER_AWS_SECRET_ACCESS_KEY=
CAMPAIGN_KEEPER_S3_BUCKET=Optional S3 settings:
CAMPAIGN_KEEPER_AWS_SESSION_TOKEN=
CAMPAIGN_KEEPER_S3_ENDPOINT=
CAMPAIGN_KEEPER_S3_FORCE_PATH_STYLE=falseNotes:
FIREBASE_SERVICE_ACCOUNTshould be the full service account JSON compressed into a single line.- The app accepts the project-scoped
CAMPAIGN_KEEPER_*S3 env vars and falls back to standard AWS names locally if needed. - Portrait uploads are written server-side to S3 and then served back through app routes, so the bucket should stay private.
DISABLE_AUTH=trueis only honored outside production and is meant for local development only.
Use /Users/johnmunn/Documents/projects/dm-session-manager/.env.example as the starting template.
Install dependencies:
npm installStart the app:
npm run devOpen http://localhost:3000.
npm run dev
npm run build
npm run start
npm run lint
npm run testnpm run test uses bun test, so Bun needs to be installed if you want to run the test suite.
Portraits are stored under private S3 object keys like:
portraits/player/<playerId>/<uuid>.jpgportraits/npc/<npcId>/<uuid>.jpg
Recommended bucket setup:
- block all public access
- bucket owner enforced
- default encryption enabled
- IAM limited to
s3:GetObject,s3:PutObject, ands3:DeleteObjectonportraits/*
Before deploying:
- Set all production env vars.
- Make sure
DISABLE_AUTHis unset. - Verify Firebase Auth and Firestore access are working in the deployed environment.
- Verify portrait upload, replacement, and deletion against your S3 bucket.
- Smoke-test public recap links, link rotation/disable, and the feedback form.
This project was built as a weekend challenge piece. The current implementation is intended to be solid and demoable, not fully production-hardened. The main follow-on improvements would be image normalization/moderation, stronger public-form abuse controls, and broader automated test coverage.