feat: complete rewrite of course (#622)
* theming improvements * border style improvements * refactor: migrate org routes to server-side data fetching Move client-side API calls to server-side load functions for better performance, security, and SEO. Implement permission-based course filtering * refactor: clean up course and community components This commit removes unused components and files related to courses and community features, streamlining the codebase. It also updates various routes to enhance performance and maintainability. Additionally, it introduces new types and constants for better type safety and organization. * refactor: fix video formatting on website and other styles * refactor: update course and dashboard components for improved styling and functionality * more icon improvements * refactor: streamline course and dashboard components This commit updates various course and dashboard components for improved styling and functionality. It includes the removal of unused files, updates to imports and exports, and enhancements to type safety. Additionally, it introduces new routes and optimizes existing ones for better performance and maintainability. * refactor: enhance lesson completion logic * chore: delete all emails on dashboard and move other api routes to backend. move all emails to the backend and * feat: Refactor API and database queries for courses, quizzes, and lessons, migrating Supabase functions and schema management. * get app loading * fix ui and build error of cio/api * refactor: move direct database queries to query layer - Create query functions: getCourseWithOrgData, getOrganizationByCourseId, updateCourseVersion, updateLessonsSectionId, getProfileByGroupMemberId, getQuestionTypesByIds - Refactor services to use query functions instead of direct db queries - Add transaction support to updateCourseVersion and updateLessonsSectionId - Fix getProfileByGroupMemberId to return profile directly (not nested) - Fix updateLessonsSectionId to use .returning() instead of rowCount This aligns services with architecture pattern where services orchestrate queries rather than executing them directly. * exercise saving improvements * fix and optimise exercise creation * refactor: update dialog components for improved styling and functionality - Adjusted dialog content width and maximum constraints for better responsiveness. - Changed heading font size for consistency in the new exercise modal. - Replaced CloseIcon with CrossIcon for a more modern appearance in dialog close button. * refactor: enhance course and exercise management components - Introduced new components for exercise creation, editing, and viewing, improving user experience. - Refactored existing lesson and exercise components for better organization and maintainability. - Updated routing for exercises and lessons to streamline navigation within the course structure. - Added new utility functions and constants to support content management across the application. * feat: implement course content management API and UI components - Added new API routes for updating and deleting course content, enhancing content management capabilities. - Introduced new Svelte components for displaying and managing course content, including content lists and action handlers. - Updated existing course routing to integrate new content management features. - Refactored content-related utility functions for improved organization and maintainability. * refactor: remove Supabase references and implement course section management - Removed Supabase-related environment variables and Dockerfile arguments to streamline configuration. - Introduced a new section management API with routes for creating, updating, deleting, and reordering course sections. - Updated course services to handle section operations, ensuring consistency in course content management. - Refactored existing lesson section logic to align with the new course section structure, enhancing maintainability. * chore: update .prettierignore to exclude additional file types - Added exclusions for SQL and environment files to the Prettier ignore list. - Included Dockerfile and .prettierignore itself to prevent formatting issues. * feat: enhance course content management with translation support and UI improvements - Added translation keys for user-facing text in course-related components, ensuring all UI copy is sourced from translation files. - Updated lesson and exercise creation components to utilize translation keys for success and error messages, improving localization. - Refactored course content management components to streamline the creation and editing process, enhancing user experience. - Introduced new constants for content options and stepper states to standardize component behavior and improve maintainability. - Improved styling and functionality of course content modals and steppers for better user interaction. * feat: enhance course content management and UI components - Updated .prettierignore to exclude storybook-static directory. - Improved exercise creation logic by refining option handling and adding UUID support for option values. - Enhanced navigation component with debugging output for custom links. - Introduced a new method in CourseApi to update content items locally, improving state management during navigation. - Added caching for exercise templates by tag in ExerciseTemplateApi to optimize performance. - Refactored lesson and exercise components for better organization and maintainability, including updates to modal styling and functionality. - Implemented local patching for lesson and exercise list items to ensure UI consistency after updates. * student + teacher invite (not fully tested) * refactor: streamline course team member middleware - Removed unnecessary console logging and timing for checking user permissions. - Simplified the logic for determining if a user is a course team member or organization admin. * reorder course sidebar to move content to its own page. * feat: implement unified course enrollment route and API - Introduced a new enrollment route `/course/:slug/enroll` to replace the previous invite system. - Unified enrollment logic for free and paid courses, allowing direct enrollment for free courses and invite token validation for paid courses. - Updated course landing page to redirect to the new enrollment route for free courses. - Removed obsolete invite routes and related logic, streamlining the enrollment process. - Enhanced API with a new `POST /course/:courseId/enroll` endpoint for handling enrollments. - Added rate limiting for enrollment requests to prevent abuse. - Updated UI components to reflect changes in enrollment flow and messaging. * chore: update docker stack, docs, and auth token automation * chore: make prettier check ignore unknown file types * fix: avoid using authClient on server file and keep authClient client focused * chore: always get the server on demand depending on the environment * chore: update dashboard configuration and Docker setup for environment variables - Modified the Svelte configuration to directly use the PUBLIC_SERVER_URL environment variable. - Enhanced the Docker Compose file to include health check formatting and added environment variable arguments for the dashboard build. - Updated the Dockerfile to echo environment variable values during the build process, ensuring clarity on the configuration being used. * chore: enhance Docker setup and documentation for production deployment - Added a new production Docker Compose file and example environment file for published images. - Updated README and usage documentation to include instructions for deploying with production images. - Modified Svelte configuration to standardize the use of the posthog domain. - Removed unnecessary console logs from the API client and layout server files for cleaner output. - Improved overall clarity in Docker documentation regarding self-hosting and image publishing. * chore(db): log function name in query catch blocks (console.error 'funcName error:', error) Co-authored-by: Cursor <[email protected]> * fix: multiple refresh issue * fix: server key not passed to api * fix: clarify usage of PRIVATE_SERVER_KEY and AUTH_BEARER_TOKEN in documentation - Updated documentation to specify that PRIVATE_SERVER_KEY is used for server-to-server API key authentication and is validated by API key middleware. - Clarified that AUTH_BEARER_TOKEN is a separate token not utilized by apiKeyMiddleware. - Adjusted examples in Docker setup and self-hosting documentation to reflect these changes. * fix migration script * fix: improve language handling and add database reset script - Updated language extraction to handle potential undefined values, defaulting to 'en' if necessary. - Added a new script for resetting the database, including functionality to truncate public tables and run setup with optional seeding. * feat: add promote ungrouped section functionality and enhance document handling in dashboard - Implemented a new API endpoint to promote synthetic "Ungrouped" content into a real persisted section, allowing for better organization of course materials. - Updated the dashboard to include a new component for displaying content count badges for lessons and exercises. - Enhanced document management features, including a new document card component with dropdown actions for viewing, downloading, and removing documents. - Improved file upload handling with a dedicated file drop zone and error handling for file type and size restrictions. - Refactored existing components for better readability and maintainability, including updates to lesson and document lists. * chore: use root env file for docker compose * docs: document docker no-build startup flow * chore: add no-build option to docker full stack script * fix(email): handle SMTP TLS mode by port * feat(auth): add configurable cookie domain env and docs * apps/dashboard/src/lib/utils/services/auth/session.ts * fix(email): make sender configurable via SMTP_SENDER * chore(i18n): regenerate locale translations and support language targeting * chore(config): switch api to PUBLIC_SERVER_URL * fix(i18n): persist selected locale across navigation * feat(assets): implement organization asset management routes and services - Added new routes for managing organization assets, including listing, creating, updating, deleting, and exporting assets. - Introduced asset-related services for handling asset operations such as creation, retrieval, and deletion. - Integrated YouTube metadata resolution and asset attachment/detachment functionalities. - Updated organization routes to include the new asset management routes. - Enhanced error handling for asset-related operations. * fix(document-list): update conditional rendering for edit mode in document list * debug self hosted layout setup * refactor(sidebar): simplify lms sidebar footer and improve navigation item rendering * feat: tags * chore: tags roadmap + translations * fix(api): add authMiddleware to exercise, invites, and content routers - exerciseRouter: apply authMiddleware before courseMemberMiddleware on all 10 routes - invitesRouter: apply authMiddleware before courseTeamMemberMiddleware on all 4 routes - contentRouter: add authMiddleware to PUT route (DELETE already had it) Both courseMemberMiddleware and courseTeamMemberMiddleware document requiring authMiddleware to be applied first. This aligns with lessonRouter pattern and ensures explicit middleware contract regardless of global session middleware. Co-authored-by: Rotimi Best <[email protected]> * fix(course): use courseTeamMemberMiddleware for PUT, DELETE, and analytics routes These routes require admin/tutor role per JSDoc but were using courseMemberMiddleware which allows any course member including students. Switch to courseTeamMemberMiddleware to restrict access to ADMIN/TUTOR group members and org admins. Co-authored-by: Rotimi Best <[email protected]> * chore: unify pricing component between dashboard and website (#630) * feat: modularize pricing card * feat: initia implementation of pricing togge * fix: minor amendment * refactor: update pricing components for improved layout and styling - Adjusted spacing in the pricing card layout for better visual consistency. - Modified button styles and sizes for enhanced usability. - Cleaned up the pricing toggle component by removing unnecessary console logs and restructuring the button rendering logic. * add handle click * chore(css): update Tailwind CSS output with new utility classes and theme variables * fix(api): enable orgMemberMiddleware for stats route This change ensures that the stats route is protected by orgMemberMiddleware, aligning access control with other routes. Additionally, removed unnecessary console log from the layout server file to clean up the code. --------- Co-authored-by: Rotimi Best <[email protected]> * pass query parameter to signup from pricing page * ui updates to pricing cards * commit all PRDs * add open api spec to api prd * make blue default theme * more prds * sync option id sequence on question update * docs: more classroomio features * chore: more ui improvements * docs: new component docs * chore: CSP Configuration (#638) * feat(validation): add GitHub repository name validation - Add ZRepositoryName schema for validating GitHub repo names - Add ZRepositoryOwner for owner/username validation - Add ZRepositoryIdentifier for owner/repo format - Follow GitHub rules: A-Za-z0-9_.-, 1-100 chars, reserved names - Add translation keys for validation messages Fixes #637 Co-authored-by: Rotimi Best <[email protected]> * Revert "feat(validation): add GitHub repository name validation" This reverts commit 3436536bb17a0f05b3a9fe8dd7682cde64dd1dce. * feat(dashboard): make CSP external domains configurable via env Move hardcoded external domains from svelte.config.js to .env configuration for on-premise deployments with strict security policies, data residency constraints, or CISO-driven allowlists. - ALLOWED_EXTERNAL_DOMAINS: single list for all CSP directives - CSP_SCRIPT_SRC_DOMAINS, CSP_STYLE_SRC_DOMAINS, CSP_CONNECT_SRC_DOMAINS, CSP_FRAME_SRC_DOMAINS, CSP_FONT_SRC_DOMAINS: per-directive override - When unset, defaults retain current SaaS behavior (backward compatible) Fixes #637 Co-authored-by: Rotimi Best <[email protected]> * fix(dashboard): self-hosted CSP uses only env vars, no defaults Co-authored-by: Rotimi Best <[email protected]> * fix(dashboard): add all classroomio.com domains to CSP defaults Co-authored-by: Rotimi Best <[email protected]> * docs: add CSP ALLOWED_EXTERNAL_DOMAINS to self-hosting guides Co-authored-by: Rotimi Best <[email protected]> * feat(docker): pass CSP domain env vars as build args to dashboard Co-authored-by: Rotimi Best <[email protected]> --------- Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Rotimi Best <[email protected]> * feat: add SSO and token authentication routes and services - Introduced new routes for SSO and token authentication under the organization namespace. - Implemented SSO connection management including creation, update, activation, and deletion. - Added token authentication configuration with support for secret rotation and status retrieval. - Enhanced error handling for SSO and token authentication processes. - Updated existing routes and services to integrate new functionalities. - Improved gradebook service to fetch student marks and exercises. - Refactored API structure for better organization and maintainability. * update translations * fix: add PUBLIC_SERVER_URL to .env.example to prevent localhost fallback in production (#641) Co-authored-by: Cursor Agent <[email protected]> * feat: new exercise question types * MinIO S3 storage backend (#639) * feat(storage): add MinIO/S3-compatible object storage backend - Add OBJECT_STORAGE_* env vars for generic S3-compatible backends (MinIO, AWS S3) - Create storage config module with Cloudflare R2 fallback for backward compatibility - Refactor s3.ts to use configurable client and bucket names from config - Update media.ts to use provider-neutral public URL (OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL or CLOUDFLARE_IMAGE_BUCKET_DOMAIN) - Add MinIO service and init to docker-compose (profile: minio) with videos/documents/media buckets - Add localhost:9000 to dashboard CSP for local MinIO presigned URL uploads - Update self-hosting and Cloudflare docs with MinIO option - Update .env examples and docker USAGE.md Fixes #635 Co-authored-by: Rotimi Best <[email protected]> * docs: add MINIO_ROOT_USER and MINIO_ROOT_PASSWORD to self-hosting guides Co-authored-by: Rotimi Best <[email protected]> * feat(media): refactor media management and integrate MinIO support - Updated media API to use a new `mediaApi` module, replacing `mediaManagerApi` references across components. - Added MinIO configuration options to the Docker setup, allowing for optional object storage exclusion. - Enhanced environment variable management for MinIO in the `.env.example` file. - Updated Content Security Policy (CSP) to allow local MinIO access in the dashboard. - Introduced new media components for asset management, including asset cards and usage dialogs. This refactor improves the modularity of the media handling system and provides better support for S3-compatible storage solutions. * feat(dashboard): enhance logging and refactor auth session handling - Added console logging for event and environment variables in hooks.server.ts to aid debugging. - Refactored auth session handling to use authServerClient instead of authClient in session.ts for improved consistency. - Updated Dockerfile to ensure the dashboard build output is present before starting the application. - Expanded self-hosting documentation to include troubleshooting steps for disk space issues in PostgreSQL and Docker. These changes improve the observability of the application and streamline the authentication process. * refactor(dashboard): remove debug logging and enhance Postgres healthcheck - Removed console logging for event and environment variables in hooks.server.ts to clean up the code. - Updated Postgres healthcheck in docker-compose.yaml to require a successful `SELECT 1` query, ensuring dependent services only start once the database is ready. - Expanded self-hosting documentation to include troubleshooting steps for database recovery issues. These changes improve code clarity and reliability in service startup sequences. --------- Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Rotimi Best <[email protected]> * feat: add Plyr media player icons and PDF.js integration - Introduced new Plyr SVG icons for media player controls in both course and dashboard applications. - Updated Plyr player configuration to use local icon assets. - Integrated PDF.js for document viewing, replacing external CDN links with local paths for improved performance and reliability. - Enhanced PDF viewer modal z-index for better visibility. This update improves the media player experience and ensures consistent access to necessary resources. * feat: enhance account and organization services with type definitions and promise return types - Updated `getAccountData` to return a structured result type including profile and organizations. - Modified `getOrganizationsWithFilters` to specify a promise return type for better type safety. - Added new type imports for organization-related data structures to improve clarity and maintainability. These changes improve type safety and enhance the overall structure of the account and organization services. * feat: implement Redis caching for presigned URLs and refactor API interactions - Added Redis caching for presigned download URLs in the API to reduce redundant S3 calls, improving performance. - Introduced a new `PresignApi` class in the frontend to centralize presigned URL requests, enhancing code maintainability. - Updated various components to utilize the new `presignApi` methods for document and video upload/download operations. - Enhanced error handling for Redis operations to ensure graceful degradation in case of cache failures. These changes optimize the presigned URL generation process and streamline API interactions across the application. * fix: add better-auth dependency to dashboard (#645) * chore: unify exercise answer data format * feat: make selfhosting run under one org for simplicity (#647) * feat: implement self-hosting PRD + license key system Self-hosting single-org: - Add PUBLIC_IS_SELFHOSTED to API env, block org creation when org exists - Auto-add non-members as students in GET /account bootstrap - getOrgSiteInfo: root domain returns single org for self-hosted - App init: route to /lms when self-hosted + no orgs - Hide org switcher and Add Org modal when self-hosted Invite-only signup: - Add settings.signup.inviteOnly via ZUpdateOrganization - Allow public signups toggle in org auth settings - Signup page: hide form when invite-only and no invite context - Landing page nav: respect inviteOnly for signup link First signup auto-verify: - Self-hosted first signup (no orgs) auto-verifies email in createProfileHook License key system: - LICENSE_KEY, LICENSE_VERIFICATION_URL for external verification API - Feature gates: SSO, token-auth, no-tracking (PostHog/Senja) - requireLicense middleware for API routes - Dashboard: license store, gate PostHog/Senja/SSO/token-auth UI - docs/LICENSE_API_CONTRACT.md for external API spec Co-authored-by: Rotimi Best <[email protected]> * feat: get rid of carbon completely * chore: license verification happens only for selfhosted instance * fix: in self hosted instance, orgSite is the default * docs: how to write math in cio editor * Update packages/db/src/queries/organization/organization.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * Update apps/api/src/services/license.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * fix: translations, cursor pr reviews, simplify license fetching and check * refactor: update analytics setup for cloud and self-hosted deployments; enhance settings merge logic * refactor: update license fetching URL for development environment; optimize organization query to prevent plan row loss * more selfhosting improvements * feat: enhance signup guard for self-hosted instances by requiring organization context when no org ID is provided * pr reviews * fix: correct query parameter handling in course navigation * pr reviews --------- Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Rotimi Best <[email protected]> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --------- Co-authored-by: Cursor <[email protected]> Co-authored-by: Rotimi Best <[email protected]> Co-authored-by: Abdulmuizz Hamzat <[email protected]> Co-authored-by: Adeyemi Adetunji <[email protected]> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
Rotimi Best
greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Cursor Agent
Abdulmuizz Hamzat
Adeyemi Adetunji
parent
a42565ad82
commit
1e9a82e912
@@ -3,4 +3,96 @@ title: Cloudflare Setup
|
||||
description: Guide to setting up Cloudflare for ClassroomIO.
|
||||
---
|
||||
|
||||
In this guide, you will learn how to get your Cloudflare environment variables. We use Cloudflare R2 for video uploads and the aim of this guide is to help you get the right variables to properly set up our api.
|
||||
In this guide, you will learn how to get your Cloudflare environment variables and configure R2 buckets. ClassroomIO supports Cloudflare R2 for video uploads, document uploads, and media (images and video thumbnails). For on-premise or data-residency deployments, you can use [MinIO](/docs/quickstart/self-hosting#option-b-minio-s3-compatible-on-premise) instead.
|
||||
|
||||
## R2 buckets overview
|
||||
|
||||
The API uses three R2 buckets:
|
||||
|
||||
| Bucket | Purpose | Access pattern |
|
||||
| ------------ | ----------------------------------------------------------------------- | ----------------------------------------------------------- |
|
||||
| **videos** | Lesson video uploads and playback | Presigned **PUT** (upload from dashboard), presigned **GET** (playback) |
|
||||
| **documents**| Lesson document uploads and downloads | Presigned **PUT** (upload), presigned **GET** (download) |
|
||||
| **media** | Images and video thumbnails (uploaded via API, served via public URL) | **GET** / **HEAD** from dashboard (e.g. thumbnail images) |
|
||||
|
||||
Create these buckets in the [Cloudflare Dashboard](https://dash.cloudflare.com/) under **R2** if they do not exist.
|
||||
|
||||
## Environment variables
|
||||
|
||||
Configure the following for the API (e.g. in `.env` or your deployment):
|
||||
|
||||
- **R2 API (videos and documents):**
|
||||
`CLOUDFLARE_ACCOUNT_ID`, `CLOUDFLARE_ACCESS_KEY`, `CLOUDFLARE_SECRET_ACCESS_KEY`, `CLOUDFLARE_BUCKET_DOMAIN` (or the API uses the account ID for the R2 endpoint).
|
||||
|
||||
- **Media bucket (images and thumbnails):**
|
||||
`CLOUDFLARE_IMAGE_BUCKET_DOMAIN` — public URL for the **media** bucket (e.g. custom domain or R2 public bucket URL). Required for image uploads and video thumbnail display.
|
||||
|
||||
## R2 CORS configuration
|
||||
|
||||
When the dashboard loads videos or images from R2, the browser may send requests to a different origin. CORS must be configured on each bucket so the browser allows those requests. Allow **GET** and **HEAD** (and **PUT** where the client uploads) so that fetches and preflight/cache checks succeed.
|
||||
|
||||
### Videos bucket
|
||||
|
||||
Used for: uploading videos (presigned PUT) and playing them back (presigned GET).
|
||||
|
||||
1. In [Cloudflare Dashboard](https://dash.cloudflare.com/) go to **R2** → select the **videos** bucket.
|
||||
2. Open **Settings** → **CORS policy**.
|
||||
3. Add a policy that allows your app origins and **GET**, **HEAD**, and **PUT** (PUT is required for presigned uploads from the browser):
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"AllowedOrigins": ["http://localhost:5173", "https://your-app-domain.com"],
|
||||
"AllowedMethods": ["GET", "HEAD", "PUT"],
|
||||
"AllowedHeaders": ["*"],
|
||||
"MaxAgeSeconds": 3600
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
4. Add every origin that will upload or play videos (e.g. dev: `http://localhost:5173`, production: `https://app.classroomio.com`). Do not use `*` for `AllowedOrigins` if you use credentials.
|
||||
|
||||
After saving, video upload and playback from the dashboard should work without “blocked by CORS policy” errors.
|
||||
|
||||
### Documents bucket
|
||||
|
||||
If the dashboard uploads or downloads documents directly to/from R2 via presigned URLs, configure CORS similarly to the videos bucket:
|
||||
|
||||
- **AllowedMethods:** include **GET**, **HEAD**, and **PUT** if the client uploads via presigned URL.
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"AllowedOrigins": ["http://localhost:5173", "https://your-app-domain.com"],
|
||||
"AllowedMethods": ["GET", "HEAD", "PUT"],
|
||||
"AllowedHeaders": ["*"],
|
||||
"MaxAgeSeconds": 3600
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Media bucket
|
||||
|
||||
Used for: serving images and video thumbnails (the dashboard loads these via the public URL from `CLOUDFLARE_IMAGE_BUCKET_DOMAIN`). If that URL is on a different origin than the dashboard, the bucket needs CORS for **GET** and **HEAD**.
|
||||
|
||||
1. In **R2** → select the **media** bucket.
|
||||
2. Open **Settings** → **CORS policy**.
|
||||
3. Add a policy that allows your app origins to load images:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"AllowedOrigins": ["http://localhost:5173", "https://your-app-domain.com"],
|
||||
"AllowedMethods": ["GET", "HEAD"],
|
||||
"AllowedHeaders": ["*"],
|
||||
"MaxAgeSeconds": 3600
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
(Image uploads go through the API server, not directly from the browser to R2, so the media bucket does not need PUT in CORS for normal dashboard use.)
|
||||
|
||||
**Troubleshooting:** If you see "Access to image at '...r2.dev/...' has been blocked by CORS policy" when loading video thumbnails or lesson images:
|
||||
|
||||
1. Ensure the **media** bucket has the CORS policy above and your origin (e.g. `http://localhost:5173`) is in `AllowedOrigins`.
|
||||
2. The Cloudflare-managed **r2.dev** public URL sometimes does not apply bucket CORS headers. If CORS errors persist after configuring CORS, use a **custom domain** for the media bucket (recommended for production) so CORS and caching work reliably. The dashboard avoids requiring CORS for poster/thumbnail display where possible.
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
---
|
||||
title: Enterprise SSO
|
||||
description: Configure Single Sign-On (SSO) with OIDC for your organization.
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout';
|
||||
|
||||
# Enterprise SSO
|
||||
|
||||
ClassroomIO supports Single Sign-On (SSO) via OpenID Connect (OIDC), allowing your organization members to sign in using your existing identity provider (IdP).
|
||||
|
||||
<Callout type="info" title="Enterprise Feature">
|
||||
SSO requires an **Enterprise** plan.
|
||||
|
||||
**Cloud:** [Contact us](mailto:[email protected]) to upgrade.
|
||||
|
||||
**Self-hosted:** Requires an Enterprise license key. If you are not using Enterprise-only features, you do not need a key.
|
||||
[Book a call](http://cal.com/classroomio/enterprise) to get your license.
|
||||
</Callout>
|
||||
|
||||
<Callout title="Where to set `LICENSE_KEY` (self-hosted)">
|
||||
Set `LICENSE_KEY` in the API service environment.
|
||||
Local development: `apps/api/.env`.
|
||||
Docker Compose: repository root `.env`.
|
||||
Published-image Docker deploys: `docker/.env.prod`.
|
||||
If you do not use Enterprise-only features, leave `LICENSE_KEY` unset.
|
||||
</Callout>
|
||||
|
||||
## Supported Providers
|
||||
|
||||
We support OIDC-based SSO with the following identity providers:
|
||||
|
||||
- **Okta**
|
||||
- **Google Workspace**
|
||||
- **Auth0**
|
||||
|
||||
## How It Works
|
||||
|
||||
### Org-Site Login
|
||||
When users visit your organization's custom domain (e.g., `yourorg.classroomio.com`), SSO is automatically detected and they can sign in directly with your IdP.
|
||||
|
||||
### Shared App Login
|
||||
On the main ClassroomIO app (`app.classroomio.com`), users enter their email address first. If their email domain matches your configured SSO domain, they'll be prompted to sign in via SSO.
|
||||
|
||||
## Provider Setup Guides
|
||||
|
||||
### Google Workspace
|
||||
|
||||
1. **Create OAuth App in Google Cloud Console:**
|
||||
- Go to [Google Cloud Console](https://console.cloud.google.com/)
|
||||
- Navigate to **APIs & Services** → **Credentials**
|
||||
- Click **Create Credentials** → **OAuth client ID**
|
||||
- Configure the OAuth consent screen (choose "External" for testing)
|
||||
- Create OAuth client ID:
|
||||
- Application type: **Web application**
|
||||
- Name: `ClassroomIO SSO`
|
||||
- Authorized redirect URIs: (leave empty for now, you'll add this after Step 2)
|
||||
|
||||
2. **In ClassroomIO:**
|
||||
- Go to **Settings** → **SSO**
|
||||
- Select **Google Workspace**
|
||||
- Fill in:
|
||||
- **Issuer URL**: `https://accounts.google.com`
|
||||
- **Email Domain**: Your workspace domain (e.g., `yourcompany.com`)
|
||||
- **Client ID**: From Google Cloud Console
|
||||
- **Client Secret**: From Google Cloud Console
|
||||
- Click **Create Connection**
|
||||
|
||||
3. **Complete IdP Configuration:**
|
||||
- Copy the **Callback URL** shown in ClassroomIO
|
||||
- Go back to Google Cloud Console
|
||||
- Edit your OAuth client and add the Callback URL to **Authorized redirect URIs**
|
||||
- Save
|
||||
|
||||
4. **Activate** the connection in ClassroomIO
|
||||
|
||||
### Okta
|
||||
|
||||
1. **Create OIDC App in Okta Admin:**
|
||||
- Go to Okta Admin Dashboard
|
||||
- Navigate to **Applications** → **Create App Integration**
|
||||
- Select **OIDC - OpenID Connect**
|
||||
- Application type: **Web Application**
|
||||
- Sign-in redirect URIs: (leave empty, you'll add after Step 2)
|
||||
- Grant type: **Authorization Code**
|
||||
|
||||
2. **In ClassroomIO:**
|
||||
- Go to **Settings** → **SSO**
|
||||
- Select **Okta**
|
||||
- Fill in:
|
||||
- **Issuer URL**: Your Okta domain (e.g., `https://your-org.okta.com`)
|
||||
- **Email Domain**: Your company domain
|
||||
- **Client ID**: From Okta app
|
||||
- **Client Secret**: From Okta app
|
||||
- Click **Create Connection**
|
||||
|
||||
3. **Complete IdP Configuration:**
|
||||
- Copy the **Callback URL** from ClassroomIO
|
||||
- In Okta, edit your app and add the Callback URL to **Sign-in redirect URIs**
|
||||
- Ensure these scopes are allowed: `openid`, `email`, `profile`
|
||||
|
||||
4. **Activate** the connection in ClassroomIO
|
||||
|
||||
### Auth0
|
||||
|
||||
1. **Create Application in Auth0:**
|
||||
- Go to Auth0 Dashboard
|
||||
- Navigate to **Applications** → **Create Application**
|
||||
- Name: `ClassroomIO SSO`
|
||||
- Type: **Regular Web Application**
|
||||
- Allowed Callback URLs: (leave empty, add after Step 2)
|
||||
|
||||
2. **In ClassroomIO:**
|
||||
- Go to **Settings** → **SSO**
|
||||
- Select **Auth0**
|
||||
- Fill in:
|
||||
- **Issuer URL**: Your Auth0 domain (e.g., `https://your-tenant.auth0.com`)
|
||||
- **Email Domain**: Your company domain
|
||||
- **Client ID**: From Auth0 app
|
||||
- **Client Secret**: From Auth0 app
|
||||
- Click **Create Connection**
|
||||
|
||||
3. **Complete IdP Configuration:**
|
||||
- Copy the **Callback URL** from ClassroomIO
|
||||
- In Auth0, add the Callback URL to **Allowed Callback URLs**
|
||||
- Ensure OIDC Conformant is enabled
|
||||
|
||||
4. **Activate** the connection in ClassroomIO
|
||||
|
||||
## Configuration Summary
|
||||
|
||||
Required scopes in all IdPs:
|
||||
- `openid`
|
||||
- `email`
|
||||
- `profile`
|
||||
|
||||
## Access Policies
|
||||
|
||||
### Force SSO
|
||||
When enabled, all users must sign in via SSO. Email/password login is disabled for your organization. This is recommended for security compliance.
|
||||
|
||||
### Auto-Join
|
||||
When enabled, users who sign in via SSO with a matching email domain are automatically added to your organization. Otherwise, they must be invited first.
|
||||
|
||||
### Break-Glass Access
|
||||
Allows organization admins to bypass SSO with email/password in case of emergency (e.g., IdP outage). Keep this enabled for safety.
|
||||
|
||||
## User Provisioning
|
||||
|
||||
### JIT (Just-In-Time) Provisioning
|
||||
Users are automatically provisioned when they first sign in via SSO:
|
||||
|
||||
1. If the user has a pending invitation, they join with the invited role
|
||||
2. If auto-join is enabled, they join with the default role (typically Student)
|
||||
3. If neither, they can join later via invitation
|
||||
|
||||
### Role Mapping
|
||||
Map IdP groups to ClassroomIO roles:
|
||||
|
||||
- Admins → `ROLE.ADMIN` (1)
|
||||
- Tutors → `ROLE.TUTOR` (2)
|
||||
- Students → `ROLE.STUDENT` (3)
|
||||
|
||||
Configure this in your SSO policy settings.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Failed to register SSO provider"
|
||||
- Verify your Issuer URL is correct and accessible
|
||||
- Check that your IdP supports OIDC (not just SAML)
|
||||
- Ensure the discovery endpoint exists at `{issuer}/.well-known/openid-configuration`
|
||||
|
||||
### "Callback URL mismatch"
|
||||
- Copy the exact callback URL from the SSO settings page
|
||||
- Ensure it matches exactly in your IdP configuration
|
||||
|
||||
### Users can't auto-join
|
||||
- Verify the email domain matches exactly (e.g., `company.com` not `@company.com`)
|
||||
- Check that "Auto-join" is enabled in your SSO policy
|
||||
- Ensure the user has a verified email from your domain
|
||||
|
||||
## Security Notes
|
||||
|
||||
- SSO secrets (client credentials) are encrypted at rest
|
||||
- All SSO login attempts are logged for audit purposes
|
||||
- Force SSO is enforced server-side, not just UI-level
|
||||
- Session management follows your IdP's policies
|
||||
|
||||
## SAML Support
|
||||
|
||||
SAML 2.0 support is planned for a future release. Currently, only OIDC is supported.
|
||||
|
||||
## API Reference
|
||||
|
||||
### Endpoints
|
||||
|
||||
- `GET /sso/[email protected]` - Check if email uses SSO
|
||||
- `GET /sso/org/:orgId` - Get SSO info for an organization
|
||||
- `POST /organization/sso` - Create SSO connection (Admin)
|
||||
- `PUT /organization/sso/policy` - Update SSO policy (Admin)
|
||||
|
||||
See the [API documentation](/docs/api) for full details.
|
||||
@@ -1,11 +1,77 @@
|
||||
---
|
||||
title: How to Invite Students
|
||||
description: Learn how to invite students to your courses.
|
||||
description: Learn how to invite students to your courses using email invitations and how enrollment works for free and paid courses.
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout';
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps';
|
||||
|
||||
Course invitations in ClassroomIO let you add students to a course by sending them a secure enrollment link by email. Each invite is unique and can have its own expiry and usage limits.
|
||||
|
||||
<Callout type="warn" title="Work in Progress">
|
||||
This page is a work in progress. Help us expand it by contributing!
|
||||
</Callout>
|
||||
## How course invitations work
|
||||
|
||||
- **Email-based invites:** You enter one or more email addresses (or paste a list). ClassroomIO creates a unique invite per recipient and can send the enrollment link by email.
|
||||
- **Enrollment link:** Each invite link points to an **enrollment page** for that course (e.g. `https://yoursite.classroomio.com/course/your-course-slug/enroll?invite_token=...`). When a student opens the link, they see the course details and a **Join course** button.
|
||||
- **One link per invite:** Invite links are tied to the course and (for paid or restricted courses) to the invite’s rules (expiry, max uses, allowed emails). Students sign in or sign up when they click **Join course**, then they’re enrolled and redirected to the learning dashboard.
|
||||
|
||||
## Free vs paid courses
|
||||
|
||||
- **Free courses:** Students can enroll without an invite. From the course landing page they click **Enroll** and are taken to the same enrollment page (`/course/:slug/enroll`) with no token; they sign in or sign up and are enrolled immediately.
|
||||
- **Paid or invite-only courses:** Students need either to pay (if you’ve set up payment) or to use a valid invite link. If they open the enrollment page without a valid invite or payment, they’ll see a message that they need to pay or be invited.
|
||||
|
||||
## Sending email invites
|
||||
|
||||
<Steps>
|
||||
<Step>
|
||||
### Open the course and go to People
|
||||
|
||||
From the dashboard, open the **Courses** page and select the course. In the course sidebar, click **People**.
|
||||
</Step>
|
||||
|
||||
<Step>
|
||||
### Open the invite modal
|
||||
|
||||
Click **Add people** (or the equivalent action that opens the invite flow). In the modal, switch to the **Invite Students** tab, then choose the **Direct email** (or **Email**) option.
|
||||
</Step>
|
||||
|
||||
<Step>
|
||||
### Configure invite settings
|
||||
|
||||
Set how the invite should behave:
|
||||
|
||||
- **Expiry:** How long the link is valid (e.g. 24 hours, 7 days, 30 days, or custom).
|
||||
- **Max uses:** How many times the link can be used (e.g. one-time or multiple uses).
|
||||
|
||||
These apply to each generated invite when you send emails.
|
||||
</Step>
|
||||
|
||||
<Step>
|
||||
### Add recipients and send
|
||||
|
||||
Enter one or more email addresses, or paste a list (comma-, space-, or newline-separated). Optionally enable **Send invite emails** so ClassroomIO emails the enrollment link to each recipient. Click the button to create the invites (and send the emails, if enabled).
|
||||
|
||||
Each recipient gets a unique link to the course enrollment page with their invite token.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## What students see
|
||||
|
||||
1. **Email:** If you sent invite emails, the student receives an email containing the course name, your organization name, and a link to the enrollment page.
|
||||
2. **Enrollment page:** Opening the link takes them to `/course/[course-slug]/enroll?invite_token=...`. They see the course title and description and a **Join course** button.
|
||||
3. **Sign in or sign up:** If they’re not signed in, clicking **Join course** sends them to sign in or create an account. After that, they’re returned to the enrollment page (the redirect is preserved).
|
||||
4. **Enrolled:** Once they’re signed in and click **Join course**, they’re enrolled in the course and redirected to the learning dashboard (e.g. **LMS** or **Home**).
|
||||
|
||||
## Managing invites
|
||||
|
||||
From the same **People** > **Invite** flow, use the **Existing invites** (or **History**) tab to:
|
||||
|
||||
- See invites you’ve created, their expiry and usage.
|
||||
- **Revoke** an invite so its link can no longer be used.
|
||||
|
||||
Revoking does not remove students who have already joined; it only invalidates the invite link for future use.
|
||||
|
||||
## Tips
|
||||
|
||||
- **Free courses:** You don’t need to create invites for free courses; students can enroll directly from the course landing page.
|
||||
- **Paid courses:** Use email invites (or payment) so only the right people can enroll.
|
||||
- **Expiry and uses:** Use short expiry and one-time use for sensitive or limited spots; use longer expiry and multiple uses for open cohorts or self-paced courses.
|
||||
- **Redirect after login:** If a student follows an invite link while logged out, they’re sent to sign in or sign up and then back to the same enrollment page so they can complete joining.
|
||||
|
||||
@@ -8,5 +8,6 @@
|
||||
"grade-exercise": "How to Grade an Exercise",
|
||||
"invite-students": "How to Invite Students",
|
||||
"invite-team-member": "Inviting a Team Member",
|
||||
"take-attendance": "How to Take Attendance"
|
||||
"take-attendance": "How to Take Attendance",
|
||||
"use-math-in-editor": "Use Math in the Editor"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
title: Use Math in the Editor
|
||||
description: Add and edit LaTeX math blocks and inline expressions inside the ClassroomIO rich text editor.
|
||||
---
|
||||
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps';
|
||||
|
||||
ClassroomIO’s rich text editor includes built-in math support with KaTeX rendering. You can insert inline expressions, add block equations, and edit them later without extra setup.
|
||||
|
||||
<Steps>
|
||||
<Step>
|
||||
### Insert inline math
|
||||
|
||||
- Type `$$your-latex$$` to instantly convert it into inline math.
|
||||
- Or open the toolbar and click **Inline Expression** (radical icon). If you have text selected, it becomes the LaTeX body.
|
||||
|
||||
</Step>
|
||||
|
||||
<Step>
|
||||
### Insert block math
|
||||
|
||||
- Use the toolbar **Block Expression** (square root icon) or the `/` slash menu and pick **Math → Block Expression**.
|
||||
- A block equation appears on its own line; replace the default `a^2 + b^2 = c^2` with your LaTeX.
|
||||
|
||||
</Step>
|
||||
|
||||
<Step>
|
||||
### Edit an existing equation
|
||||
|
||||
- Click any math node (inline or block). A prompt appears with the current LaTeX.
|
||||
- Update the LaTeX and confirm to re-render the expression.
|
||||
|
||||
</Step>
|
||||
|
||||
<Step>
|
||||
### Quick tips
|
||||
|
||||
- Inline math stays within a sentence; block math is best for standalone equations or derivations.
|
||||
- Use standard KaTeX/LaTeX syntax (fractions, roots, superscripts/subscripts, Greek symbols, etc.).
|
||||
- You can mix math with other formatting, tables, and media in the same document.
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
@@ -7,10 +7,12 @@
|
||||
|
||||
"---Core Features---",
|
||||
"course",
|
||||
"tags",
|
||||
"audience",
|
||||
"community",
|
||||
"organization",
|
||||
"student-dashboard",
|
||||
"enterprise-sso",
|
||||
|
||||
"---Guides---",
|
||||
"how-to-guides",
|
||||
|
||||
@@ -12,19 +12,19 @@ This guide will help you quickly self-host ClassroomIO.
|
||||
|
||||
ClassroomIO is made up of the following applications:
|
||||
|
||||
- [apps/api](https://github.com/rotimi-best/classroomio/tree/main/apps/api) - an Express server
|
||||
- [apps/dashboard](https://github.com/rotimi-best/classroomio/tree/main/apps/dashboard) - the admin dashboard + LMS
|
||||
- [supabase](https://github.com/rotimi-best/classroomio/tree/main/supabase) - the database + REST API
|
||||
- [apps/api](https://github.com/classroomio/classroomio/tree/main/apps/api) - an Express server
|
||||
- [apps/dashboard](https://github.com/classroomio/classroomio/tree/main/apps/dashboard) - the admin dashboard + LMS
|
||||
- [packages/db](https://github.com/classroomio/classroomio/tree/main/packages/db) - shared database layer for PostgreSQL
|
||||
|
||||
The quickest and easiest way to deploy both the api and dashboard is by using our [Railway Template](https://railway.app/template/dl9RG7?referralCode=8OyIj5). However there are a couple of things that you'd need first before heading over to the template, which are:
|
||||
|
||||
1. SMTP values - For sending emails on the api
|
||||
2. Supabase - For the database used on the dashboard
|
||||
3. Cloudflare R2 (optional) - Video uploads on the api
|
||||
2. PostgreSQL + Redis - For the API and database layer
|
||||
3. Object storage (MinIO for Docker self-hosting, or Cloudflare R2 for hosted) - Video, document, and image uploads
|
||||
|
||||
## SMTP Setup
|
||||
|
||||
Both for the supabase and api setup, you'd need a custom SMTP for email sending. Most importantly you need these values:
|
||||
You need a custom SMTP configuration for email sending. Most importantly you need these values:
|
||||
|
||||
```zsh title="env"
|
||||
SMTP_HOST=
|
||||
@@ -38,28 +38,122 @@ Make sure you have them handy because you'd need them while we go through this s
|
||||
|
||||
<Callout title="Learn More">
|
||||
Learn more about setting up a custom SMTP server
|
||||
[here](https://supabase.com/docs/guides/auth/auth-smtp)
|
||||
[here](https://nodemailer.com/smtp/)
|
||||
</Callout>
|
||||
|
||||
## Supabase
|
||||
## PostgreSQL + Redis
|
||||
|
||||
There are 2 options to go about setting up Supabase, you could [self-host supabase](https://supabase.com/docs/guides/self-hosting) on your own servers or you could use their cloud version. We recommend you use the cloud version because it has a very generous free tier and you get to deploy it very quickly.
|
||||
ClassroomIO now runs directly on PostgreSQL and Redis (no Supabase dependency in Docker setup).
|
||||
|
||||
We have a standalone guide to help you setup your Supabase Cloud and you can find it [here](/docs/contributor-guides/supabase-cloud).
|
||||
|
||||
After you go through that guide you will need the following environment variables, please also store them somewhere:
|
||||
For deployments, keep these connection variables ready:
|
||||
|
||||
```zsh title="env"
|
||||
PUBLIC_SUPABASE_URL=
|
||||
PUBLIC_SUPABASE_ANON_KEY=
|
||||
PRIVATE_SUPABASE_SERVICE_ROLE=
|
||||
DATABASE_URL=
|
||||
PRIVATE_DATABASE_URL=
|
||||
REDIS_URL=
|
||||
```
|
||||
|
||||
## Cloudflare R2
|
||||
## Object Storage (Videos, Documents, Images)
|
||||
|
||||
We use Cloudflare R2 for video uploading within the course builder. It is not required though and you can do without it in the app if you don't need video upload.
|
||||
ClassroomIO supports two object storage backends for video uploads, document uploads, and media (images/thumbnails):
|
||||
|
||||
<Callout title="Work in Progress">This section is a work in progress.</Callout>
|
||||
### Option A: MinIO (S3-compatible, default for Docker self-hosting)
|
||||
|
||||
For Docker self-hosting, MinIO is included by default. Use MinIO or any S3-compatible storage for on-premise deployments:
|
||||
|
||||
```zsh title="env"
|
||||
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
|
||||
```
|
||||
|
||||
**Docker:** MinIO is included by default when you run `./run-docker-full-stack.sh`. The script auto-configures these. Add to your `.env`:
|
||||
|
||||
```zsh title="env"
|
||||
# MinIO container credentials (must match OBJECT_STORAGE_* below)
|
||||
MINIO_ROOT_USER=minioadmin
|
||||
MINIO_ROOT_PASSWORD=minioadmin
|
||||
|
||||
# When API runs in Docker: use minio (Docker service name), not localhost
|
||||
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
|
||||
```
|
||||
|
||||
<Callout type="warn" title="API in Docker">
|
||||
When the API runs inside Docker, set `OBJECT_STORAGE_ENDPOINT=http://minio:9000`. From a container, `localhost` refers to the container itself; use the service name `minio` to reach MinIO on the Docker network. When the API runs locally (e.g. `pnpm api:dev`), use `OBJECT_STORAGE_ENDPOINT=http://localhost:9000`.
|
||||
</Callout>
|
||||
|
||||
When you run `./run-docker-full-stack.sh`, MinIO starts automatically. To start MinIO manually (e.g. if you used `--no-minio`):
|
||||
|
||||
```zsh title="Terminal"
|
||||
docker compose -f docker/docker-compose.yaml --profile minio up -d
|
||||
```
|
||||
|
||||
**Access URLs:**
|
||||
|
||||
| Purpose | URL |
|
||||
|---------|-----|
|
||||
| API | http://localhost:9000 |
|
||||
| Web UI | http://localhost:9001 (login: `minioadmin` / `minioadmin`) |
|
||||
|
||||
<Callout type="error" title="Change default credentials">
|
||||
Do not use `minioadmin` / `minioadmin` in production. Set `MINIO_ROOT_USER` and `MINIO_ROOT_PASSWORD` in `.env`, and use the same values for `OBJECT_STORAGE_ACCESS_KEY_ID` and `OBJECT_STORAGE_SECRET_ACCESS_KEY`.
|
||||
</Callout>
|
||||
|
||||
<Callout type="warn" title="MinIO Web UI not loading?">
|
||||
MinIO uses a random port for the Web Console by default. If the Web UI at http://localhost:9001 does not load, remove the old container and recreate it: `docker rm -f cio-minio` then run the MinIO start command again. See [docker/docs/USAGE.md](https://github.com/classroomio/classroomio/blob/main/docker/docs/USAGE.md#minio-troubleshooting) for full troubleshooting.
|
||||
</Callout>
|
||||
|
||||
<Callout title="Storage Optional">
|
||||
With Docker, MinIO is included by default. Use `./run-docker-full-stack.sh --no-minio` to exclude it if you don't need video/document uploads.
|
||||
</Callout>
|
||||
|
||||
### Option B: Cloudflare R2 (hosted deployments)
|
||||
|
||||
For hosted deployments (e.g. Railway), Cloudflare R2 can be used. Configure these env vars:
|
||||
|
||||
```zsh title="env"
|
||||
CLOUDFLARE_ACCOUNT_ID=
|
||||
CLOUDFLARE_ACCESS_KEY=
|
||||
CLOUDFLARE_SECRET_ACCESS_KEY=
|
||||
CLOUDFLARE_BUCKET_DOMAIN=
|
||||
CLOUDFLARE_IMAGE_BUCKET_DOMAIN=
|
||||
```
|
||||
|
||||
See the [Cloudflare Setup](/docs/contributor-guides/cloudflare-setup) guide for R2 bucket and CORS configuration.
|
||||
|
||||
## SSO Configuration
|
||||
|
||||
<Callout type="warning" title="Enterprise License Required">
|
||||
SSO is an Enterprise-only feature for self-hosted instances.
|
||||
If you do not use Enterprise-only features, you do not need a license key.
|
||||
If you need Enterprise features, request a key from us and set `LICENSE_KEY` in your API environment.
|
||||
[Book a call](http://cal.com/classroomio/enterprise) to get your license.
|
||||
</Callout>
|
||||
|
||||
For Enterprise SSO support, you need to configure the following:
|
||||
|
||||
1. **License Key**: Set `LICENSE_KEY` in your API environment:
|
||||
```zsh
|
||||
LICENSE_KEY="your-issued-license-key"
|
||||
```
|
||||
|
||||
2. **Trusted Origins**: Add your IdP domains to `TRUSTED_ORIGINS`:
|
||||
```zsh
|
||||
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}`
|
||||
|
||||
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.
|
||||
|
||||
## Deploy on Railway
|
||||
|
||||
@@ -75,13 +169,19 @@ Go ahead and click this button to get started.
|
||||
|
||||
```zsh title="env"
|
||||
# Required
|
||||
DATABASE_URL=""
|
||||
PRIVATE_DATABASE_URL=""
|
||||
REDIS_URL=""
|
||||
SERVER_URL=""
|
||||
TRUSTED_ORIGINS=""
|
||||
BETTER_AUTH_SECRET=""
|
||||
AUTH_BEARER_TOKEN=""
|
||||
AUTH_COOKIE_DOMAIN=""
|
||||
SMTP_HOST=""
|
||||
SMTP_PORT=""
|
||||
SMTP_USER=""
|
||||
SMTP_SENDER=""
|
||||
SMTP_PASSWORD=""
|
||||
PUBLIC_SUPABASE_URL=""
|
||||
PUBLIC_SUPABASE_ANON_KEY=""
|
||||
|
||||
# Optional
|
||||
CLOUDFLARE_BUCKET_ID=""
|
||||
@@ -90,81 +190,189 @@ CLOUDFLARE_ACCOUNT_ID=""
|
||||
CLOUDFLARE_BUCKET_DOMAIN=""
|
||||
CLOUDFLARE_SECRET_ACCESS_KEY=""
|
||||
SENTRY_DNS=""
|
||||
|
||||
# Enterprise (Optional)
|
||||
# Required only when using Enterprise-only features (SSO, token-auth, no-tracking)
|
||||
LICENSE_KEY=""
|
||||
|
||||
# SSO (Optional)
|
||||
# For enterprise SSO with SAML (future support)
|
||||
# SSO_ENCRYPTION_KEY="your-32-character-secret-key"
|
||||
```
|
||||
|
||||
<Callout title="Learn More">
|
||||
Learn more about the environment variables
|
||||
[here](https://github.com/rotimi-best/classroomio/blob/main/apps/api/.env.example)
|
||||
[here](https://github.com/classroomio/classroomio/blob/main/apps/api/.env.example)
|
||||
</Callout>
|
||||
|
||||
#### cio-dashboard
|
||||
|
||||
```zsh title="env"
|
||||
# Required
|
||||
PUBLIC_SUPABASE_URL=""
|
||||
PUBLIC_SUPABASE_ANON_KEY=""
|
||||
PRIVATE_SUPABASE_SERVICE_ROLE=""
|
||||
PUBLIC_SERVER_URL=""
|
||||
PRIVATE_SERVER_KEY=""
|
||||
PUBLIC_IS_SELFHOSTED="true"
|
||||
|
||||
# Required when self-hosting: CSP external domains (no defaults in self-hosted mode)
|
||||
# Comma-separated list of domains allowed in Content-Security-Policy.
|
||||
# Example: ALLOWED_EXTERNAL_DOMAINS=assets.cdn.clsrio.com,cdnjs.cloudflare.com,cdn.plyr.io,https://*.classroomio.com,https://api.classroomio.com,https://pgrest.classroomio.com,wss://*.classroomio.com
|
||||
ALLOWED_EXTERNAL_DOMAINS=""
|
||||
|
||||
# Optional - per-directive override (script, style, connect, frame, font)
|
||||
# CSP_SCRIPT_SRC_DOMAINS=""
|
||||
# CSP_CONNECT_SRC_DOMAINS=""
|
||||
|
||||
# Optional
|
||||
PRIVATE_SERVER_URL=""
|
||||
OPENAI_API_KEY=""
|
||||
PUBLIC_IP_REGISTRY_KEY=""
|
||||
UNSPLASH_API_KEY=""
|
||||
PRIVATE_APP_HOST=""
|
||||
PRIVATE_APP_SUBDOMAINS=""
|
||||
|
||||
# Optional - meta tags for custom branding (title, description, OG image)
|
||||
# PUBLIC_APP_TITLE="My Learning Platform"
|
||||
# PUBLIC_APP_DESCRIPTION="A flexible LMS for your organization"
|
||||
# PUBLIC_OG_IMAGE_URL="https://app.yourdomain.com/og-image.png"
|
||||
```
|
||||
|
||||
<Callout title="Important">
|
||||
Set `PRIVATE_SERVER_KEY` in both API and dashboard environments for server-to-server API key authentication. `AUTH_BEARER_TOKEN` is a separate token and is not used by `apiKeyMiddleware`.
|
||||
</Callout>
|
||||
|
||||
<Callout type="warn" title="CSP for Self-Hosted">
|
||||
When `PUBLIC_IS_SELFHOSTED="true"`, the dashboard uses **only** environment variables for CSP external domains—no defaults. You must set `ALLOWED_EXTERNAL_DOMAINS` with a comma-separated list of domains your instance needs (CDN, analytics, video providers, etc.). This supports strict security policies, data residency, and CISO allowlists.
|
||||
</Callout>
|
||||
|
||||
<Callout title="Important">
|
||||
Dashboard URL routing is environment-aware:
|
||||
browser requests use `PUBLIC_SERVER_URL`, while server-side dashboard requests use
|
||||
`PRIVATE_SERVER_URL` (fallback: `PUBLIC_SERVER_URL`). In hosted deployments, set
|
||||
`PUBLIC_SERVER_URL` to your public API domain (not `localhost`).
|
||||
</Callout>
|
||||
|
||||
<Callout title="Important">
|
||||
Set `AUTH_COOKIE_DOMAIN` to `.your-domain.com` for cross-subdomain cookies. If you need host-only cookies, set it
|
||||
to your exact API host (for example `api.your-domain.com`).
|
||||
</Callout>
|
||||
|
||||
<Callout title="Meta tags for self-hosted branding">
|
||||
When self-hosting, page title, description, and Open Graph image default to your organization's branding (name, avatar, landing page banner). Set `PUBLIC_APP_TITLE`, `PUBLIC_APP_DESCRIPTION`, and `PUBLIC_OG_IMAGE_URL` to override with custom values. If unset, a bundled fallback image is used so the dashboard never depends on external CDNs.
|
||||
</Callout>
|
||||
|
||||
<Callout title="Learn More">
|
||||
Learn more about the environment variables
|
||||
[here](https://github.com/rotimi-best/classroomio/blob/main/apps/dashboard/.env.example)
|
||||
[here](https://github.com/classroomio/classroomio/blob/main/apps/dashboard/.env.example)
|
||||
</Callout>
|
||||
|
||||
## Using Docker
|
||||
|
||||
We provide a docker-compose configuration to deploy both the api and dashboard services. Here's how to use it:
|
||||
We provide `docker/docker-compose.yaml` for local self-hosting.
|
||||
|
||||
The stack includes:
|
||||
|
||||
- `postgres` (database)
|
||||
- `redis` (cache/rate-limit store)
|
||||
- `db-init` (one-off DB setup)
|
||||
- `api` (backend on `3081`)
|
||||
- `dashboard` (frontend on `3082`)
|
||||
|
||||
1. Clone the repository:
|
||||
|
||||
```zsh title="Terminal"
|
||||
git clone https://github.com/rotimi-best/classroomio.git
|
||||
git clone https://github.com/classroomio/classroomio.git
|
||||
cd classroomio
|
||||
```
|
||||
|
||||
2. Create a `.env` file with your environment variables (see Environment Variables section above)
|
||||
|
||||
3. Start the services:
|
||||
2. Start the full stack (includes MinIO for object storage by default):
|
||||
|
||||
```zsh title="Terminal"
|
||||
docker compose up --build
|
||||
./run-docker-full-stack.sh
|
||||
```
|
||||
|
||||
3. Verify services:
|
||||
|
||||
```zsh title="Terminal"
|
||||
docker compose -p classroomio -f docker/docker-compose.yaml ps
|
||||
curl -sS http://localhost:3081/
|
||||
curl -I http://localhost:3082/
|
||||
```
|
||||
|
||||
The services will be available at:
|
||||
|
||||
- Dashboard: http://localhost:3082
|
||||
- Backend: http://localhost:3081
|
||||
- API: http://localhost:3081
|
||||
|
||||
### Service Configuration
|
||||
### API-Only Smoke Test
|
||||
|
||||
The docker-compose file sets up two services:
|
||||
If you want to validate API startup first:
|
||||
|
||||
#### Backend Service
|
||||
```zsh title="Terminal"
|
||||
docker compose -p classroomio -f docker/docker-compose.yaml up --build -d postgres redis db-init api
|
||||
docker compose -p classroomio -f docker/docker-compose.yaml logs --tail=100 api
|
||||
curl -sS http://localhost:3081/
|
||||
```
|
||||
|
||||
- Runs on port 3081
|
||||
- Uses Node.js slim image
|
||||
- Mounts the api code directory
|
||||
- Requires environment variables for:
|
||||
- Supabase configuration
|
||||
- Cloudflare R2 for video uploads
|
||||
- SMTP for email notifications
|
||||
### Environment Variables for Docker
|
||||
|
||||
#### Dashboard Service
|
||||
Local defaults are provided in compose, but set these for real deployments:
|
||||
|
||||
- Runs on port 3082
|
||||
- Uses Node.js Alpine image
|
||||
- Uses pnpm for package management
|
||||
- Requires environment variables for:
|
||||
- Supabase configuration
|
||||
- Application settings
|
||||
- Optional API keys (OpenAI, Unsplash)
|
||||
```zsh title="env"
|
||||
POSTGRES_DB=classroomio
|
||||
POSTGRES_USER=postgres
|
||||
POSTGRES_PASSWORD=postgres
|
||||
|
||||
Note: We're currently working on including Supabase in the docker-compose file. If you'd like to help with this, please open a PR on GitHub! 👍
|
||||
BETTER_AUTH_SECRET=
|
||||
AUTH_BEARER_TOKEN=
|
||||
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=
|
||||
```
|
||||
|
||||
<Callout title="Important">
|
||||
`run-docker-full-stack.sh` auto-generates secure values for `AUTH_BEARER_TOKEN` and
|
||||
`PRIVATE_SERVER_KEY` when missing/insecure. API key middleware for dashboard server-side
|
||||
calls validates `PRIVATE_SERVER_KEY`. Set `LICENSE_KEY` only if you need Enterprise-only features; otherwise leave it empty.
|
||||
</Callout>
|
||||
|
||||
### If PostgreSQL or Docker fails with "No space left on device"
|
||||
|
||||
The error `could not write lock file "postmaster.pid": No space left on device` means the disk (or Docker storage) is full. Free space, then restart the stack.
|
||||
|
||||
**1. Check disk usage**
|
||||
|
||||
```zsh title="Terminal"
|
||||
df -h
|
||||
docker system df
|
||||
```
|
||||
|
||||
**2. Free Docker space** (safe — removes unused build cache and images)
|
||||
|
||||
```zsh title="Terminal"
|
||||
docker builder prune -f
|
||||
docker image prune -a -f
|
||||
docker container prune -f
|
||||
```
|
||||
|
||||
**3. If still low on space, remove unused volumes** (only if you do not need data in other projects’ volumes)
|
||||
|
||||
```zsh title="Terminal"
|
||||
docker volume prune -f
|
||||
```
|
||||
|
||||
<Callout type="warn" title="Volume prune can delete your data">
|
||||
`docker volume prune` removes volumes not used by any container. If you run it after `docker compose ... down`, it can remove Classroom.io’s `postgres-data`, `redis-data`, and `minio-data`, and you will lose database and object storage data. To free space without touching Classroom.io data, use only the prunes in step 2, or remove specific unused volumes by name.
|
||||
</Callout>
|
||||
|
||||
**4. Restart the stack**
|
||||
|
||||
```zsh title="Terminal"
|
||||
./run-docker-full-stack.sh
|
||||
```
|
||||
|
||||
If the host disk is still full after pruning, free space elsewhere (e.g. large files, old logs, other apps) or increase disk capacity. See [docker/docs/SELF_HOST.md](https://github.com/classroomio/classroomio/blob/main/docker/docs/SELF_HOST.md) for the full self-host runbook.
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
title: Tags
|
||||
description: Organize and filter courses with organization-scoped tags in ClassroomIO.
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout';
|
||||
|
||||
Tags in ClassroomIO help teams structure course catalogs and improve discovery across admin
|
||||
and learner experiences.
|
||||
|
||||
## Scope and Ownership
|
||||
|
||||
- Tags are organization-scoped.
|
||||
- A tag cannot be shared across organizations.
|
||||
- Each tag belongs to a tag group (facet/category).
|
||||
|
||||
## Roles and Permissions
|
||||
|
||||
- Admin:
|
||||
- Create and update tag groups.
|
||||
- Create and update tags.
|
||||
- Configure tag properties used for filtering.
|
||||
- Tutor:
|
||||
- Assign existing tags to managed courses.
|
||||
- Student:
|
||||
- Filter and discover courses using tags.
|
||||
|
||||
## Admin Workflow
|
||||
|
||||
1. Open `Org -> Tags` at `/org/[slug]/tags`.
|
||||
2. Create a tag group (for example `Difficulty`, `Technology`, `Job Offering`).
|
||||
3. Create tags inside a group.
|
||||
4. Choose a tag color from the fixed 10-color popover palette.
|
||||
5. Use the `Total Courses` value in the table to deep-link into filtered org courses:
|
||||
`/org/[slug]/courses?tags=<tagSlug>`.
|
||||
|
||||
## Filtering Surfaces
|
||||
|
||||
- Org Courses:
|
||||
- Supports URL-based filtering with `?tags=<comma-separated-tag-slugs>`.
|
||||
- Public `/courses` (org site/custom domain):
|
||||
- Displays published courses.
|
||||
- Supports tag filtering with URL query params.
|
||||
|
||||
## URL Query Format
|
||||
|
||||
- Single tag:
|
||||
- `/org/acme/courses?tags=beginner`
|
||||
- Multiple tags:
|
||||
- `/org/acme/courses?tags=beginner,react`
|
||||
- Public catalog:
|
||||
- `/courses?tags=beginner,react`
|
||||
|
||||
## API Notes
|
||||
|
||||
Current tags-related endpoints include:
|
||||
|
||||
- `GET /organization/tags`
|
||||
- `POST /organization/tags`
|
||||
- `PUT /organization/tags/:tagId`
|
||||
- `GET /organization/tags/public`
|
||||
- `GET /course/:courseId/tags`
|
||||
- `PUT /course/:courseId/tags`
|
||||
- `GET /organization/courses/public?siteName=<siteName>&tags=<...>`
|
||||
|
||||
<Callout title="In Progress">
|
||||
Archive/restore lifecycle, advanced facet semantics, and expanded LMS filtering UX are being
|
||||
incrementally rolled out.
|
||||
</Callout>
|
||||
Reference in New Issue
Block a user