Skip to content

Repository files navigation

Majestor

A university platform with two core features: Carpool (ride sharing between students) and Study Hub (study groups, document sharing, group chat). Built with Spring Boot and React Native (Expo).

Spring Boot Java React Native Expo PostgreSQL


Repository Structure

majestor/
├── apps/
│   ├── api/          # Spring Boot REST API (port 8080)
│   └── mobile/       # React Native + Expo app
├── docker-compose.yml
└── README.md

Each app has its own README with full setup and architecture details:


Quick Start

Prerequisites

  • Java 21+
  • Node.js 18+
  • Docker (for PostgreSQL)
  • Expo Go app on your phone (for mobile dev)

1. Start the database

docker-compose up -d

2. Start the API

cd apps/api
./mvnw spring-boot:run

API runs at http://localhost:8080. Swagger UI at http://localhost:8080/swagger-ui.html.

3. Start the mobile app

cd apps/mobile
npm install
npm start

Scan the QR code with Expo Go. Before running on a physical device, set your machine's LAN IP in apps/mobile/src/constants/index.tsLOCAL_IP.


Features

Carpool

  • Students post rides with start/end location, vehicle details, and seat count
  • Passengers submit ride requests with pickup/dropoff locations
  • Driver sees deviation on a map for each booking request and estimated fare
  • Real-time booking status updates via WebSocket (STOMP)
  • 1-on-1 chat between driver and passenger after booking is accepted
  • Fare calculated as pricePerKm × deviationKm — see Fare System

Study Hub

  • Create and join study groups (public/private, official university groups)
  • Upload documents (past papers, notes, assignments) to personal, public, or group vault
  • Filter vault by course, type, year, semester
  • Like documents and sort by popularity
  • Group chat via WebSocket
  • Invite other students to your study group
  • Rate study groups

User

  • University email signup with email verification
  • JWT authentication with refresh tokens
  • Profile management (avatar, phone, personal email)
  • Push notifications via Expo Push API
  • Premium tier with storage limits

Tech Stack

Layer Technology
Mobile framework React Native 0.81.5 + Expo 54
Language (mobile) TypeScript
Navigation Expo Router v3 (file-based)
Server state TanStack Query v5
Client state Zustand
Styling NativeWind (Tailwind)
HTTP client Axios
Backend framework Spring Boot 3.5.6
Language (backend) Java 21
Database PostgreSQL
ORM Spring Data JPA / Hibernate
Auth JWT (HS256, 30min access / 30day refresh)
File storage AWS S3
Real-time WebSocket / STOMP
Email Gmail SMTP
Error tracking Sentry (both apps)
Push notifications Expo Push API
Subscriptions RevenueCat + react-native-purchases

In-App Subscriptions (Majestor Elite)

Subscriptions are powered by RevenueCat. RevenueCat sits between the app and the stores — it normalises Apple and Google purchase receipts into a single entitlement check, and drives the paywall UI remotely without app updates.

How it works in code

File Role
src/services/purchases.service.ts All RevenueCat calls (configure, logIn, getCustomerInfo, etc.)
src/stores/purchasesStore.ts Zustand store — holds customerInfo and reactive isElite boolean
src/app/_layout.tsxPurchasesInitializer Configures SDK at startup, syncs RevenueCat user ID with auth, listens for live entitlement updates
src/components/ui/PremiumModal.tsx Shown on 402 API responses — presents the RevenueCat paywall
src/components/screens/user/UserSettings.tsx Subscription card — "Upgrade to Elite" (paywall) or "Manage your subscription" (Customer Center)

The entitlement identifier in RevenueCat is Majestor Pro (identifier) / Majestor Elite (display name). The constant is ENTITLEMENT_ID in purchases.service.ts.


One-time dashboard setup

Complete this before testing purchases. All steps are in the RevenueCat dashboard.

1. Create the app entries

Go to Project Settings → Apps and add:

  • An Apple App Store app (needs App Store Connect API key)
  • A Google Play app (needs Google Play service account JSON)

2. Create products in each store

App Store Connect (Monetization → Subscriptions):

  1. Create a Subscription Group (e.g. "Majestor Elite")
  2. Add two auto-renewable subscriptions:
    • Product ID: monthly — 1 month duration
    • Product ID: yearly — 1 year duration
  3. Fill in display name, price, and localisation for each

Google Play Console (Monetization → Subscriptions):

  1. Create two subscriptions:
    • Product ID: monthly
    • Product ID: yearly
  2. Add a base plan and price for each, then activate them

3. Add products to RevenueCat

In RevenueCat → Products, import or manually add the four products (2 iOS + 2 Android).

4. Verify the entitlement

Go to Entitlements and confirm Majestor Pro has all four products attached (iOS monthly, iOS yearly, Android monthly, Android yearly).

5. Create an Offering with a Paywall

Go to Offerings → create (or edit) the default offering:

  • Add a Package for monthly and one for yearly
  • Click Paywalls → design your paywall in the visual editor
  • Attach the paywall to the offering

