β οΈ Disclaimer: This is a personal learning & practice project, vibe coded to explore full-stack development. Not affiliated with, endorsed by, or connected to luminalearning.com in any way.
- Overview
- App-Overview
- Tech Stack
- Architecture
- Key Features
- Security
- Redis & Session Management
- Search & Discovery
- Getting Started
- Diagnostics & Inspection
- CI/CD Pipeline
- Observability
- AI Log Analyzer
- Contributing
Lumina Learning is a vibe-coded, full-stack EdTech platform built purely for learning purposes. The goal was simple β take a real-world project idea and build it properly, with production-grade patterns: Redis sessions, Docker containerization, secure auth, and a dynamic React frontend.
No shortcuts. No tutorials copy-pasted. Just vibes, curiosity, and a lot of debugging. π
| Layer | Technology |
|---|---|
| Frontend | React 19, Vite (HMR + Asset Bundling) |
| Backend | Node.js, Express |
| Database | PostgreSQL (primary data store) |
| Cache / Sessions | Redis 7 |
| Containerization | Docker & Docker Compose |
| Auth | bcryptjs, express-session, Custom Redis Store |
graph TD
User((User Browser)) -->|HTTPS / JSON| App[Express + Vite Server]
subgraph Application_Layer
App --> Auth[Auth & Session Logic]
App --> API[Course & Enrollment API]
Auth -->|Sanitize| Env[Environment Utility]
end
subgraph Data_Persistence
Auth -->|Custom Store| Redis[(Redis 7 - Sessions)]
API -->|SQL| DB[(PostgreSQL - Data)]
end
subgraph Frontend_SPA
Vite[Vite Dev Server] -->|HMR / Assets| User
React[React 19 UI] -->|State Management| User
end
Standard session libraries (e.g., connect-redis) fail to communicate correctly with Redis 7 in Docker environments, producing ERR syntax error at runtime.
Solution: A fully custom Redis Session Store was implemented directly in the backend, using native Redis commands (SET key value EX seconds) rather than library abstractions.
Result:
- β 100% compatibility with Redis 7
- β Zero session-save failures
- β Full control over TTL and serialization
Docker environments can silently introduce bugs when .env files contain literal quote characters in connection strings, causing the app to fail on startup.
Solution: A cleanEnv utility automatically sanitizes all configuration strings β DATABASE_URL, REDIS_URL, etc. β before the application consumes them.
Result:
- β Prevents malformed connection strings
- β Works reliably across all host environments
- β Zero manual debugging of quote-escaping issues
Instead of leaking a "User already exists" error (which enables account enumeration attacks), the auth layer intelligently detects an existing account and logs the user in silently.
Result:
- β Improved security posture β no account enumeration
- β Better UX β no confusing errors on duplicate registration
Security is baked into the core of the application, not added as an afterthought.
| Threat | Mitigation |
|---|---|
| Plaintext passwords | bcryptjs hashing with salt factor 10 β raw passwords are never stored |
| XSS cookie theft | httpOnly: true on all session cookies |
| CSRF attacks | sameSite: "lax" on all session cookies |
| Session forgery | Sessions signed with a 64-character hex SESSION_SECRET |
| SQL Injection | Parameterized queries (PostgreSQL) & prepared statements (SQLite) |
| Stale sessions | maxAge of 7 days with automatic TTL expiration enforced in Redis |
| Secret rotation | Rotating SESSION_SECRET instantly invalidates all active sessions |
Note: Rotating
SESSION_SECRETis a hard logout for all users. This is intentional behavior for incident response.
Redis serves as the short-term memory of the application β purpose-built for fast session lookups and stateless horizontal scaling.
1. User submits credentials β POST /api/auth/login
2. Server validates password against PostgreSQL (bcrypt compare)
3. Server generates a unique Session ID (sid)
4. Server stores { userId: 1 } in Redis β key: sess:<sid>
5. Server sends sid to browser in a signed, HttpOnly cookie
6. On every subsequent request:
Browser sends cookie
β Server reads sid
β Server fetches userId from Redis (sub-millisecond)
β Request is authenticated β
| Property | Value |
|---|---|
| Storage backend | Redis 7 (persists across server restarts) |
| TTL | 7 days, calculated manually by CustomRedisStore |
| Key format | sess:<sessionId> |
| Scaling model | Stateless β supports horizontal scaling behind a load balancer |
| Lookup latency | Sub-millisecond |
The platform features a dynamic, real-time course search built entirely on the client β no page reloads, no server round-trips.
User types in search box
β
Client-side filter executes on `courses` state (zero API calls)
β
Algorithm scans: Title Β· Description Β· Category
β
Results update instantly on every keystroke
- Real-time filtering β results appear as you type
- Multi-field matching β searches title, description, and category simultaneously
- Case-insensitive β
Reactmatchesreact,REACT, andReAcT - Zero-state handling β a dedicated "No results found" view renders when the query returns nothing
git clone https://github.com/nafisrahman006/lumina-ed.git
cd lumina-edcp .env.example .envOpen .env and fill in your values:
Generate a Secure Session Secret
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"Copy the output
β οΈ Do not wrap values in quotes inside.envfiles. ThecleanEnvutility handles sanitization, but bare values are always preferred.
docker compose up --buildThe platform will be available at http://localhost:3000
Use these commands to inspect the live state of the application at runtime.
docker exec -it edtech-platform-db psql -U edtech_user -d edtech_db \
-c "SELECT id, email, name FROM users;"docker exec -it edtech-platform-redis redis-cli keys "*"docker exec -it edtech-platform-redis redis-cli get "sess:YOUR_SESSION_ID"docker exec -it edtech-platform-db psql -U edtech_user -d edtech_db -c "
SELECT
u.name AS student_name,
u.email,
c.title AS course_name,
e.enrolled_at
FROM enrollments e
JOIN users u ON e.user_id = u.id
JOIN courses c ON e.course_id = c.id
ORDER BY e.enrolled_at DESC;"# Wipe PostgreSQL
docker exec -it edtech-platform-db psql -U edtech_user -d edtech_db \
-c "TRUNCATE users, courses, lessons, enrollments RESTART IDENTITY CASCADE;"
# Wipe Redis
docker exec -it edtech-platform-redis redis-cli FLUSHALL
β οΈ Irreversible. Use only in development or staging environments.
Built with GitHub Actions, Docker, and Trivy security scanning.
flowchart LR
A[Push to dev] --> B[π Lint]
B --> C[π§ͺ Test]
C --> D[π Auto PR]
D --> E[β
Merge to main]
flowchart LR
A[Push to docker] --> B[π³ Docker Build]
B --> C[π‘οΈ Trivy Scan]
C --> D[π Auto PR]
D --> E[β
Merge to main]
flowchart LR
A[git tag v1.0.0] --> B[π³ Docker Build]
B --> C[π¦ Push to Docker Hub]
C --> D[β
lumina-learning:v1.0.0]
The platform includes a comprehensive observability stack built with Prometheus, Grafana, and industry-standard exporters to monitor system health, metrics, and performance in real-time.
| Component | Purpose | Port |
|---|---|---|
| Prometheus | Metrics scraping and time-series database | 9090 |
| Grafana | Visualization & dashboard creation | 3001 |
| cAdvisor | Container metrics (CPU, memory, network) | 8080 |
| Redis Exporter | Redis performance and key statistics | 9121 |
| Node Exporter | Host-level system metrics (disk, CPU, memory) | 9100 |
Exporters (cAdvisor, Redis, Node)
β
Prometheus (Scrapes every 15s)
β
Grafana (Visualizes & Alerts)
β
Dashboards & Analytics
The platform comes pre-configured with Grafana dashboards for:
- System Health β CPU, memory, disk usage
- Container Metrics β Application container performance
- Redis Monitoring β Key counts, memory usage, command latency
- Application Uptime β Service availability and response times
Real-time monitoring dashboards showing system metrics, container performance, and application health powered by Prometheus.
Redis exporter providing detailed metrics including key counts, memory consumption, command latency, and eviction statistics.
- Prometheus (raw metrics):
http://localhost:9090 - Grafana (dashboards):
http://localhost:3001- Default credentials:
admin/admin - Pre-configured datasource: Prometheus (
http://prometheus:9090)
- Default credentials:
Prometheus scrapes metrics every 15 seconds from the following targets:
# Prometheus β Targets
- localhost:9090 # Prometheus itself
- edtech-cadvisor:8080 # Container metrics
- edtech-redis-exporter:9121 # Redis stats
- edtech-node-exporter:9100 # Host metricsConfiguration file: observability/prometheus.yml
The platform includes an AI-powered sidecar container that watches Docker logs in real time, detects errors, and sends an instant analysis to Slack β with zero changes to the application source code.
flowchart TD
A[edtech-platform-app] -->|stdout/stderr| B[Docker Log Stream]
B -->|watched by| C[ai-analyzer sidecar]
C --> D{Is it an ERROR?}
D -->|No| E[Ignored]
D -->|Yes| F[π§Ή Sanitize Log]
F -->|remove emails, passwords, IPs, tokens| G[Clean Log]
G -->|send to| H[Google Gemini 2.0 Flash]
H -->|analysis| I[π΄ Root Cause + π§ Fix + π‘ Code]
I --> J[π¨ Slack Alert]
I --> K[Docker Logs]
Docker Compose Stack
βββ edtech-platform-app β app
βββ edtech-platform-db β PostgreSQL
βββ edtech-platform-redis β Redis
βββ edtech-ai-analyzer β sidecar (reads app logs via docker.sock)
β
βββ Mounts: /var/run/docker.sock (read-only access to Docker logs)
βββ No ports exposed
βββ No changes to app container needed
Before any log is sent to Gemini, all sensitive data is automatically stripped:
| Data | Before | After |
|---|---|---|
| Emails | [email protected] |
[EMAIL] |
| DB URLs | postgresql://user:pass@host |
postgresql://[REDACTED] |
| Redis URLs | redis://:secret@host |
redis://[REDACTED] |
| Session IDs | sess:abc123xyz |
sess:[REDACTED] |
| JWT tokens | eyJhbGci... |
[JWT] |
| IP addresses | 192.168.1.105 |
[IP] |
| API keys | AIzaSyD8x... |
[SECRET] |
Gemini only ever sees the error type and stack trace β never real credentials or user data.
Add to .env:
GEMINI_API_KEY=your_key_here
SLACK_WEBHOOK_URL=your_webhook_here View analyzer logs:
docker logs edtech-ai-analyzerThis is a personal learning project, but PRs and suggestions are always welcome!
- Fork the repository
- Create your feature branch:
git checkout -b feature/your-feature-name - Commit your changes:
git commit -m 'feat: describe your change' - Push to the branch:
git push origin feature/your-feature-name - Open a Pull Request
Please follow Conventional Commits for all commit messages.
Vibe coded with curiosity. Built with real-world patterns. Broken many times. Fixed every time. π
Lumina Learning Β· Report a Bug Β· Request a Feature
β οΈ This is a learning/practice project. Not affiliated with luminalearning.com



