Skip to content

habinrahman/Hub

Repository files navigation

MicroDegree Hub

Student Operations & Cohort Management Platform

The post-payment command center for live tech education at scale.

TypeScript Next.js React PostgreSQL Drizzle Tailwind CSS License

Production Architecture Auth Region

Product Vision · V1 Scope · Architecture · Database · Security · Deploy · Roadmap


Table of Contents

  1. Product Vision
  2. Platform Overview
  3. Current V1 Launch Scope
  4. Key Features
  5. Architecture Deep Dive
  6. Database Design
  7. Authentication & Security
  8. Student Journey
  9. Batch Management System
  10. Zoom Integration
  11. Roadmap Engine
  12. Role-Based Access Control
  13. API Documentation
  14. Folder Structure
  15. Local Development Setup
  16. Environment Variables
  17. Deployment Guide
  18. Performance Optimizations
  19. Operational Philosophy
  20. Scalability Vision
  21. Future Roadmap
  22. Testing Strategy
  23. Monitoring & Observability
  24. Contributor Guide
  25. Lessons Learned
  26. Screenshots
  27. License
  28. Contact

1. Product Vision

Why MicroDegree Hub exists

MicroDegree runs live cohort-based programs in Cloud Computing and Gen AI for Indian learners. After payment, operations historically fractured across spreadsheets, WhatsApp groups, ad-hoc Zoom links, and disconnected tools. Students asked the same question repeatedly: “Where is my class link?” Ops burned hours on manual coordination instead of delivery quality.

MicroDegree Hub is the Student Experience Layer + Internal Operations Console — a single domain (hub.microdegree.work) that owns everything after money changes hands:

Problem (before) Hub response (after)
Fragmented onboarding Stateful enrollment lifecycle with audit trail
Lost Zoom links Session-aware join with join windows + S2S Zoom URLs
No cohort visibility Batch assignments, attendance, ops dashboards
Roadmap opacity Plan-ordered course roadmap with tentative future batches
No accountability Immutable audit_logs on every mutation
Role chaos Server-side RBAC via can() — not UI-only hiding

What we deliberately are not

Hub is not an LMS (Thinkific remains), not a CRM (LeadSquared pre-sale), not a placement portal (portal.microdegree.work), not a mock-test platform (practice.microdegree.work). We deep-link to sister products and own cohort operations.

Engineering thesis

Correctness and operability beat feature breadth.
A monolithic Next.js application with a strict service layer, Postgres state machines, and defense-in-depth auth ships faster and fails more predictably than premature microservices.


2. Platform Overview

Ecosystem map

flowchart TB
  subgraph Public["hub.microdegree.work"]
    STU["/app — Student Portal"]
    OPS["/admin/ops — Operations"]
    AC["/admin/ac — Academic Counselor"]
    SALES["/sales — Sales AC"]
    AUTH["/auth — OTP + Google SSO"]
    API["/api — Cron + Zoom join"]
  end

  subgraph Data["Supabase ap-south-1"]
    PG[(PostgreSQL)]
    SA[Supabase Auth]
    STORE[Storage - future]
  end

  subgraph External["Integrations"]
    ZOOM[Zoom S2S OAuth]
    RESEND[Resend SMTP]
    THINK[Thinkific deep links]
    RAZOR[Razorpay - future]
  end

  STU --> AUTH
  OPS --> AUTH
  STU --> PG
  OPS --> PG
  AUTH --> SA
  API --> ZOOM
  API --> RESEND
  STU --> THINK
Loading

Portal matrix

