Files
the-collective-hub/docs/07-development-plan.md

9.8 KiB

Development Implementation Plan

How to Use This

This is a step-by-step build order for Phase 1 and beyond. Each step lists what to build, what it depends on, and how to verify it works.

Follow the order. Each step builds on the previous one. Don't skip ahead.


Phase 1: Foundation

Step 1: Initialize SvelteKit Project

What to do:

  • Create a new SvelteKit project with create-svelte (choose TypeScript, ESLint, Prettier)
  • Set up the directory structure as outlined in the architecture plan
  • Configure svelte.config.js with the Node adapter for production builds
  • Create a basic +page.svelte that says "Hello World"
  • Create a Dockerfile for production builds

Depends on: Nothing.

Verify: npm run dev starts. Browser shows "Hello World". docker build succeeds.


Step 2: Set Up Postgres and Drizzle

What to do:

  • Install drizzle-orm, drizzle-kit, and postgres (the npm package)
  • Create drizzle.config.ts
  • Create src/lib/server/db/index.ts with the Postgres connection
  • Create src/lib/server/db/schema.ts with the sites table only (start simple)
  • Run drizzle-kit push to create the table in Postgres
  • Insert a test site row manually: INSERT INTO sites (slug, name) VALUES ('test-site', 'Test Site');

Depends on: Step 1. A running Postgres instance (local Docker or remote).

Verify: drizzle-kit studio shows the table. A simple script can query the test site.


Step 3: Create Core Tables

What to do:

  • Add all Phase 1 tables to schema.ts: sites, users, memberships, siteSettings
  • Define types, enums, indexes, and foreign keys
  • Run drizzle-kit push to create all tables
  • Optionally create a seed script for a test site

Depends on: Step 2.

Verify: All tables exist in the database. Relationships are correct in Drizzle Studio.


Step 4: Implement Site Resolver

What to do:

  • Create src/lib/server/site-resolver.ts
  • Export a function getSiteBySlug(slug: string) that queries the sites table and includes siteSettings
  • In src/hooks.server.ts, read SITE_SLUG from env, call the resolver, attach site to locals
  • Augment app.d.ts so locals.site is typed
  • In src/routes/+layout.server.ts, load site data from locals and return it
  • The homepage +page.svelte should display the site name

Depends on: Step 3.

Verify: Set SITE_SLUG=test-site in .env. Homepage shows "Test Site". Change the slug, restart, page breaks cleanly.


Step 5: Set Up Better Auth with Discord

What to do:

  • Install Better Auth and follow its SvelteKit setup guide
  • Configure Discord OAuth provider
  • Create src/lib/server/auth.ts with the Better Auth configuration
  • Add the Better Auth API route: src/routes/api/auth/[...betterAuth]/+server.ts
  • Create a /login page with a "Login with Discord" button
  • On successful login, upsert the user into the users table

Depends on: Step 3 (users table must exist).

Verify: Visit /login, click the Discord button, authorize, get redirected back. User record appears in the users table.


Step 6: Implement Owner Bootstrap

What to do:

  • In the auth callback or post-login hook, check if user.discordId === process.env.OWNER_DISCORD_ID
  • If match: upsert a membership row with role owner for the current site
  • In src/hooks.server.ts, after auth, load the user's membership for the current site and attach to locals
  • Create an admin auth guard: a +layout.server.ts in /admin that checks locals.membership

Depends on: Step 4 (site resolver), Step 5 (auth).

Verify: Log in with the Discord account matching OWNER_DISCORD_ID. Membership row with owner role appears. Navigate to /admin — accessible. Log in with a different Discord account — /admin redirects to /login with an error message.


Step 7: Build Admin Layout

What to do:

  • Create src/routes/admin/+layout.svelte with sidebar navigation and top bar
  • Create src/routes/admin/+layout.server.ts with the auth guard
  • Create a minimal src/routes/admin/+page.svelte dashboard

Depends on: Step 6.

Verify: Owner logs in, sees admin layout with nav sidebar. Non-owner cannot access.


Step 8: Build Admin Settings Page

What to do:

  • Create src/routes/admin/settings/+page.svelte
  • Form fields: site name, tagline
  • Load current values from siteSettings.settings JSON
  • Create a form action (or API endpoint) that updates the JSON
  • Add basic form validation and success/error feedback

Depends on: Step 7.

Verify: Owner can edit site name and tagline. Changes persist after reload. Public homepage reflects changes.


Step 9: Build Public Homepage

What to do:

  • Update src/routes/+page.server.ts to load site settings as a flat object
  • Build src/routes/+page.svelte with sections:
    • Hero (site name, tagline, CTA button if configured)
    • About (text from settings if configured)
  • Apply basic CSS styling with CSS custom properties (hardcode dark theme defaults for now)
  • Handle empty states: sections hide when no content configured

