Company memos written in a rich-text editor, approved along a chain, e-signed through Mekari and published as PDFs on the letterhead.
The problem
A company memo needs a number in the register, a multi-step approval, a legally valid signature and a clear record of who approved which version, and when.
What I built
- A TipTap editor feeding a Go service that renders HTML to an A4 PDF with headless Chrome (chromedp), stamps a watermark and uploads it to S3-compatible storage.
- Approval chains with delegation, recall, cancel and expiry, plus registrars who keep the memo register.
- Mekari e-signature integration with HMAC-signed requests. Signature positions are found automatically by reading marker coordinates from the PDF's text layer.
- Notifications in the app (SSE), by debounced email and digest, and on Telegram, each with per-user preferences.
- Publishing and signing run through outboxes with retries, bounce handling and a manual-publish fallback.
Key decisions
- When CSS backgrounds didn't survive Chrome's PDF output, the watermark moved to a separate stamping step on the finished PDF.
- Each submit freezes a memo version, so approvers always sign exactly the text they reviewed.
- Business rules live in the backend, and the front end follows its JSON:API contract instead of copying the rules.
- 59 feature slices, 80 migrations and 540 test files, with the domain rules written down per topic.
- Go
- Gin
- PostgreSQL
- chromedp
- Mekari e-Sign
- S3 (Wasabi)
- SSE
- React SSR
- TipTap
Clinker and cement production, domestic sales and safety KPIs shown on office wall screens, with a separate confidential view for the board.
The problem
The office screens must never show confidential tonnages. The first version had one API for every screen, synced every source hourly whether or not it had changed, and returned 0 when a value couldn't be computed.
What I built
- A Go API with two faces: a public, rate-limited API that only ever returns percentages, and a management API (behind an API key) with the actual figures.
- Scheduled and manual syncs from the ERP, Excel files on an SMB share, and a Google Drive folder of overlay videos, so the dashboard only ever reads its own Postgres.
- A React SSR wall-screen app with rotating banners, a BMKG weather theme per site, and an auto-reload when a new version is deployed.
Key decisions
- Confidential values never leave the server for the public screen. The API key is only added by the server-side proxy.
- Each KPI card is compared with its target to date, prorated per day, and a value that can't be computed is null instead of 0.
- Go
- PostgreSQL
- SMB
- Google Drive API
- React SSR
- Redux
- Vitest
Daily pre-use inspections of heavy equipment, follow-up investigations and fitness checklists, across several business units and sites.
The problem
Operators check every unit before a shift. Failed checkpoints have to become investigations, units have to move between sites, and safety teams need reports per site.
What I built
- The Strapi backend: checkpoints, investigations with attachments, unit availability history and site moves, Bugar Selamat fitness checklists, Excel exports and email to the safety team.
- A redevelopment to support multiple business units, each with multiple sites.
- A responsive React CMS for units, groups, sites, departments, users and shifts.
Key decisions
- Moved the access policies into middleware, and a user's token is revoked when their account changes, so a role change takes effect immediately.
- Strapi
- Node.js
- MySQL
- Redis
- React
- Vite
- Docker
Bonded-warehouse documents (BC 1.1 inbound, SPPB/PIB outbound) drafted, validated and submitted to Indonesian Customs over SOAP/XML.
The problem
Every vessel arrival and cargo movement at the bonded warehouse (TPS) has to be reported to Bea Cukai. The documents depend on each other, and a wrong quantity is a compliance problem.
What I built
- A Go REST API that follows the domain: header (vessel) → bill of lading → inbound and outbound documents, each drafted and then sent.
- A SOAP client that builds the XML payload, wraps it in an envelope and reads Bea Cukai's result.
- Sign-in through the company SSO (JWT verified against JWKS, session allowlist in Redis), plus an activity log of who did what.
Key decisions
- Sending validates the whole document chain first. Available weight is computed inside a transaction with row locks, so two outbound documents can't take the same cargo.
- A feature flag turns the live customs call on or off, so the whole flow could be tested without filing anything real.
- Go
- Gin
- PostgreSQL
- SOAP/XML
- Redis
- sqlc
- testcontainers
Job Safety & Environmental Analysis and Permit to Work for the safety (SHE) team, with registration approved per plant.
The problem
Contractors and staff register at a plant, admins of that plant approve them, and every plant must always keep at least one admin.
What I built
- The Go backend foundation: JSON:API 1.1 (content negotiation, include, sparse fieldsets, sort, filter), ES256 auth with rotating refresh tokens, and an admin user directory.
- The domain rules written down before any code: who approves whom, how companies are tied to plants, and the last-admin guard.
Key decisions
- Presenting a refresh token that was already used revokes the whole login, so a stolen token stops working as soon as both parties use it.
- The last-admin check locks the plant row first, so two revokes at the same moment can't both pass.
- Go
- Gin
- PostgreSQL
- sqlc
- JSON:API
- testcontainers
Cemindo · 2025 – 2026In progress
Single Sign-On & Identity Provider
Sole engineer: protocol design, Go service, React SSR web
One login for the company's internal apps, with per-app sessions, device limits and a logout that reaches every app at once.
The problem
The company's internal apps needed one account per person, one place for admins to approve who may use which app, and a logout or a disabled account that takes effect in every app immediately.
What I built
- An authorization-token flow where the client exchanges an opaque, single-use, Redis-backed token for a session-bound access and refresh token pair.
- Apps verify ES256 JWTs against the SSO's JWKS and keep their own session allowlist in Redis.
- A global logout over Kafka (SASL): every app drops the user's access tokens right away instead of waiting for them to expire.
- Email verification through a transactional outbox with exponential backoff, and a user and app-access approval dashboard.
Key decisions
- Login takes the same time whether or not the account exists, so response timing can't reveal which emails are registered.
- Passwords are SHA-256 pre-hashed before bcrypt, so long passwords are never silently cut at bcrypt's 72 bytes.
- Cleanup workers lock rows with SELECT … FOR UPDATE, so two instances can't race on the same session.
- A rate limiter guards the auth endpoints, and the number of concurrent device sessions per user is capped.
- Go
- Gin
- PostgreSQL
- Redis
- Kafka
- sqlc
- React SSR
- testcontainers