Portal Route prefix Primary users Maturity
Student /app/* Enrolled learners V1 — production
Operations /admin/ops/* Ops, PM, support V1 — production
Academic Counselor /admin/ac/* AC staff Built; de-emphasized in V1 pilot
Sales /sales/* Sales / AC submitters Built; out of V1 pilot scope
Staff auth /admin/login, /auth/* All staff V1 — production

Operational model

  1. Identity mirrors Supabase Auth into users + role-specific profiles.
  2. Enrollment is the root aggregate — state machine drives lifecycle (ACTIVE, PAUSED, EXPIRED, …).
  3. Batch assignments connect enrollments to live cohorts (batch_assignments).
  4. Sessions schedule Zoom-backed classes; students join via gated API route.
  5. Notifications queue transactional email; cron workers drain the queue.
  6. Audit captures before/after snapshots on every mutation.

3. Current V1 Launch Scope

V1 = Existing Student Pilot

V1 is an intentionally narrow internal launch — “Student Learning Hub Beta” — not full commercial GTM.

In V1 Deferred (V1.5+)
Preloaded existing AWS / running cohort students New payer sales funnel as primary path
Manual CSV import + batch calendar CSV Automated CRM → Hub lead sync
OTP login + dashboard + Zoom join Full counselor-led onboarding UX
Roadmap with tentative future batches Payment automation / Razorpay webhooks in critical path
Ops batch/session management Tally AC webhook
Tickets + SLA crons (supporting) WhatsApp BSP (WATI) automation

Launch philosophy

Reliability → Clarity → Throughput → Features

Why limit scope?

  • Operational truth: Running cohorts already exist; software must not block Monday class.
  • Risk reduction: Fewer moving parts = fewer failure modes during first live week.
  • Data quality: Manual preload forces clean batch ↔ student mapping before automation.
  • Feedback loop: Validate join flow, roadmap clarity, and ops console speed before opening sales firehose.

Pilot enablement

Set HUB_PILOT_LAUNCH=true to de-emphasize sales/AC navigation and optimize ops paths for bulk legacy import (/admin/ops/students/import).


4. Key Features

4.1 Student Portal (/app)

Feature Business value Technical implementation
OTP / magic-link login Zero password friction; inbox-verified identity Supabase signInWithOtp/auth/confirmprovisionStudentUserFromInvite
Home dashboard “What do I do next?” in one screen RSC bundle: enrollment + next session + validity (getStudentEnrollmentBundle)
Classes (/app/batches) Current cohort visibility + attendance getMyBatches, batch_assignments + attendance_records
Zoom join Eliminates “link in WhatsApp” failure mode POST /api/app/zoom/join — S2S OAuth, join window, payment gate
Roadmap (/app/roadmap) Retention via future learning visibility plan_courses order + tentative batch start labels
Plan & pause (/app/plan) Self-serve pause ≤30 days requestEnrollmentPause with auto-approve rules
Batch transfer (/app/batches) Self-serve cohort change request requestBatchTransfer → ops approval
Calendar Schedule clarity Sessions across assigned batches
Tickets (/app/tickets) Structured support vs DMs Ticket state machine + SLA cron
Workshops (/app/workshops) Supplementary live events RSVP + eligibility rules
Profile / intake Data completeness intake_forms, profile mutations

4.2 Operations Portal (/admin/ops)

Feature Business value Technical implementation
Student search & detail Single pane of glass for support getStudentOpsDetail composite query
CSV import (pilot) Bulk legacy student load importLegacyStudentsFromCsv + synthetic verified AC
Batch lifecycle Forming → active → cancelled startBatch, cancelBatch, auto-transfer
Session Zoom IDs Ops controls join without deploy sessions.zoom_meeting_id per row
Pipeline / onboarding Task checklist per enrollment onboarding_tasks
Tickets queue SLA visibility listTicketsQueue, processSlaBreaches
Enrollment lifecycle panel Pause / transfer / upgrade Pack 008 services + audit
Workshops admin Create / manage events createWorkshop, ops CRUD

4.3 Future portals (built, not V1-primary)

Portal Purpose
Sales /sales Native AC form submission → ops verification queue
AC /admin/ac Counselor verification + student detail

5. Architecture Deep Dive

5.1 High-level system design

flowchart LR
  subgraph Client
    Browser[Browser]
  end

  subgraph Next["Next.js 16 App Router"]
    RSC[React Server Components]
    SA[Server Actions]
    RH[Route Handlers /api]
    MW[Middleware + guards]
  end

  subgraph Domain["Domain Layer src/lib/services"]
    SVC[Services]
    SM[State machines]
    AUD[Audit emitter]
  end

  subgraph Data
    DRZ[Drizzle ORM]
    PG[(Postgres)]
  end

  Browser --> RSC
  Browser --> SA
  RSC --> SVC
  SA --> SVC
  RH --> SVC
  SVC --> DRZ --> PG
  SVC --> AUD
Loading

5.2 Architectural principles (non-negotiable)

  1. No business logic in routes — Server Actions orchestrate; lib/services/* decides.
  2. No raw SQL state updates — enrollment/batch/ticket transitions go through state-machine functions.
  3. Every mutation auditsaudit_logs with before_snapshot / after_snapshot.
  4. Authorization server-sideassertCan(ctx, action, resource); UI hiding is courtesy only.
  5. Money in integer paisaamount_paisa; format at display layer.
  6. Timestamps UTC — display in IST via date-fns-tz.
  7. Soft deletedeleted_at IS NULL default scopes.

5.3 Frontend architecture

Layer Choice Rationale
Framework Next.js App Router Collocated UI + API + auth; RSC reduces client JS
Language TypeScript strict Contract safety across services and forms
Styling Tailwind 4 + shadcn/ui Consistent design system, accessible primitives
Forms React Hook Form + Zod Shared client/server validation shapes
State Server-first No global client store for domain data; mutations revalidate paths

Rendering strategy

  • RSC for dashboards, lists, detail pages — data fetched on server with request-scoped auth context.
  • Client components only for interactivity: OTP form, join button, filters, toasts (sonner).
  • revalidatePath / tags after Server Actions — freshness without full SPA refetch.

5.4 Backend architecture

There is no separate FastAPI/Node microservice. The “backend” is:

Surface Use case
Server Actions Mutations from staff/student UI
Route Handlers Cron endpoints, Zoom join API, health check
Service layer Transactions, rules, audit, events
// Canonical mutation flow (simplified)
export async function requestEnrollmentPause(input: { ctx, data }) {
  return getDb().transaction(async (tx) => {
    const enrollment = await loadEnrollment(tx, data.enrollmentId);
    await assertCan(input.ctx, 'enrollment_pause.request', enrollment);
    // business rules...
    const pause = await tx.insert(enrollmentPauses).values({...}).returning();
    await logAudit(tx, input.ctx, { action: 'pause.requested', ... });
    return pause;
  });
}

5.5 Auth architecture

sequenceDiagram
  participant S as Student
  participant H as Hub /auth
  participant SB as Supabase Auth
  participant DB as Postgres

  S->>H: Email on /auth/login
  H->>SB: signInWithOtp
  SB-->>S: Email OTP / magic link
  S->>H: /auth/confirm?flow=student
  H->>SB: exchangeCodeForSession
  H->>DB: provisionStudentUserFromInvite
  H-->>S: Session cookie → /app
Loading

Staff flow uses Google OAuth/auth/callbackprovisionStaffUserOnFirstLogin with allowlist (allowed_staff_emails + env lists).

5.6 Caching & consistency

Strategy Application
Request-scoped DB reads No cross-request cache for auth-bound data
Postgres as SoT No Redis in V1 — simplicity
Cron idempotency Notification sends use idempotency keys
Optimistic UI Limited; prefer server revalidation for financial/state data

5.7 Scalability considerations (monolith-first)

  • Connection pooling via Supabase pooler (port 6543).
  • Indexed foreign keys on enrollment, batch, ticket queues.
  • BRIN on audit_logs.created_at for time-range queries.
  • Horizontal scale = stateless Next.js replicas behind load balancer; cron via external scheduler.
  • Future: read replicas, queue workers (Inngest/BullMQ), event bus — see §20.

6. Database Design

6.1 Domain model (ER overview)

erDiagram
  users ||--o| student_profiles : has
  users ||--o| staff_profiles : has
  student_profiles ||--o{ enrollments : owns
  enrollments ||--o{ batch_assignments : has
  batches ||--o{ sessions : contains
  batches ||--o{ batch_assignments : receives
  enrollments ||--o{ enrollment_pauses : may
  enrollments ||--o{ batch_transfers : may
  enrollments ||--o{ tickets : raises
  users ||--o{ audit_logs : actor
Loading

6.2 Core tables

Table Purpose
users Mirrors auth.users; role + identity
student_profiles MD-NNNN code, PII extensions
staff_profiles Trainer/ops metadata
allowed_staff_emails Google SSO allowlist
student_code_counter Atomic MD-1001 generation
plans / courses / plan_courses Catalog & roadmap ordering
ac_forms Admission completion record
enrollments Root lifecycle aggregate
payments Verified money in paisa
intake_forms Student intake submission
onboarding_tasks Checklist per enrollment
batches Cohort container
sessions Scheduled class instances
batch_assignments Enrollment ↔ batch ↔ course
attendance_records Per session per assignment
batch_transfers Cohort change workflow
enrollment_pauses Plan pause workflow
tickets / ticket_messages Support
notifications Email queue
audit_logs Immutable mutation trail
workshops / workshop_rsvps Supplementary events

6.3 Enrollment state machine

PAYMENT_VERIFIED → PRE_ONBOARDING → BATCH_PENDING → BATCH_ASSIGNED → ACTIVE
                                                                      ↓
                                                              PAUSED / EXPIRED / COMPLETED

Transitions only via transitionEnrollment() and related service triggers — never ad-hoc UPDATE state.

6.4 Schema excerpt (illustrative)

-- Enrollments carry plan validity and explicit state
CREATE TYPE enrollment_state AS ENUM (
  'PAYMENT_VERIFIED', 'PRE_ONBOARDING', 'BATCH_PENDING',
  'BATCH_ASSIGNED', 'ACTIVE', 'PAUSED', 'SUSPENDED',
  'COMPLETED', 'EXPIRED', 'REFUNDED', 'CANCELLED'
);

CREATE TABLE enrollments (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  student_profile_id UUID NOT NULL REFERENCES student_profiles(id),
  plan_id UUID NOT NULL REFERENCES plans(id),
  ac_form_id UUID NOT NULL REFERENCES ac_forms(id),
  state enrollment_state NOT NULL,
  plan_validity_end_date DATE,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  deleted_at TIMESTAMPTZ
);

-- One active assignment per enrollment+course (partial unique index)
CREATE TABLE batch_assignments (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  enrollment_id UUID NOT NULL REFERENCES enrollments(id),
  batch_id UUID NOT NULL REFERENCES batches(id),
  course_id UUID NOT NULL REFERENCES courses(id),
  status assignment_status NOT NULL,
  transfer_count INT NOT NULL DEFAULT 0,
  assigned_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

6.5 Indexing philosophy

  • FK columns indexed by default usage paths (enrollment_id, batch_id, student_profile_id).
  • Ticket queue: composite on (status, assigned_to_user_id).
  • Audit: BRIN on created_at for time-slice queries.
  • Partial unique indexes enforce one active assignment per course per enrollment.

6.6 Migrations

13 versioned SQL packs (00010013) applied via:

pnpm db:apply:all

Ledger table _hub_schema_migrations makes re-apply safe on existing environments.


7. Authentication & Security

7.1 Student authentication

Mechanism Detail
Primary Email OTP + optional magic link (/auth/login, /auth/confirm)
Session Supabase SSR cookies via @supabase/ssr
Provisioning users + student_profiles created on first successful auth
Invite (pilot) inviteUserByEmail on CSV import (optional)

7.2 Staff authentication

Mechanism Detail
Primary Google OAuth
Allowlist allowed_staff_emails table + STAFF_OPS_ALLOWLIST / STAFF_SALES_ALLOWLIST env
First login provisionStaffUserOnFirstLogin assigns role from allowlist
Denied Non-allowlisted Google accounts → /auth/denied

7.3 RBAC model

Authorization is action-based, not route-based:

await assertCan(ctx, 'batch_transfer.request', enrollment);

Policies live in src/lib/auth/can.ts30+ actions (enrollment.read_own, ticket.manage, batch.create, …).

7.4 Route protection

  • Middleware validates session for /app/* and /admin/*.
  • Server layouts call getAuthContextOrThrow() — fail closed.
  • Service layer re-checks permissions on every mutation.

7.5 Defense in depth

Layer Mechanism
Application assertCan, service-level ownership checks
Database RLS policies on student-facing tables (Pack 007+)
Network Cron routes require Authorization: Bearer CRON_SECRET
Secrets SUPABASE_SERVICE_ROLE_KEY server-only; never NEXT_PUBLIC_*

7.6 Audit & PII

  • Mutations → audit_logs with snapshots (restricted table).
  • PII reads by non-owning roles can emit audit rows per policy.
  • No PII in unstructured action strings.

7.7 Environment & secrets

Class Examples Exposure
Public NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SITE_URL Client bundle
Server-only DATABASE_URL, SUPABASE_SERVICE_ROLE_KEY, CRON_SECRET, ZOOM_*, RESEND_API_KEY Node runtime only

Run pnpm verify:prod-env before every deploy.


8. Student Journey

journey
  title Student Journey (V1 Pilot)
  section Onboarding
    Receive WhatsApp/email with Hub link: 5: student
    OTP login: 4: student
    Land on dashboard: 5: student
  section Weekly loop
    View next session: 5: student
    Join Zoom (join window): 5: student
    Check roadmap for future batches: 4: student
  section Exceptions
    Request pause on /app/plan: 3: student
    Request batch transfer: 3: student
    Open support ticket: 4: student
Loading
Step Student experience System behavior
1 Receives login link (ops import or manual) Auth user + enrollment pre-created
2 /auth/login → OTP Supabase email via Resend SMTP
3 /app dashboard Next session, validity banner, join CTA
4 Join class ZoomJoinButton/api/app/zoom/join
5 /app/roadmap Completed / in-progress / upcoming courses
6 Optional pause/transfer/ticket Server Actions → ops approval queues

9. Batch Management System

Batch lifecycle

planning → forming → active → completed
                      ↘ cancelled (may auto-transfer students)
Status Meaning
planning Future placeholder cohort (roadmap labels)
forming Accepting assignments
active Live — sessions run, join enabled
cancelled Ops-initiated with reason; transfer engine fires

Assignment model

  • batch_assignments links enrollment_id + course_id + batch_id.
  • Status: assignedactivecompleted / transferred_out.
  • CSV import can bulk-assign via student_emails column on batch calendar upload.

Tentative roadmap

Future batches in planning/forming show “Starts YYYY-MM-DD (tentative)” on student roadmap — retention without over-committing dates.


10. Zoom Integration

Architecture

Component Role
lib/integrations/zoom/* S2S OAuth token cache, meeting API
sessions.zoom_meeting_id Per-class meeting ID (ops-set)
batches.zoom_join_url Optional fallback
POST /api/app/zoom/join AuthZ + join window + registrant display name

Join gate rules (simplified)

  1. Student authenticated with active assignment.
  2. Enrollment not blocked by payment verification (pilot: legacy AC bypass).
  3. Session within join window (scheduled start ± policy).
  4. Meeting ID present on session or batch.

Future

  • Zoom webhooks → automatic attendance sync.
  • Recording link automation.

11. Roadmap Engine

Data sources

Source Drives
plan_courses.display_order Vertical roadmap sequence
batch_assignments + courses In-progress vs completed
Open batches (planning/forming) Tentative upcoming labels

Business logic

Roadmap reduces “what happens after AWS?” anxiety — critical for multi-month programs and upsell continuity without sales calls.

Student transfer interaction

Self-serve transfer on /app/batches creates batch_transfers row → ops approves → optional fee → payment confirm → assignment swap.


12. Role-Based Access Control

Roles

Role Portal V1 usage
student /app Primary
ops /admin/ops Primary
ops_manager /admin/ops Approvals, refunds, overrides
program_manager /admin/ops Batch ownership filters
customer_support /admin/ops Tickets
trainer Limited ops views Roster
academic_counselor /admin/ac, /sales Future-primary
sales /sales Future-primary
leadership All Allowlist admin

Permission matrix (excerpt)

Action student ops ops_manager leadership
enrollment.read_own
batch.read
batch.create
batch_transfer.request
batch_transfer.approve
ticket.create
ticket.manage
allowed_staff_email.create

Full matrix: src/lib/auth/can.ts + planning docs (RBAC_MATRIX.md in planning bundle).


13. API Documentation

Note: Most mutations use Server Actions, not public REST. Below are Route Handlers and representative patterns.

Health

GET /api/health
{ "status": "ok" }

Student Zoom join

POST /api/app/zoom/join
Cookie: sb-access-token=...
Content-Type: application/json

{ "sessionId": "uuid" }
{ "joinUrl": "https://zoom.us/j/..." }

Cron (secured)

POST /api/cron/process-notifications
Authorization: Bearer <CRON_SECRET>
POST /api/cron/enrollment-lifecycle
Authorization: Bearer <CRON_SECRET>
{ "expired": 2, "pausesActivated": 0, "transferTimeouts": 1 }

Server Action pattern (internal)

// app/app/plan/actions.ts
'use server';
export async function requestPauseAction(input) {
  const ctx = await getAuthContextOrThrow();
  const pause = await requestEnrollmentPause({ ctx, data: input });
  revalidatePath('/app/plan');
  return { success: true, pauseId: pause.id };
}

14. Folder Structure

src/
├── app/
│   ├── (public)/          # Marketing landing
│   ├── auth/              # OTP, Google callback, denied
│   ├── app/               # Student portal
│   ├── admin/
│   │   ├── (console)/ops/ # Operations console
│   │   └── ac/            # Academic counselor
│   ├── sales/             # AC submission (future-primary)
│   └── api/
│       ├── cron/          # Scheduled workers
│       ├── health/
│       └── app/zoom/join/
├── components/
│   ├── ui/                # shadcn primitives
│   ├── student/           # Student feature UI
│   ├── ops/               # Ops feature UI
│   └── layout/            # Shells, nav, tables
├── lib/
│   ├── auth/              # can(), context, allowlist
│   ├── db/                # Drizzle schema + client
│   ├── services/          # Domain logic (THE core)
│   ├── integrations/      # Zoom, Resend adapters
│   ├── events/            # Domain event bus
│   └── utils/             # Pure formatters
├── scripts/               # Ops CLI + smoke tests
└── tests/                 # Vitest unit + integration

drizzle/                   # Versioned SQL migrations

Philosophy: services/{domain}/{verb}-{entity}.ts — discoverable, testable, transaction-friendly.


15. Local Development Setup

Prerequisites

Tool Version
Node.js 20+
pnpm 9+
Supabase project ap-south-1 recommended
Docker Optional (for local Postgres)

Installation

git clone <repository-url>
cd hub
pnpm install
cp .env.example .env.local
# Fill: NEXT_PUBLIC_SUPABASE_*, SUPABASE_SERVICE_ROLE_KEY, DATABASE_URL, CRON_SECRET

Database

pnpm db:apply:all    # migrations 0001–0013
pnpm db:seed         # plans, courses, email templates

Run

pnpm dev
# http://localhost:3000

Quality gates

pnpm exec tsc --noEmit
pnpm lint
pnpm test
pnpm staging:gates -- student@example.com   # needs DB + test student

Troubleshooting

Issue Fix
OTP email not arriving Configure Supabase custom SMTP (Resend); check redirect URLs
user_not_found in scripts Set DEV_FOUNDER_EMAIL / OPS_VERIFY_EMAIL
Migration drift pnpm db:sync-migration-ledger then pnpm db:apply:all
Zoom join disabled Set zoom_meeting_id on session; open join window script

16. Environment Variables

Variable Required Description
NEXT_PUBLIC_SITE_URL Canonical URL for OTP redirects
NEXT_PUBLIC_SUPABASE_URL Supabase project URL
NEXT_PUBLIC_SUPABASE_ANON_KEY Anon key (client-safe)
SUPABASE_SERVICE_ROLE_KEY Server-only admin API
DATABASE_URL Postgres pooler URL (:6543)
CRON_SECRET Bearer token for cron routes
RESEND_API_KEY ⚠️ App email queue
RESEND_FROM_EMAIL ⚠️ Verified sender
STAFF_OPS_ALLOWLIST Comma-separated Gmail
STAFF_SALES_ALLOWLIST ⚠️ Sales portal access
ZOOM_ACCOUNT_ID S2S OAuth
ZOOM_CLIENT_ID S2S OAuth
ZOOM_CLIENT_SECRET S2S OAuth
THINKIFIC_BASE_URL ⚠️ Deep links
HUB_PILOT_LAUNCH true for existing-student pilot UI
STUDENT_SUPPORT_EMAIL Support card
DEV_FOUNDER_EMAIL dev Local AC/ops bypass

See .env.example for full list. Validate with pnpm verify:prod-env.


17. Deployment Guide

Recommended: DigitalOcean App Platform

The repo ships .do/app.staging.yaml — Node build, port 3000, env secrets in DO console.

# Build
pnpm install && pnpm build
# Start
pnpm start
Environment Host
Staging hub-staging.microdegree.work
Production hub.microdegree.work

Alternative: Vercel

Architecture is compatible (Next.js 16). Configure same env vars; set Cron via Vercel Cron or external scheduler hitting /api/cron/*.

Supabase production

  1. Separate project per environment.
  2. Enable pgcrypto.
  3. Auth → SMTP (Resend) + redirect URLs for /auth/confirm, /auth/callback.
  4. Backup before pnpm db:apply:all.

Cron scheduler (required)

Schedule Endpoint
Every 10 min /api/cron/process-notifications
Hourly /api/cron/ticket-sla
Daily 02:00 IST /api/cron/ticket-auto-close
Daily 03:00 IST /api/cron/enrollment-lifecycle
curl -fsS -X POST \
  -H "Authorization: Bearer $CRON_SECRET" \
  "https://hub.microdegree.work/api/cron/process-notifications"

Verify: pnpm staging:verify-host -- https://hub.microdegree.work

CI/CD (recommended)

# Illustrative pipeline
- pnpm install --frozen-lockfile
- pnpm exec tsc --noEmit
- pnpm lint
- pnpm test
- pnpm build
# deploy to DO / Vercel

Rollback

  • App: Redeploy previous container image / Vercel deployment.
  • DB: Forward-only migrations — restore from Supabase backup if needed (last resort).

18. Performance Optimizations

Technique Application
React Server Components Zero client JS for read-heavy dashboards
Selective use client Forms, join button, interactive tables only
Connection pooling Supabase pooler; short-lived connections
Transactional writes Consistent mutations without distributed locks
Path revalidation Surgical cache bust after mutations
IST formatters Pure functions; no runtime timezone guessing
Pagination Ops queues (tickets, students) — cursor/offset patterns

Not in V1: Redis, edge caching of authenticated pages, React Query — intentional simplicity.


19. Operational Philosophy

Reliability over feature surface

V1 removed counselor-first onboarding from the critical path because:

  • Running cohorts do not need sales workflow to attend Monday class.
  • Every extra portal is a failure mode and training burden.
  • Manual CSV preload guarantees data correctness before automation.

Ops is the customer

Hub optimizes for:

  1. Time-to-join (student).
  2. Time-to-assign (ops).
  3. Time-to-answer (support tickets).

Auditability is non-negotiable

When ops asks “who moved this student?” — audit_logs answers with actor, before/after, timestamp.


20. Scalability Vision

Phase Capability
V1 Monolith, Postgres, cron HTTP
V1.5 Razorpay webhooks, Tally AC, WhatsApp notifications
V2 Event outbox, queue workers, read replicas
V3 Multi-tenant isolation, regional deployments, mobile apps
flowchart TB
  subgraph Future["Future event-driven"]
    API[Hub API]
    OUTBOX[(outbox_events)]
    WORKER[Queue workers]
    WH[Webhooks Zoom/Razorpay/WATI]
  end
  API --> OUTBOX --> WORKER
  WH --> API
Loading

21. Future Roadmap

V1 — Existing Student Pilot ✅ (current)

  • OTP login, dashboard, Zoom join, roadmap, ops CSV import, batch/session ops, tickets, workshops, pause/transfer.

V1.5 — Operational automation

  • Native sales AC as primary acquisition path.
  • Razorpay payment confirmation webhooks.
  • Resend domain production hardening.
  • Zoom attendance webhook ingestion.

V2 — Growth platform

  • CRM sync (LeadSquared).
  • WhatsApp BSP (WATI).
  • Advanced analytics / ops dashboards.
  • Student mobile PWA polish (Serwist groundwork exists).

V3 — Intelligence & scale

  • AI cohort recommendations.
  • Predictive churn alerts.
  • LMS deeper integration.
  • Multi-program tenant model.

22. Testing Strategy

Layer Tool Scope
Unit Vitest Services, utils, RBAC helpers
Integration Vitest + real DB AC verify → student, tickets, auth provision
Smoke pnpm staging:gates Env + E2E service paths
Host pnpm staging:verify-host Health + cron auth
pnpm test
pnpm test:integration:db
pnpm test:integration:auth
pnpm smoke:post-migrate -- student@example.com

Future: Playwright for browser E2E, k6 load tests on join endpoint.


23. Monitoring & Observability

Signal V1 implementation
Health GET /api/health — uptime checks
Audit audit_logs — forensic
Cron success HTTP status + JSON counts from cron routes
Logs Structured server logs (avoid PII)

Future stack candidates: Sentry, Better Stack, Axiom, OpenTelemetry.


24. Contributor Guide

Branch strategy

main          → production-ready
feature/*     → scoped changes

PR expectations

  • tsc --noEmit clean.
  • pnpm lint clean.
  • Service mutations include audit emission.
  • No business logic in route files.
  • One implementation pack per PR when possible.

Commit format

feat(student): self-serve batch transfer on /app/batches
fix(zoom): join window timezone edge case
chore(db): apply 0013 enrollment changes migration

25. Lessons Learned

  1. Monolith + strict services beat premature microservices for a small ops-heavy team.
  2. State machines in code with Postgres enums — not free-form status strings — saved support hours.
  3. Pilot scope discipline prevented launch paralysis; CSV import unlocked real users faster than perfect sales flow.
  4. Supabase Auth + custom SMTP separation confused teams until documented (Auth email ≠ Resend app email).
  5. Audit logs paid off the first time ops asked “who changed this enrollment?”

26. Screenshots

Replace placeholders with real captures before investor demo.

Screen Placeholder
Student dashboard docs/assets/student-dashboard.png
Roadmap docs/assets/roadmap.png
Ops student detail docs/assets/ops-student-detail.png
Batch sessions docs/assets/batch-sessions.png

27. License

Proprietary. © MicroDegree. All rights reserved.

Unauthorized copying, distribution, or use of this software is prohibited.


28. Contact

Channel Link
Product microdegree.in
Student hub hub.microdegree.work
Support support@microdegree.work

Built with discipline for operators and students who deserve a calmer Monday morning.

⭐ If this README helped you onboard — star the repo.

About

MicroDegree Hub — student operations & cohort management (Next.js + Supabase)

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages