Website: https://eco.wahb.space
Repository: github.com/wahb-amir/ecolens
Status: Ready (documentation)
EcoLens turns everyday waste photos into measurable environmental impact. Users scan or upload waste, an AI classifies the material, and EcoLens calculates CO₂ offset, water saved, and energy saved. Gamified achievements, material-specific tips, and a global leaderboard drive sustained behavior change.
| Dashboard | Achievements | Leaderboard |
|---|---|---|
![]() |
![]() |
![]() |
- AI Classification (Camera & Upload) — Detects plastic, metal, paper, glass, organic, and more; returns label + confidence.
- Impact Estimation — Converts classifications into estimated weight → CO₂ offset, water & energy savings, and eco points.
- Gamification — Achievements, streaks, and a global leaderboard to boost retention.
- User Dashboard — Lifetime impact, daily streaks, category breakdowns, and progress visualizations.
- Real-time Feedback — Immediate toast notifications and in-app unlocks on new achievements.
- Privacy-first Auth — HTTP-only cookies for tokens; minimal PII exposure.
- Scan / Upload: User captures an image via mobile or desktop.
- Predict: Frontend sends
POST /api/predict(withaccess_tokencookie) to backend. - Inference: Backend proxies image to the hosted ML service, receiving labels + confidences.
- Calculate: Label → estimated weight → environmental impact (CO₂, water, energy) → points.
- Persist: Creates
Scanrecord, updatesUserstats, runs achievement engine, recalculates leaderboard rank. - Notify: UI updates in real time (websocket or polling) and displays results & badges.
- Frontend: Next.js, React, Tailwind CSS
- Backend: Node.js, Next.js API routes (TypeScript), Express-style handlers where applicable
- ML Service: Python (PyTorch / TensorFlow) or hosted Hugging Face Spaces endpoint
- Database: MongoDB (primary); Redis (caching, leaderboard, rate-limits)
- Auth: JWTs delivered via secure,
httpOnlycookies (access_token,refresh_token) - Deployment: Hostinger VPS with NGINX, PM2, Let’s Encrypt SSL
Base URL: https://eco.wahb.space/api
All endpoints use JSON. Auth is via
httpOnlycookies unless otherwise noted.
- Recommended cookie names:
access_token(short-lived ~15m),refresh_token(long-lived ~7d),verification_token(OTP flow). - Tokens are set with
httpOnlyandsecureflags in production. UsesameSite: 'strict'. - OTP TTL: 1 hour. Max OTP attempts: 10.
Create a new user and start email verification (sends OTP).
Request
{ "name": "Wahb Amir", "email": "wahb@example.com", "password": "supersecurepassword" }Responses
201 Created— account created, verification OTP sent. Cookie set:verificationToken(httpOnly, 1h).{ "msg": "Account created. Verification code sent to wahb@example.com" }400— validation error409— email already exists500— server / email provider failure
Notes: Creation runs inside a MongoDB transaction (user + OTP). If email fails post‑commit, the system attempts cleanup.
Authenticate existing user. Handles verified and unverified flows.
Request
{ "email": "wahb@example.com", "password": "supersecurepassword" }Behavior
- Invalid credentials →
401 Invalid email or password(generic to prevent enumeration). - If unverified: triggers OTP flow (returns
200withpending_verificationorotp_sent). - If verified: issues
access_token&refresh_tokencookies.
Success
{ "success": true, "message": "Login successful" }Cookie policy (prod)
access_token:httpOnly,secure,sameSite: 'strict', expires ≈ 15 minutesrefresh_token:httpOnly,secure,sameSite: 'strict', expires ≈ 7 days
Verify email OTP. Uses verificationToken cookie if present, or email + otp in body.
Request
{ "otp": "123456" }Success (200)
- Marks user
isVerified = true, deletes OTP record(s), issues auth cookies, clearsverificationToken.
{ "message": "Account verified and logged in", "user": { "id": "...", "email": "...", "role": "user" } }Errors
400invalid body401incorrect OTP410OTP expired429too many attempts404user not found500server error
Implementation note: standardize cookie names to access_token / refresh_token (some code paths use accessToken).
Terminate session. Clears relevant cookies and sets Clear-Site-Data headers.
Response (200)
{ "success": true, "message": "Session terminated successfully", "timestamp": "2026-02-24T12:34:56.789Z" }Request password reset OTP.
Request
{ "email": "wahb@example.com" }Responses
200OTP sent (or generic 200 to avoid enumeration)500email send error
Submit OTP + new password.
Request
{ "email":"wahb@example.com", "otp":"123456", "newPassword":"newStrongPassword" }Responses
200password reset successful400/401/410/429OTP related errors500server error
Get top users (verified-only) and optionally current user's rank.
Query
userId(optional) — compute rank for this user if not in top N
Response (200)
{ "success": true, "data": [ /* top list */ ], "currentUser": { /* optional */ } }Notes
- Only verified users appear on global leaderboard. If a supplied
userIdis outside top results, service returns computed rank.
Run AI prediction for an image. Auth required (access_token).
Request
{ "dataUrl": "data:image/jpeg;base64,..." }Valid match (Success)
- Backend proxies to inference service, maps label → category, computes
pointsEarned = round(confidence * 20), updates user & achievements, creates aScan.
{
"predictions": [{ "label": "Plastic Bottle", "prob": 0.92 }],
"scanId": "624f...",
"pointsEarned": 18,
"newAchievements": ["waste_warrior"],
"userStats": { "streak": 3, "totalScans": 51, "ecoScore": 680, "achievementsCount": 5 },
"inference_time": 1.234
}No confident match
{ "predictions": [{ "label": "unknown", "prob": 0.20 }], "noMatch": true, "message": "No confident match" }Errors
401missing/invalid token400missingdataUrl422model failed to identify500internal
Check auth & optionally rotate tokens with refresh_token.
Response (200)
{ "id": "userId" } // or null if unauthenticatedReturn authenticated user's dashboard stats.
Response
{
"totalScans": 48,
"ecoScore": 663,
"streak": 1,
"categoryStats": { "plastic":9, "paper":12, "glass":3, "metal":2, "organic":0, "other":0 },
"unlockedAchievements": ["first_step","recycling_rookie"]
}Errors: 401 / 404 / 500
users
{
"name": "String",
"email": "String",
"password": "String",
"isVerified": "Boolean",
"ecoScore": "Number",
"totalScans": "Number",
"streak": "Number",
"lastScanDate": "Date",
"categoryStats": { "plastic":0, "paper":0, "glass":0, "metal":0, "organic":0, "other":0 },
"achievements": [{ "achievementId":"String", "unlockedAt":"Date" }],
"tokens": [{ "token":"String", "createdAt":"Date" }],
"createdAt": "Date",
"updatedAt": "Date"
}scans
{
"userId": "ObjectId",
"label": "String",
"confidence": "Number",
"imageUrl": "String (optional)",
"pointsEarned": "Number",
"metadata": "Mixed",
"createdAt": "Date"
}otp
{
"userId": "ObjectId",
"type": "email_verification | password_reset",
"codeHash": "String",
"expiresAt": "Date",
"attempts": "Number"
}Indexes: { userId, type } unique; expiresAt TTL index.
Login (verified user)
curl -X POST https://eco.wahb.space/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"supersecurepassword"}' \
-c cookies.txt -iPredict
curl -X POST https://eco.wahb.space/api/predict \
-H "Content-Type: application/json" \
-b cookies.txt \
-d '{"dataUrl":"data:image/jpeg;base64,..."}'Get leaderboard
curl https://eco.wahb.space/api/leaderboard- Enforce HTTPS and
securecookie flag in all environments except local dev. - Rate limit
POST /api/predictand auth endpoints to prevent abuse (e.g. 10–30 r/m per IP for predict depending on infra). - Monitor ML latency & fallback gracefully (return
422withnoMatchif model unavailable). - Log critical events (OTP sends, login failures, token rotations) with a centralized logger.
- Add automated tests for auth flows, OTP edge cases, and leaderboard ranking logic.
- Clone repo:
git clone https://github.com/wahb-amir/ecolens.git
cd ecolens- Install:
pnpm install # or npm install- Environment:
cp .env.example .env
# populate MONGO_URI, JWT secrets, SMTP creds, HF_SPACE_URL (if any)- Run
📄 components.json
📄 next.config.ts
📄 next-env.d.ts
📄 package.json
📄 package-lock.json
📄 pnpm-lock.yaml
📄 postcss.config.mjs
📁 public
├── 🌙 2k_moon.avif
├── 🌙 2k_moon.jpg
├── 🌞 earth_day.avif
├── 🌞 earth_day.jpg
├── 🌃 earth_night.avif
├── 🌃 earth_night.jpg
├── 🟩 favicon_io
│ ├── 128x128.png
│ ├── 16x16.png
│ ├── 256x256.png
│ ├── 32x32.png
│ ├── 48x48.png
│ └── 64x64.png
└── 🏷️ logo.jpg
📄 README.md
📁 screenshots
└── 🖼️ flowchart.png
📁 src
├── 📁 app
│ ├── ⚙️ actions
│ │ └── process-scan.ts
│ ├── 🔑 api
│ │ ├── auth/... (login, logout, register, verify, password)
│ │ ├── leaderboard/route.ts
│ │ ├── predict/route.ts
│ │ └── user/... (me, stats)
│ ├── (auth)/... (login, register, forgot-password, verify-otp)
│ ├── 🖥️ dashboard/... (achievements, leaderboard, page.tsx, layout)
│ ├── 🌐 globals.css
│ ├── 🏗️ layout.tsx
│ ├── ❌ not-found.tsx
│ └── 🔐 providers/AuthProvider.tsx
├── 📁 components/... (auth, dashboard, landing, layout, ui, waste-classifier)
├── 🎣 hooks/... (use-eco-tracker, useLeaderboard, use-toast)
├── 📚 lib/... (achievements, data, eco-points, leaderboard, mail, mongo, password, placeholder-images, token, types, utils)
└── 🗂️ Modal/... (otp.ts, scan.ts, user.ts)
📄 tailwind.config.ts
📄 tsconfig.json
✨ Notes:
📁 = Folder
📄 = File
🖼️ = Screenshot/Image
⚙️ / 🔑 / 🖥️ / 🎣 / 📚 / 🗂️ = Custom emojis to make sections visually distinct
Files in parentheses (...) represent grouped files for brevity



