Aksara
A searchable library for Otorita IKN's knowledge, where the server checks every reader's access across three levels

- organizational units documents are filed under
- 43
- knowledge domains organizing the whole archive
- 10
- topic subcategories under those ten domains
- 92
- access levels checked on the server
- 3
On this page
The problem
Otorita IKN produces a lot of institutional knowledge across its ten Eselon-1 units: regulations, technical reports, academic studies and field notes. The old system kept it all but made it hard to use. There was no search, so staff asked colleagues for files over chat. Uploading meant a long two-step form with no drafts, so most knowledge never got uploaded, and managers had no view of what existed or which units were contributing.
Three groups share Aksara: the public, browsing published knowledge with no login; staff, uploading and reading internal material; and verifiers, curators and administrators, who approve and organise it. Much of the material isn't public, and some is limited to the directorate that owns it.
What I built
Every document gets a permanent public code
Each document has a private internal ID from the moment it's a draft. On its first publish it also gets a public code, and that code becomes its public address. I chose this because a public link has to be permanent and citable, and a link built from the title would break the moment someone fixed a typo. Codes are never reused, and a document that's unpublished and republished gets its old code back.
One function decides who can open a restricted document
There are three access levels: public, internal to OIKN, and restricted, checked on the server for every request against a person's work unit, rank, roles and any temporary access they were granted. One function decides who can open a restricted document without asking, called from every place a document is served, so the rule can't drift between the listing, search and file downloads. I first gave that power to a broader admin role, then narrowed it to one superadmin role once I saw how sensitive it was.
Review follows fixed steps with a full history
A document moves from draft to verification, then curation, then published, or it's returned or rejected. Only the system changes that status, never a manual edit. Verifiers and curators each work a split-screen queue with three choices: approve, return with a required note, or reject. Every step lands on a timeline nobody can edit, and a revised, already-published document stays live until the revision passes review again.
Search that forgives typos and keeps the link
I shipped database full-text search with typo tolerance and a "did you mean" suggestion, behind a single interface so a larger search engine can replace it later without touching the pages. Every filter, sort and page number lives in the address bar, so a filtered result can be bookmarked, shared or refreshed.
National legal records are federated, never copied
National legal records live in a system OIKN already runs, called JDIH, so Aksara never copies its documents. I store only light metadata, refresh it with a daily job, and fetch the real content from JDIH's API when someone opens a record, cached briefly server-side. Copying JDIH's store instead would have created a second copy of the same regulation that drifts out of date. The integration ended up bigger than planned: it now includes an auto-classifier and its own full-text search, on top of the metadata sync the original spec called for.
Result
I built Aksara alone, starting in July 2026, and it is live at aksara.ikn.go.id. The public portal is open to any visitor, and every document is checked on the server against three access levels. Documents are filed under 43 organizational units, 10 knowledge domains and 92 more specific topic subcategories. Beyond the original plan, the JDIH integration grew its own auto-classifier and search index, and I added a gazetteer and an achievements module. A native mobile app now shares the same backend.
Under the hoodTechnical detail for engineers
- Public codes look like
AKS-2026-0042-K7mPand appear in URLs such as/pengetahuan/AKS-2026-0042-K7mP. The sequence part is allocated under a row lock so simultaneous publishes can't collide. - Access levels Publik / Internal OIKN / Terbatas are resolved in the queryset. The database enum kept the PRD's original values (Publik / Terbatas / Tertutup); only the UI labels changed, through one presentation-layer map, because "Terbatas" and "Tertutup" are near-synonyms.
- Attachments moved off raw
/media/paths onto an access-checked download endpoint. A Celery sweep revokes expired access grants and sends a notification. - Workflow statuses: Draft → Menunggu Verifikasi → Menunggu Kurasi → Terpublikasi, plus Dikembalikan and Ditolak, changed only through transition services. The per-document timeline is "Riwayat Alur", backed by a global audit log recording every create, update, delete, approval, rejection, login and logout.
- Search is Postgres full-text (GIN indexes, ranking, highlighting) behind a
SearchBackendabstraction so Elasticsearch can drop in later. Apg_trgmtrigram fallback powers the "did you mean" suggestions from previously successful queries. - Listing and facet counts were merged into one round-trip (
with_facets=1). Filter switching useskeepPreviousDataso the filter rail doesn't reflow. - The JDIH integration grew to about 22 source files and 6,200 lines, including the daily sync job, a live API client, an auto-classifier, and its own full-text index, on top of the
jdih_indexmetadata table the original spec called for. - A place/gazetteer sub-system for geographic browsing, and an achievements module for contributors, were both added after the original 12-milestone build plan.
- The entity-relationship diagram is generated on request straight from the Django models, so it renders both a docs page and a backoffice "Skema Basis Data" page and never goes stale.
- Portals: Kontributor, Verifikator, Kurator and Administrator, with
active_roleheld server-side. - Keycloak SSO and JDIH were built against mockable client interfaces first, then real implementations. A token-exchange endpoint serves the mobile app.
- Contribution is a four-step wizard with drafts, up to 10 attachments, and a CSV bulk import of up to 200 rows where invalid rows land as flagged drafts.
- Django + DRF API-only backend and a React SPA, feature-sliced, with TanStack Query and 52 route files split between the public portal and the backoffice. No component, form or state library on the frontend. No ML in V1.
- 16 Django apps, 45 concrete models, 14 DRF ViewSets, 206 masterdata rows seeded, 53,323 lines of Python and 30,022 lines of TypeScript, 1,595 backend test functions across 65 test files, 429 commits from 6 July to 25 September 2026.
Screenshots
Hiring, or building something similar?
I'm open to full-time roles and select freelance work.