Files
zasderq 7588ddce1f feat(db): add nav_links, social_links, and events tables
- Add nav_links table with siteId, label, url, position, sortOrder, isExternal
- Add social_links table with siteId, platform, label, url, icon, sortOrder
- Add events table with siteId, title, description, eventType, startTime, endTime, timezone, location, externalLink, imageCdnKey, isPublished, isRecurring
- Include corresponding Drizzle migration entries
2026-06-06 02:25:49 -04:00

214 lines
9.5 KiB
Markdown

# The Collective Hub
> A reusable SvelteKit website template for launching branded landing pages for online theater hosts, watch-party communities, and similar groups. One codebase, multiple deployed websites, one shared database, one CDN.
## Features
- Public homepage for a theater/community host with event and schedule display
- Discord OAuth login for site owners and admins
- Admin panel for customizing branding (name, logo, colors, tagline)
- Homepage content editor (intro text, hero, buttons, links)
- Multi-site support from a single codebase using `SITE_SLUG` environment variable
- Shared PostgreSQL database with data scoped by `siteId` — logically multi-tenant
- Shared CDN/object storage bucket with per-site path namespacing
- Full image upload flow with automatic WebP conversion and optimization
- Asset library for browsing and managing uploaded files
- Super admin cross-site access via `SUPER_ADMIN_DISCORD_IDS`
## Tech Stack
| Layer | Choice | Notes |
|-------|--------|-------|
| Framework | SvelteKit | File-based routing, SSR, API routes |
| Language | TypeScript | Strict mode |
| Database | PostgreSQL 16 | Single shared instance for all sites |
| ORM | Drizzle ORM | Type-safe, SQL-first |
| Auth | Better Auth | Discord OAuth provider |
| CDN / Storage | Bunny CDN | Single bucket, site-scoped paths, auto WebP conversion |
| Image Processing | Sharp | Server-side resize and format conversion |
| Containerization | Docker | Multi-stage build, Node 22 Alpine |
| Deployment | Coolify | Multiple deployments from one Git repo |
## Getting Started
### Prerequisites
- Node.js 22+
- Docker (for local PostgreSQL)
### Setup
```bash
# 1. Clone and install
git clone <repo-url>
cd collective-terminal
npm install
# 2. Start PostgreSQL
docker compose up -d
# 3. Create your .env file
```
Create a `.env` file at the project root with these values for local development:
```env
DATABASE_URL=postgresql://hub_dev:hub_dev_password@localhost:5432/collective_hub
SITE_SLUG=local-dev
```
```bash
# 4. Create database tables
npm run db:push
```
> **Alternative:** Generate and run formal migrations instead of `db:push`:
> ```bash
> npm run db:generate
> npm run db:migrate
> ```
```bash
# 5. Seed the default "local-dev" site
npm run db:seed
# 6. Start the dev server
npm run dev
```
The app opens at [http://localhost:5173](http://localhost:5173).
> **Note:** Discord login will not work locally without a configured Discord OAuth application with `http://localhost:5173` in its redirect URL list. The site resolver and public homepage function without Discord auth.
## Environment Variables
### Required
| Variable | Example | Description |
|----------|---------|-------------|
| `SITE_SLUG` | `bad-movies-theater` | Identifies which site this deployment serves. Must match a `slug` in the `sites` table. |
| `PUBLIC_SITE_URL` | `https://badmovies.example.com` | Public URL of this deployment. Used for auth callbacks and canonical URLs. |
| `DATABASE_URL` | `postgresql://user:pass@host:5432/collective_hub` | PostgreSQL connection string. Same value across all deployments. |
| `BETTER_AUTH_SECRET` | *(generated)* | Secret key for session signing. Must be identical across all deployments. |
| `BETTER_AUTH_URL` | `https://badmovies.example.com` | Base URL for auth callbacks. Must match `PUBLIC_SITE_URL`. |
| `DISCORD_CLIENT_ID` | `123456789012345678` | Discord OAuth application client ID. Shared across all deployments. |
| `DISCORD_CLIENT_SECRET` | `abc123...` | Discord OAuth application client secret. Shared across all deployments. |
| `OWNER_DISCORD_ID` | `123456789012345678` | Discord user ID of the site owner. Bootstraps ownership on first login. Per-site. |
### CDN (Required for asset uploads)
| Variable | Example | Description |
|----------|---------|-------------|
| `CDN_BASE_URL` | `https://cdn.example.com` | Base URL for constructing public CDN URLs. |
| `CDN_STORAGE_ENDPOINT` | `https://ny.storage.bunnycdn.com` | Storage API endpoint. |
| `CDN_ACCESS_KEY` | `abc123...` | Storage access key (BunnyCDN password or S3 access key). |
| `CDN_BUCKET` | `collective-hub` | Bucket or storage zone name. |
### Optional
| Variable | Example | Description |
|----------|---------|-------------|
| `SUPER_ADMIN_DISCORD_IDS` | `123456789,987654321` | Comma-separated Discord user IDs with cross-site super admin access. |
| `RUN_MIGRATIONS` | `true` | Whether this deployment runs database migrations on startup. Only **one** deployment should have `true`. |
| `NODE_ENV` | `production` | Node environment (`development` or `production`). |
| `LOG_LEVEL` | `info` | Logging verbosity (`debug`, `info`, `warn`, `error`). |
### Shared vs Per-Site
**Shared** (same across all deployments): `DATABASE_URL`, `BETTER_AUTH_SECRET`, `DISCORD_CLIENT_ID`, `DISCORD_CLIENT_SECRET`, `SUPER_ADMIN_DISCORD_IDS`, `CDN_*`
**Per-site** (unique per deployment): `SITE_SLUG`, `PUBLIC_SITE_URL`, `BETTER_AUTH_URL`, `OWNER_DISCORD_ID`, `RUN_MIGRATIONS`
## npm Scripts
| Script | Command | Description |
|--------|---------|-------------|
| `dev` | `vite dev` | Start the Vite dev server with HMR. |
| `build` | `vite build` | Build for production (output to `build/`). |
| `preview` | `vite preview` | Preview the production build locally. |
| `db:generate` | `drizzle-kit generate` | Generate SQL migration files from Drizzle schema changes. |
| `db:migrate` | `drizzle-kit migrate` | Apply pending SQL migrations to the database. |
| `db:push` | `drizzle-kit push` | Push schema changes directly to the database (no migration files). Convenient for early development. |
| `db:studio` | `drizzle-kit studio` | Open Drizzle Studio, a GUI for browsing and editing data. |
| `db:seed` | `tsx src/lib/server/db/seed.ts` | Insert the default `local-dev` site and its settings row. Idempotent — safe to run multiple times. |
| `check` | `svelte-kit sync && svelte-check` | Type-check the project with `svelte-check`. |
| `lint` | `eslint .` | Run ESLint across the project. |
| `format` | `prettier --write .` | Format all source files with Prettier. |
## Project Structure
```
.
├── src/
│ ├── app.d.ts # App types, locals augmentation
│ ├── app.html # HTML shell
│ ├── hooks.server.ts # Site resolver, auth handling
│ ├── lib/
│ │ ├── server/
│ │ │ ├── auth.ts # Better Auth configuration
│ │ │ ├── cdn.ts # CDN URL helpers
│ │ │ ├── site-resolver.ts # Site loading by SITE_SLUG
│ │ │ └── db/
│ │ │ ├── index.ts # Drizzle + Postgres connection
│ │ │ ├── migrate.ts # Automated migration runner
│ │ │ ├── schema.ts # All table definitions
│ │ │ └── seed.ts # Local dev seed data
│ │ └── shared/
│ │ └── types.ts # Shared TypeScript types
│ └── routes/
│ ├── +layout.server.ts # Root layout, loads site context
│ ├── +layout.svelte
│ ├── +page.server.ts # Public homepage data
│ ├── +page.svelte # Public homepage
│ ├── login/
│ │ └── +page.svelte # Discord OAuth login page
│ ├── admin/
│ │ ├── +layout.server.ts # Admin auth guard
│ │ ├── +layout.svelte # Admin shell / navigation
│ │ ├── +page.svelte # Admin dashboard
│ │ ├── branding/ # Logo, colors, theme editor
│ │ ├── settings/ # Site settings editor
│ │ └── assets/ # Asset library (upload, browse)
│ └── api/
│ └── assets/ # Asset upload API endpoint
├── drizzle/ # Drizzle migration files
├── scripts/ # Utility scripts
├── docs/ # Project documentation
├── docker-compose.yml # Local PostgreSQL
├── Dockerfile # Multi-stage production build
├── drizzle.config.ts # Drizzle Kit configuration
├── svelte.config.js # SvelteKit configuration
├── tsconfig.json
├── vite.config.ts
└── package.json
```
## Deployment
The project is deployed via **Coolify** with Docker. Each community site is a separate Coolify deployment pointing to the same Git repository and branch. Deployments are differentiated solely by environment variables — specifically `SITE_SLUG`.
```
Git Repo (main branch)
├── Coolify Deployment "bad-movies-theater"
│ └── Env: SITE_SLUG=bad-movies-theater
├── Coolify Deployment "garbage-day"
│ └── Env: SITE_SLUG=garbage-day
└── Coolify Deployment "future-site"
└── Env: SITE_SLUG=future-site
```
Key deployment rules:
- All deployments share one PostgreSQL database and one CDN bucket.
- Exactly **one** deployment must have `RUN_MIGRATIONS=true`. All others set `RUN_MIGRATIONS=false`.
- The migration-runner deployment should be deployed first when schema changes are included in a release.
- Sensitive values (secrets, keys, connection strings) are stored in Coolify's environment variable management — never committed to the repository.
- The Dockerfile uses a multi-stage build (Node 22 Alpine) producing a small production image that runs [`build/index.js`](build/index.js).
## License
TBD