05 — DOMBO STORE / 2026

A WHOLE SHOP,
ONE ENGINEER

A production online shop for a porcelain and ceramics brand from Baikal, built alone on Medusa v2 and Nuxt 4. Real payments through Robokassa, shipping through ApiShip, a Russian-language merchant admin, and AI tools for the owner — live, and run by me.

ROLE
Solo Architect & Developer
YEAR
2026
STACK
Medusa 2.21 + Nuxt 4 + PostgreSQL 17 + Redis 7
HOSTING
1 VPS · Docker Compose · Caddy
01 — CONTEXT & PROBLEM

ONE OWNER FOR EVERY PIECE OF DATA

The rule that shaped everything: before building anything, decide which system owns the data. Medusa owns commerce state — products, carts, orders, payments, stock — and PostgreSQL stores it. Object storage holds image files; the database holds only their URLs. The payment provider owns the external transaction, and a browser redirect is never treated as proof of payment.

The storefront only presents. It reads through the Medusa SDK, keeps one cart-id cookie, and never computes a price or stores an order. Nothing about commerce lives in JSON files, Nuxt server memory or localStorage.

Orders, payments and stock are where bugs cost money — so the architecture is built around who is allowed to change them.
02 — LIVE PRODUCT

CAPTURED FROM PRODUCTION

Real screens from the running shop and its admin, taken on 15 October 2026. Admin captures show catalog data only — orders and customer pages are left out on purpose.

DOMBO storefront home page with the brand hero, main navigation and delivery highlights
HOME — server-rendered Nuxt page. Hero, slides and highlights come from Medusa and the merchant's content settings, never from hardcoded URLs. Images are served as responsive AVIF or WebP.
DOMBO catalog page with category tabs, shopping intents, filter bar and product grid
CATALOG — 192 products, filters for collection, artist, price and availability. Counts and ranges come from one /store/catalog route; out-of-stock items stay visible and labelled.
DOMBO home page on a phone
PHONE — the same page at 390 px, covered by Playwright on desktop and mobile.
03 — ARCHITECTURE

COMMERCE IN MEDUSA, PRESENTATION IN NUXT

┌──────────────────────────────────────────────┐
│  Edge — Caddy                                 │
│  HTTPS · 3 hosts (shop / admin / automation)  │
│  allow-list of public Medusa paths            │
└───────────────────────┬──────────────────────┘
                        ▼
┌──────────────────────────────────────────────┐
│  Application                                  │
│  Nuxt 4 storefront (SSR, presents only)       │
│  Medusa v2 server  ·  Medusa worker           │
│  Admin dashboard (own host, Russian)          │
└───────────────────────┬──────────────────────┘
                        ▼
┌──────────────────────────────────────────────┐
│  State                                        │
│  PostgreSQL 17 · Redis 7 · S3-compatible      │
└───────────────────────┬──────────────────────┘
                        ▼   always from Medusa
┌──────────────────────────────────────────────┐
│  Integrations                                 │
│  Robokassa (live) / YooKassa · ApiShip (live) │
│  / CDEK · DaData · Yandex ID · Postbox        │
│  · Telegram · n8n · LLM providers (1 port)    │
└───────────────────────┬──────────────────────┘
                        ▼   private network, one scoped token
