# Architecture RenewCMS is a Go HTTP API and a Vue 3 SPA, shipped as a single binary. ## Backend layering Dependencies point inward: ``` api/ ──▶ internal/infrastructure/useCases ──▶ internal/domain ◀── internal/infrastructure/{persistence,mailer} ``` | Layer | Location | Rules | |---|---|---| | Domain | `internal/domain//` | Entities, `Err…` sentinel errors, `Repository` interfaces. Imports only the stdlib and other domain packages, with no GORM tags. | | Use cases | `internal/infrastructure/useCases/` | Application logic. Depends only on domain interfaces, never on persistence, mailer, GORM or `net/http`. | | Adapters | `internal/infrastructure/persistence/{models,mappers,repositories}`, `internal/infrastructure/mailer` | Implement a domain `Repository` (assert with `var _ domain.Repository = &X{}`). GORM structs stay in `models` and are converted by `mappers`. | | Delivery | `api/handlers//`, `api/router.go` | HTTP handlers receive use cases through `NewHandler(...)`. They never import persistence, mailer or GORM. | | Composition root | `api/dependecyInjection.go` | The only place that opens the DB, builds repositories and use cases, and lists models for `AutoMigrate`. | | Entry point | `cmd/server/main.go` | Env/config, HTTP server, graceful shutdown. | Nothing under `internal/` imports `api/`. ### Delivery packages | Package | Role | |---|---| | `api/handlers/user` | Authenticated user endpoints: profile update (`UpdateMe`) and the users list with invitations (`CreateInvite`, `ListUsers`). | | `api/pkg/publicuser` | `PublicUser`, the JSON shape of a user with no password, codes or token version. `publicuser.New` converts a domain `user.User`, so handlers never serialise the domain struct directly. | ## Frontend ↔ backend ``` web/ (Vue, Vite) │ task build:web (vue-tsc + vite build) ▼ internal/infrastructure/assets/dist/ gitignored build output │ //go:embed all:dist (assets_embed.go, default build) ▼ bin/renewcms ── /v1/* API ── /healthz ── /* SPA (api/handlers/frontend), index.html fallback ``` - **Production:** a single origin. The SPA calls the API with relative URLs, so the JWT cookie works without CORS. Files under `assets/` are content-hashed by Vite and cached for a year. `index.html` is never cached. - **Development:** built with `-tags nofrontend` (`assets_stub.go`, `assets.Dist == nil`). Vite serves the SPA on :5173 and proxies `/v1` to the API on :8080. See [development.md](development.md).