Depends on: Step 4 (site resolver), Step 8 (settings exist).

Verify: Public homepage shows site name and content. Changing settings in admin updates the public page.


Step 10: Add Theme CSS Variables

What to do:

  • In the root layout, generate CSS custom properties from theme settings
  • Fall back to sensible defaults if no theme configured
  • Apply variables to the public homepage
  • Admin panel can use a fixed theme (don't need the public theme in admin)

Depends on: Step 9.

Verify: Changing accent color in admin (once branding page is built) changes the public page's button color. Default theme looks clean.


Step 11: Set Up CDN and Asset Upload

What to do:

  • Configure CDN storage client (Bunny CDN or S3-compatible)
  • Create src/lib/server/cdn.ts with upload, delete, and URL construction helpers
  • Create an image upload API endpoint: src/routes/api/assets/+server.ts
  • Implement webp conversion and optimization (using sharp or similar)
  • File validation: accepted types (PNG, JPEG, WebP input), max size
  • Auto-generate CDN keys: sites/{siteSlug}/{type}/{uuid}.webp
  • Create asset records in the assets table on successful upload
  • Create asset library page: src/routes/admin/assets/+page.svelte

Depends on: Step 4 (site resolver for siteSlug), Step 7 (admin layout).

Verify: Upload an image via admin. Asset record appears in database. File exists in CDN bucket. Asset library page shows uploaded files. Image is served correctly via CDN URL.


Step 12: Set Up Migration Automation

What to do:

  • Add RUN_MIGRATIONS env var check in app startup
  • If RUN_MIGRATIONS=true, run drizzle-kit migrate on startup
  • If RUN_MIGRATIONS=false (or unset), skip migrations entirely
  • Log migration status on startup for observability
  • Document which deployment is the migration runner

Depends on: Step 2 (Drizzle configured).

Verify: Start app with RUN_MIGRATIONS=true — migrations run. Start with RUN_MIGRATIONS=false — migrations are skipped. Both deployments work against the same database.


Step 13: Implement Super Admin Access

What to do:

  • Parse SUPER_ADMIN_DISCORD_IDS env var (comma-separated) on startup
  • In the auth hook / membership check, compare user's Discord ID against the super admin list
  • If match: bypass site-scoped membership checks, grant full admin access
  • Attach super admin flag to locals for conditional UI (e.g., "View All Sites" link)

Depends on: Step 6 (owner bootstrap / membership logic).

Verify: Log in with a Discord ID in SUPER_ADMIN_DISCORD_IDS. Access any site's admin panel. Log in with a non-super-admin ID — restricted to their own site.


Step 14: Admin Branding Page

What to do:

  • Create src/routes/admin/branding/+page.svelte
  • Color pickers for accent, background, text colors
  • Theme preset dropdown (Dark, Light, Custom)
  • Logo and background selectors: pick from asset library (uploaded in Step 11)

Depends on: Step 8 (settings save pattern), Step 11 (asset library).

Verify: Owner can change colors. Public page reflects changes immediately. Logo and background can be selected from uploaded assets.


Step 15: Dockerize and Deploy

What to do:

  • Finalize Dockerfile (multi-stage build for Node)
  • Create docker-compose.yml for local testing with Postgres
  • Set up first Coolify deployment (designate as migration runner: RUN_MIGRATIONS=true)
  • Configure all environment variables in Coolify
  • Deploy and verify public site is accessible

Depends on: Step 9 (working public site), Step 12 (migration automation).

Verify: Site is live at the configured URL. Login works. Admin works. Migrations ran on startup.


Phase 2 Onward

Once Phase 1 is fully working, continue with:

  1. Nav links + social links admin pages and public rendering
  2. Homepage content editor (hero title, subtitle, about text, CTA)
  3. Events: schema, admin CRUD, public render
  4. Super admin dashboard (Phase 4)
  5. Role management (Phase 5)

Detailed steps for Phases 2-5 should be written once Phase 1 is complete and any lessons learned are incorporated.


Development Rules

  • One migration per schema change. Don't batch unrelated changes into one migration.
  • Test with multiple SITE_SLUG values locally. Use different .env files or a script that switches them.
  • Commit often. Small, focused commits are easier to reason about.
  • Migrations run automatically on startup, gated by RUN_MIGRATIONS. Only one deployment runs them. All others skip.
  • Keep the public site SSR-only. No client-side data fetching for public pages.
  • Admin pages can use client-side interactivity. Forms, file uploads, etc. can use Svelte's client-side features.
  • All uploaded images are converted to webp. No raw user files stored on CDN.