┌──────────────────────────────────────────────┐
│  Hermes agent container (owner's assistant)   │
│  sees shop data only via 13 typed tools       │
└──────────────────────────────────────────────┘

Medusa runs as a server and a worker container; the Redis-backed modules (event bus, cache, workflow engine, locking) are what make that split safe. Payment and delivery are Medusa provider modules — Robokassa and ApiShip are live, YooKassa and CDEK are built behind the same interfaces.

Admin runs on its own host, so a storefront bug cannot act as a logged-in admin. Zod validates every API boundary, since the storefront is untrusted input.

Every LLM provider sits behind one port — OpenRouter, Anthropic or any OpenAI-compatible endpoint — switchable from Admin with sealed keys, no restart.
04 — TRADEOFFS

DECISIONS & THEIR COSTS

Extend Medusa instead of building or renting a shop

Orders, carts and payment state are where bugs cost money. Reusing tested core flows beats re-deriving them, and the admin comes for free. Extension order: what Medusa provides, then a module, workflow, route, subscriber or provider, and only then custom code.

I inherit Medusa's gaps and close them with tests. Its computed order fields are not returned by query.graph — the golden-number tests caught it.

Payments that survive retries and forged callbacks

The amount always comes from the server-side cart, every notification's signature is verified, and handling is idempotent. A browser redirect proves nothing, and providers do retry.

More code and tests per provider. The payoff: test and live credentials differ only in configuration.

Idempotency as a design rule, not a patch

Callbacks, order.placed and double clicks can all arrive twice. A unique index on the natural key, a reserve-or-read step and first-write-wins mean a repeat creates no second payment and no double stock decrement.

A unique index and a reconcile step on every feature that touches money or stock. Concurrency tests fire two approvals at once and expect exactly one effect.

Typed contracts between Medusa and Nuxt

Raw API shapes are narrowed, then mapped into presentation types by one mapper file. Pages never call the SDK, and contract fields are only ever added.

One mapping layer, and no `any` at any external boundary.

Optional features behind environment switches

Each integration turns on only when its environment variables exist. One codebase serves shops with different needs, and a missing key never takes checkout down.

Every integration needs a test for its off state.

One VPS, sized on purpose

Docker Compose with Caddy, a nightly database dump and a throwaway stage stack with a time-to-live. The load does not justify an orchestrator.

No high availability. Heavy services have memory limits, and the plan says when to move them to a second machine.

05 — AI FOR THE OWNER

AN ASSISTANT THAT CAN READ THE SHOP BUT CANNOT CHANGE IT

The catalog is small, so retrieval is not the point — the owner's time is. Hermes runs in its own container for the Telegram chat and schedule, and Medusa is the only door to shop data, through 13 typed tools. The agent is on stage and ready; the production switch stays off until I have watched it on real use.

READ TOOLS

Digest, sales, top products, stock, abandoned carts, customers. Aggregates only, built by the server, so the model formats numbers it did not compute.

PROPOSE TOOLS

Product text, price, stock, reply draft, page text, percentage promotion. They store a proposal and change nothing.

HUMAN APPROVAL

The owner sees old and new values in Admin and approves. Medusa runs an existing workflow once, even on a double click.

AUDIT

Each tool call and decision is logged by tool, person and outcome — never arguments or message text.

AI landing builder workspace with chat, live preview and page structure
admin.model03.ru/app/landings — AI landing builder. A plain-language request becomes a page of real shop blocks with live products and prices. Every id, image and link in the model output is checked server-side before anything is saved.
AI insights chat answering a stock question with figures and a table
admin.model03.ru/app/insights — AI insights. A stock question returns a dashboard built from figures the server computed. The model only arranges them, and it receives no names, emails or phone numbers.
Owner's agent page with chat, proposals and access tabs and a pause switch
admin.model03.ru/app/agent — Owner's agent. It only proposes; a change runs after the owner approves. Proposals show old and new values, and a pause switch stops the agent at once.
Medusa admin products list with DOMBO custom sidebar sections
admin.model03.ru/app/products — Products. Medusa's admin in Russian with custom sidebar sections: agent, alerts, scene backgrounds, journal, landings, certificates, master classes.
A bug the tests were built to catch. Golden-number tests create real orders with hand-computed totals and compare them with the agent's figures. They showed the digest would have reported zero unpaid orders and zero stuck shipments on a live shop: Medusa's payment_status and fulfillment_status are computed by its routes and are not returned by query.graph. The fix reads payment collections and fulfillment timestamps instead, and the test now guards it.
06 — ENGINEERING PRACTICE

HOW CHANGE STAYS SAFE

1,644
Unit tests

1,341 backend and 303 storefront, plus HTTP integration suites and Playwright catalog flows on desktop and mobile.

13
Architecture decision records

Media storage, runtime AI settings, sealed secrets, RBAC, n8n, plugins, ApiShip and the agent — each written down.

13
Typed agent tools, 0 direct writes

Money, orders and publication are never direct writes; legal pages cannot be drafted by the agent at all.

Husky refuses commits on main and refuses payment, inventory, order or certificate code without a test in the same commit. Push runs lint, typecheck and tests; CI runs the checks before anything merges. Risky changes go to a throwaway stage stack first.

Releases are trunk-based: release.sh bumps the version, writes the changelog and tags it; migrations run as a separate step before the new server starts. I shipped v1.4.0 this way, with a database migration — and rollback is a redeploy of the previous tag.

07 — WHY THIS MATTERS

FOR FREELANCE / PROJECT CLIENTS

If you need a real shop — payments, delivery, a localized admin and tools your team can actually use — this is the whole path, delivered and operated by one engineer: money paths that survive retries and forged callbacks, and AI that proposes while a human approves.

FOR FULL-TIME / HIRING TEAMS

One person owned architecture, backend, storefront, infrastructure and release process, with 1,644 unit tests, 13 ADRs and hooks that block untested money code. Every choice above comes with its written cost.

NEXT STEP

LIKE WHAT YOU SEE? LET'S TALK ABOUT YOURS.