GitHub - reusserdesign/codecamp-ide: Self-hosted in-browser HTML/CSS/JS editor for teaching beginners — live sandboxed preview, lessons, and an instructor view. Single Go image, bring your own Postgres and curriculum. · GitHub
Skip to content

reusserdesign/codecamp-ide

Repository files navigation

codecamp-ide

In-browser HTML/CSS/JS editor for Code Camp. Self-hosted: Go API + Vite/React web + Postgres.

Stack

  • Web: Vite + React + TypeScript + Tailwind + CodeMirror 6 (built and embedded into the Go binary for prod)
  • API: Go (chi), Postgres via pgx — Postgres is required (no in-memory fallback)
  • DB: Postgres; lessons, assets, users, work, sessions, and locks all live here
  • Lessons: a catalog.json manifest + files. A neutral sample course ships embedded at api/internal/seed/source and is seeded into the DB on first boot (app seed). Bring your own course by pointing SEED_SOURCE_DIR at a directory with the same layout — no rebuild needed.
  • Deploy: single image, runs anywhere Postgres is reachable (Docker Compose, plain k8s/k3s)

Quick start (Docker)

Build and run the whole stack — app + Postgres — with one command:

ADMIN_EMAILS=you@example.com docker compose up --build

The app migrates the DB and seeds the embedded sample course on first boot, then serves on http://localhost:8080. Sign in with the email you set in ADMIN_EMAILS to get the instructor view; any other email registers as a camper.

Local development

Run Postgres in Docker and the API + web dev server on the host (three terminals, or background the first two).

DB only:

docker compose up -d db

API (Postgres required; first run self-seeds the sample course):

cd api
go mod tidy
DATABASE_URL='postgres://codecamp:codecamp@localhost:5432/codecamp?sslmode=disable' \
ADMIN_EMAILS='you@example.com' \
go run ./cmd/api

Listens on :8080 (matches the Vite proxy). Subcommands: go run ./cmd/api migrate (migrations only) and go run ./cmd/api seed (migrate + load lessons/assets). On a fresh DB, plain serve auto-seeds when the lessons table is empty.

Web:

cd web
npm install
npm run dev

Vite dev server on :5173, proxies /api to :8080. (In prod the Go binary serves the built web itself; Vite is dev-only.)

Bring your own course

The embedded sample course lives at api/internal/seed/source — a catalog.json manifest plus the HTML/CSS/JS/asset files it references. To run your own lessons, copy that layout into a directory and point the app at it:

SEED_SOURCE_DIR=/path/to/your/course go run ./cmd/api seed

Each catalog entry maps a source file to a lesson and may apply optional transforms: variant: "sectioned" + step: N keeps the first N of <header>/<section class="hero">/<section class="cards-section">/<footer> for cumulative build-up lessons, and break: {find, replace} injects a deliberate bug for fix-it lessons. No rebuild is needed — the source is read at seed time.

Env vars

Var Purpose Default
DATABASE_URL Postgres DSN — required
ADMIN_EMAILS Comma-separated instructor emails, promoted at startup
SEED_SOURCE_DIR Path to a course directory (catalog.json at its root) to seed instead of the embedded sample course — (embedded sample)
RUN_MIGRATIONS Set false to skip migrate-on-boot (e.g. when a separate job runs them) true
SESSION_TTL Session token lifetime (Go duration) 720h
PORT API listen port 8080
OIDC_CLIENT_ID Enables single sign-on when set; unset → password-only
OIDC_CLIENT_SECRET OIDC client secret (inject from a secret manager, never commit)
OIDC_REDIRECT_URI Registered callback, e.g. https://codecamp.example.com/auth/callback
OIDC_DISCOVERY_URL Provider discovery doc; or set the OIDC_*_URL endpoints explicitly
OIDC_PROVIDER_NAME Sign-in button label ("Sign in with …") SSO
OIDC_SCOPES Space-separated scopes openid profile email

Auth

