Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/vanegasjoseignacio2-cyber/Eco-It/llms.txt

Use this file to discover all available pages before exploring further.

Eco-It is designed as a vertically integrated platform where every layer — from the database schema to the browser UI — is maintained in a single repository and deployed as two coordinated services. This page explains how those layers are structured, how they communicate, and how security concerns are handled at each boundary.

Monorepo Layout

Eco-It uses pnpm workspaces to manage two packages from a single repository root. The pnpm-workspace.yaml declares both workspace members, and the root package.json provides shared scripts that orchestrate both services together.
Eco-It/                          ← repository root
├── pnpm-workspace.yaml          ← workspace manifest (backend, frontend)
├── package.json                 ← root scripts + concurrently devDep

├── backend/                     ← Express 4 API workspace
│   ├── index.js                 ← application entry point
│   ├── controllers/             ← route handler logic
│   ├── middlewares/             ← auth, limiters, validation
│   ├── models/                  ← Mongoose schemas
│   ├── routes/                  ← Express router definitions
│   └── utils/                   ← Cloudinary config, helpers

└── frontend/                    ← React 19 + Vite workspace
    ├── vite.config.js           ← Vite config + dev proxy
    ├── src/
    │   ├── App.jsx              ← root router
    │   ├── components/          ← pages, UI, animations
    │   ├── context/             ← React context + route guards
    │   └── Routes/              ← custom route wrappers
    └── dist/                    ← production build output
The root-level scripts delegate to each workspace:
ScriptWhat it does
pnpm devRuns concurrently "pnpm dev:backend" "pnpm dev:frontend"
pnpm dev:backendpnpm --filter backend devnodemon index.js
pnpm dev:frontendpnpm --filter frontend devvite
pnpm buildpnpm --filter frontend buildvite build
The allowBuilds key in pnpm-workspace.yaml explicitly permits @openrouter/sdk to run its postinstall build script. This is required for the OpenRouter SDK to function correctly in the backend workspace.

Backend

The backend is a Node.js / Express 4 HTTP server that also hosts a Socket.io WebSocket server on the same port. Both are attached to a shared httpServer instance created by Node’s built-in http.createServer.

Middleware Stack

Requests pass through the following middleware in order before reaching any route handler:
The order of middleware registration in index.js is intentional. Security headers (Helmet) and CORS must run before any business logic so that responses cannot leak sensitive headers even when a handler throws early.
  1. helmet — Sets HTTP security headers including a strict Content Security Policy.
  2. cors — Restricts cross-origin requests to an explicit allowlist (see Security).
  3. express.json / express.urlencoded — Parses request bodies with a 10 MB size cap.
  4. express-mongo-sanitize (manual) — Strips MongoDB operator characters ($, .) from req.body and req.params to prevent NoSQL injection.
  5. globalLimiter — Applies the global rate limit (300 requests / 15 min / IP) to all routes.
  6. express-session + passport — Initializes Passport.js session support for OAuth flows.

Authentication

Eco-It implements two parallel authentication strategies via Passport.js:
  • Local strategy — email + password login. Credentials are verified directly using bcryptjs.compare in controllers/loginController.js; the User model stores passwords pre-hashed via a pre('save') hook.
  • Google OAuth 2.0 (passport-google-oauth20) — one-click sign-in via setupGoogleAuth() in controllers/AutheticationGoogle.js.
After a successful login (either strategy), the server issues a signed JWT (jsonwebtoken) that the client stores and sends with every subsequent request in the Authorization: Bearer <token> header. The authMiddleware.js verifies this token before any protected route handler runs. Socket.io connections are also authenticated: the middleware at the top of io.use(...) extracts the JWT from socket.handshake.auth.token and calls jwt.verify before allowing the WebSocket upgrade.

Mongoose Models

