Skip to content

FastPix/FastPix-llms-oss

fastpix-lms-oss

Open-source LMS reference built on FastPix Video Data. Postgres + NextAuth, multi-tenant by design, with three engagement signals layered on top of the FastPix SDK.

Status: reference implementation. This is a working, production-shaped example you can fork and build on. It is not a hosted product. There is no SLA. Read What this is NOT before deploying.


What it does

Owner side (auth-gated, magic-link email):

  • Sign in via emailed magic link (no passwords)
  • Create courses, add lessons by pasting a FastPix playback ID
  • Per-course dashboard showing students, engagement curves, and at-risk learners
  • Remove individual lessons, students, or entire courses (with cascading cleanup)
  • Sign out clears the session in Postgres + the browser cookie

Student side (anonymous, no signup):

  • Visit a public course URL (/learn/<slug>)
  • Enter a display name once → server-side dedup means the same name across two browsers maps to one student record
  • Watch lessons via the FastPix player; telemetry flows in the background
  • Returning visitors see a "welcome back" and can jump to any lesson
  • Placeholder "🎓 Certificate" button (stub — fork-friendly to wire up)

Three engagement signals computed from heartbeats + the FastPix Data API:

  1. Per-segment engagement curve — 30-second buckets of watched-seconds, replays, and backward seeks. Surfaces where attention dips inside a lesson.
  2. Manifest-level drop-offplaying_time × video_encoding_variant from the FastPix Data API. Flags encoding-related abandonment.
  3. Per-user struggle scorereplay_density × 0.4 + seek_back_density × 0.6, with an absolute-events floor so short videos don't false-positive.

Tech stack

Layer Choice
Framework Next.js 15 (App Router, Server Actions)
UI React 18 + Tailwind CSS
Database Postgres 16 (via Docker for local dev)
ORM Prisma 5
Auth NextAuth.js 4 (magic-link email, DB sessions)
Email (dev) MailHog (via Docker, catches all SMTP locally)
Input validation Zod
Video @fastpix/fp-player (web component) + @fastpix/video-data-core
Lint / format ESLint + Prettier

Zero front-end JS framework outside React. Zero CSS framework outside Tailwind. Zero secret-management dependency beyond .env. The dependency graph is deliberately minimal — easier to audit, easier to keep on a current version.

What this is NOT

  • Not a hosted product. You run it yourself.
  • Not a multi-tenant SaaS template. Each user owns their own courses; there are no orgs, no teams, no billing.
  • Not feature-complete vs. a real LMS. No quizzes, no certificates (only a placeholder button), no DRM, no live streams, no payments.
  • Not maintained on a fixed cadence. PRs welcome for the reference patterns; feature requests will likely be declined.

Quickstart

Requires Node 18.17+, Docker (for local Postgres + SMTP), openssl (for generating the secret), and a FastPix workspace for the player.

# 1. Clone & install
git clone https://github.com/fastpix/fastpix-lms-oss.git
cd fastpix-lms-oss
npm install

# 2. Start Postgres + MailHog via Docker (~10s on first run, instant after)
docker compose up -d
docker compose ps   # confirm both containers show "Up" / "(healthy)"

# 3. Configure env — note: `.env`, NOT `.env.local`. See "Env file naming" below.
cp .env.example .env
#  Then edit `.env` and fill in:
#   - NEXTAUTH_SECRET: run `openssl rand -base64 32`, paste the output
#   - NEXT_PUBLIC_FASTPIX_WORKSPACE_KEY: from FastPix dashboard → Settings → Workspaces
#   - (optional) FASTPIX_ACCESS_TOKEN_ID + FASTPIX_SECRET_KEY: from FastPix
#     dashboard → Settings → Access Tokens. Required only for the manifest-level
#     drop-off chart on the dashboard. Leave blank if you don't need it.
#  Defaults for DATABASE_URL, NEXTAUTH_URL, and the MailHog SMTP settings
#  already work — don't change them unless you know why.

# 4. Generate Prisma client + apply migrations
npx prisma generate
npm run db:migrate
#   When prompted for a migration name, type `init` and press Enter.

# 5. Start the dev server
npm run dev

Open http://localhost:3000, click Sign in as owner, enter any email. The magic-link email appears at http://localhost:8025 (MailHog's web UI). Click the link in that email and you're signed in.

First run: from sign-in to seeing data

