diff --git a/apps/api/.env.example b/apps/api/.env.example index 0c5e3f11..ebecb7a3 100644 --- a/apps/api/.env.example +++ b/apps/api/.env.example @@ -1,40 +1,43 @@ -DATABASE_URL= +# Required +DATABASE_URL="" +REDIS_URL="" +PUBLIC_SERVER_URL="" -## Object storage - choose one option below +TRUSTED_ORIGINS="" +BETTER_AUTH_SECRET="" -# Option A: Cloudflare R2 (legacy) -# CLOUDFLARE_BUCKET_DOMAIN=https://cdn.example.com -# CLOUDFLARE_ACCESS_KEY= -# CLOUDFLARE_SECRET_ACCESS_KEY= -# CLOUDFLARE_ACCOUNT_ID= -# CLOUDFLARE_IMAGE_BUCKET_DOMAIN=https://media.example.com +AUTH_BEARER_TOKEN="" +AUTH_COOKIE_DOMAIN="" -# Option B: MinIO or other S3-compatible storage (on-premise, data residency) -# When API runs locally: use localhost. When API runs in Docker: use http://minio:9000 -# OBJECT_STORAGE_ENDPOINT=http://localhost:9000 -# OBJECT_STORAGE_PUBLIC_ENDPOINT=http://localhost:9000 -# OBJECT_STORAGE_ACCESS_KEY_ID=minioadmin -# OBJECT_STORAGE_SECRET_ACCESS_KEY=minioadmin -# OBJECT_STORAGE_FORCE_PATH_STYLE=true -# OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL=http://localhost:9000/media +SMTP_HOST="" +SMTP_PORT="" +SMTP_USER="" +SMTP_SENDER="" +SMTP_PASSWORD="" -# Email -SMTP_HOST= -SMTP_USER= -SMTP_PASSWORD= -SMTP_PORT= -SMTP_SENDER= +# Optional - MinIO (S3-compatible, for Docker / on-premise self-hosting) +# When API runs in Docker: use http://minio:9000. For local API: use http://localhost:9000 +MINIO_ROOT_USER="" +MINIO_ROOT_PASSWORD="" +OBJECT_STORAGE_ENDPOINT="" +OBJECT_STORAGE_PUBLIC_ENDPOINT="" +OBJECT_STORAGE_ACCESS_KEY_ID="" +OBJECT_STORAGE_SECRET_ACCESS_KEY="" +OBJECT_STORAGE_FORCE_PATH_STYLE="" +OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL="" -# Enterprise features (optional for self-hosted deployments) -# Request a key from ClassroomIO. Leave empty if you do not use Enterprise-only features. -LICENSE_KEY= +# Optional - Cloudflare R2 (for hosted deployments) +CLOUDFLARE_BUCKET_ID="" +CLOUDFLARE_ACCESS_KEY="" +CLOUDFLARE_ACCOUNT_ID="" +CLOUDFLARE_BUCKET_DOMAIN="" +CLOUDFLARE_SECRET_ACCESS_KEY="" +SENTRY_DNS="" -# Sentry DNS -SENTRY_DNS= +# Enterprise (Optional) +# Required only when using Enterprise-only features (SSO, token-auth, no-tracking) +LICENSE_KEY="" -OPENAPI_URL= - -PRIVATE_SERVER_KEY= - -# Public API URL (used for absolute URLs in emails, redirects, etc.) -PUBLIC_SERVER_URL= +# SSO (Optional) +# For enterprise SSO with SAML (future support) +# SSO_ENCRYPTION_KEY="your-32-character-secret-key" \ No newline at end of file diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index 46bbe513..2af73cf2 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -3,9 +3,9 @@ import 'dotenv/config'; import { API_PORT } from '@api/constants'; import { app } from '@api/app'; import { configureOpenAPI } from '@api/utils/openapi'; +import { connectRedis } from '@api/utils/redis/redis'; import { serve } from '@hono/node-server'; import { showRoutes } from 'hono/dev'; -import { connectRedis } from '@api/utils/redis/redis'; // Start server async function startServer() { diff --git a/apps/docs/content/docs/quickstart/self-hosting.mdx b/apps/docs/content/docs/quickstart/self-hosting.mdx index 73fc64ce..7b00c48a 100644 --- a/apps/docs/content/docs/quickstart/self-hosting.mdx +++ b/apps/docs/content/docs/quickstart/self-hosting.mdx @@ -150,8 +150,8 @@ For Enterprise SSO support, you need to configure the following: TRUSTED_ORIGINS="https://app.classroomio.com,https://your-org.okta.com,https://accounts.google.com" ``` -3. **Callback URLs**: Ensure your `SERVER_URL` and `PUBLIC_SERVER_URL` are correctly set. SSO callbacks will be: - - `{SERVER_URL}/api/auth/sso/callback/{providerId}` +3. **Callback URLs**: Ensure your `PUBLIC_SERVER_URL` is correctly set. SSO callbacks will be: + - `{PUBLIC_SERVER_URL}/api/auth/sso/callback/{providerId}` 4. **License Check**: The system will verify your Enterprise license before allowing SSO configuration. Without a valid license, the SSO settings page will show an upgrade prompt. @@ -170,20 +170,31 @@ Go ahead and click this button to get started. ```zsh title="env" # Required DATABASE_URL="" -PRIVATE_DATABASE_URL="" REDIS_URL="" -SERVER_URL="" +PUBLIC_SERVER_URL="" TRUSTED_ORIGINS="" BETTER_AUTH_SECRET="" AUTH_BEARER_TOKEN="" AUTH_COOKIE_DOMAIN="" +PRIVATE_SERVER_KEY="" SMTP_HOST="" SMTP_PORT="" SMTP_USER="" SMTP_SENDER="" SMTP_PASSWORD="" -# Optional +# Optional - MinIO (S3-compatible, for Docker / on-premise self-hosting) +# When API runs in Docker: use minio service name. For local API: use http://localhost:9000 +MINIO_ROOT_USER=minioadmin +MINIO_ROOT_PASSWORD=minioadmin +OBJECT_STORAGE_ENDPOINT=http://minio:9000 +OBJECT_STORAGE_PUBLIC_ENDPOINT=http://localhost:9000 +OBJECT_STORAGE_ACCESS_KEY_ID=minioadmin +OBJECT_STORAGE_SECRET_ACCESS_KEY=minioadmin +OBJECT_STORAGE_FORCE_PATH_STYLE=true +OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL=http://localhost:9000/media + +# Optional - Cloudflare R2 (for hosted deployments) CLOUDFLARE_BUCKET_ID="" CLOUDFLARE_ACCESS_KEY="" CLOUDFLARE_ACCOUNT_ID="" @@ -327,11 +338,20 @@ PRIVATE_SERVER_KEY= # Required only for Enterprise-only features (SSO, token-auth, no-tracking) LICENSE_KEY= -SERVER_URL= PUBLIC_SERVER_URL= PRIVATE_SERVER_URL= TRUSTED_ORIGINS= AUTH_COOKIE_DOMAIN= + +# MinIO (object storage, when using ./run-docker-full-stack.sh with MinIO) +MINIO_ROOT_USER=minioadmin +MINIO_ROOT_PASSWORD=minioadmin +OBJECT_STORAGE_ENDPOINT=http://minio:9000 +OBJECT_STORAGE_PUBLIC_ENDPOINT=http://localhost:9000 +OBJECT_STORAGE_ACCESS_KEY_ID=minioadmin +OBJECT_STORAGE_SECRET_ACCESS_KEY=minioadmin +OBJECT_STORAGE_FORCE_PATH_STYLE=true +OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL=http://localhost:9000/media ``` diff --git a/docker/.env.prod.example b/docker/.env.prod.example index 21b40be4..18773bda 100644 --- a/docker/.env.prod.example +++ b/docker/.env.prod.example @@ -8,7 +8,7 @@ POSTGRES_USER=postgres POSTGRES_PASSWORD=postgres # API runtime -SERVER_URL=https://api.domain.com +PUBLIC_SERVER_URL=https://api.domain.com TRUSTED_ORIGINS=https://app.domain.com # Optional: cookie domain for Better Auth cross-subdomain cookies. # Use either .domain.com (shared across subdomains) or your API host. diff --git a/packages/db/README.md b/packages/db/README.md index 1f0f2341..7f541cd1 100644 --- a/packages/db/README.md +++ b/packages/db/README.md @@ -34,12 +34,30 @@ Now when I am ready to deploy the feature, I can use [option 2](https://orm.driz I can make changes directly to the schema file and run `pnpm db push` to push the changes to the source db. -## Change email flow +## Setting up a new DB on Railway -1. User changes email on dashboard to new email -2. We send an email verification to the current, permitting the change -3. Meanwhile the frontend reverts to the previous email. -4. User clicks approve link in email -5. User is redirected to settings page with message that another email has been sent to the new email -6. User goes to new email inbox and clicks approve link in email -7. User is taken to the settings page with new email address. +Once you setup a Postgres DB on Railway, you should copyt the `DATABASE_PUBLIC_URL` and paste into your `.env`. In the `.env` we expect `DATABASE_URL`: + + ```.env + DATABASE_URL=what-you-copied-from-railway + ``` + +### Commands + +#### db:setup + +Prepares the database so the application can run. Run this for new databases or after provisioning Postgres. + +``` +pnpm db:setup +``` + +It performs three steps in order: + +1. **Schema sync** – Runs `drizzle-kit push` to apply the TypeScript schema to the database (creates/updates tables, enums, etc.). +2. **PostgreSQL roles** – Creates the `authenticated` and `anon` roles if missing (used by RLS policies). +3. **Essential seed data** – Inserts required reference data: roles (ADMIN, TUTOR, STUDENT), submission statuses, and question types. + +**Optional:** Add `--seed` (or `-s`) to run the full seed, including demo users, organizations, courses, and other example data. + +Requires `DATABASE_URL` or `PRIVATE_DATABASE_URL` in the environment. diff --git a/turbo.json b/turbo.json index ea2ecd03..121704bf 100644 --- a/turbo.json +++ b/turbo.json @@ -7,7 +7,7 @@ }, "build": { "dependsOn": ["^build"], - "outputs": [".next/**", "!.next/cache/**", ".svelte-kit/**", ".vercel/**", "dist/**"] + "outputs": [".next/**", "!.next/cache/**", ".svelte-kit/**", ".vercel/**", "dist/**", "build/**"] }, "start": { "cache": false,