Add initial project files

This commit is contained in:
Igor Martyniuk
2026-09-01 13:03:41 +03:00
parent 22e111bb44
commit 74cb5b6176
1841 changed files with 242819 additions and 30 deletions
+65
View File
@@ -0,0 +1,65 @@
# Web Manager
The CMS behind the public website. Editors assemble localized pages from ready-made blocks in the
admin panel; the public Next.js site renders what they publish.
| Part | Source | Owns |
| ---------------------- | ------------------------------------------- | -------------------------------------------------------- |
| Backend (`WebManager`) | `apps/backend/app/base_modules/WebManager/` | Data, validation, draft/publish transactions, APIs |
| Admin (`Web Studio`) | `apps/admin/src/core/WebStudio/` | Editor UI, drafts, publishing actions, preview, settings |
| Public site | `apps/web/web-runtime/`, `apps/web/app/` | Server rendering, layouts, blocks, caching, SEO |
## Documentation
| Page | Purpose |
| ------------------------------- | ------------------------------------------------------------------ |
| [Backend](./backend.md) | Data model, HTTP API, query budget, tuning constants. |
| [Admin](./admin.md) | Web Studio: routes, editor, config files, UI conventions. |
| [Web](./web.md) | Public site rendering, layouts, hero, SEO, degradation. |
| [Integration](./integration.md) | Contracts, cross-app flows, caching, env wiring, extending, tests. |
## For Editors
A page is a title, an optional hero, an ordered list of blocks, and SEO fields. Blocks are the
prebuilt sections — text columns, FAQ, media, banner, article list — so building a page is picking
blocks and filling them in, not writing markup.
| Term | What it means |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| **Page** | One address in one language, e.g. `/en/about-us`. |
| **Group key** | Ties the same page across languages together (`about-us` links `/en/about-us` and `/uk/pro-nas`) so the site can offer a language switch. |
| **Page type** | Landing, Blog, or Blog item. Decides the layout and which blocks are offered. |
| **Hero** | The opening section. Four intensities: none, low, middle, high. |
| **Draft** | Your working copy. Never visible to visitors. |
| **Published** | Live on the site. |
| **Unpublished** | Taken down, but kept — content, blocks and address stay reserved, so you can put it back. |
| **Preview** | A private, time-limited link that renders the draft exactly as the site would. |
| **Site settings** | Header menus, footer, and legal links — shared by every page in one language. |
Everything you edit is a draft. A live page keeps serving the old version until you press Publish,
so an unfinished edit can never reach visitors. Nothing is deleted unless you press Delete.
## Page Lifecycle
```txt
create publish
(none) ────────► draft ───────────► published ◄──────┐
│ │ │
discard│ │unpublish │publish
▼ ▼ │
deleted unpublished ───────┘
published + open draft clone: visitors see the published row, the editor edits the clone
```
Every write happens in one transaction with rows locked `FOR UPDATE`; routes and permissions are in [backend.md](./backend.md), purge behavior in [integration.md](./integration.md#caching-and-revalidation).
- **Create** — `createDraft` validates the payload, asserts the `locale` + `path` is free across every root row (any status), and inserts the page with `status = draft` plus its components. Nothing public yet.
- **Save a draft** — `ensureDraft` edits a `draft` or `unpublished` page in place; for a `published` page it reuses or creates the draft clone. Components are replaced wholesale in the submitted order; a clone inherits its original's address.
- **Edit a published page** — the clone is a second row with `draft_of_id` pointing at the original (one clone per page, enforced by a partial unique index). Edit always opens the clone; the original keeps serving until the clone is published.
- **Preview** — a signed token (HMAC over `APP_KEY`, TTL 900s clamped to 60–3600s) lets the web app render the draft in an iframe sheet: `force-dynamic`, `noindex`, disallowed in robots. Absent, tampered, or expired tokens render "preview unavailable".
- **Publish** — publishes the **last saved** draft (the confirmation dialog says so). A pending clone is always preferred, so publishing can never drop the editor's work: a root draft goes live directly; a clone is folded into its original and deleted. A _structural_ change (first publish, republish, or changed locale/path/title/group key/page type) purges the whole cache tag; anything else purges only the affected page tags.
- **Unpublish** — same permission as publish; only valid on a published page (else 409). Sets `status = unpublished`, keeping content, components, and any open clone; the address stays reserved. Full purge — the URL now 404s.
- **Republish** — publish again; an unpublished page is edited in place, and republishing counts as structural.
- **Discard a draft** — deletes the draft row and its components; a clone's original is untouched. No purge.
- **Delete** — the only destructive action, behind a confirmation dialog. Components and any open clone cascade with the row; full purge if it was published.