A microservices-based platform for managing shared resources (lecture halls, labs, equipment) across university faculties with multi-tenant isolation, booking conflict prevention, and real-time notifications.
┌─────────────────────────────────────────────────────┐
│ Nginx Gateway (:80/:443) │
│ Rate Limiting + TLS Termination │
├─────────┬──────────┬───────────┬──────────┬─────────┤
│ Tenant │ User │ Resource │ Booking │ Notify │
│ Service │ Service │ Service │ Service │ Service │
│ :3001 │ :3002 │ :3003 │ :3004 │ :3005 │
├─────────┴──────────┴───────────┴──────────┴─────────┤
│ @rso/shared (common lib) │
├──────────────────────┬──────────────────────────────┤
│ Supabase (DB) │ Redis (Events) │
└──────────────────────┴──────────────────────────────┘
| Service | Port | Purpose |
|---|---|---|
| Tenant | 3001 | Faculty/department CRUD |
| User | 3002 | Profiles, signup, role management, Firebase claims |
| Resource | 3003 | Resource catalog, availability checks |
| Booking | 3004 | Booking CRUD, approve/reject workflow, conflict detection |
| Notification | 3005 | In-app notifications, email via Resend, Redis event consumer |
| Gateway | 80/443 | Nginx reverse proxy, rate limiting, TLS |
| Redis | 6379 | Event streaming between services |
- Runtime: Node.js 22 + TypeScript + Fastify
- Database: Supabase (PostgreSQL) with Row Level Security
- Auth: Firebase Authentication + Custom Claims
- Events: Redis Streams (pub/sub)
- Email: Resend API
- Gateway: Nginx with rate limiting
- Containers: Docker + Docker Compose
- DNS/TLS: Cloudflare (Origin Certificate, Full Strict mode)
- Node.js ≥ 22
- Docker Desktop
- A Supabase project
- A Firebase project with Authentication enabled
- A Resend account (for email notifications)
git clone <repo-url>
cd "Resource Share"
npm installcp infra/.env.example infra/.env
# Edit infra/.env with your actual secretsRequired secrets:
- Firebase:
FIREBASE_PROJECT_ID,FIREBASE_API_KEY,FIREBASE_AUTH_DOMAIN - Firebase Admin SDK: Place your service account JSON at
config/firebase-service-account.json - Supabase:
SUPABASE_URL,SUPABASE_SERVICE_ROLE_KEY,SUPABASE_ANON_KEY,SUPABASE_JWT_SECRET - Resend:
RESEND_API_KEY
Apply the SQL migrations to your Supabase project via the SQL Editor:
Run each file in order from supabase/migrations/:
001_extensions.sql002_tenants.sql003_user_profiles.sql004_resources.sql005_bookings.sql006_notifications.sql007_optimization_logs.sql008_rls_policies.sql009_triggers_functions.sql
- Enable Email/Password and Google sign-in in Firebase Console → Authentication → Sign-in method
- Download the Admin SDK service account key and save as
config/firebase-service-account.json - See
docs/firebase-setup.mdfor detailed instructions
# Build all TypeScript services
npm run build --workspaces
# Start with Docker Compose
cd infra
docker compose up -d# Gateway health check
curl http://localhost/health
# → {"status":"ok","gateway":"nginx"}
# All services return 401 (auth required) — correct!
curl http://localhost/api/v1/tenants/
# → {"success":false,"error":{"code":"AUTH_MISSING_TOKEN",...}}All endpoints require a Firebase ID token: Authorization: Bearer <token>
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /api/v1/tenants/ |
Any | List tenants (paginated) |
| GET | /api/v1/tenants/:id |
Any | Get tenant by ID |
| POST | /api/v1/tenants/ |
super_admin | Create tenant |
| PUT | /api/v1/tenants/:id |
tenant_admin+ | Update tenant |
| DELETE | /api/v1/tenants/:id |
super_admin | Deactivate tenant |
| GET | /api/v1/tenants/:id/stats |
tenant_admin+ | Tenant statistics |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /api/v1/users/signup |
Any (Firebase token) | Create profile + set claims |
| GET | /api/v1/users/me |
Any | Get own profile |
| GET | /api/v1/users/ |
tenant_admin+ | List users (paginated) |
| GET | /api/v1/users/:uid |
Any | Get user by UID |
| PUT | /api/v1/users/:uid |
Self or admin | Update profile |
| PUT | /api/v1/users/:uid/role |
tenant_admin+ | Change user role |
| DELETE | /api/v1/users/:uid |
tenant_admin+ | Deactivate user |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /api/v1/resources/ |
Any | List resources (paginated, filterable) |
| GET | /api/v1/resources/:id |
Any | Get resource |
| POST | /api/v1/resources/ |
tenant_admin+ | Create resource |
| PUT | /api/v1/resources/:id |
tenant_admin+ | Update resource |
| DELETE | /api/v1/resources/:id |
tenant_admin+ | Retire resource |
| GET | /api/v1/resources/:id/availability |
Any | Check availability by date |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /api/v1/bookings/ |
Any | List bookings (filterable) |
| GET | /api/v1/bookings/:id |
Any | Get booking |
| POST | /api/v1/bookings/ |
Any | Create booking |
| PUT | /api/v1/bookings/:id/approve |
tenant_admin+ | Approve booking |
| PUT | /api/v1/bookings/:id/reject |
tenant_admin+ | Reject booking |
| PUT | /api/v1/bookings/:id/cancel |
Owner or admin | Cancel booking |
| GET | /api/v1/bookings/optimization/stats |
tenant_admin+ | Optimization logs |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /api/v1/notifications/ |
Any | Get own notifications |
| PUT | /api/v1/notifications/:id/read |
Any | Mark as read |
| PUT | /api/v1/notifications/read-all |
Any | Mark all as read |
| GET | /api/v1/notifications/unread-count |
Any | Get unread count |
Every user belongs to exactly one tenant (faculty). Data isolation is enforced at two levels:
- Application layer — Every query filters by
tenant_idfrom the JWT claims - Database layer — Supabase RLS policies scope all queries by
tenant_id
| Role | Permissions |
|---|---|
student |
View resources, create bookings, view own bookings/notifications |
lecturer |
Same as student |
staff |
Same as student |
tenant_admin |
Manage resources, approve/reject bookings, manage users within tenant |
super_admin |
Full access across all tenants |
Resource Share/
├── config/ # Firebase service account key
├── docs/ # Setup guides
│ ├── firebase-setup.md
│ └── supabase-firebase-setup.md
├── infra/ # Docker + Gateway
│ ├── docker-compose.yml
│ ├── Dockerfile.service
│ ├── .env / .env.example
│ └── gateway/
│ ├── nginx.conf
│ └── ssl/ # Cloudflare Origin Certificate
├── scripts/ # Migration & test scripts
│ ├── run-migrations.ts
│ ├── verify-rls.ts
│ └── test-firebase-claims.ts
├── services/
│ ├── shared/ # @rso/shared — common library
│ │ └── src/
│ │ ├── auth-middleware.ts
│ │ ├── supabase-client.ts
│ │ ├── redis-client.ts
│ │ ├── error-handler.ts
│ │ ├── logger.ts
│ │ ├── role-guard.ts
│ │ └── types.ts
│ ├── tenant-service/
│ ├── user-service/
│ ├── resource-service/
│ ├── booking-service/
│ └── notification-service/
├── supabase/
│ └── migrations/ # 9 ordered SQL migration files
├── package.json # npm workspaces root
└── tsconfig.base.json
# Build all services
npm run build --workspaces
# Run a single service locally (without Docker)
npm run dev -w services/tenant-service
# Run Firebase claims test
npx tsx scripts/test-firebase-claims.ts
# Run RLS verification
npx tsx scripts/verify-rls.ts- Add an A record pointing
pro.isuruhub.siteto your server's IP - Set SSL/TLS mode to Full (Strict)
- Generate an Origin Certificate in Cloudflare dashboard
- Save the certificate and key to
infra/gateway/ssl/origin.pemandorigin-key.pem
Private — University of Kelaniya