Generated page. Written from IdaMilk/Portal_API at commit 1410956 on 2026-07-28, version 1.2.3. Operational detail is generalised per the wiki's redaction policy.
Portal API
What is it?
An Express gateway sitting between the public Portal frontend and Suntado's internal services. It authenticates callers with JWT, enforces fine-grained permissions, and reshapes internal data into clean DTOs before it reaches the browser.
Purpose
Portal is internet-facing; the services behind it are not. This is the boundary.
Its README states the design intent plainly: the gateway curates data rather than blindly proxying it. Endpoints fetch from the internal service and reshape responses, "so the frontend never sees internal URLs, field names, or system structure." Internal URLs and credentials stay server-side.
That is a meaningfully different posture from the SunApps Gateway, which is a transparent proxy on a trusted network. Here, the shape of the internal system is deliberately not exposed.
How it Works
server.js validates configuration and starts the listener; app.js assembles the Express app. Routes pass through an auth boundary — /api/health, /api/auth/login and /api/auth/refresh are public, and everything else requires a Bearer token.
Login returns an access token and a refresh token in the JSON body. The frontend stores them and sends Authorization: Bearer <accessToken>.
Beyond authentication, authorisation is unusually granular — one middleware per capability rather than a role check scattered through controllers:
| Middleware | Gate |
|---|---|
requireAdmin | Administrative access |
requireEmployee | Employee-only areas |
requireFullAccess | Unrestricted data access |
requireReportAccess | Reports generally |
requireLeadsView / requireLeadsEdit | Sales leads, read and write |
requireLinksView / requireLinksEdit | Link sheets, read and write |
requireMilkDiversionView / requireMilkDiversionEdit | Milk diversion data, read and write |
Because it is exposed to the internet, it also carries brute-force defences that the internal services do not: LoginAttempt records failed logins, IpBan holds blocked addresses, and ipBanCheck middleware rejects them before authentication runs. Helmet sets security headers. Passwords are bcrypt hashed.
Two scheduled jobs capture point-in-time data that would otherwise be unrecoverable: dailyDiversionSnapshot and weeklySnapshot, both on node-cron, writing into ReportSnapshot and MilkDiversionHistory. Reports over historical periods read the snapshot rather than recomputing from a live system that has since moved on.
API
| Endpoint | Auth | Purpose |
|---|---|---|
GET /api/health | public | Liveness check |
POST /api/auth/login | public | { username, password } → access + refresh tokens |
POST /api/auth/refresh | public | { refreshToken } → new tokens |
| everything else | Bearer required | Protected by requireAuth plus a capability middleware |
Controllers cover auth, reports, leads, links and SCC compliance.
Data model
| Model | Holds |
|---|---|
ReportSnapshot | Periodic report captures |
MilkDiversionHistory | Historical diversion records |
ReworkTracking / ReworkPallet | Finished-product rework |
PortalLink | Link sheet entries |
LoginAttempt | Failed login tracking |
IpBan | Blocked addresses |
Tech Stack
| Technology | Version | Purpose |
|---|---|---|
| Express | 5.0.0 | HTTP framework |
| jsonwebtoken | 9.0.2 | JWT issue and verification |
| bcryptjs | 2.4.3 | Password hashing |
| helmet | 8.0.0 | Security headers |
| Sequelize | 6.37.8 | ORM |
| tedious | 19.2.1 | SQL Server driver |
| node-cron | 4.2.1 | Scheduled snapshots |
| nodemailer | 8.0.10 | |
| morgan | 1.10.0 | Request logging |
| rotating-file-stream | 3.2.0 | Log rotation |
| cors | 2.8.5 | Cross-origin support |
Node 22.
Interfaces
| System | Direction | Purpose |
|---|---|---|
| Portal | inbound | The only intended caller |
| Internal SunApps services | outbound | Report data, via clients/sunappsClient.js |
| SQL Server | outbound | Snapshots, leads, links, auth state |
| SMTP | outbound | Password reset and notification email |
Operations
Runs on Linux under a process supervisor, behind a reverse proxy that terminates TLS and exposes it at portal.idamilk.com/api/*. Deployed by GitHub Actions — production_deploy.yml, auto_tag.yml and first_build.yml, the same auto-tagging flow as Portal.
Configuration is environment variables; names only. JWT signing uses JWT_SECRET and JWT_REFRESH_SECRET, each a 32-byte random value generated at setup. AUTH_PASSWORD_HASH holds a bcrypt hash produced by npm run hash.
The user store is a stub. services/userService.js reads credentials from environment configuration rather than a user table — the README is explicit that real portal user authentication is a separate, later effort. Anything reasoning about portal identity today should account for that: the permission middleware is fully built out, but the population it draws on is not.
Repository
| Repository | IdaMilk/Portal_API |
| Version | 1.2.3 |
| Primary language | JavaScript (Node, CommonJS) |
| Files | 73 |
| Last activity | 2026-07-24 |
| Status | Active |
The README is genuinely good — setup, auth model, project layout, and an honest note about the stub user store. It also contains internal hostnames and deployment paths, which is why the Operations section above is deliberately vaguer than the source.