Email-first flow: /api/auth/check branches the client to password login, first-time password setup (legacy email-only accounts), or registration. Passwords are bcrypt-hashed; sessions are DB-backed bearer tokens.

SSO (optional): when OIDC_CLIENT_ID is set, the sign-in screen also offers a "Sign in with OIDC_PROVIDER_NAME" button. The Authorization Code flow runs server-side (/api/oidc/login → provider → /auth/callback), the id_token is verified, and the same DB session is issued; the token returns to the SPA in the URL fragment. SSO accounts whose email is in ADMIN_EMAILS become instructors. Campers without an SSO account keep using email + password. Forgot a password? An instructor clears it from the Admin → Campers roster ("reset password"); the account becomes passwordless, so the camper picks a new one on their next sign-in (the email-first flow routes them through set-password). Out of band, the CLI still works: go run ./cmd/api set-password <email> <password>.

Instructors (role='instructor', via ADMIN_EMAILS) get the admin panel at ?admin=1. Two tabs:

  • Lessons — lock/unlock per-lesson or per-day. Locked lessons are disabled in the camper picker and 403 on open; saved camper work is never deleted by locking.
  • Campers — roster of every camper and the lesson work they've saved. Open a project to see it run live and remotely fix it (save fix overwrites that camper's copy for the lesson).

Admin routes live under /api/admin/* behind RequireInstructor. The legacy unscoped pen CRUD (/api/pens) is instructor-only too, since it lists every camper's work; campers use the (user, lesson)-scoped routes under /api/users/{id}/pens/{lesson}.

Lessons & assets

Lesson content is pre-rendered at seed time: the seeder runs the catalog transforms (step truncation, deliberate fix-it bugs, self-ref stripping) once and stores the final HTML/CSS/JS per slug, so the runtime just reads rows. Binary assets (images) are stored as bytea and served publicly from /api/assets/<module>/<path> with long-lived cache headers — the sandboxed preview iframe loads them. The IDE shows them in a read-only assets/ file tree (GET /api/lessons/{slug}/assets).

Deploy

The whole app is one image — the Go binary serves both the embedded web app and /api. It runs anywhere it can reach a Postgres database.

  1. Build the image (build context is the repo root): docker build -t codecamp-ide .
  2. Run a Postgres instance and give the app DATABASE_URL.
  3. Set ADMIN_EMAILS to your instructor address(es). Inject any OIDC secrets from your platform's secret manager.
  4. Serve it over HTTPS behind your ingress/reverse proxy of choice.

The app migrates and seeds on first boot. To run migrations as a separate pre-deploy step instead, run app migrate (and optionally app seed) and set RUN_MIGRATIONS=false on the serving pods.

Pre-built multi-arch images are published to ghcr.io/reusserdesign/codecamp-ide (tags: latest, vX.Y.Z, branch, sha).

Kubernetes (Helm)

A chart lives in deploy/helm:

helm install codecamp ./deploy/helm \
  --set adminEmails=you@example.com \
  --set database.existingSecret=codecamp-db

Bring your own Postgres. A pre-install Job migrates + seeds before the pods roll. Run your own (private) curriculum on the public image by pointing course.gitRepo at a repo with a catalog.json — an initContainer clones it and the seeder reads it via SEED_SOURCE_DIR, so no custom image build is needed. See the chart README for ingress, OIDC, and the bring-your-own-course options.

Test

cd api && go test ./...

Security note

Preview/share iframes run user JS with sandbox="allow-scripts" and no allow-same-origin, so they cannot read parent cookies or storage. Do not add allow-same-origin. The authoring Preview also gets allow-modals (so a camper's own alert/confirm/prompt works) — but ShareView deliberately omits it, since a shared pen is another user's code on our origin and modal dialogs could be used to phish the viewer. Session tokens live in the parent app's localStorage only — the iframe can't reach them.

About

Self-hosted in-browser HTML/CSS/JS editor for teaching beginners — live sandboxed preview, lessons, and an instructor view. Single Go image, bring your own Postgres and curriculum.

Resources

License

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

Contributors