# Carpa — Turborepo A multilingual content platform built as a Turborepo monorepo: - **Payload CMS** backend on SQLite, multilingual (**Spanish default** + English, French, Portuguese) - **Typesense** for faceted search, filtering and full-text search - **Astro** frontend (Tailwind v4 + DaisyUI) — the first of three parallel frontends - Shared **design-system** package (DaisyUI + React) documented in **Storybook** > Astro is live in this pass. Two more parallel frontends are planned on the same > backend + Typesense foundation: **Nuxt** (NuxtUI) and **Next.js** (DaisyUI). ## Layout ``` carpa_turborepo/ ├── apps/ │ ├── cms/ # Payload 3 + Next.js 16, SQLite, es/en/fr/pt localization │ └── web-astro/ # Astro 7 SSR, Tailwind v4 + DaisyUI, Typesense search ├── packages/ │ ├── typesense/ # shared client, collection schema, mappers + indexer CLI │ ├── ui/ # shared DaisyUI/React design system + Storybook │ ├── tailwind-config/# shared Carpa DaisyUI theme (carpa / carpa-dark) │ ├── tsconfig/ # shared TS base configs │ └── eslint-config/ # shared flat ESLint config ├── docker-compose.yml # Typesense service ├── turbo.json └── pnpm-workspace.yaml ``` ## Prerequisites - **Node ≥ 20.9**, **pnpm 10** (`corepack enable`) - **Docker** (for Typesense) — or a Typesense server binary ## Quick start ```bash # 1. Install pnpm install # 2. Environment — copy the examples and set secrets cp .env.example .env cp apps/cms/.env.example apps/cms/.env cp apps/web-astro/.env.example apps/web-astro/.env # In apps/cms/.env set a real PAYLOAD_SECRET: openssl rand -hex 32 # 3. Start Typesense pnpm typesense:up # 4. Start the CMS (creates the SQLite schema on first boot) pnpm --filter @carpa/cms dev # http://localhost:3000/admin # 5. (optional) Seed multilingual demo content pnpm --filter @carpa/cms seed # admin@carpa.test / changeme123 # 6. Create the browser search-only key, then build the search index pnpm typesense:keys pnpm typesense:index # 7. Start the Astro frontend pnpm --filter @carpa/web-astro dev # http://localhost:4321 (redirects to /es/) ``` Or run everything at once with `pnpm dev` (Turborepo). ## Pretty local URLs (Portless) Instead of juggling `localhost:3000` / `:4321`, you can serve the apps behind stable, HTTPS `.localhost` names with [Portless](https://portless.sh). It's wired in as an overlay — each app stays pinned to its normal port (`portless.json`), so the plain `localhost` workflow and the Typesense CLIs keep working unchanged; Portless just adds nice URLs on top. ```bash # From the repo root — starts the apps behind their URLs and trusts a local CA pnpm portless ``` | URL | App | | --- | --- | | https://cms.carpa.localhost | Payload admin + REST | | https://web.carpa.localhost | Astro frontend | For the **in-browser search** to work over HTTPS (a browser blocks a page on `https://…` from calling `http://localhost:8108`), expose Typesense over HTTPS too and point the frontend at it: ```bash pnpm portless:typesense # → https://typesense.carpa.localhost ``` then add the values from `apps/web-astro/.env.portless.example` to `apps/web-astro/.env` and restart the Astro dev server. Server-side calls (Astro SSR → Payload) can stay on `http://localhost:3000`. > First run trusts a local CA system-wide and may prompt for your password. > Portless also injects that CA into the dev servers it starts, so their > Node processes trust the HTTPS URLs automatically. ## How the pieces connect **Localization.** Locales live in one place — `packages/typesense/src/config.ts` (`LOCALES`, `DEFAULT_LOCALE`). Payload's `localization` config, the search index, and every frontend read from it, so adding a language is a one-line change. **Search sync.** Content stays searchable two ways: - **Live** — Payload `afterChange` / `afterDelete` hooks (`apps/cms/src/hooks/typesense.ts`) upsert/remove documents across all locales as you edit. If Typesense is down the hooks log a warning and the CMS keeps working. - **Bulk** — `pnpm typesense:index` rebuilds the whole `carpa_content` collection from Payload's REST API. The Typesense collection is flat: one row per `(document, locale)`, with `locale`, `type`, `categories` and `tags` as facets. The Astro UI filters by the active locale and refines on the rest. **Search-only key.** The browser uses a scoped `documents:search` key, never the admin key. `pnpm typesense:keys` provisions it deterministically. ## Frontends | App | Framework | UI library | Status | | --- | --- | --- | --- | | `web-astro` | Astro 7 (SSR) | DaisyUI | ✅ implemented | | `web-nuxt` | Nuxt | NuxtUI | ⏳ planned | | `web-next` | Next.js | DaisyUI | ⏳ planned | All three consume the same Payload REST API and the same Typesense index. ## Useful scripts (root) | Script | What it does | | --- | --- | | `pnpm dev` | Run all apps via Turborepo | | `pnpm build` | Build everything | | `pnpm typecheck` | Typecheck every workspace | | `pnpm typesense:up` / `:down` | Start/stop Typesense (Docker) | | `pnpm typesense:keys` | Create the browser search-only key | | `pnpm typesense:index` | Full reindex from Payload into Typesense | | `pnpm ui storybook` | Run the design-system Storybook (port 6006) | ## Notes - **SQLite → Turso/libSQL:** the SQLite adapter uses a libSQL client, so pointing `DATABASE_URI` at a Turso URL (with an auth token) is a drop-in for production. - **Migrations:** dev uses Drizzle `push` (auto-sync). For production run `pnpm --filter @carpa/cms migrate:create` then `migrate`.