ModelCollectionPurpose
UserusersCore user document — profile, role (user, admin, superadmin), OAuth data, last-seen timestamp
PuntoReciclajepuntoreciclajesVerified recycling collection points with geolocation and material categories
ChatchatsEcoBot conversation history linked to a user
NotificationnotificationsAdmin-action notification records (e.g., ban alerts, language warnings); tracks which admins have read each record via a readBy array; auto-expires after 7 days via a TTL index
AuditLogauditlogsTimestamped record of admin actions for compliance
CarouselSlidecarouselslidesHomepage carousel content managed by admins
PendingRegistrationpendingregistrationsTemporary records for users mid-OAuth profile-completion flow

API Routes

PrefixRouter fileScope
/api/authauthRoutes.jsLogin, register, Google OAuth, password recovery
/api/useruserRoutes.jsProfile read/update, avatar upload
/api/aiaiRoutes.jsEcoBot chat completions via OpenRouter
/api/adminadmin.jsAdmin-only CRUD, audit logs, user management
/api/contactcontactRoutes.jsContact form → Nodemailer email dispatch
/api/carouselcarouselRoutes.jsCarousel slide management
/api/mapmap.jsPublic recycling map point reads and admin writes

Real-time Layer (Socket.io)

The Socket.io server exposes two exported singletons — io (the server instance) and usuariosConectados (an in-memory Map of active user sessions). Route handlers access the io instance through req.app.get('io') to push events to connected clients without coupling the socket logic into controllers. Key events:
EventDirectionDescription
usuario:conectadoClient → ServerClient announces its identity after the handshake; server adds user to usuariosConectados and joins admin users to the admins room
usuario:estadoServer → AllBroadcasts { userId, isOnline: true/false } when a user connects or fully disconnects
usuarios:onlineServer → AllBroadcasts the current count of unique online users

Frontend

The frontend is a single-page application built with React 19 and bundled by Vite 8. It is served independently from the backend — in development via the Vite dev server, in production as a static bundle on a CDN.

Core Dependencies

PackageVersionRole
react / react-dom19.2Core UI rendering
react-router-dom7.14Client-side routing with nested routes
tailwindcss4.2Utility-first CSS (via @tailwindcss/vite plugin)
framer-motion / motion12.xPage transitions and micro-animations
leaflet1.9Interactive recycling map
socket.io-client4.8Real-time connection to the backend
@cloudinary/react1.14Optimized image delivery and upload widget
lucide-react0.562Icon library
styled-components6.1Component-scoped CSS for complex UI pieces

Routing and Route Guards

All routes are defined in src/App.jsx. Eco-It uses four route guard components:
GuardBehavior
PrivateRouteRedirects unauthenticated users to /login; accepts an optional rolRequerido prop (e.g., "admin") to enforce role-based access
PublicRouteRedirects already-authenticated users away from /login and /register
RecoveryRouteEnsures password-recovery steps are visited in the correct order
AdminRestrictionGuardPrevents admin/superadmin users from accessing regular user pages (they are redirected to /admin)
Key page routes:
PathComponentAccess
/HomePublic
/mapsMapapage + LeafletPublic
/aiAIPage (EcoBot)Authenticated users
/gameGamePage (Unity WebGL)Authenticated users
/perfilProfileEcoItAuthenticated users
/adminAdminLayoutadmin or superadmin role
/auth/google/successGoogleSuccessOAuth callback

Third-Party Integrations

Cloudinary

User avatars and carousel images are uploaded through the Cloudinary SDK (cloudinary v2 on the backend, @cloudinary/react on the frontend). The backend utility at utils/cloudinary.js initializes the SDK with credentials from environment variables. Helmet’s CSP explicitly allows image sources from https://res.cloudinary.com.

OpenRouter / Google Gemini

EcoBot AI uses the @openrouter/sdk to stream chat completions from Google Gemini. Requests are routed through OpenRouter’s API endpoint so that the model can be swapped without changing application code. The AI router at /api/ai applies its own rate limiter on top of the global one to prevent prompt-flooding.