This is what RevenueCatUI.presentPaywall() renders. Without an attached paywall the modal will show an error.


Testing on Android

Purchases require a native build — they will not work in Expo Go.

Step 1 — Add a licence tester (no real charges)

In Google Play Console → Setup → Licence Testing, add the Google account email you'll use on the test device. Licence testers can make purchases that go through the full flow without being charged.

Step 2 — Build and install a development APK

cd apps/mobile
npx expo run:android

Or with EAS:

eas build --platform android --profile development
# install the downloaded .apk on your device

Step 3 — Sign in with the licence tester account

On the Android device go to Settings → Accounts and make sure the licence tester Google account is added.

Step 4 — Make a test purchase

Open the app → Settings → Upgrade to Elite (or trigger a 402 from a premium-gated screen). The Google Play purchase sheet appears. Complete the purchase — no real charge occurs for licence testers.

Step 5 — Verify in RevenueCat

Go to RevenueCat dashboard → Customers, search for your user ID or email, and confirm the Majestor Pro entitlement shows as Active.


Testing on iOS

Step 1 — Create a Sandbox Tester

In App Store Connect → Users & Access → Sandbox → Testers, create a new tester with a fresh Apple ID (use an email address not already registered with Apple).

Step 2 — Build a development build

cd apps/mobile
npx expo run:ios

Or with EAS:

eas build --platform ios --profile development
# install via TestFlight or direct device install

Step 3 — Sign out of your real Apple ID on the device

On the device go to Settings → App Store and sign out. Do not sign out of iCloud — only the App Store account.

Step 4 — Make a test purchase

Trigger the paywall in the app. When prompted to sign in to the App Store, enter the sandbox tester credentials (not your real Apple ID). The purchase completes in the sandbox — no real charge.

Step 5 — Verify in RevenueCat

Same as Android — check the customer record in the RevenueCat dashboard for an active Majestor Pro entitlement.


Verifying the entitlement in-app

After a successful test purchase, the isElite flag in purchasesStore should flip to true. You can confirm this by:

  1. Going to Settings — the Subscription card should now show "Majestor Elite" with a manage option instead of "Upgrade to Elite"
  2. Accessing a premium-gated document in Study Hub — it should no longer show the lock/blur
  3. Checking the RevenueCat dashboard → Customer → Entitlements

Resetting test purchases

  • Android: In Google Play → Licence Testing you can revoke purchases, or simply use a fresh licence tester account
  • iOS: In App Store Connect → Sandbox Testers, click the tester and use Clear Purchase History to reset subscriptions for that sandbox account

Fare System

Carpool fares are calculated based on the extra distance the driver travels to serve the passenger — not the full trip.

fare = pricePerKm × |deviationKm|

deviationKm = |passenger's route distance − driver's route distance|

Default Rates

Vehicle Price per Km Example (3.1 km detour)
CAR Rs 24/km 24 × 3.1 = Rs 74
BIKE Rs 13/km 13 × 3.1 = Rs 40

How rates are derived

pricePerKm is the fuel cost per km, calculated from Pakistan petrol prices (~Rs 270/liter) divided by average vehicle mileage:

CAR — Rs 24/km

  • Petrol ≈ Rs 270/liter
  • Average car mileage ≈ 13 km/liter
  • 270 ÷ 13 ≈ Rs 20/km (base fuel cost) + Rs 4/km (driver margin) = Rs 24/km

BIKE — Rs 13/km

  • Petrol ≈ Rs 270/liter
  • Average bike mileage ≈ 45 km/liter
  • 270 ÷ 45 ≈ Rs 6/km (base fuel cost) + Rs 7/km (driver margin) = Rs 13/km

Rates are stored in the fare_configs DB table and can be updated via the admin endpoint without touching code:

curl -X PATCH http://localhost:8080/api/v1/ride/fare-config/CAR \
  -H "Content-Type: application/json" \
  -d '{ "pricePerKm": 22 }'

Environment Variables

Create a .env file (never committed). See each app's README for the full list.

Variable Used By Description
POSTGRES_USER API DB username
POSTGRES_PASSWORD API DB password
POSTGRES_DB API DB name
JWT_SECRET_KEY API Base64-encoded HMAC secret
S3_BUCKET_NAME API AWS S3 bucket
S3_BUCKET_REGION API AWS region
S3_ACCESS_KEY API AWS access key
S3_SECRET_KEY API AWS secret key
SUPPORT_EMAIL API Gmail address for sending email
APP_PASSWORD API Gmail app password
DATABASE_URL API (prod) Full JDBC URL for production DB

API Base Path

All endpoints are prefixed with /api/v1/.

