# Setup Templates & Checklists

Quick-reference templates for recurring setup tasks. Copy and customize for new projects.

## Table of Contents

1. [New SvelteKit Web App](#1-new-sveltekit-web-app)
2. [New NestJS Backend](#2-new-nestjs-backend)
3. [Deploying New Service to Staging](#3-deploying-new-service-to-staging)
4. [Adding Backend to ManaCore Dashboard](#4-adding-backend-to-manacore-dashboard)
5. [Quick Reference Port Assignments](#5-quick-reference-port-assignments)

---

## 1. New SvelteKit Web App

### Checklist

- [ ] Create `src/hooks.server.ts` with runtime env injection
- [ ] Update auth store to use `getAuthUrl()` pattern
- [ ] Update user-settings store to use `getAuthUrl()` pattern
- [ ] Update any API services to use lazy client initialization
- [ ] Add Dockerfile with pnpm symlink preservation
- [ ] Add to `docker-compose.staging.yml` with both internal and client URLs
- [ ] Test locally with `pnpm dev`
- [ ] Deploy and verify `window.__PUBLIC_*__` variables in browser console

### Template: hooks.server.ts

```typescript
// src/hooks.server.ts
import type { Handle } from '@sveltejs/kit';

// Runtime environment variables for client-side injection
const PUBLIC_MANA_CORE_AUTH_URL_CLIENT =
	process.env.PUBLIC_MANA_CORE_AUTH_URL_CLIENT || process.env.PUBLIC_MANA_CORE_AUTH_URL || '';
const PUBLIC_BACKEND_URL_CLIENT =
	process.env.PUBLIC_BACKEND_URL_CLIENT || process.env.PUBLIC_BACKEND_URL || '';

export const handle: Handle = async ({ event, resolve }) => {
	return resolve(event, {
		transformPageChunk: ({ html }) => {
			const envScript = `<script>
window.__PUBLIC_MANA_CORE_AUTH_URL__ = "${PUBLIC_MANA_CORE_AUTH_URL_CLIENT}";
window.__PUBLIC_BACKEND_URL__ = "${PUBLIC_BACKEND_URL_CLIENT}";
</script>`;
			return html.replace('<head>', `<head>${envScript}`);
		},
	});
};
```

### Template: getAuthUrl() Pattern

```typescript
// src/lib/stores/auth.svelte.ts
import { browser } from '$app/environment';

function getAuthUrl(): string {
	if (browser && typeof window !== 'undefined') {
		const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
			.__PUBLIC_MANA_CORE_AUTH_URL__;
		return injectedUrl || 'http://localhost:3001';
	}
	return process.env.PUBLIC_MANA_CORE_AUTH_URL || 'http://localhost:3001';
}

// Usage
const auth = initializeWebAuth({ baseUrl: getAuthUrl() });
```

### Template: Lazy API Client Initialization

```typescript
// src/lib/api/services/myservice.ts
import { browser } from '$app/environment';
import { createApiClient } from '../base-client';

function getApiUrl(): string {
	if (browser && typeof window !== 'undefined') {
		const injectedUrl = (window as unknown as { __PUBLIC_BACKEND_URL__?: string })
			.__PUBLIC_BACKEND_URL__;
		if (injectedUrl) {
			return `${injectedUrl}/api/v1`;
		}
	}
	return 'http://localhost:3000/api/v1';
}

// IMPORTANT: Lazy initialization - don't create client at module level!
let _client: ReturnType<typeof createApiClient> | null = null;

function getClient() {
	if (!_client) {
		_client = createApiClient(getApiUrl());
	}
	return _client;
}

export async function getData() {
	return getClient().get('/data');
}
```

### Template: Dockerfile (SvelteKit + pnpm)

```dockerfile
# Build stage
FROM node:20-alpine AS builder

RUN npm install -g pnpm@9.15.0

WORKDIR /app

# Copy workspace files
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
COPY apps/MYPROJECT/apps/web/package.json apps/MYPROJECT/apps/web/
COPY packages/ packages/

# Install all dependencies
RUN pnpm install --frozen-lockfile

# Copy source and build
COPY apps/MYPROJECT/apps/web apps/MYPROJECT/apps/web
RUN pnpm --filter @myproject/web build

# Production stage - PRESERVE PNPM SYMLINK STRUCTURE
FROM node:20-alpine AS production

# Keep same directory structure as builder
WORKDIR /app/apps/MYPROJECT/apps/web

# Copy pnpm store (target of symlinks)
COPY --from=builder /app/node_modules/.pnpm /app/node_modules/.pnpm

# Copy app's node_modules (contains symlinks)
COPY --from=builder /app/apps/MYPROJECT/apps/web/node_modules ./node_modules

# Copy built app
COPY --from=builder /app/apps/MYPROJECT/apps/web/build ./build
COPY --from=builder /app/apps/MYPROJECT/apps/web/package.json ./

EXPOSE 5173
CMD ["node", "build"]
```

### Template: docker-compose.staging.yml Entry

```yaml
myproject-web:
  image: ghcr.io/memo-2023/myproject-web:${MYPROJECT_WEB_VERSION:-latest}
  container_name: myproject-web-staging
  restart: unless-stopped
  ports:
    - '51XX:5173'
  environment:
    NODE_ENV: production
    # Server-side URLs (Docker internal network)
    PUBLIC_BACKEND_URL: http://myproject-backend:30XX
    PUBLIC_MANA_CORE_AUTH_URL: http://mana-core-auth:3001
    # Client-side URLs (browser access via public IP)
    PUBLIC_BACKEND_URL_CLIENT: http://46.224.108.214:30XX
    PUBLIC_MANA_CORE_AUTH_URL_CLIENT: http://46.224.108.214:3001
  depends_on:
    myproject-backend:
      condition: service_healthy
  healthcheck:
    test: ['CMD', 'wget', '--no-verbose', '--tries=1', '--spider', 'http://localhost:5173/health']
    interval: 30s
    timeout: 10s
    retries: 3
    start_period: 40s
  networks:
    - manacore-network
```

---

## 2. New NestJS Backend

### Checklist

- [ ] Use `text` type for all `user_id` columns (NOT `uuid`)
- [ ] Add health check endpoint at `/api/v1/health`
- [ ] Configure CORS to include manacore-web origin (port 5173)
- [ ] Add database to `docker/init-db/01-create-databases.sql`
- [ ] Add to `scripts/setup-databases.sh`
- [ ] Add `dev:myproject:full` command to root `package.json`
- [ ] Add Dockerfile with correct health check
- [ ] Add to `docker-compose.staging.yml` with proper CORS config

### Template: Drizzle Schema (user_id as text)

```typescript
// src/db/schema/main.schema.ts
import { pgTable, uuid, text, timestamp } from 'drizzle-orm/pg-core';

export const items = pgTable('items', {
	id: uuid('id').defaultRandom().primaryKey(),
	userId: text('user_id').notNull(), // ALWAYS text, not uuid!
	title: text('title').notNull(),
	createdAt: timestamp('created_at').defaultNow().notNull(),
	updatedAt: timestamp('updated_at').defaultNow().notNull(),
});
```

### Template: Health Controller

```typescript
// src/health/health.controller.ts
import { Controller, Get } from '@nestjs/common';

@Controller('api/v1/health')
export class HealthController {
	@Get()
	check() {
		return { status: 'ok', timestamp: new Date().toISOString() };
	}
}
```

### Template: CORS Configuration

```typescript
// src/main.ts
async function bootstrap() {
	const app = await NestFactory.create(AppModule);

	const corsOrigins = process.env.CORS_ORIGINS?.split(',') || [
		'http://localhost:5173', // Local dev
		'http://localhost:51XX', // App's own web
	];

	app.enableCors({
		origin: corsOrigins,
		credentials: true,
	});

	await app.listen(process.env.PORT || 30XX);
}
```

### Template: docker-compose.staging.yml Entry

```yaml
myproject-backend:
  image: ghcr.io/memo-2023/myproject-backend:${MYPROJECT_BACKEND_VERSION:-latest}
  container_name: myproject-backend-staging
  restart: unless-stopped
  ports:
    - '30XX:30XX'
  environment:
    NODE_ENV: production
    PORT: 30XX
    DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/myproject
    MANA_CORE_AUTH_URL: http://mana-core-auth:3001
    # CORS - Include app's web AND manacore-web dashboard
    CORS_ORIGINS: http://46.224.108.214:51XX,http://46.224.108.214:5173,http://localhost:51XX,http://localhost:5173
  depends_on:
    postgres:
      condition: service_healthy
    mana-core-auth:
      condition: service_healthy
  healthcheck:
    test: ['CMD', 'wget', '--no-verbose', '--tries=1', '--spider', 'http://localhost:30XX/api/v1/health']
    interval: 30s
    timeout: 10s
    retries: 3
    start_period: 40s
  networks:
    - manacore-network
```

---

## 3. Deploying New Service to Staging

### Pre-Deployment Checklist

- [ ] Database exists on staging PostgreSQL
- [ ] Dockerfile has correct health check path (`/api/v1/health` for backends)
- [ ] `docker-compose.staging.yml` has service definition
- [ ] CORS_ORIGINS includes all required origins
- [ ] Environment variables set correctly
- [ ] Tag format matches project name exactly

### Create Database on Staging

```bash
ssh -i ~/.ssh/hetzner_deploy_key deploy@46.224.108.214

# Create database
docker exec manacore-postgres-staging psql -U postgres -c 'CREATE DATABASE myproject;'

# Verify
docker exec manacore-postgres-staging psql -U postgres -c '\l' | grep myproject
```

### Deployment Tag Formats

| Project | Correct Tag Format | Wrong Format |
|---------|-------------------|--------------|
| mana-core-auth | `mana-core-auth-staging-v1.0.X` | `auth-staging-v1.0.X` |
| chat | `chat-staging-v1.0.X` or `chat-all-staging-v1.0.X` | - |
| todo | `todo-staging-v1.0.X` or `todo-all-staging-v1.0.X` | - |
| calendar | `calendar-staging-v1.0.X` | - |
| clock | `clock-staging-v1.0.X` | - |
| myproject | `myproject-staging-v1.0.X` | - |

### Post-Deployment Verification

```bash
# Check container is running correct version
docker ps --format '{{.Names}}: {{.Image}}' | grep myproject

# Check health endpoint
curl http://46.224.108.214:30XX/api/v1/health

# Check logs for errors
docker logs myproject-backend-staging --tail 50

# Test CORS (from manacore-web origin)
curl -I -X OPTIONS http://46.224.108.214:30XX/api/v1/endpoint \
  -H "Origin: http://46.224.108.214:5173" \
  -H "Access-Control-Request-Method: GET"
```

---

## 4. Adding Backend to ManaCore Dashboard

When adding a new backend service that manacore-web dashboard should call:

### Checklist

- [ ] Add CORS origin for manacore-web (port 5173) to backend
- [ ] Create API service file in `manacore/apps/web/src/lib/api/services/`
- [ ] Add runtime URL injection in `manacore/apps/web/src/hooks.server.ts`
- [ ] Add environment variables to `docker-compose.staging.yml` for manacore-web
- [ ] Deploy both manacore-web and the backend with new config

### Template: API Service File

```typescript
// apps/manacore/apps/web/src/lib/api/services/myservice.ts
import { browser } from '$app/environment';
import { createApiClient, type ApiResult } from '../base-client';

function getMyServiceApiUrl(): string {
	if (browser && typeof window !== 'undefined') {
		const injectedUrl = (window as unknown as { __PUBLIC_MYSERVICE_API_URL__?: string })
			.__PUBLIC_MYSERVICE_API_URL__;
		if (injectedUrl) {
			return `${injectedUrl}/api/v1`;
		}
	}
	return 'http://localhost:30XX/api/v1';
}

let _client: ReturnType<typeof createApiClient> | null = null;

function getClient() {
	if (!_client) {
		_client = createApiClient(getMyServiceApiUrl());
	}
	return _client;
}

// Export API functions
export async function getItems(): Promise<ApiResult<Item[]>> {
	return getClient().get('/items');
}

export async function createItem(data: CreateItemDto): Promise<ApiResult<Item>> {
	return getClient().post('/items', data);
}
```

### Template: hooks.server.ts Addition

```typescript
// Add to existing hooks.server.ts
const PUBLIC_MYSERVICE_API_URL_CLIENT =
	process.env.PUBLIC_MYSERVICE_API_URL_CLIENT || process.env.PUBLIC_MYSERVICE_API_URL || '';

// In transformPageChunk, add:
window.__PUBLIC_MYSERVICE_API_URL__ = "${PUBLIC_MYSERVICE_API_URL_CLIENT}";
```

### Template: docker-compose.staging.yml Addition

```yaml
manacore-web:
  environment:
    # ... existing env vars ...
    # Add new backend URL
    PUBLIC_MYSERVICE_API_URL: http://myservice-backend:30XX
    PUBLIC_MYSERVICE_API_URL_CLIENT: http://46.224.108.214:30XX
```

---

## 5. Quick Reference Port Assignments

### Backend Ports (3000-3099)

| Port | Service |
|------|---------|
| 3000 | chat-web (legacy) |
| 3001 | mana-core-auth |
| 3002 | chat-backend |
| 3006 | picture-backend |
| 3007 | zitare-backend |
| 3009 | manadeck-backend |
| 3015 | contacts-backend |
| 3016 | calendar-backend |
| 3017 | clock-backend |
| 3018 | todo-backend |

### Web App Ports (5100-5199)

| Port | Service |
|------|---------|
| 5173 | manacore-web |
| 5175 | picture-web |
| 5177 | zitare-web |
| 5179 | calendar-web |
| 5184 | contacts-web |
| 5186 | calendar-web (staging) |
| 5187 | clock-web |
| 5188 | todo-web |

### Next Available Ports

- **Backend**: 3019, 3020, 3021...
- **Web**: 5189, 5190, 5191...

---

## Common Mistakes Quick Reference

| Mistake | Fix |
|---------|-----|
| `import.meta.env` in Docker | Use `window.__PUBLIC_*__` injection |
| API client at module level | Use lazy `getClient()` pattern |
| `uuid` type for user_id | Use `text` type |
| Missing CORS for 5173 | Add manacore-web to CORS_ORIGINS |
| `auth-staging-v*` tag | Use `mana-core-auth-staging-v*` |
| ALTER TABLE without USING | Use `USING column::text` |
| `/api/health` endpoint | Use `/api/v1/health` |
