Skip to main content

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:

MiddlewareGate
requireAdminAdministrative access
requireEmployeeEmployee-only areas
requireFullAccessUnrestricted data access
requireReportAccessReports generally
requireLeadsView / requireLeadsEditSales leads, read and write
requireLinksView / requireLinksEditLink sheets, read and write
requireMilkDiversionView / requireMilkDiversionEditMilk 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

EndpointAuthPurpose
GET /api/healthpublicLiveness check
POST /api/auth/loginpublic{ username, password } → access + refresh tokens
POST /api/auth/refreshpublic{ refreshToken } → new tokens
everything elseBearer requiredProtected by requireAuth plus a capability middleware

Controllers cover auth, reports, leads, links and SCC compliance.

Data model

ModelHolds
ReportSnapshotPeriodic report captures
MilkDiversionHistoryHistorical diversion records
ReworkTracking / ReworkPalletFinished-product rework
PortalLinkLink sheet entries
LoginAttemptFailed login tracking
IpBanBlocked addresses

Tech Stack

TechnologyVersionPurpose
Express5.0.0HTTP framework
jsonwebtoken9.0.2JWT issue and verification
bcryptjs2.4.3Password hashing
helmet8.0.0Security headers
Sequelize6.37.8ORM
tedious19.2.1SQL Server driver
node-cron4.2.1Scheduled snapshots
nodemailer8.0.10Email
morgan1.10.0Request logging
rotating-file-stream3.2.0Log rotation
cors2.8.5Cross-origin support

Node 22.

Interfaces

SystemDirectionPurpose
PortalinboundThe only intended caller
Internal SunApps servicesoutboundReport data, via clients/sunappsClient.js
SQL ServeroutboundSnapshots, leads, links, auth state
SMTPoutboundPassword 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

RepositoryIdaMilk/Portal_API
Version1.2.3
Primary languageJavaScript (Node, CommonJS)
Files73
Last activity2026-07-24
StatusActive

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.