Google OAuth 2.0

passport-google-oauth20 handles the OAuth handshake. After a successful callback, Eco-It either logs in an existing user or creates a PendingRegistration record and redirects the user to /completar-perfil to finish their profile before receiving a JWT.

MongoDB Atlas

In production, MONGODB_URI points to a MongoDB Atlas cluster. Mongoose 9 manages the connection lifecycle with automatic reconnection. In development, a local MongoDB instance works identically since the URI format is the same.

Request Flow

The following describes how a typical authenticated API request travels through the system end-to-end:
Browser (React)

    │  fetch('/api/user/profile', { headers: { Authorization: 'Bearer <JWT>' } })

Vite Dev Proxy  (development only)
    │  /api/* → http://localhost:3000

Express HTTP Server  :3000

    ├─ helmet()               ← attach security headers
    ├─ cors()                 ← validate Origin against allowlist
    ├─ express.json()         ← parse request body
    ├─ mongoSanitize()        ← strip NoSQL injection characters
    ├─ globalLimiter()        ← check rate-limit counter for this IP

    ├─ Router: /api/user
    │       └─ authMiddleware ← jwt.verify(token, JWT_SECRET)
    │               │
    │               ▼
    │          userController.getProfile()
    │               │
    │               ▼
    │          Mongoose → MongoDB Atlas
    │               │
    │               ▼  (if notification needed)
    │          io.to(userId).emit('notification', payload)
    │               │
    ▼               ▼
  JSON Response   Socket.io push to connected client
In production, the Vite proxy is replaced by a CDN / reverse proxy (e.g., Netlify redirects or an Nginx rule) that forwards /api/* traffic to the deployed backend URL.

Security

Eco-It applies defence-in-depth across multiple layers:
All security controls described here are active by default when the application starts. No additional configuration is required beyond setting the environment variables described in the Quickstart.

Helmet Content Security Policy

The CSP is customized in index.js to extend Helmet’s safe defaults with platform-specific sources:
contentSecurityPolicy: {
  directives: {
    ...helmet.contentSecurityPolicy.getDefaultDirectives(),
    "img-src":     ["'self'", "data:", "https://res.cloudinary.com",
                    "https://*.tile.openstreetmap.org"],
    "script-src":  ["'self'", "'unsafe-inline'"],
    "connect-src": ["'self'", "https://api.cloudinary.com",
                    "wss://*.openrouter.ai"]
  }
}
crossOriginEmbedderPolicy is disabled to allow the Unity WebGL game to load cross-origin resources.

Rate Limiting

Two limiters are defined in middlewares/limiters.js:
LimiterWindowMax requestsApplied to
globalLimiter15 minutes300 per IPEvery route (app.use(globalLimiter))
authLimiter15 minutes15 per IPAuth routes (login, register, password recovery)
The stricter authLimiter on authentication endpoints makes brute-force credential attacks impractical.

CORS Allowlist

Only the following origins may make cross-origin requests:
origin: [
  'http://localhost:5173',
  'http://localhost:5174',
  'http://localhost:5175',
  'https://eco-it.netlify.app'
]
The same allowlist is applied to both cors() middleware and the Socket.io server configuration, ensuring WebSocket upgrade requests are also origin-checked.

NoSQL Injection Prevention

Rather than relying on express-mongo-sanitize@2.2.0’s automatic middleware (which attempts to patch req.query, a property that is read-only in some Express configurations), Eco-It applies sanitization manually to req.body and req.params via an inline middleware:
app.use((req, res, next) => {
  if (req.body)   req.body   = mongoSanitize.sanitize(req.body);
  if (req.params) req.params = mongoSanitize.sanitize(req.params);
  next();
});
This strips any keys containing $ or . before they reach Mongoose, preventing operator injection attacks.

Build docs developers (and LLMs) love