Skip to content

refactor: restructure layers and close the leaks it surfaced - #1

Merged
canakyuz merged 23 commits into
mainfrom
refactor/structure
Oct 4, 2026
Merged

canakyuz merged 23 commits into
mainfrom
refactor/structure

Conversation

@canakyuz

@canakyuz canakyuz commented Oct 4, 2026

Copy link
Copy Markdown
Owner

Restructures the repository so each layer has one name and one place, and fixes the security bugs the restructuring surfaced. Every fix has a test that fails when the fix is reverted.

Security

  • gRPC CreateTenant skipped the platform permission. REST checked it in a route middleware; the gRPC interceptor only checks membership of the caller's own tenant, so with GRPC_ADDR set any member of any tenant, a viewer included, could create tenants. The check now lives in internal/usecase/operation, which both ports call.
  • Suspended tenants kept access for up to ten minutes. The schema cache is what refuses an inactive tenant, and nothing invalidated it. The tenant service now drops the schema and plan caches on every status and plan change. Other replicas keep their in-process copy for at most 30 seconds (ADR-0006 updated).
  • Database errors reached the client. TenantScope and 25 handler paths in the control plane, plus 57 in examples/verticals, wrote err.Error(), so an outage returned the driver message with host and port, often under a 404 or 403. Client-safe errors are now pkg/clienterr; internal/handler/respond answers anything else with a logged, generic 500 on its own, independent of the app's error handler. The checkout.com adapter's raw provider response no longer reaches the browser.

Structure

  • Registry services moved to internal/usecase/registry, DTOs next to their handlers.
  • Operation, webhook and upload rules moved from handlers into usecases. Rule-free reads (audit, gRPC ListUsers) still call the repository directly; ADR-0009 records why and when that stops being true.
  • Interfaces are declared by their consumer; repositories return concrete types.
  • pkg/database moved to internal/database (it imported internal/config). An architecture test now forbids pkg/ importing internal/.
  • Tenant schema templates are embedded with go:embed. The container image never copied templates/, so any template with real SQL would have been skipped silently.
  • Root cleanup: Dockerfile at the root, swagger page under api/docs, k6 profile in scripts/.
  • Removed: the unused generated OpenAPI client, the project example module (never mounted), and about 60 unreachable functions found with deadcode and staticcheck.

Behaviour changes

  • Payment routes in examples/verticals answer 500 instead of 400 on database or provider failures. User-facing payment errors keep their message.

Verification

go test -race ./... against real PostgreSQL (26 packages), gofmt, go vet, staticcheck -checks U1000, scripts/langcheck.py, and a Docker build.

@canakyuz
canakyuz merged commit bbc5286 into main Oct 4, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant