3.3 KiB
3.3 KiB
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/<name>/ |
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/<name>/, 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/handlers/media |
The media library under /v1/media (list, upload, rename, delete, merge, usage). Images belong to the library, not to an article: deleting an article never deletes an image, and an image's "used in" is read from article bodies each time. |
api/pkg/upload |
Multipart file reading and the mapping of upload errors (415, 413, 400) shared by both upload routes. |
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. |
Image metadata backfill
At boot, BackfillImageMetadata fills filename, size, content_type, width, height and sha256 for image rows that lack them, reading the files in UPLOAD_DIR. It is idempotent, and a row whose file is missing or unreadable is left as it is without stopping the boot. The old article_id column and the article_images table are kept and never written.
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.htmlis never cached. - Development: built with
-tags nofrontend(assets_stub.go,assets.Dist == nil). Vite serves the SPA on :5173 and proxies/v1to the API on :8080. See development.md.