After you sign in, you'll land on Your courses (empty). Here's how to fill it:

  1. Click + New course, give it a title, submit. You're now on the course detail page.
  2. Click Add a lesson. Paste a FastPix playback ID (find one in FastPix dashboard → Media → click any asset → "Playback IDs"). Add a title and the video duration in seconds. Submit.
  3. Click View public page ↗ (top-right). It opens the student-facing course page in a new tab.
  4. In that new tab, you're the student. Enter a name (e.g., "Test") and click "Start course." The player loads.
  5. Watch for at least 30 seconds (one heartbeat bucket fires every 30s).
  6. Switch back to your owner tab and click Dashboard. You should see your test student in the table with a non-zero completion %, and a dot on the engagement curve.

That's the full loop. From here you can add more lessons, test as multiple students in different browsers, and watch the at-risk classifier kick in if you abandon a video early.

Env file naming

We use .env (not Next.js's conventional .env.local) because Prisma's CLI does not read .env.local. Keeping a single .env file avoids the "Prisma can't find DATABASE_URL" footgun. Both Next.js and Prisma read .env happily, and it's gitignored.

If you forked an older copy of this repo that referenced .env.local, cp .env.local .env and you're good.

Architecture

┌──────────────────┐      heartbeat       ┌──────────────────┐
│  CourseFpPlayer  │  ─────────────────▶  │ /api/heartbeat   │
│  (browser)       │   (idempotent POST)  │  (Zod + Prisma)  │
└──────────────────┘                      └──────────┬───────┘
        │                                            │
        │ <fastpix-player>                           ▼
        │ + segment-tracker                  ┌──────────────┐
        ▼                                    │  Postgres    │
┌──────────────────┐  ◀─────────────────────│  Heartbeat   │
│  FastPix Data    │   /v1/data/...         │  table       │
│  (view metrics + │   (server-side fetch   └──────────────┘
│  drop-off API)   │    in dashboard)               ▲
└──────────────────┘                                │
                                                    │
                                      ┌─────────────┴────────┐
                                      │  lib/signals.ts      │
                                      │  (pure functions)    │
                                      └──────────────────────┘

The signal computations live in lib/signals.ts as pure functions — they take heartbeat rows and return derived metrics. No DB access, no FastPix coupling. Easy to unit-test, easy to lift into your own LMS without taking the rest of the project.

Folder layout

app/
├─ api/                    # route handlers
│  ├─ auth/[...nextauth]/  # NextAuth endpoints (auto-generated)
│  ├─ heartbeat/           # POST /api/heartbeat (Zod + idempotent upsert)
│  └─ student-session/     # POST /api/student-session (name-based dedup)
├─ owner/                  # auth-gated owner area
│  ├─ page.tsx             # courses list
│  ├─ new/page.tsx         # create course form
│  └─ [id]/
│     ├─ page.tsx          # course detail (lessons, add/remove, delete course)
│     └─ dashboard/page.tsx
├─ learn/[id]/             # public student area (no auth)
│  ├─ page.tsx             # course landing + name prompt
│  └─ watch/[videoId]/     # player page
├─ signin/page.tsx         # magic-link entry
├─ page.tsx                # marketing home
└─ layout.tsx              # root layout (header + global styles)

actions/courses.ts         # server actions: create/delete course, add/remove lesson, remove student
components/                # React components (CourseFpPlayer, StudentNamePrompt, SiteHeader, etc.)
lib/                       # pure helpers (signals, segment-tracker, auth, db, fastpix client, rate-limit, zod schemas)
prisma/                    # schema + generated migrations

Key files

File What it does
prisma/schema.prisma Data model: User (NextAuth), Course, Video, Student, ViewerSession, Heartbeat. Unique constraint on (sessionClientId, segmentIndex) makes heartbeat ingestion idempotent.
lib/segment-tracker.ts Client-side 30s heartbeat tracker. Handles shadow-DOM, sub-30s videos, replay/seek dedup, idempotency.
lib/signals.ts Pure signal computation: engagement curve, struggle score, at-risk classification. Easy to lift out.
lib/fastpix.ts Server-side FastPix Data API client (basic auth).
lib/auth.ts NextAuth config + Prisma adapter + requireUser() server helper.
lib/rate-limit.ts In-memory token-bucket limiter for the heartbeat endpoint. Swap for Redis in production.
app/api/heartbeat/route.ts Idempotent POST. Rate-limited. Validates the (student, video) pair is in the same course.
app/api/student-session/route.ts Server-side dedup by (courseId, name) so the same student on two browsers maps to one record.
actions/courses.ts Server actions for course/lesson/student mutations. Every action does an ownership check.
components/CourseFpPlayer.tsx Shadow-DOM-aware player wrapper that attaches the tracker.
components/SiteHeader.tsx Path-aware nav. Owner vs. student vs. marketing chrome. Sign-out button.
components/StudentNamePrompt.tsx Anonymous student onboarding + returning-visitor handling.
app/owner/[id]/dashboard/page.tsx The dashboard — at-risk list, student table, per-video engagement curve.

Scripts

npm run dev          # start the dev server
npm run build        # production build
npm run start        # run the production build
npm run typecheck    # tsc --noEmit
npm run lint         # next lint
npm run format       # prettier --write
npm run db:migrate   # prisma migrate dev
npm run db:deploy    # prisma migrate deploy (use in production)
npm run db:studio    # open Prisma Studio in the browser

What's intentionally not here

Compared to a full LMS, the following are absent on purpose to keep the OSS surface minimal:

  • Excel/CSV exports — straightforward to add, but pulls in a heavy dependency. See the fastpix-video-data-lms-playbook demo for an example using exceljs.
  • Quizzes, certificates, grades — out of scope. There's a placeholder Certificate button on student pages, wired to nothing. Implement in your fork.
  • Multi-org / teams — single owner per course.
  • Webhooks → warehouse — for production scale you'd write heartbeats to Kafka/Kinesis/PubSub and aggregate in BigQuery/ClickHouse instead of querying Postgres directly. The pattern is the same; only the storage layer changes.

Troubleshooting

zsh: command not found: docker → Docker Desktop isn't installed or isn't in your PATH. Install from https://www.docker.com/products/docker-desktop/, open the app once so the daemon starts (whale icon should be solid in your menu bar), then retry.

Homebrew permission errors when installing Docker on Apple Silicon → run sudo chown -R $(whoami) /opt/homebrew and retry. This happens when prior brew operations left the directory owned by a different user.

npm install fails with ERESOLVE on nodemailer → make sure you're on the version of package.json from the current main (we use nodemailer 7 to satisfy next-auth's peer dep). If the error persists, npm install --legacy-peer-deps.