Public routes (no auth required):

  • POST /api/v1/auth/login
  • POST /api/v1/auth/signup
  • GET /api/v1/auth/signup/verify
  • POST /api/v1/auth/forgetPassword
  • POST /api/v1/auth/refresh
  • GET /api/v1/university/getWithFaculties
  • /ws/** (WebSocket)

All other routes require Authorization: Bearer <token>.


Roadmap

Prioritized post-v1.0 plan. Order is by impact-per-effort, not chronology.

🔴 Critical hardening (this week)

These are pre-existing risks now amplified by being live in production.

  • Fix IDOR vulnerability in UserController and audit all controllers that take an {id} path param. Pull authenticated user from SecurityContextHolder and verify ownership before allowing reads/writes. (See UserController.java:20.)
  • Restrict Google Maps API key in Google Cloud Console — Android package + SHA-1 restriction, API restrictions to Maps/Places/Directions only, daily quota cap. Key is currently in source (apps/mobile/src/constants/index.ts).
  • Move admin credentials out of source. DataInitializer.java hardcodes admin email + password. Read from ADMIN_EMAIL / ADMIN_PASSWORD env vars; fail to boot if missing.
  • Implement forgetPassword. Endpoint exists and is documented as public, but AuthService.forgetPassword() has an empty body. Live users who forget their password are permanently locked out.
  • Make DataInitializer idempotent. Currently runs unconditionally on every startup. Wrap each init method with a count() == 0 guard, or move seed data into a Flyway V2__seed_data.sql migration.

🟡 v1.1 — biggest product unlocks (next 2 weeks)

The four features most likely to materially improve retention and adoption.

  • Ride ratings + driver profile. After a completed ride, prompt both sides for a 1–5 star rating and optional one-line review. Surface average rating + total rides on driver profiles and on every ride listing. Single biggest trust unlock for Carpool.
  • Women-only rides toggle. Flag on ride posts and ride requests. "Female passengers only" rides are visible only to female users. Requires verified gender on signup. Important for the Pakistani university context.
  • New-document push notifications by course. When a user uploads a document tagged with a course another user has enrolled in, push them. Push infra already exists — only need course-to-user matching logic. Daily engagement driver.
  • Class timetable. Users add their classes (course + day + time + location). Home screen shows "Next class: SE-201 in 23 mins." Pairs with carpool ("ride to your 9am") and study groups. The feature most likely to turn the app into a daily habit.

🟢 v1.2 — growth & engagement (month 2)

  • Recurring rides. "Repeat: Mon/Wed/Fri at 8am for 16 weeks." Auto-generates daily instances; passengers book individual ones. Removes the friction of posting the same commute every day.
  • Document bookmarks ("Save for later"). Private save action, separate from public Like. Most study hub usage is "I'll need this for finals" — bookmarks are the right primitive.
  • Referral system. "Invite a friend with university email → both get 1 month of Elite when they sign up." Track via referral code on signup payload. Per-university leaderboard. Word-of-mouth is the dominant acquisition channel for campus apps; make it explicit.
  • Define what Elite unlocks, concretely. Replace vague "premium resources" copy in PremiumModal with a concrete table: free vs. Elite limits on study groups, storage, document uploads, etc.

🛡️ Trust & safety (Carpool)

  • SOS button during a ride. Held-down emergency button on the live ride screen — shares location + ride details with a pre-set emergency contact and support@majestor.org. Table-stakes for ride-sharing.
  • One-time vehicle verification. First ride post requires uploading vehicle registration + license. Manual admin review. "Verified vehicle" badge on listings.

📊 Observability & quality (background work)

  • Add product analytics (PostHog or Mixpanel). Track ~10 events: signup_completed, ride_posted, ride_booked, group_joined, document_uploaded, message_sent, etc. Don't over-instrument.
  • Watch Play Console Vitals weekly once installs cross a few hundred. ANR rate, crash rate, slow rendering, slow startup.
  • API monitoring beyond Sentry. Alert on 5xx rate > 1% over 10 minutes. Digital Ocean's built-in app metrics is enough for v1.
  • Pragmatic test coverage. Backend integration tests for the 5 most-used endpoints (Spring Boot + Testcontainers). Mobile: skip unit tests, add a single Maestro/Detox end-to-end smoke test in CI.

🔭 Future bets — only if user data validates them

These are natural extensions but should not be built without seeing demand in analytics first.

  • Campus marketplace — used textbooks, calculators, lab coats.
  • Lost & found board.
  • Tutor matching — Elite-only feature pairing seniors with juniors.
  • Faculty/admin announcements — official university news pushed to enrolled students.
  • Study sessions calendar — "Group SE-201 meeting at library, 7pm Thursday" with RSVP.
  • Multi-university scaling. Move single-university seed data into per-university SQL migrations or a tiny admin UI. Currently DataInitializer hardcodes one university.

Philosophy

  • Build trust features (ratings, SOS, verification) before growth features. A carpool app with one bad story is dead.
  • Ship v1.1 with analytics enabled. Solo developers waste the most time building features users don't want; analytics is the cheapest insurance against that.
  • Don't promote betas to production while testing tracks have older non-compliant builds active. Google reviews the union of all tracks (learned this the hard way during the v1.0 launch).

License

MIT

About

Resources

Code of conduct

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages