Piecu Procentu Ordenis — case study

Piecu Procentu Ordenis
← ARCHIJS.DEV
5% ORDENIS / CASE STUDY LIVE · PIECUPROCENTUORDENIS.LV
Piecu Procentu Ordenis

The annual awards platform for Latvian hip-hop — designed, built, and operated end to end for the 5% Akadēmija community.

THE CASE STUDY
01 / THE PRODUCT

An award show
that lives on
a Discord server

Piecu Procentu Ordenis — “the Five Percent Order” — is the annual, community-voted award of Latvian hip-hop, run by and for the 5% Akadēmija Discord community.

For its first editions it lived in Google Forms: manual deduplication, manual tallies, no memory between years. The community needed something no off-the-shelf poll tool does — an electorate defined by live Discord membership. You sign in with Discord; you can nominate and vote only while you are a member of the server, re-verified in the background. The winners are revealed live at the annual ceremony, category by category, and every edition is preserved in a public archive.

The award came before me; the platform didn’t — I built and operate all of it: backend, frontend, admin, the design overhaul, and the box it runs on.

  • 5EDITIONS 2022–2026
  • 28CATEGORIES / CYCLE
  • 13,334VOTES IN 2025
  • 259VOTERS IN 2025
02 / THE ANNUAL CYCLE

Six phases, one year,
zero hand-cranking

  1. DRAFT

    The next edition is assembled: categories, deadlines, rules.

  2. NOMINATIONS OPEN

    Members submit entries through type-adapted forms; duplicates are caught on the way in.

  3. NOMINATIONS CLOSED

    Moderators curate the ballot — approve, reject with reasons, reorder.

  4. VOTING OPEN

    One final ballot per member per category — single pick or a capped multi-pick.

  5. VOTING CLOSED

    Reveals happen live at the ceremony; each writes an immutable result snapshot.

  6. ARCHIVED

    The edition freezes at its yearly URL, joining every prior year.

A scheduler advances phases automatically when deadlines pass — a state machine with transition guards, not a checklist for an admin at 02:00.

03 / THE PRODUCT, UP CLOSE

Not screenshots —
the UI, rebuilt

These panels are re-created in HTML/CSS for this page, faithful to production — the Latvian strings and the 2025 results are the real thing.

Every nomination type renders its own form — this one searches the Spotify catalogue, stages the pick, and re-fetches metadata server-side on submit. The amber panel is a pg_trgm “did you mean”: trigram similarity over diacritics-stripped text (mūzika ≈ muzika), a soft threshold suggests, a hard threshold blocks.
One final ballot per member per category — single pick or a capped multi-pick. Concurrent submissions are serialized with a Postgres advisory lock; phase, ballot size, and already-voted are re-checked inside the lock, with a unique index as the last line of defense. Award-show integrity the community’s old Google Forms never had.

Real 2025 result, 5% ALBUMS (757 votes, 21 nominations): winner — Ods, “Tavai Zināšanai Man Viss ir Labi”, 160 votes (21% share, +65 margin); then Wiesulis “500 mezgli” 95, galeniex “Ambrāža” 76, Prospekti “BIĻETE UZ VIENU GALU” 69, MAUZOLEJU MUUZIKA “DEMONU DIENAS 5” 57.

Reveals happen live on ceremony night — an admin clicks “Reveal” per category and the site updates in step with the stage. Each reveal writes an immutable results snapshot (count + rank, ties honored), so archive pages read a small indexed table and never re-aggregate. Five editions back to 2022 live at their yearly URLs; the pre-app years were migrated from Google Forms exports.
04 / THE ENGINEERING

The parts that
have to hold

The electorate is a Discord server

Discord is the identity provider and the membership gate. A four-branch account-matching ladder handles OAuth edge cases; a bot re-verifies stale memberships every 24h — daily batch plus lazy per-request refresh — and treats 200 / 404 / 5xx distinctly, so someone who leaves the server loses the ballot without ever seeing a login screen.

Two music catalogues, one interface

Nomination search runs through a MetadataResolver interface with per-category resolvers — Spotify track, album, artist; YouTube video — so forms and dedup stay provider-agnostic. Spotify’s bearer token is cached for 59 minutes of its 60-minute life. A YouTube search costs 100 quota units, so results are enriched with one batched 1-unit call instead of per-video lookups. When a provider dies or the quota burns out, the UI collapses to a URL-paste fallback and nominations keep flowing.

Caching that can’t serve stale

Read paths are cache-aside on Redis with versioned keys: invalidation is a single counter increment fired by model observers — no key scans, no wildcard deletes, a one-hour safety TTL as the net. External API responses cache 24h for real hits, 10 minutes for empty results, and errors are never cached — a momentary Spotify outage can’t freeze “no results” for a day.

Duplicates, in a language full of diacritics

Fuzzy dedup runs in Postgres — pg_trgm trigram similarity over unaccent-normalized text (Edavārdi ≈ Edavardi) on a GIN index, one query. A soft threshold powers the live “did you mean” hint; a hard threshold blocks the submit. The public hint endpoint only ever surfaces approved entries, so anonymous probes can’t read the pending queue.

One ballot, no races

Ballot submission takes a Postgres advisory lock per user and category, re-checks everything inside it — phase, ballot size, nomination validity, already-voted — and backs it with a unique index. Votes denormalize category and cycle so tallies are one indexed aggregation; reveals snapshot results into an immutable table read by the archive.

The server fetches what users paste

Text nominations auto-fill page titles from pasted URLs — a classic SSRF hole, closed: private, loopback, and cloud-metadata ranges blocked, DNS rebinding checked by resolving before fetching, every redirect hop re-validated, all on a 4-second / 4-redirect / 512 KB budget.

Proof it holds

449 Pest test cases — OAuth flows, cache invalidation, dedup behavior against a real Postgres with pg_trgm, voting guards, admin authorization — running in CI on every push. Production is six Docker services behind Caddy with Cloudflare-locked ingress, nightly rotated backups, and opcache-safe deploys.

THE PLATFORM · 6 SERVICES · 449 TESTS · ONE BOX

5% BACKEND2026
Laravel 11
PHP 8.46 services
5% DATABASE2026
PostgreSQL 16
pg_trgm + unaccent1-query dedup
5% CACHE2026
Redis 7
versioned keys6 families
5% FRONTEND2026
Vue 3 + Inertia
no separate API~350 KB JS
5% ADMIN2026
Filament v3
RBAC + audit log8 widgets
5% OPS2026
Docker + Caddy
Cloudflare-locked ingressnightly backups
PIECU PROCENTU ORDENIS · NO 2022 UZ BEZGALĪBU · RADĪTS 5% AKADĒMIJAI IZSTRĀDE: ARTŪRS GREBEŽS · ARCHIJS.DEV