MailHog logs a platform warning (linux/amd64 does not match host platform linux/arm64) on Apple Silicon → harmless. MailHog doesn't ship an ARM image; Docker emulates it under Rosetta. Slower but works.

Magic-link email doesn't appear in MailHog → check three things in .env:

  • EMAIL_SERVER_HOST must be localhost (not smtp.example.com)
  • EMAIL_SERVER_PORT must be 1025 (not 587)
  • EMAIL_FROM must be set to anything non-empty

You also need to restart npm run dev after changing these — Next.js doesn't hot-reload server env vars.

Environment variable not found: DATABASE_URL when running prisma migrate → you have a .env.local instead of .env. Rename or copy: cp .env.local .env. See "Env file naming" above.

Port 3000 is already in use → some other dev server is running. Kill it:

lsof -ti :3000 | xargs kill

Or change NEXTAUTH_URL in .env to whatever port Next.js falls back to (usually 3001).

The player area is blank when a student opens a lesson → most likely NEXT_PUBLIC_FASTPIX_WORKSPACE_KEY isn't set. This is a public env var that gets baked into the client bundle at build time, so you also need to restart npm run dev after setting it. Confirm in the browser console — there should be a [CourseFpPlayer] attached segment tracker to inner <video> log within ~3s of page load.

Heartbeats don't appear in the dashboard even after watching for 30s+ → check the dev server terminal for POST /api/heartbeat 200. If you see 403 scope_mismatch, clear localStorage for localhost:3000 (DevTools → Application → Local Storage) — you have a stale student ID from a different course. If you see 400 invalid_payload, the schema and the client tracker have drifted; re-run npx prisma generate.

Production checklist

If you do deploy this, here's what you need to harden first:

  • Swap the in-memory rate limiter (lib/rate-limit.ts) for Redis (Upstash, KV).
  • Move heartbeat ingestion off the main DB (Kafka/SQS → batched writes).
  • Add an allow-list to NextAuth's signIn callback if you don't want anyone-with-an-email signing in.
  • Replace findMany heartbeats in the dashboard with windowed/aggregated SQL.
  • Add observability — at minimum, structured logs + an error reporter (Sentry, etc.).
  • Add DB connection pooling (PgBouncer / Prisma Accelerate) for serverless deployments.
  • Switch SMTP from MailHog to a real provider (Resend, SendGrid, SES).
  • Set NEXTAUTH_URL to your real production URL.

License

Apache 2.0. Use it, fork it, ship it.

Disclaimer

FastPix publishes this repository as a reference implementation. It is provided "as is" without warranty of any kind. Bugs in this code do not reflect bugs in the FastPix Video Data SDK or hosted product — the signal computations (engagement curve, struggle score) are implemented in this repo and are subject to the same Apache 2.0 disclaimer as the rest of the code. If you find an issue with FastPix's hosted analytics product, report it via FastPix support, not this repo's issue tracker.

About

No description, website, or topics provided.

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages