Brainrush
Multiplayer quiz game: a Flutter app for iOS and Android, a Next.js website where people play in the browser, an Admin panel and the API, plus a background worker. You add your own keys; everything else is ready.
1. Welcome
Thank you for buying Brainrush. This guide is written for beginners: follow the chapters in order and you will have the server, the website and the app running under your own name and keys.
What is in the download
| Folder | What it is |
|---|---|
brainrush_mobile_flutter/ | The mobile app (Flutter, iOS and Android): levels, the daily quiz, live battles, contests, leagues, exams, player-made quizzes, coin store and ads. |
brainrush_web_nextjs/ | One Next.js app that is the website (play in the browser), the Admin panel (/admin), the API the app uses (/api/v1) and the background worker (pnpm worker: battle clock, contest payouts, weekly leagues, daily quizzes, pushes). |
deploy/ | The Docker stack: website + worker + PostgreSQL database + MinIO file storage (+ optional automatic HTTPS), started with one command. |
tools/rename.mjs | Rebrands the whole kit (name, app id, colour, font, icon) in one command. |
docs/ | This documentation (HTML and PDF). |
How the parts fit
The mobile app and the website both talk to the same server (the Next.js app). All data lives in your PostgreSQL database: players, questions, games, coins, leagues. Question pictures, sounds and avatars live in S3-compatible storage (MinIO in the Docker stack, or Amazon S3, Cloudflare R2…). Firebase is used only for sign-in and push notifications.
2. Requirements
To run the server
- A Linux server (VPS) with at least 2 CPU cores, 4 GB RAM and 20 GB disk. Ubuntu 24.04 is used in this guide.
- Docker with the Compose plugin (install guide).
- A domain name, for example
your-domain.com, with a DNS record pointing at the server.
To build the mobile app
- Flutter 3.44 or newer (Dart 3.12; install), Android Studio for Android, a Mac with Xcode for iOS.
- Node.js 22 or newer for the rename tool, and pnpm (
npm i -g pnpm) if you run the website without Docker.
Accounts you will create (all have free tiers)
- Firebase (required: sign-in and push).
- Optional, when you want them: RevenueCat (in-app purchases), Google AdMob (ads), an AI key from Anthropic, OpenAI or Google Gemini (question generator), any SMTP email provider.
3. Quick start (Docker)
This gets the whole server running on your VPS in about 15 minutes. Sign-in needs the Firebase keys from chapter 4; you can do this chapter first and add them after.
- Copy the kit to the server, for example with
scp brainrush-1.0.0.zip root@YOUR_SERVER_IP:, then on the server:apt install -y unzip unzip brainrush-1.0.0.zip cd brainrush/deploy cp .env.example .env nano .env - In
.env, fill at least these values:Key What to put POSTGRES_PASSWORDA long random password, letters and digits only (run openssl rand -hex 24to make one).S3_SECRET_ACCESS_KEYAnother random value (at least 8 characters) for the built-in file storage. APP_URLYour website address, e.g. https://your-domain.com(for a first test:http://YOUR_SERVER_IP:3000).NEXT_PUBLIC_FIREBASE_*,FIREBASE_*From chapter 4. - Start everything:
The first build takes 5–10 minutes. Each start creates or updates the database tables, then loads the badges and coin packs.docker compose up -d - Open
APP_URLin a browser. The first visit opens the install wizard: your admin account, the app name and the sample quizzes (chapter 7).APP_URL/api/v1/healthshows{"ok":true,…}. - Put it on your domain with HTTPS (chapter 5).
WEB_PORT in .env..env? Run docker compose up -d again. Changed code? Run docker compose up -d --build.4. Firebase setup
Firebase handles sign-in (guest, Google, Apple, email) and push notifications. Your data stays in your own database.
- Go to the Firebase console → Add project. Give it your app name.
- Build → Authentication → Get started → Sign-in method. Turn on: Anonymous (guest play), Google, Email/Password (players and staff) and Apple (see chapter 15).
- Authentication → Settings → Authorized domains: add
your-domain.com. - Project settings (gear) → General → Your apps → Add app → Web (name it "Website"). Copy the values from the
firebaseConfigshown intodeploy/.env:
The website reads these when it starts, so a restart (NEXT_PUBLIC_FIREBASE_API_KEY="…apiKey…" NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN="your-project.firebaseapp.com" NEXT_PUBLIC_FIREBASE_PROJECT_ID="your-project" NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET="your-project.firebasestorage.app" NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID="…" NEXT_PUBLIC_FIREBASE_APP_ID="1:…:web:…"docker compose up -d) is enough after a change. - Project settings → Service accounts → Generate new private key. A JSON file downloads. Copy three values from it into
deploy/.env:
Keep theFIREBASE_PROJECT_ID="your-project" FIREBASE_CLIENT_EMAIL="firebase-adminsdk-xxxx@your-project.iam.gserviceaccount.com" FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIIE…\n-----END PRIVATE KEY-----\n"\nas they are in the file. Keep this file secret. - Add the mobile apps: Project settings → Your apps → Add app, once for Android and once for iOS, with your app id (for example
com.yourcompany.brainrush; see chapter 16 to change it first). Downloadgoogle-services.jsonandGoogleService-Info.plist: you only need to copy values out of them (the app does not use the files themselves). Or runflutterfire configure, which does the same and prints the values. - Put the values in
brainrush_mobile_flutter/.env:Key in the app's .env Where to find it FIREBASE_PROJECT_ID,FIREBASE_MESSAGING_SENDER_ID,FIREBASE_STORAGE_BUCKETProject settings → General (project id, project number, storage bucket) FIREBASE_ANDROID_API_KEY,FIREBASE_ANDROID_APP_IDgoogle-services.json:api_key.current_keyandmobilesdk_app_idFIREBASE_IOS_API_KEY,FIREBASE_IOS_APP_ID,FIREBASE_IOS_BUNDLE_ID,FIREBASE_IOS_CLIENT_IDGoogleService-Info.plist:API_KEY,GOOGLE_APP_ID,BUNDLE_ID,CLIENT_IDGOOGLE_SERVER_CLIENT_IDAuthentication → Sign-in method → Google → Web SDK configuration → Web client ID (Google sign-in on Android) - iOS: copy
ios/Flutter/Keys.xcconfig.exampletoios/Flutter/Keys.xcconfig(the download already has one) and setGOOGLE_REVERSED_CLIENT_ID(theREVERSED_CLIENT_IDfromGoogleService-Info.plist) andFIREBASE_ENCODED_APP_ID(yourGOOGLE_APP_IDwith the colons turned into dashes, e.g.1:123:ios:abc→app-1-123-ios-abc). - Android Google sign-in needs your signing key fingerprints: run
cd brainrush_mobile_flutter/android && ./gradlew signingReportand add the SHA-1 and SHA-256 under Project settings → Your apps → Android → Add fingerprint. Add the fingerprints of your upload key and of Google Play's app signing key too when you publish.
5. Your domain and HTTPS
Easiest: the stack can get a free certificate for you with Caddy. Point your domain's DNS at the server, open ports 80 and 443, then in deploy/.env set DOMAIN="your-domain.com" and APP_URL="https://your-domain.com", and start with the https profile:
docker compose --profile https up -d
Already use Nginx, Traefik or Caddy on the server? Point your-domain.com at port 3000 (WEB_PORT), set APP_URL, run docker compose up -d, and leave the https profile off. Close port 3000 in your firewall once the proxy works (ufw allow 22,80,443/tcp && ufw enable).
Backups
cd brainrush/deploy
docker compose exec postgres pg_dump -U brainrush brainrush | gzip > backup-$(date +%F).sql.gz
Run it daily with cron and copy the files off the server. Uploaded pictures and sounds are in the minio-data Docker volume; back it up too, or use a cloud bucket (next chapter).
6. Other hosting
Website on Vercel (or any Node host)
- Create a PostgreSQL database (Neon, Supabase, Railway, or your own) and copy its connection string.
- Import
brainrush_web_nextjsinto Vercel (Root directory:brainrush_web_nextjs). Add the keys ofbrainrush_web_nextjs/.env.exampleunder Settings → Environment Variables, withDATABASE_URLset to your database and theS3_*keys set to a cloud bucket. - Create the tables once from your computer: in
brainrush_web_nextjs, put the sameDATABASE_URLin.env, then runpnpm install,pnpm prisma:migrate:deployandpnpm prisma:seed --base. Then open your site: the install wizard does the rest. - The worker (battle clock, contest payouts, leagues, daily quizzes, pushes) runs all the time, so it cannot run on Vercel. Run it on any small server:
cd brainrush_web_nextjs && pnpm install && pnpm workerwith the same.env(use a process manager such as pm2 or systemd), or with Docker usingdeploy/docker-compose.yml'sworkerservice.
Cloud storage instead of MinIO
Any S3-compatible bucket works. Set S3_ENDPOINT (empty for Amazon S3; for Cloudflare R2 https://<account>.r2.cloudflarestorage.com), S3_REGION, S3_BUCKET, S3_ACCESS_KEY_ID and S3_SECRET_ACCESS_KEY. Keep the bucket private: files are served through your API. Without any S3_* key (outside Docker), uploads go to a local uploads/ folder.
7. First admin and sample quizzes
The first visit to your site opens the install wizard (/install). It asks for:
- the app name (set your support email later in Admin → Settings),
- your name, email and password: this makes your super admin account (it creates the Firebase email account for you),
- whether to load the sample quizzes: 14 categories, about 830 questions in all six question types, daily quizzes, articles and badges. Keep them while you test; you can delete or edit any of it later.
Then sign in at https://your-domain.com/admin. Invite more staff from Admin → Roles (roles: super admin, content editor, moderator, finance). The wizard only runs while no super admin exists.
docker compose down -v deletes the database and files (everything), then docker compose up -d and open the site again for the wizard.8. Setup check
Open Admin → Setup check: it lists every key, says which are missing, and tests each connection (database, storage, Firebase sign-in, push, purchases, email, AI, worker) with a clear message. The same check runs in a terminal:
docker compose run --rm migrate pnpm run doctor # Docker
cd brainrush_web_nextjs && pnpm run doctor # without Docker
9. Run the mobile app
cd brainrush_mobile_flutter. The download has a.envwith empty values (a copy of.env.example). Set:API_BASE_URL=https://your-domain.com APP_NAME=Brainrush FIREBASE_… (from chapter 4)- Get the packages and run on a phone or emulator:
flutter pub get flutter run - Build for testing:
flutter build apk --release(Android) or openios/Runner.xcworkspacein Xcode (iOS).
http://10.0.2.2:3000 and the iOS simulator at http://localhost:3000. Release builds need an https address.The app reads every setting from .env: no keys live in Dart code. After changing .env, stop the app and run it again (hot reload does not reload .env). Without the Firebase values the app shows a setup screen that says what is missing.
10. In-app purchases (RevenueCat)
The app sells coin packs (consumables) and "Remove ads" (non-consumable) through the App Store and Google Play, with RevenueCat in between. The server is told about every purchase and adds the coins; the app never decides a balance itself.
- Create the products in App Store Connect and Google Play Console with these ids (or your own; the id of each pack is set in Admin → Coin Packs & IAP):
- Coins (consumable):
br.coins.500,br.coins.1200,br.coins.3000,br.coins.6500 - Remove ads (non-consumable):
br.removeads
- Coins (consumable):
- In RevenueCat: create a project, add your iOS and Android apps and import the products. The app buys products by id, so no offering or entitlement is needed.
- App keys: RevenueCat → Project → API keys → the public SDK keys. In
brainrush_mobile_flutter/.env:REVENUECAT_APPLE_KEYandREVENUECAT_GOOGLE_KEY. - Server keys in
deploy/.env:REVENUECAT_SECRET_KEY(a secret API key) andREVENUECAT_WEBHOOK_AUTH(any long random value). - RevenueCat → Integrations → Webhooks → add
https://your-domain.com/api/v1/webhooks/revenuecatwith the Authorization header value equal toREVENUECAT_WEBHOOK_AUTH.
REVENUECAT_SECRET_KEY is set, purchases are granted without payment and labelled DEMO, so you can try every flow. Setting the key switches to the stores; DEMO_PURCHASES=false forces store mode.The website does not sell coins: players buy them in the app, and the same balance shows on the website.
11. Push notifications
- Android works once Firebase is set up (chapter 4).
- iOS: in the Apple Developer site create an APNs key (Keys → +, Apple Push Notifications service), then upload it in Firebase → Project settings → Cloud Messaging → Apple app configuration.
- Every app joins the topic
brainrush_all, and the server also sends to each player's own devices (battle invites, contest results, streak reminders, league results). Send your own messages from Admin → Notifications, now or scheduled.
12. Ads (Google AdMob)
The app has three ad types: rewarded (watch an ad to earn coins), interstitial (between games) and banner. Players who bought "Remove ads" see no interstitials or banners.
- In AdMob, add your Android and iOS apps and create one ad unit of each type you want.
- In
brainrush_mobile_flutter/.envsetADMOB_ANDROID_APP_IDand the unit idsADMOB_ANDROID_REWARDED_ID,ADMOB_ANDROID_INTERSTITIAL_ID,ADMOB_ANDROID_BANNER_ID,ADMOB_IOS_REWARDED_ID,ADMOB_IOS_INTERSTITIAL_ID,ADMOB_IOS_BANNER_ID. - The iOS app id goes in
ios/Flutter/Keys.xcconfig:ADMOB_IOS_APP_ID=ca-app-pub-…~…. - Control ads in Admin → Ads: on or off, coins per rewarded ad, rewarded ads per day, an interstitial every N games, banners on or off.
Without your own ids the app shows Google's test ads, so the flows work while you build. Other networks join through AdMob mediation, set up in your AdMob account.
13. AI question generator
Admin → AI Generator writes new questions for any topic, level and question type; you review them before they go live. Set one of ANTHROPIC_API_KEY, OPENAI_API_KEY or GEMINI_API_KEY in deploy/.env, or paste a key in Admin → Settings → AI. Without a key the page says "add your key", and you can still write questions by hand or import a CSV.
14. Email
Staff invites and password resets are sent by email. Set SMTP_HOST, SMTP_PORT (587, or 465 with SMTP_SECURE=true), SMTP_USER, SMTP_PASS from any provider (Amazon SES, Postmark, Mailgun, Brevo, your host). Without SMTP_HOST, emails are written to the server log instead (docker compose logs web), which is handy while testing. Player password resets are sent by Firebase.
15. Sign-in methods
- Guest: Firebase Anonymous. Turn guest play on or off in Admin → Settings. A guest who signs in later keeps their coins and progress. The website's shared-quiz links use guest play for "Play in browser — no sign-up".
- Google: on in Firebase; Android needs the SHA fingerprints and
GOOGLE_SERVER_CLIENT_ID, iOS needsGOOGLE_REVERSED_CLIENT_ID(chapter 4). - Apple (required by Apple when you offer Google sign-in on iOS): Apple Developer → your app id → enable Sign in with Apple. In Firebase → Authentication → Apple, turn it on (for iOS only, the bundle id is enough).
- Email and password: players can create an account with email on the app and website; staff sign in at
/adminwith the account the wizard or an invite made.
DEMO_MODE empty on your real site. DEMO_MODE=true is for a public demo only: it adds one-click demo sign-in buttons and resets the data every night.16. Rebrand: name, app id, colour
One command changes the app name everywhere (app, website, admin, emails, push topic), the Android application id and iOS bundle id, the brand colour (with its lighter and darker shades), the website font and the icons:
cd brainrush
node tools/rename.mjs --name "QuizNova" --id com.mycompany.quiznova --color "#2563EB" --font "DM Sans" --icon my-icon-1024.png
Every option is optional, so you can run it again later for one change. Add --dry to see what would change. Afterwards:
cd brainrush_mobile_flutter
flutter pub get
dart run flutter_launcher_icons
dart run flutter_native_splash:create
then add the Firebase apps for the new app id (chapter 4) and rebuild the website (docker compose up -d --build). The folder names (brainrush_mobile_flutter and so on) stay as they are; people never see them. The app name shown inside the running app and website comes from Admin → Settings → App name too.
By hand: the app's colours are in brainrush_mobile_flutter/lib/theme/br_colors.dart (a light and a dark set) and the website's in brainrush_web_nextjs/src/app/globals.css (--br-primary and friends).
17. Logo, icon and fonts
- App icon: replace the 1024×1024 PNGs in
brainrush_mobile_flutter/assets/icon/:icon.png(full square, no transparency), and for Android's round and squircle iconsicon_foreground.png(your symbol on a transparent background, inside the middle two thirds) andicon_background.png. Then rundart run flutter_launcher_icons. - Logo in the app and launch screen:
assets/images/logo.png(512×512). The launch screen colour is underflutter_native_splash:inpubspec.yaml; rundart run flutter_native_splash:createafter a change. - Website icons: replace
brainrush_web_nextjs/src/app/icon.png(256×256) andapple-icon.png(180×180). - App fonts: Fredoka (titles and numbers) and Nunito (text). Put your
.ttffiles inassets/fonts/, list them underfonts:inpubspec.yaml, and change the family names inlib/theme/app_theme.dart. Check the font's licence allows apps. - Website fonts:
--fontin the rename tool changes the body font; titles are set inbrainrush_web_nextjs/src/app/layout.tsx(next/font/google).
18. Basic edits
| I want to change… | Where |
|---|---|
| Coins per right answer, level rewards, lifeline prices, streak freeze price, welcome and referral coins, questions per level, seconds per question, stars rules | Admin → Coin Economy |
| Coin packs, prices shown, store product ids | Admin → Coin Packs & IAP |
| Categories, subcategories, premium (coin-locked) categories | Admin → Categories |
| Questions | Admin → Questions (editor, CSV import), Admin → AI Generator |
| Today's and upcoming daily quizzes | Admin → Daily Quiz (the worker fills the next 7 days on its own) |
| Contests, entry fees and prize split | Admin → Contests |
| Exams for a class or a company | Admin → Exams |
| Battle rules (entry fees, bot wait, room size) | Admin → Battles |
| League tiers, players per league, how many move up and down, board rewards | Admin → Leagues |
| Badges | Admin → Badges |
| Fun & Learn articles and their quizzes | Admin → Fun & Learn |
| App name, support email, store links, FAQ, guest play, minimum app version (force update), maintenance mode | Admin → Settings |
| Terms and privacy pages | Admin → Settings (or point the links at your own pages) |
| App texts | The page files in brainrush_mobile_flutter/lib/pages/ |
| Website texts | The page files in brainrush_web_nextjs/src/app/(site)/ and src/components/site/ |
19. Adding questions
Each question belongs to a subcategory and a level. A level is played with questions per level picked at random from its pool, so put more questions in a level than are played (for example 15–20 for 10 played) for variety.
- Types: multiple choice (2–4 options), true/false, image (a picture plus options), audio (a sound plus options), maths (a formula plus options) and guess the word (the player builds the answer from letter tiles).
- CSV import (Admin → Questions → Import, up to 5,000 rows): download the template from that page, fill one row per question, and upload. The page checks every row and shows the problems before anything is saved.
- Pictures and sounds: upload them in the question editor (stored in your S3 storage).
- Players can report a wrong question; reports land in Admin → Reports.
20. Every key, explained
"Where" says which .env file it goes in: Docker = deploy/.env (website and worker in the Docker stack), website = brainrush_web_nextjs/.env (without Docker), app = brainrush_mobile_flutter/.env. Admin → Setup check shows the same list with the live status of each key.
| Key | Required | Where | What it does |
|---|---|---|---|
| Database | |||
DATABASE_URL | Yes | website | PostgreSQL connection string |
POSTGRES_DB | Yes | Docker | Database the Docker stack creates |
POSTGRES_USER | Yes | Docker | Database user for the Docker stack |
POSTGRES_PASSWORD | Yes | Docker | Database password for the Docker stack |
| App | |||
APP_URL | Yes | website, Docker | Public URL of the website/API, e.g. https://quiz.example.com |
NEXT_PUBLIC_APP_NAME | — | website, Docker | Name shown before the admin sets one |
WEB_PORT | — | Docker | Host port the web container listens on |
DOMAIN | — | Docker | Your domain for automatic HTTPS (docker compose --profile https) |
CORS_ORIGINS | — | website, Docker | Browser apps allowed to call /api/v1 (Flutter web builds) |
DEMO_MODE | — | website, Docker | "true" only for a public demo: one-click demo sign-in |
| Firebase sign-in | |||
NEXT_PUBLIC_FIREBASE_API_KEY | Yes | website, Docker | Firebase web app config |
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN | Yes | website, Docker | Firebase web app config |
NEXT_PUBLIC_FIREBASE_PROJECT_ID | Yes | website, Docker | Firebase web app config |
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET | — | website, Docker | Firebase web app config |
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID | — | website, Docker | Firebase web app config |
NEXT_PUBLIC_FIREBASE_APP_ID | Yes | website, Docker | Firebase web app config |
| Firebase Admin + push | |||
FIREBASE_PROJECT_ID | Yes | website, Docker | Firebase project id |
FIREBASE_CLIENT_EMAIL | Yes | website, Docker | Service account e-mail |
FIREBASE_PRIVATE_KEY | Yes | website, Docker | Service account private key |
| In-app purchases | |||
REVENUECAT_SECRET_KEY | — | website, Docker | RevenueCat secret API key — verifies store purchases (empty = demo purchases) |
REVENUECAT_WEBHOOK_AUTH | — | website, Docker | Authorization header RevenueCat sends to /api/v1/webhooks/revenuecat |
DEMO_PURCHASES | — | website, Docker | "false" hides demo purchases when RevenueCat is not set |
| AI question generator | |||
ANTHROPIC_API_KEY | — | website, Docker | Claude API key (or set it in Admin → Settings → AI) |
OPENAI_API_KEY | — | website, Docker | OpenAI API key |
GEMINI_API_KEY | — | website, Docker | Google Gemini API key |
| Storage | |||
S3_ENDPOINT | — | website, Docker | S3-compatible endpoint (MinIO, R2); empty for AWS |
S3_REGION | — | website, Docker | Bucket region |
S3_BUCKET | — | website, Docker | Bucket for photos and documents (empty = local uploads folder) |
S3_ACCESS_KEY_ID | — | website, Docker | Storage access key |
S3_SECRET_ACCESS_KEY | — | website, Docker | Storage secret key |
SMTP_HOST | — | website, Docker | SMTP server; empty = emails are printed to the log |
SMTP_PORT | — | website, Docker | 587 (STARTTLS) or 465 (TLS) |
SMTP_USER | — | website, Docker | SMTP user |
SMTP_PASS | — | website, Docker | SMTP password |
SMTP_SECURE | — | website, Docker | "true" for port 465 (TLS); empty for 587 |
| Storage | |||
UPLOAD_DIR | — | website | Folder for uploads when no bucket is set (default ./uploads) |
| Mobile apps | |||
API_BASE_URL | Yes | app | Your server; the app calls <url>/api/v1 |
APP_NAME | — | app | App name in the UI |
FIREBASE_PROJECT_ID | Yes | app | Firebase project id |
FIREBASE_MESSAGING_SENDER_ID | Yes | app | Firebase config |
FIREBASE_STORAGE_BUCKET | — | app | Firebase config |
FIREBASE_ANDROID_API_KEY | Yes | app | Firebase Android config |
FIREBASE_ANDROID_APP_ID | Yes | app | Firebase Android config |
FIREBASE_IOS_API_KEY | Yes | app | Firebase iOS config |
FIREBASE_IOS_APP_ID | Yes | app | Firebase iOS config |
FIREBASE_IOS_CLIENT_ID | — | app | Google sign-in on iOS |
FIREBASE_IOS_BUNDLE_ID | — | app | iOS bundle id |
GOOGLE_SERVER_CLIENT_ID | — | app | Web OAuth client id — Google sign-in on Android |
SUPPORT_EMAIL | — | app | Fallback support e-mail until the server config loads |
REVENUECAT_APPLE_KEY | — | app | RevenueCat public iOS key (empty = demo purchases) |
REVENUECAT_GOOGLE_KEY | — | app | RevenueCat public Android key (empty = demo purchases) |
ADMOB_ANDROID_APP_ID | — | app | AdMob Android app id (empty = Google test ads) |
ADMOB_ANDROID_REWARDED_ID | — | app | AdMob Android rewarded ad unit id (empty = Google test ads) |
ADMOB_IOS_REWARDED_ID | — | app | AdMob iOS rewarded ad unit id (empty = Google test ads) |
ADMOB_ANDROID_INTERSTITIAL_ID | — | app | AdMob Android interstitial ad unit id (empty = Google test ads) |
ADMOB_IOS_INTERSTITIAL_ID | — | app | AdMob iOS interstitial ad unit id (empty = Google test ads) |
ADMOB_ANDROID_BANNER_ID | — | app | AdMob Android banner ad unit id (empty = Google test ads) |
ADMOB_IOS_BANNER_ID | — | app | AdMob iOS banner ad unit id (empty = Google test ads) |
DEMO_SIGN_IN | — | app | "true" shows one-tap demo sign-in (your public demo only) |
21. File structure
Mobile app (brainrush_mobile_flutter/lib)
main.dart loads .env, starts Firebase, runs the app (short on purpose)
app.dart theme, router and app-wide listeners
router.dart every route (go_router) and the sign-in / setup gates
config/ .env values (app_env.dart) and Firebase options
api/ the typed client for your server (/api/v1)
models/ data models parsed from the API
providers/ app-wide state (Riverpod): session, config, wallet, home…
services/ push, purchases (RevenueCat or demo), ads (AdMob), sounds, sign-in
theme/ colours (br_colors.dart), fonts and text styles
components/ shared widgets, names start with cc_ (buttons, cards, sheets, avatars…)
pages/ one folder per area: launch, auth, home, play, quiz, results, battle, compete,
leagues, exam, learn, create, store, profile, settings…
utils/ formatting and small helpers
Lists and grids use the lazy .builder constructors, and state flows through Riverpod providers rather than long chains of widget parameters.
Server (brainrush_web_nextjs/src)
app/(site)/ the public website (landing, play, leaderboard, contests, shared quizzes…)
app/(game)/quiz/ the full-screen quiz player
app/admin/ Admin panel pages
app/install/ the first-run wizard
app/api/v1/[...path]/ the single API entry point; routes live in lib/server/handlers/
components/site|play|panel|ui website, quiz player, admin and base UI (shadcn) components
lib/server/handlers/ every API route by area: play, compete, social, me, admin…
lib/server/ games and scoring, battles, contests, leagues, exams, economy, purchases,
storage, email, AI, push, settings, setup check
database/prisma_client/ database schema (schema.prisma) and migrations
database/seed/ the sample quizzes, badges and coin packs
worker/ background jobs (pnpm worker)
scripts/doctor.ts the setup check in a terminal
tests/ API tests (Vitest); e2e/ has browser smoke tests (Playwright)
22. Publish to the stores
You publish the app under your own developer accounts. Before you start: set your app id (chapter 16), your Firebase values (chapter 4) and an https API_BASE_URL. The stores ask for a privacy policy link: https://your-domain.com/privacy; terms are at /terms. Players delete their account in the app (Profile → Settings → Account → Delete account).
Google Play
- Create an upload key inside
android/app:cd brainrush_mobile_flutter/android/app && keytool -genkey -v -keystore upload-keystore.jks -keyalg RSA -keysize 2048 -validity 10000 -alias upload. Back this file up and never share it. - Create
android/key.properties:
Without this file, release builds are signed with the debug key (fine for testing, refused by Play).storePassword=… keyPassword=… keyAlias=upload storeFile=upload-keystore.jks - Set the version in
pubspec.yaml(version: 1.0.0+1; raise the number after+for every upload), thenflutter build appbundle. - In Play Console: create the app, fill the store listing, content rating, data safety (the app collects account info, game progress, purchase history, device ids for ads and push) and upload
build/app/outputs/bundle/release/app-release.aabto a testing track first. - Add the SHA-1/SHA-256 of Play's app signing key (Play Console → Test and release → App integrity) to Firebase.
App Store
- In the Apple Developer site, register your bundle id with Push Notifications, Sign in with Apple and In-App Purchase.
- Open
ios/Runner.xcworkspacein Xcode → Runner → Signing & Capabilities → choose your team. - In App Store Connect create the app, fill the listing and App Privacy, and create the in-app purchases (chapter 10).
flutter build ipa, then uploadbuild/ios/ipa/*.ipawith the Transporter app, and send it to TestFlight first.
23. Updating
When a new version comes out, read the changelog, back up your database (chapter 5), then copy the new files over your copy, keeping your .env files and any changes you made. Run docker compose up -d --build: database changes are applied automatically on start. Using git for your copy makes this much easier: commit before you copy the update in, and review the differences.
24. FAQ and troubleshooting
The site opens the install wizard again
The wizard shows only while no super admin exists. If you see it on a site you already set up, the site is connected to an empty database: check DATABASE_URL / the POSTGRES_* values.
Sign-in fails on the website
Add your domain under Firebase → Authentication → Settings → Authorized domains, check the NEXT_PUBLIC_FIREBASE_* values, and restart (docker compose up -d).
Google sign-in fails on Android
Add the SHA-1 and SHA-256 of the key that signed the build to Firebase, and check GOOGLE_SERVER_CLIENT_ID is the Web client id.
Battles never find an opponent
Matchmaking and the battle clock run in the worker. Check it runs: docker compose logs worker; Admin → Setup check shows when it last answered. With few players, a bot joins after the wait set in Admin → Battles.
Purchases say DEMO
No RevenueCat key is set yet. That is on purpose: add your keys (chapter 10).
Push notifications do not arrive
iOS needs the APNs key in Firebase, and the phone must allow notifications. Admin → Setup check tests the Firebase push connection.
The app shows "Can't reach the server"
API_BASE_URL in the app's .env must be your site's address (https, no /api/v1 at the end). Open https://your-domain.com/api/v1/health in the phone's browser.
Images or sounds in questions do not load
Check the S3_* keys and Admin → Setup check → File storage. With a cloud bucket, the server needs read and write access to it.
I changed .env and nothing happened
Server: docker compose up -d. App: stop it and run it again.
Where are the logs?
docker compose logs -f web and docker compose logs -f worker.
25. Credits
Brainrush is built on these open-source projects. Each keeps its own licence (mostly MIT, BSD-3 or Apache 2.0).
Mobile app
Flutter, flutter_riverpod, go_router, FlutterFire (firebase_core, firebase_auth, firebase_messaging), google_sign_in, sign_in_with_apple, purchases_flutter (RevenueCat), google_mobile_ads, audioplayers, flutter_dotenv, shared_preferences, http, intl, lucide_icons_flutter, share_plus, url_launcher, package_info_plus, image_picker, qr_flutter, connectivity_plus, flutter_launcher_icons, flutter_native_splash.
Website, Admin, API and worker
Next.js, React, Prisma, PostgreSQL, pg, Zod, Tailwind CSS, shadcn/ui, Base UI, Lucide icons, Sonner, next-themes, qrcode, Nodemailer, Firebase JS SDK, Firebase Admin SDK, AWS SDK for JavaScript, MinIO, Caddy, Docker.
Fonts
Fredoka and Nunito, SIL Open Font License 1.1.
Content
The sample questions, explanations, articles, flag pictures, audio clips, sound effects, category names and player names are original and part of your licence.
26. Changelog
1.0.0 — 2026-10-07
First release. See CHANGELOG.md in the download for the full list.
27. Support
Questions or a problem? Use the Support tab on the item page on CodeCanyon, with your purchase code, what you did, and what you saw (a screenshot and the output of pnpm run doctor help a lot). Support covers questions about the item, help with bugs, and help with the third-party services it uses as far as this documentation goes. It does not cover changes or new features you want to build; for that, or to have it installed for you, ask about our installation service on the item page.