Monorepo Restructure Plan
1. Motivation
- The production VM (
ptikn3-vm) already uses a flat snake_case layout (merchant_core_api/,merchant_dashboard/, etc.). The local repo still uses mixed conventions (apps/,backend/,services/,packages/), so the VM and repo paths do not align → deploy scripts need rename mapping, developer onboarding gets confusing. - Simplify the mental model: 1 domain = 1 top-level folder, no need to navigate
apps/merchant_dashboardvsmerchant_core_apivsservices/dashboard_api. - Reserve slots for mobile projects that don't exist yet (
merchant_mobile_user,merchant_mobile_partner).
2. Target structure (confirmed)
kesles_merchant/
├── merchant_dashboard/ Flutter — admin web dashboard
├── merchant_mobile_user/ Flutter — merchant mobile app (NOT YET BUILT)
├── merchant_mobile_partner/ Flutter — partner mobile app (NOT YET BUILT)
│
├── merchant_core_api/ Go — core API (port 8080) for mobile/merchant
├── merchant_dashboard_api/ Go — admin API + PSP integration (port 8082)
├── merchant_whatsapp/ Node — WhatsApp service (port 8091)
│
├── merchant_database/ PostgreSQL — migrations + seeds (ex merchant_database/)
├── merchant_docs/ Docusaurus — 2 sites (public + internal)
3. Placement decisions (not final — decisions needed)
Existing folders not explicitly in the target → I provide a recommendation + reason.
3.1 packages/ — KEEP
- Contents:
psp-integration-go(shared Go lib for HMAC, DTO, signer, verifier) used bymerchant_dashboard_apiand to be used bymerchant_core_apilater when the PSP integration expands. - If dissolved into one service → code duplication across services → signature/DTO drift → broken integration.
- Recommendation: keep
packages/at the root. Future:packages/psp-integration-tsfor the TypeScript version (used bymerchant_gateway_apiif it needs to sign the same way).
packages/
├── psp-integration-go/ shared Go lib (current)
└── psp-integration-ts/ future — TS version for the Node service
3.2 workers/ — MERGE into the related service
- The current contents need verification, but the common pattern: a worker is usually 1-1 with a domain service (e.g.
core-apihas a transactional email worker,dashboard-apihas a settlement sync worker). - With the rename, the worker can be moved as a subfolder
cmd/in the same service:
merchant_core_api/
├── cmd/
│ ├── server/ main HTTP server
│ └── worker/ background worker (ex workers/core-tasks)
- Recommendation: delete the top-level
workers/folder, move intocmd/worker/of the respective service. - Action item: audit
workers/first — list files and owners, then decide where each worker goes.
3.3 deployments/ — KEEP as deploy/
- Contents: systemd unit files, nginx config, docker-compose, CI templates.
- Doesn't belong in any single service because it's cross-cutting (multi-service deployment orchestration).
- Recommendation: rename
deployments/→deploy/(consistent snake_case single-word), structure:
deploy/
├── nginx/
│ ├── api-merchant.kesles.com.conf
│ ├── www.kesles.com.conf
│ └── ...
├── systemd/
│ ├── merchant-core-api.service
│ ├── merchant-dashboard-api.service
│ └── ...
├── docker-compose.dev.yml
├── docker-compose.prod.yml
└── scripts/ see section 3.5
3.3.1 Reverse-proxy strategy — one vhost per consumer category
Two public hostnames are served by the same nginx instance, but carry different traffic profiles. They must live in separate .conf files — never collapsed into one — because their proxy_pass style, security model, and catch-all behaviour are inherently incompatible.
| Aspect | kesles.com.conf | api-merchant.kesles.com.conf |
|---|---|---|
server_name | kesles.com (+ www.kesles.com via separate vhost) | api-merchant.kesles.com |
| Consumer | Browser users (mobile app, dashboard staff, public landing page) | Backend integrators (PSP, partner bank, partner non-bank) — server-to-server |
| Content served | Static files (landing page, Flutter dashboard SPA) + reverse proxy | Reverse proxy only — no static |
| Mounted paths | / → static /var/www/html/kesles_new /merchant/dashboard/ → static SPA /merchant/api/ → port 8080 (mobile API) /merchant/dashboard-api/ → port 8082 (dashboard API) | /api/psp/v1/* → port 8082 /api/psp/v1/payment-events/* → port 8082 /api/partner/v1/* → port 8086 / → return 404 |
proxy_pass style | http://localhost:8080/; with trailing slash → strips prefix /merchant/api/ before forwarding | http://localhost:8082; without trailing slash → forwards path as-is (PSP integrator needs the full path /api/psp/v1/merchants) |
| Auth/security | CORS + dashboard session cookie | HMAC-SHA256 + IP allowlist (handler middleware, nginx is not involved in auth) |
| Internet visibility | Public for all end users | Public, but only callers with credentials at partner.api_credentials / psp.api_keys can authenticate |
| SSL cert | /etc/letsencrypt/live/kesles.com-0001/ | Same cert via --expand, OR a separate cert |
| Catch-all | try_files $uri $uri/ /index.html (SPA fallback) | return 404 (must NOT serve HTML / landing) |
Why split:
- Mixing SPA fallback with API path matching risks integrators receiving stale HTML when a backend route is missing.
- Browser-session middleware (CORS, cookies) and HMAC-only middleware should not coexist behind the same
server_name. - The two
proxy_passpatterns (strip vs preserve prefix) are easy to miswire when sharing one config. - Blast radius — an incident on the integration side should not force touching the dashboard / mobile vhost.
For step-by-step provisioning, see Runbook: Setup api-merchant.kesles.com Subdomain.
3.4 shared/ — AUDIT first, likely MERGE
- Contents not yet checked — likely shared code between Node services or assets.
- If the contents are: Go code → move to
packages/. TS code → move topackages/. Assets (logo, image) → move tomerchant_docs/static/or each app respectively. - Recommendation: empty top-level
shared/, distribute according to contents. - Action item: audit first.
3.5 scripts/ — MOVE to deploy/scripts/
- Contents: build scripts, deploy scripts (such as rsync binary to VM).
- More natural as a
deploy/subfolder because they are tied to the deployment lifecycle. - Recommendation: merge into
deploy/scripts/. Except dev-only scripts (e.g.generate-mock-data.sh) → move to the related service.
3.6 Existing merchant_docs/ — SPLIT into merchant_docs/ (public/internal) + REMOVE
The existing merchant_docs/ contains:
merchant_docs/
├── api/ ← API reference markdown
├── architecture/ ← service topology, DB schema
├── development/ ← dev checklist, local setup, API contracts
├── guidelines/ ← coding standards, PR template
├── operations/ ← ops SOP (role-access, etc.)
├── planning/ ← plan docs (this file too)
├── public/ ← public-facing docs (draft)
├── runbooks/ ← ops runbook (db backup, deploy)
└── secret/ ← 🚨 audit first — if it really contains secrets, move to a password manager, NOT docs
Mapping into merchant_docs/:
| Current folder | Target merchant_docs/ | Reason |
|---|---|---|
api/ | public/api-reference/ | external devs need this |
architecture/ | internal/architecture/ | internal only |
development/ | internal/development/ | internal dev workflow |
development/psp-integration-api-contract.md | public/psp-integration/ | the payment.kesles.com team needs it |
guidelines/ | internal/guidelines/ | internal coding standards |
operations/ | internal/operations/ | internal ops SOP |
planning/ | KEEP at merchant_docs/planning/ or move to internal/planning/ | planning docs are not developer-facing |
public/ | public/ | aligns directly |
runbooks/ | internal/runbooks/ | internal ops only |
secret/ | DELETE — audit and move to 1Password/Bitwarden | secrets NEVER live in the repo |
After the migration, the top-level merchant_docs/ folder is deleted or kept only for merchant_docs/planning/ (plan/ADR docs are not a fit for a public website).
Decision point: planning docs → keep at merchant_docs/planning/ or move to merchant_docs/internal/planning/?
- Argument to keep: a plan doc is not a developer guide, it's more like an ADR (Architecture Decision Record).
- Argument to move: one-stop-shop in Docusaurus, searchable.
My recommendation: keep merchant_docs/planning/ at the root for ADR-style docs, move the rest to merchant_docs/.
4. Final structure proposal (for confirmation)
kesles_merchant/
├── merchant_dashboard/ Flutter
├── merchant_mobile_user/ Flutter (NOT YET BUILT)
├── merchant_mobile_partner/ Flutter (NOT YET BUILT)
│
├── merchant_core_api/ Go
│ ├── cmd/
│ │ ├── server/
│ │ └── worker/
│ ├── internal/
│ └── go.mod
├── merchant_dashboard_api/ Go
│ ├── cmd/
│ │ ├── server/
│ │ └── psp-encrypt/
│ ├── internal/
│ └── go.mod
├── merchant_gateway_api/ Node (BFF)
├── merchant_whatsapp/ Node
│
├── merchant_database/ Postgres migrations
│ ├── db_kesles_merchant/
│ └── db_reference/
├── merchant_docs/ Docusaurus (2 sites)
│ ├── public/
│ ├── internal/
│ ├── shared-theme/
│ └── package.json
│
├── packages/ Shared libs (Go + TS)
│ ├── psp-integration-go/
│ └── psp-integration-ts/ (future)
│
├── deploy/ Infra-as-code
│ ├── nginx/
│ ├── systemd/
│ └── scripts/ (merged from scripts/)
│
├── merchant_docs/ ADR + planning only
│ └── planning/
│
├── .github/ CI/CD workflows (if any)
├── .gitignore
├── LICENSE
└── README.md root overview (links to merchant_docs)
5. Execution phasing
Phase 0 — NOW (safe, no rename)
- Create the new
merchant_docs/with Docusaurus (monorepo workspace) — this can run immediately because the folder is new and does not touch what exists. - Import existing markdown content from
merchant_docs/as Docusaurus source. The originalmerchant_docs/folder stays. - Deploy the Docusaurus static build to nginx (public at
docs.kesles.com, internal atdocs-internal.kesles.com).
Delivery: docs site live & accessible.
Phase 1 — SOON (gradual rename, low-risk)
After PSP integration is stable in prod (±1-2 weeks of monitoring):
merchant_database→merchant_database(dash to underscore)services/whatsapp_service→merchant_whatsappdeployments/→deploy/scripts/→ merge intodeploy/scripts/
Per rename:
- Make a separate branch
- Update all import paths + config path references
- Run full build + test
- Update the VM deploy script
- Merge → deploy → verify
Phase 2 — LATER (Go service rename, high-impact)
Go services have many import path references, the most complex rename:
merchant_core_api→merchant_core_apiservices/dashboard_api→merchant_dashboard_api
High effort because:
go.modmodule path changes- hundreds of file import statements change
systemdunitExecStartchangesnginxproxy config changes- VM deploy path
~/kesles_merchant/merchant_dashboard_api/already matches but the systemd binary path must be updated - CI/CD workflow changes
Strategy: run during a maintenance window if there is active traffic, or during a quiet hour.
Phase 3 — FUTURE (new projects)
- Build
merchant_mobile_user/(Flutter) — needs a spec + mobile dev team. - Build
merchant_mobile_partner/(Flutter) — needs a spec + mobile dev team. - Refactor shared UI to
packages/kesles_ui/(Flutter shared design system) so dashboard + mobile stay consistent.
Not touched until there is a clear team & spec.
6. What is needed to move forward
| # | Action | Owner | Blocker? |
|---|---|---|---|
| 1 | Confirm the final structure in section 4 | Keslespay | yes |
| 2 | Decide planning docs location (section 3.6) | Keslespay | no (default: keep merchant_docs/planning/) |
| 3 | Audit the contents of workers/ + shared/ to decide the merge strategy | Dev team | yes for Phase 1 |
| 4 | Audit the contents of merchant_docs/secret/ — make sure it's not a real secret | Keslespay | yes (security) |
| 5 | Kick off Phase 0 (Docusaurus scaffold merchant_docs/) | Dev team | nothing else blocks |
| 6 | Decide public/internal Docusaurus split — 1 monorepo workspace or 2 separate projects | Keslespay | for Phase 0 scaffolding |
| 7 | Final domain name for internal docs (docs-internal.kesles.com?) | Keslespay | for Phase 0 deploy |
7. Risks
- Massive rename: the biggest risk is the Go service import path change (hundreds of files). If test suite coverage isn't great, regressions may not be caught until runtime.
- Mitigation:
go vet ./...+ build verify + per-service E2E smoke test before merge.
- Mitigation:
- Production downtime: if the old binary at the old path is still on the VM and the new binary at the new path isn't deployed yet, the service goes down.
- Mitigation: parallel deploy (new binary at the new location, symlink old location to new, switch nginx, remove old location).
- Git history: renaming a folder breaks history if done with a plain
mv.- Mitigation: use
git mv+ Git's track-rename heuristic. Or usegit filter-repoto preserve history across the rename.
- Mitigation: use
8. Open questions (to discuss)
- Mobile apps tech stack: Flutter is confirmed — will the shared design system (
packages/kesles_ui) from the dashboard be reused, or will mobile have its own design system? merchant_gateway_api(Node) — scope: only a BFF, or the API gateway for all external calls? Will it become the entry point for payment.kesles.com or will that still hitmerchant_dashboard_apidirectly?merchant_docs— one Docusaurus project with two build outputs (public + internal via env flag), or two separate Docusaurus projects undermerchant_docs/public/andmerchant_docs/internal/?- Should Docusaurus support multi-language (EN + ID) from the start?
- Auth model for
docs-internal.kesles.com: nginx basic auth, Cloudflare Zero Trust, SSO (Google Workspace), or VPN IP allowlist?
9. Next steps after this plan is approved
- User approves the plan (or edits + comments).
- Run Phase 0 — scaffold
merchant_docs/Docusaurus. - Wait for PSP integration to stabilize in prod.
- Run Phase 1 low-risk renames (
merchant_database,merchant_gateway_api,merchant_whatsapp,deploy/). - Schedule a maintenance window + execute Phase 2 Go service rename.
- Phase 3 follows when there are mobile dev team resources.