Four Contracts Behind a Public Upload Form
One multipart endpoint enforces four contracts: closed enums, conditional file slots, bounded streaming, and safe schema migration.
TL;DR
The upload form enforces four contracts: hand-maintained Go enums for validation, files that become required only when a flag says so, streaming writes capped at 10 MiB with magic-byte checks into private storage, and schema migrations that clean up enum values before narrowing on rollback. All four must stay synchronized server-side. Any drift breaks the endpoint predictably.
Four Hidden Contracts Behind a Public Upload Form
The terminal showed inconsistent validation errors while I was wiring up a new public form. Data that passed on the client side got rejected by the server with a confusing message. My first guess pointed at a reverse proxy body limit that was too tight, or a CORS misconfiguration.
Checking the proxy logs showed the payload arrived intact. The assumption that the server was cutting the request off was wrong. The request reached the application, and the failure happened at the business layer.
Digging deeper revealed that one public multipart form endpoint actually enforces four separate contracts. All four have to stay in sync. Failure happens when one contract drifts out of alignment with the others.
Closed Enums at the Application Layer
The first contract governs text field validation. Instead of leaning on database enumerations, the system enforces closed-set field enums maintained by hand in Go. The approach uses slice allowlists to validate input before anything is processed.
This decision avoids coupling business logic to the database schema, so the allowed values can change without a schema migration. The reference implementation for multipart parsing lives in Go's net/http package [1].
Conditionally Required Files
The second contract handles dependencies between fields. Upload slots are not always mandatory. For example, the izin_keramaian slot only becomes required when the ButuhIzinKeramaian field is true.
This keeps users from being blocked by uploads that do not apply to their scenario, while still guaranteeing the permit document shows up when the event actually needs one. Enforcing the condition on the server, not just in the browser form, is what makes it hold.
A Streaming Write Path with Hard Limits
The third contract protects infrastructure from resource abuse. The path leans on ParseMultipartForm from net/http, which caps how much of the file parts stay in memory (maxMemory) before spilling the remainder to temporary disk files. If that limit is unmanaged, a denial-of-service loop can fill the server's disk.
To keep abuse bounded, every file passes through an io.LimitReader at 10 MiB plus one byte. OWASP recommends storing uploads outside the webroot, in a location with administrative access only [2]. Before saving, the system sniffs magic bytes to verify the real file type, because the extension the client sends is trivial to fake.
Files that pass validation land in private storage outside the webroot. File names are randomized to prevent direct URL guessing.
Schema Migration and Access Control
The fourth contract keeps schema evolution safe. Migration 000073 widens ENUM columns such as jenis and submission_type; ENUM itself is a string type whose permitted values are enumerated explicitly at table creation time [4]. When a rollback is needed, the columns are not narrowed immediately, because old rows would break the smaller enum.
Instead, the down migration cleans up values that no longer fit before narrowing the definition, following the same ordered-file pattern that migration tools apply [5]. Cleanup usually means rewriting no-longer-valid enum values to a default so the narrowed schema can apply without conflict. Without that ordering, a rollback produces inconsistencies that are painful to trace and can damage table integrity.
Role-based access control rides on top through the rekomendasiScope mapping: a single bidang maps to its own code, while cross-bidang submissions map to ALL so staff distribution works. Keeping these four contracts aligned is what makes the endpoint predictable in production.
Sources
[1] raw.githubusercontent.com golang go request.go
[2] cheatsheetseries.owasp.org File Upload Cheat Sheet