From 796443c06df44be9a60dfd978bab3a52572f36db Mon Sep 17 00:00:00 2001 From: giles Date: Tue, 24 Feb 2026 20:10:23 +0000 Subject: [PATCH] Add monorepo README Co-Authored-By: Claude Opus 4.6 --- README.md | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..a64885a --- /dev/null +++ b/README.md @@ -0,0 +1,138 @@ +# Rose Ash + +Monorepo for the Rose Ash cooperative platform — six Quart microservices sharing a common infrastructure layer, a single PostgreSQL database, and an ActivityPub federation layer. + +## Services + +| Service | URL | Description | +|---------|-----|-------------| +| **blog** | blog.rose-ash.com | Content management, Ghost sync, navigation, editor | +| **market** | market.rose-ash.com | Product listings, scraping, market pages | +| **cart** | cart.rose-ash.com | Shopping cart, checkout, orders, SumUp payments | +| **events** | events.rose-ash.com | Calendar, event entries, container widgets | +| **federation** | federation.rose-ash.com | OAuth2 authorization server, ActivityPub hub, social features | +| **account** | account.rose-ash.com | User dashboard, newsletters, tickets, bookings | + +All services are Python 3.11 / Quart apps served by Hypercorn, deployed as a Docker Swarm stack. + +## Repository structure + +``` +rose-ash/ +├── shared/ # Common code: models, services, infrastructure, templates +│ ├── models/ # Canonical SQLAlchemy ORM models (all domains) +│ ├── services/ # Domain service implementations + registry +│ ├── contracts/ # DTOs, protocols, widget contracts +│ ├── infrastructure/ # App factory, OAuth, ActivityPub, fragments, Jinja setup +│ ├── templates/ # Shared base templates and partials +│ ├── static/ # Shared CSS, JS, images +│ ├── editor/ # Prose editor (Node build, blog only) +│ └── alembic/ # Database migrations +├── blog/ # Blog app +├── market/ # Market app +├── cart/ # Cart app +├── events/ # Events app +├── federation/ # Federation app +├── account/ # Account app +├── docker-compose.yml # Swarm stack definition +├── deploy.sh # Local build + restart script +├── .gitea/workflows/ # CI: build changed apps + deploy +├── _config/ # Runtime config (app-config.yaml) +├── schema.sql # Reference schema snapshot +└── .env # Environment variables (not committed) +``` + +Each app follows the same layout: + +``` +{app}/ +├── app.py # App entry point (creates Quart app) +├── path_setup.py # Adds project root + app dir to sys.path +├── entrypoint.sh # Container entrypoint (wait for DB, run migrations, start) +├── Dockerfile # Build instructions (monorepo context) +├── bp/ # Blueprints (routes, handlers) +│ └── fragments/ # Fragment endpoints for cross-app composition +├── models/ # Re-export stubs pointing to shared/models/ +├── services/ # App-specific service wiring +├── templates/ # App-specific templates (override shared/) +└── config/ # App-specific config +``` + +## Key architecture patterns + +**Shared models** — All ORM models live in `shared/models/`. Each app's `models/` directory contains thin re-export stubs. `factory.py` imports all six apps' models at startup so SQLAlchemy relationship references resolve across domains. + +**Service contracts** — Apps communicate through typed protocols (`shared/contracts/protocols.py`) and frozen dataclass DTOs (`shared/contracts/dtos.py`), wired via a singleton registry (`shared/services/registry.py`). No direct HTTP calls between apps for domain logic. + +**Fragment composition** — Apps expose HTML fragments at `/internal/fragments/` for cross-app UI composition. The blog fetches cart, account, navigation, and event fragments to compose its pages. Fragments are cached in Redis with short TTLs. + +**OAuth SSO** — Federation is the OAuth2 authorization server. All other apps are OAuth clients with per-app first-party session cookies (Safari ITP compatible). Login/callback/logout routes are auto-registered via `shared/infrastructure/oauth.py`. + +**ActivityPub** — Each app has its own AP actor (virtual projection of the same keypair). The federation app is the social hub (timeline, compose, follow, notifications). Activities are emitted to `ap_activities` table and processed by `EventProcessor`. + +## Development + +### Quick deploy (skip CI) + +```bash +# Rebuild + restart one app +./deploy.sh blog + +# Rebuild + restart multiple apps +./deploy.sh blog market + +# Rebuild all +./deploy.sh --all + +# Auto-detect changes from git +./deploy.sh +``` + +### Full stack deploy + +```bash +source .env +docker stack deploy -c docker-compose.yml coop +``` + +### Build a single app image + +```bash +docker build -f blog/Dockerfile -t registry.rose-ash.com:5000/blog:latest . +``` + +### Run migrations + +Migrations run automatically on the **blog** service startup when `RUN_MIGRATIONS=true` is set (only blog runs migrations; all other apps skip them). + +```bash +# Manual migration +docker exec -it $(docker ps -qf name=coop_blog) bash -c "cd shared && alembic upgrade head" +``` + +## CI/CD + +A single Gitea Actions workflow (`.gitea/workflows/ci.yml`) handles all six apps: + +1. Detects which files changed since the last deploy +2. If `shared/` or `docker-compose.yml` changed, rebuilds all apps +3. Otherwise rebuilds only apps with changes (or missing images) +4. Pushes images to the private registry +5. Runs `docker stack deploy` to update the swarm + +### Required secrets + +| Secret | Value | +|--------|-------| +| `DEPLOY_SSH_KEY` | Private SSH key for root access to the deploy host | +| `DEPLOY_HOST` | Hostname or IP of the deploy server | + +## Infrastructure + +- **Runtime**: Python 3.11, Quart (async Flask), Hypercorn +- **Database**: PostgreSQL 16 (shared by all apps) +- **Cache**: Redis 7 (page cache, fragment cache, sessions) +- **Orchestration**: Docker Swarm +- **Registry**: `registry.rose-ash.com:5000` +- **CI**: Gitea Actions +- **Reverse proxy**: Caddy (external, not in this repo)