Ristek Link Backend - Documentation
| Document Status | ||
|---|---|---|
| Document Owner | Product Engineering 2026 | Product Engineering 2026 |
| Contributors | ||
| Project Links | https://github.com/RistekCSUI/ristek-link-backend | https://github.com/RistekCSUI/ristek-link-backend |
| Project Links | https://ristek.link | https://ristek.link |
| Project Links | Sentry (configured via SENTRY_DSN) | Sentry (configured via SENTRY_DSN) |
| Project Links | Swagger UI at /docs (non-production environments only) | Swagger UI at /docs (non-production environments only) |
| Team | Theodore Kevin Himawan | theodore.kevin@ristek.cs.ui.ac.id |
| Team | Ari Darrell Muljono | darrell@ristek.cs.ui.ac.id |
| Team | Grace Karina | gracekarin@ristek.cs.ui.ac.id |
| Team | Yeshua Marco G. Manurung | marco@ristek.cs.ui.ac.id |
π Background
Overviewβ
Ristek.Link is a URL shortening service that allows users to create custom short URLs, track click analytics, generate QR codes, and manage custom subdomains. It provides both anonymous and authenticated shortening, bulk URL creation via Excel upload, per-link analytics (visitors, unique visitors, retention over configurable time ranges), and a superuser admin system for custom domain management.
Stakeholdersβ
- Users: RISTEK members, general public (anonymous shortening), superusers (domain management)
- Upstream Dependencies: Google OAuth API, Google Web Risk API (currently disabled), SendinBlue (Brevo) for transactional emails, AWS S3 (bulk Excel result storage)
- Downstream Consumers: Ristek.Link Frontend, any service consuming short URLs via redirect
Scope & Boundariesβ
- In Scope: URL shortening (single & bulk), custom short URL aliases, click tracking & analytics, QR code generation with custom colors/logo, user authentication (email/password + Google OAuth), password reset flow, custom subdomain management (superuser), phishing/unsafe URL detection, device-based unique click tracking.
- Out of Scope: Frontend application, DNS configuration for custom subdomains, Google Web Risk billing (feature disabled), advanced analytics dashboards.
βοΈ Architecture & Design
System Diagramβ
Architecture Decisions (ADRs)β
- Language/Framework: TypeScript / Node.js / Express 4.18.1
- Database: Google Cloud Firestore (NoSQL document database) β separate projects for prod (ristek-link) and dev (ristek-link-dev)
- Authentication: JWT (Bearer token) with bcrypt password hashing; Google OAuth as alternative; separate superuser JWT for admin operations
- Communication: Synchronous RESTful API over HTTP
- File Storage: AWS S3 for bulk Excel result uploads
- Email: SendinBlue (Brevo) for password reset emails
- Error Monitoring: Sentry with tracing
- QR Generation: @cheprasov/qrcode (SVG-based with customizable colors and logo)
- URL Safety: Local unsafe host blacklist (Firestore unsafe_host collection) + hardcoded email blacklist; Google Web Risk integration exists but is disabled due to GCP billing
- API Documentation: Swagger/OpenAPI via swagger-jsdoc + swagger-ui-express (non-production only)
π»Technical Specifications
API Documentationβ
All routes are prefixed under /api/v1. Authentication uses Authorization: Bearer <token> headers. Superuser routes additionally require authorization-superuser: <token> header. Swagger UI is available at /docs in non-production environments.
Auth Endpoints (/api/v1/auth)β
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /register | β | Register with fullName, email, password |
| POST | /login | β | Login, returns JWT (24h expiry) + optional superUserToken |
| POST | /google | β | Google OAuth login (auto-registers if new) |
| POST | /forgot-password | β | Sends password reset email via SendinBlue |
| POST | /reset-password | β | Reset password using token from email |
Shorten Endpoints (/api/v1/shorten)β
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | / | β | Create short URL (optional auth for ownership) |
| POST | /bulk | β | Bulk create from Excel file URL (max 100 links per attempt) |
| GET | / | Bearer | List all URLs owned by authenticated user |
| GET | /bulk | Bearer | List bulk Excel import results for user |
| PUT | / | Bearer | Edit existing short URL (url and/or alias) |
| POST | /redirect | β | Resolve short URL β target URL (tracks clicks + unique visitors) |
| GET | /:shorten | β | Get short URL details (public) |
| PATCH | /generate-qr | β | Generate custom QR code with color and logo options |
Analytics Endpoints (/api/v1/analytics)β
| Method | Path | Auth | Query Params | Description |
|---|---|---|---|---|
| GET | / | Bearer | shorten, category, page, range | Get analytics for a short URL |
| Query parameter enums: | ||||
| category: visitor | unique_visitor | retention | ||
| range: week (7 daily data points) | month (4 weekly data points) | six_month (6 monthly data points) | ||
| Returns: percentage change vs previous period, current/previous totals, avg, max, min, paginated data with labels. |
Domain Endpoints (/api/v1/domain) β Superuser Onlyβ
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | / | Bearer + Superuser | Create custom subdomain (one per user, alpha-only, max 11 chars) |
| POST | /find | Bearer | Find domain by authenticated user's email |
| PATCH | /:id | Bearer + Superuser | Update domain (subdomain name, user email) |
| DELETE | /:id | Bearer + Superuser | Delete domain |
| PATCH | /:id/active-status | Bearer + Superuser | Toggle domain active/inactive |
| GET | /all | Bearer + Superuser | List all domains (paginated, filterable by active status and search) |
| GET | /test | Bearer + Superuser | Superuser auth verification test |
Data Model/Schemaβ
Firestore collections (schema-less NoSQL documents):
| Collection | Document ID | Key Fields | Purpose |
|---|---|---|---|
| user | User email | fullName, password (bcrypt hash), google (boolean), malicious (boolean) | User accounts |
| super_user | User email | (membership check only) | Superuser role mapping |
| shorten | UUID | email, url, shortenUrl, click, uniqueClick, createdDate, qrCode (SVG), withLogo, phising, subdomain? | Shortened URLs |
| analytics | UUID | shortenUrl, YYYY-MM-DD: { totalClicks, totalUniqueClick } | Per-day click analytics |
| devices | device_id | deviceId, createdAt, url-{shorten}: boolean | Device tracking for unique clicks |
| bulk_excel | UUID | email, fail, success, createdAt, excelUrl | Bulk import results |
| unsafe_host | Hostname | hostName, threatTypes[], createdAt | Blocked unsafe hostnames |
| domain | UUID | id, subdomain, userEmail, isActive, createdAt, updatedAt, lastUpdatedBy | Custom subdomains |
Project Structureβ
ristek.link-backend/
βββ .github/
β βββ workflows/
β βββ deploy.yml # Production: push to master β Vercel prod deploy
β βββ preview.yml # Staging: push/PR to dev β Vercel preview deploy
β βββ deploy-pusilkom.yml # Manual: ECR build β Pusilkom (staging/prod/all)
βββ src/
β βββ index.ts # Entry point: Firestore init, health check, server start
β βββ config.ts # Express app setup: Sentry, CORS, helmet, morgan, session, routes
β βββ controllers/
β β βββ authController.ts # Register, login, Google OAuth, forgot/reset password
β β βββ shortenController.ts # URL shorten, bulk import, redirect, edit, QR generation
β β βββ analyticsController.ts # Click analytics with week/month/6-month ranges
β β βββ domainController.ts # Custom subdomain CRUD (superuser)
β βββ routers/
β β βββ authRouter.ts # Auth route definitions + validation rules
β β βββ shortenRouter.ts # Shorten route definitions + validation rules
β β βββ analyticsRouter.ts # Analytics route definitions
β β βββ domainRouter.ts # Domain route definitions
β βββ middlewares/
β β βββ authorization.ts # JWT Bearer token verification + Firestore user lookup
β β βββ superuserAuthorization.ts # Superuser JWT verification via authorization-superuser header
β βββ database/
β β βββ firestore.ts # Firebase Admin SDK initialization (prod/dev creds)
β β βββ blacklistedEmail.ts # Hardcoded email blacklist array
β βββ types/
β β βββ auth.ts # Auth DTOs and response interfaces
β β βββ shorten.ts # Shorten DTOs, response interfaces, controller interface
β β βββ analytics.ts # Analytics DTOs and response interfaces
β β βββ domain.ts # Domain DTOs, pagination types, controller interface
β βββ error/
β β βββ error.ts # ApiError class (statusCode, message, status)
β β βββ serviceError.ts # Predefined error instances (30+ error types)
β βββ utils/
β β βββ validator.ts # express-validator validation schemas for all endpoints
β β βββ mailer.ts # SendinBlue transactional email for password reset
β β βββ s3.ts # AWS S3 upload for bulk Excel results
β β βββ webrisk.ts # Google Web Risk API URL threat check (disabled)
β β βββ wrapper.ts # Response wrapper utility
β βββ log/
β βββ log.ts # Winston logger configuration (console + file)
βββ firebase.json # Firebase service account credentials (production)
βββ firebase.dev.json # Firebase service account credentials (development)
βββ swagger.json # OpenAPI 3.0 config (JWT security, source paths)
βββ vercel.json # Vercel serverless build config (all routes β index.ts)
βββ tsconfig.json # TypeScript config (ES6, CommonJS, strict)
βββ .env.local # Local environment variables
βββ .vercelignore # Vercel deploy ignore patterns
βββ .gitignore # Git ignore patterns
βββ package.json # Dependencies, scripts, metadata
Componentsβ
| Layer | Paths | Responsibility |
|---|---|---|
| Auth Controller | src/controllers/authController.ts | Handles user registration with bcrypt password hashing, email/password login returning a 24-hour JWT (plus a separate superUserToken if the user exists in the super_user collection), Google OAuth login (auto-creates users on first login), and a password reset flow using SendinBlue transactional emails with JWT-based reset tokens. |
| Shorten Controller | src/controllers/shortenController.ts | Core URL shortening engine. Creates short URLs with duplicate checking, optional custom subdomain association, and automatic SVG QR code generation with the RISTEK logo. Enforces HTTPS-only URLs, checks against the unsafe_host blacklist and hardcoded email blacklist, and flags phishing URLs. Supports bulk creation from Excel files (max 100 links per attempt) with results uploaded to S3. The redirect flow tracks total clicks and device-based unique clicks, updating both the shorten document and the analytics collection with daily breakdowns. |
| Analytics Controller | src/controllers/analyticsController.ts | Provides paginated analytics for individual short URLs across three time ranges: weekly (7 daily data points), monthly (4 weekly aggregates), and six-month (6 monthly aggregates). Calculates percentage change vs the previous period, averages, min/max, and total page count for pagination. |
| Domain Controller | src/controllers/domainController.ts | Superuser-only custom subdomain management. Supports creating subdomains (one per user, alphabetic characters only, max 11 chars), updating subdomain names and owner emails with duplicate validation, toggling active/inactive status, paginated listing with search and active-status filtering, and deletion. |
| Authorization Middleware | src/middlewares/authorization.ts | Verifies Authorization: Bearer <token> header, decodes the JWT, validates the user exists in Firestore, and injects the decoded payload into req.body.data. |
| Superuser Middleware | src/middlewares/superuserAuthorization.ts | Verifies a separate authorization-superuser header token, checks role === "super_user" in the JWT payload, and validates the user in Firestore. Used alongside authorization for domain management endpoints. |
| Firestore Database | src/database/firestore.ts | Initializes the Firebase Admin SDK with service account credentials, selecting between production (ristek-link) and development (ristek-link-dev) Firebase projects based on NODE_ENV. |
βοΈ Operational Playbook
Infrastructureβ
- Primary Platform: Vercel (serverless deployment via @vercel/node)
- Secondary Platform: AWS ECR + Pusilkom instance (Docker container deployment)
- Production Deploy: Push to master β GitHub Actions β Vercel production (deploy.yml)
- Staging Deploy: Push/PR to dev β GitHub Actions β Vercel preview (preview.yml)
- Pusilkom Deploy: Manual workflow dispatch β ECR build (deploy-pusilkom.yml) for staging (latest tag) and/or production (stable tag)
- ECR Registry: 638207107223.dkr.ecr.ap-southeast-1.amazonaws.com/ristek-link-backend
- Database: Google Cloud Firestore β ristek-link (prod), ristek-link-dev (dev)
Environment Variablesβ
| Key | Description | Default (Dev) | Sensitive? |
|---|---|---|---|
| PORT | Server listening port | 4000 | No |
| NODE_ENV | Environment (development / production) β controls Firebase creds and Swagger visibility | development | No |
| JWT_SECRET | JWT signing secret for user and superuser tokens | β | Yes |
| GOOGLE_CLIENT_ID | Google OAuth Client ID (note: env var has typo GOOGLE_CLIENT_I in code) | β | Yes |
| GOOGLE_CLIENT_SECRET | Google OAuth Client Secret | β | Yes |
| SENDINBLUE_API_KEY | SendinBlue (Brevo) API key for transactional emails | β | Yes |
| CLIENT_HOST | Base URL for password reset links (e.g. http://localhost:3000/login/reset?token=) | http://localhost:3000/login/reset?token= | No |
| SENTRY_DSN | Sentry DSN for error tracking | β | Yes |
| AWS_ACCESS_KEY_ID | AWS access key for S3 bulk Excel uploads | β | Yes |
| AWS_SECRET_KEY_VALUE | AWS secret key for S3 | β | Yes |
| AWS_REGION | AWS region for S3 | β | No |
| AWS_BUCKET_NAME | S3 bucket name for bulk Excel results | β |
πQuestions
π List of frequently asked questions or question that need to be answered that is related to this initiative ..