跳到正文
GitHub·· 10 天前AI 评分17

ChurchCore LMS:面向教会的开源学习平台

ricardojjulia/ChurchCore-LMS

AI 导读

ChurchCore LMS 是一个面向教会与信仰社群的开源学习平台,覆盖项目定义、课程排期、学员分组、进度追踪到结业证书的完整教学流程,采用 AGPL-3.0 许可、可免费自托管,商业或白标使用需商业授权。当前版本 0.26.6 修复了租户隔离与小组讨论,并保留平台管理员读权限。平台内置 Claude 驱动的 AI 助教、OpenAI 嵌入向量的语义搜索,以及每周 AI 生成的形成进度提醒。

正文

What is ChurchCore LMS?

ChurchCore LMS is an open-source learning platform purpose-built for churches and faith communities. It handles the full academic lifecycle — from defining ministry programs and scheduling courses, to enrolling students in cohorts, tracking progress block-by-block, and issuing completion certificates.

This is not a generic LMS wrapper. Every design decision reflects the specific needs of ministry education:

  • Volunteer training tracks with defined start/end terms
  • Discipleship cohorts that move through courses together
  • Multi-campus program tracks with shared blueprints
  • Pastoral oversight of student formation progress
  • AI-generated weekly formation nudges delivered pastor-voice
  • AGPL-3.0 licensed — free to self-host; commercial/white-label requires a commercial license

Current delivery status

Version 0.26.6 repairs tenant isolation and group discussions, preserves platform-admin read access, enforces group capacity in the database, and verifies deploy-hook releases through Vercel's commit status. The LMS side of signed OneRoster delivery is released; Academy sender integration and the later Gradebook/REST milestones remain open. See MVP and verification status for tested behavior and release gates.

Features

Learning Experience

Feature Details
Block-based course builder Pages, videos, quizzes, assignments, file resources, external links, discussion threads
Interactive learning shell Collapsible module sidebar, prev/next navigation, block-level progress tracking
Auto-graded quizzes Multiple choice with per-question point values, attempt limits, and instant feedback
Assignment submission Text submissions with instructor grading, rubric scoring, and inline feedback
Graded discussion threads Per-block discussion boards; teachers grade participation inline; students see their grade below their post
Video embedding YouTube, Vimeo, or native HTML5 video
Certificate issuance Auto-issued on course completion with letter grade and unique certificate number
PDF certificate download @react-pdf/renderer-powered certificate with name, course, grade, and date
PWA + offline support Service worker via @ducanh2912/next-pwa; page/assignment/quiz blocks work offline; amber offline banner on course pages

Gamification & Motivation

Feature Details
XP system Awarded on block completion, quiz scores (grade-scaled), assignment grading, and course completion
10-level progression Automatic level-up via calculate_level() SQL function; thresholds: 100 → 250 → 500 → 1000 → 2000 → 4000 → 8000 → 15000 → 30000 XP
Leaderboard Top 50 students with podium placement, level bars, and personal rank
Formation Pulse Weekly AI-generated pastoral nudge — trajectory-classified, cohort-percentile-aware, delivered via in-app notification and email
Badges Earned on course completion and milestones

Academic Structure

Feature Details
Program tracks Multi-year pathways: diplomas, associate degrees, discipleship, residency, leadership
Course blueprints Reusable catalog definitions with course code, credits, and track placement
Academic terms Named scheduling windows (Fall 2026, Spring 2027) with bounded dates
Course sections Scheduled blueprint offerings with delivery format (hybrid / self-paced), capacity, and access windows
Cohorts Student groups that move through a sequence of sections together
Direct enrollment Bridge trigger auto-creates course enrollment from section enrollment
Bulk enrollment Staff search-and-enroll UI per course with role guard

Administration & Communication

Feature Details
Role system admin, manager, teacher, student, guardian with ENUM enforcement at DB layer
User management Paginated admin panel with role assignment and XP/level display
Bulk CSV import Upload a CSV of users (email, name, role); preview before inviting; per-row error reporting with downloadable error CSV; 50-row cap
Announcements Staff-authored with audience targeting: all, course members, or specific cohort
Threaded messaging Direct and group threads with real-time unread badge counts
Notifications In-app bell with full /notifications center, type icons, and dismiss-all
Guardian links Parent/guardian accounts with read-only progress view; automatic email alerts on course completion and badge awards (30-minute debounce); opt-out unsubscribe link
Course analytics Per-course class stats, at-risk detection, CSV export
Grading queue /submissions with status filters and inline grade + feedback forms
Self-serve billing Org admins view plan, features, and manage subscription via Stripe Customer Portal; suspension banner; "Upgrade Plan" CTA for free-plan orgs
AI Tutor Claude-powered question assistant embedded in the learning shell
Semantic search OpenAI embeddings-backed course content search via ⌘K modal
Weekly AI digest Anthropic-powered personalized learning summary (cron-triggered)
System health panel Admin dashboard with live checks for DB, embedding jobs, RLS helpers, and bridge sync

Infrastructure & Security

Feature Details
Row Level Security All tables secured at DB layer — zero trust of client-side state
RLS helper functions current_user_uid() and current_user_role() — SECURITY DEFINER, read from profile_roles (not profiles) to prevent recursion
Service role isolation Service role key is server-side only; never in client components or NEXT_PUBLIC_ vars
Error boundaries Route-level boundaries for admin, courses, learn shell, and dashboard; never expose stack traces in production DOM
CI/CD pipeline GitHub Actions: lint → typecheck → unit tests → build; e2e on PRs; manual approval gate for production
Test suite Vitest + Testing Library; 72+ unit tests; per-file coverage thresholds enforced in CI
Materialized view mv_academic_performance with SECURITY DEFINER access functions
WCAG 2.1 AA Skip link, aria-expanded, role="dialog", aria-live, :focus-visible
Mobile-first 5-tab fixed bottom nav, iOS safe-area insets, mobile admin drawer

Architecture

System Overview

graph TB
    subgraph Browser["Browser — Next.js 16 App Router"]
        SC["Server Components\ndata fetching & page shells"]
        CC["Client Components\ninteractivity & realtime"]
        SA["Server Actions\nmutations & writes"]
    end

    subgraph Supabase["Supabase (PostgreSQL 15)"]
        Auth["Auth\nemail · magic-link · SSR session"]
        DB["PostgreSQL + RLS\nall tables row-secured"]
        RT["Realtime\nmessages · notifications · badge counts"]
        EF["Edge Functions\nhealth-check · search-users"]
        Cron["pg_cron\nembedding jobs · weekly digest"]
    end

    subgraph AI["AI Services"]
        Anthropic["Anthropic Claude\nAI Tutor · weekly digest · Formation Pulse"]
        OpenAI["OpenAI\nembeddings · semantic search"]
    end

    subgraph Email["Email"]
        Resend["Resend\nenrollment · certificate · digest · nudge"]
    end

    SC -->|"PostgREST (server)"| DB
    SA -->|"service role"| DB
    SA -->|"transactional"| Resend
    SA -->|"server-side only"| Anthropic
    CC -->|"anon key + RLS"| DB
    CC <-->|"websocket"| RT
    Auth --> DB
    EF -->|"service role"| DB
    EF --> OpenAI
    Cron -->|"service role"| DB
    Cron --> OpenAI

Loading

Academic Data Model

The academic structure is the defining architectural feature — a clean separation between the catalog (what you teach), the schedule (when you teach it), and delivery (tracking students through it).

graph LR
    PT["Program Tracks\ndiploma · associates\ndiscipleship · residency"]
    CB["Course Blueprints\ncatalog definition\ncode · credits · track"]
    CO["Courses\ncontent + blocks\nenrolled students"]
    AT["Academic Terms\nFall 2026\nSpring 2027"]
    CS["Course Sections\nscheduled offerings\nformat · capacity · access window"]
    GC["Cohorts\nstudent groups\nmove through sections together"]
    CM["Cohort Members\nuser ↔ cohort link"]
    DE["Direct Enrollments\nuser in section\nbridge trigger fires"]
    EN["Enrollments\nlearning progress\npercent · XP · last_accessed"]

    PT --> CB
    CB -.->|"linked via blueprint_id"| CO
    CB --> CS
    AT --> CS
    GC --> CM
    CM -->|"source"| DE
    CS --> DE
    DE -. "bridge trigger\nauto-creates" .-> EN
    CO --> EN

Loading

Identity & Security Model

graph TB
    AU["auth.users\n(Supabase Auth)"]
    PR["profiles\nuid · role · xp · level · email"]
    PRR["profile_roles\nauth_id · uid · role · status\n(lightweight RLS read table)"]
    T["trg_sync_profile_roles\nAFTER INSERT/UPDATE/DELETE ON profiles"]
    CUU["current_user_uid()\nSECURITY DEFINER\nreads profile_roles only"]
    CUR["current_user_role()\nSECURITY DEFINER\nreads profile_roles only"]
    RLS["All RLS policies\nuse these two functions\nnot auth.uid() directly"]

    AU -->|"uid = auth.users.id"| PR
    PR --> T
    T --> PRR
    PRR --> CUU
    PRR --> CUR
    CUU --> RLS
    CUR --> RLS

Loading

Why profile_roles? Direct profiles queries inside RLS policies cause infinite recursion. The lightweight profile_roles table — kept in sync by a trigger — breaks the cycle and keeps every read under 1 ms.

Role Hierarchy

graph LR
    A["Admin\nfull system access\nuser management\nhealth panel"]
    M["Manager\nacademic operations\nbluprints · terms · sections\ncohort enrollment"]
    T["Teacher\ncourse builder\ngrading queue\nanalytics"]
    S["Student\nlearning shell\ngamification\nXP · badges · certificates"]
    G["Guardian\nread-only\nprogress view"]

    A --> M --> T --> S
    G -. "linked to student" .-> S

Loading


Tech Stack

Layer Technology Notes
Framework Next.js 16 (App Router) Server Components, Server Actions, Turbopack
Language TypeScript 5 Strict mode
Database Supabase (PostgreSQL 15) RLS on all tables, pg_cron, SECURITY DEFINER functions
Auth Supabase Auth Email/password + magic link; SSR session via @supabase/ssr
Styling Tailwind CSS 3 + shadcn/ui tailwind-merge, class-variance-authority
Rich text Tiptap 3 Course block content, announcements
AI — Tutor Anthropic Claude (claude-haiku-4-5) Server-side only; zero PII in prompts
AI — Embeddings OpenAI text-embedding-3-small; semantic course search
Email Resend Enrollment, certificate, digest, guardian notifications
Payments Stripe Checkout, webhooks, Customer Portal; idempotency via platform_audit_log
PWA @ducanh2912/next-pwa Workbox service worker; offline fallback; App Router compatible
Testing Vitest + Testing Library 72+ unit tests; Proxy mock chain for Supabase
CI/CD GitHub Actions Lint → typecheck → test → build → approval gate → production
Deployment Vercel Edge-optimized; cron via vercel.json
PDF @react-pdf/renderer Server-rendered certificate PDFs
Charts Recharts + Tremor Course analytics, admin dashboards

Getting Started

Prerequisites

  • Node.js 18+
  • A Supabase project (free tier works)
  • An Anthropic API key (for AI Tutor and weekly digest)
  • A Resend API key (for transactional email)
  • A Vercel account (for deployment — or any Node.js host)

1. Clone and install

git clone https://github.com/ricardojjulia/ChurchCore-LMS.git
cd ChurchCore-LMS
npm install

2. Create your Supabase project

  1. Go to supabase.com → New project
  2. Note your Project URL, Anon key, and Service role key (Settings → API)
  3. Install the Supabase CLI:
npm install -g supabase
supabase login
supabase link --project-ref YOUR_PROJECT_REF

3. Environment variables

cp .env.example .env.local

Edit .env.local:

# Supabase (required)
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key

# Server-side only — never prefix with NEXT_PUBLIC_
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key

# AI features — server-side only
ANTHROPIC_API_KEY=your-anthropic-key
OPENAI_API_KEY=your-openai-key

# Email (optional — features degrade gracefully if absent)
RESEND_API_KEY=your-resend-key
RESEND_FROM_EMAIL=noreply@yourdomain.com

# Cron auth (used by API routes called by Vercel/pg_cron)
CRON_SECRET=a-long-random-string

# Stripe billing (server-side only)
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_PRICE_STARTER=price_...

# Guardian unsubscribe token verification (server-side only)
SUPABASE_JWT_SECRET=<your-supabase-jwt-secret>

Security note: SUPABASE_SERVICE_ROLE_KEY, ANTHROPIC_API_KEY, and OPENAI_API_KEY are server-side only. They must never appear in client components or be prefixed NEXT_PUBLIC_. All AI calls are proxied through /api/ai and /api/digest.

4. Apply database migrations

supabase db push

This applies all migrations in order — tables, RLS policies, helper functions, triggers, materialized views, pg_cron schedules, and the academic structure schema.

5. Create your first admin user

  1. Start the app locally: npm run dev
  2. Open http://localhost:3000 and sign up with your email
  3. In the Supabase dashboard → Table Editor → profile_roles, set role = 'admin' for your row
  4. Refresh the app — you now have full admin access

6. Run the demo (optional)

The repo ships a guarded destructive reset that builds a full demo school: 6 program tracks, 20 course blueprints, 6 cohorts, 27 students, 138 enrollments, course content, grades, and calendar events.

node scripts/reset-demo-data.mjs --confirm --retain-email=you@example.com

⚠️ This deletes all non-retained data in the linked Supabase project. Only run against a dedicated demo/dev project.
See docs/demo-data.md for a full description of what it creates.

7. Deploy to Vercel

Deploy with Vercel

Add all environment variables in your Vercel project settings (Settings → Environment Variables).

For the full GitHub Actions CI/CD setup with the approval gate and production deploy, see docs/github-setup.md.


How-To

Add a course

  1. Admin or Manager → Courses → New Course
  2. Fill in title, description, and settings → Save
  3. Use the Course Builder to add modules and blocks
  4. Publish the course when ready
  5. Optionally link the course to a Blueprint (Admin → Blueprints → Edit → Link course)

Create a program track

  1. Admin → Programs → New Track
  2. Add Blueprints to the track (reusable course definitions)
  3. Create Terms (Admin → Terms → New Term) for each academic period
  4. Schedule sections — Blueprint → New Section → pick term, format, dates, capacity
  5. Create a Cohort and assign students
  6. Enroll the cohort into sections via Admin → Cohorts → [Cohort] → Enroll in Section

Enroll students in a course

Via cohort (recommended):
Admin → Cohorts → select cohort → Enroll in Section → pick the blueprint's section

Direct enrollment:
Course → Enroll → search by name or email → click Enroll

Configure email notifications

  1. Add RESEND_API_KEY and RESEND_FROM_EMAIL to your environment
  2. Email is sent automatically on: student enrollment, certificate issuance, weekly AI digest, and Formation Pulse nudges
  3. Students can opt out of emails via their profile → Email notifications toggle (respects email_digest_enabled)

Configure AI features

  • AI Tutor — add ANTHROPIC_API_KEY to your environment; the tutor appears automatically in the learning shell
  • Semantic search — add OPENAI_API_KEY; content is embedded via pg_cron automatically after publishing
  • Formation Pulse — add both OPENAI_API_KEY and RESEND_API_KEY; runs Monday 9 AM via Vercel cron

Set up CI/CD

See docs/github-setup.md for:

  • Branch protection rules
  • Required GitHub Actions secrets
  • Production deploy with manual approval gate

Customise the sidebar navigation

See docs/HOWTO-sidebar-nav.md for adding links, changing icons, toggling collapse behavior, and hiding the nav on specific routes.

Run the test suite

npm run test:run          # unit tests only (fast)
npm run test:coverage     # with coverage report
npm run test:ci           # full CI run with thresholds

See docs/testing.md for coverage thresholds, test patterns, mock strategy, and e2e setup.


Project Structure

src/
├── app/
│   ├── actions/               # Server actions — learning, enrollment, messages, cohorts
│   ├── api/                   # API routes — AI proxy, search, calendar, digest, health
│   ├── admin/                 # Cohorts, sections, terms, blueprints, users, health panel
│   │   ├── billing/           # Self-serve billing — plan display, Stripe Customer Portal
│   │   └── users/import/      # Bulk CSV user import
│   ├── api/
│   │   ├── stripe/            # Checkout, webhook, Customer Portal session
│   │   └── guardian/          # Unsubscribe route (HMAC-SHA256 token)
│   ├── courses/[id]/
│   │   ├── build/             # Course builder
│   │   ├── learn/             # Learning shell + block players
│   │   ├── complete/          # Completion page + certificate
│   │   ├── enroll/            # Bulk enrollment UI
│   │   ├── analytics/         # Instructor analytics + CSV export
│   │   └── submissions/       # Grading queue
│   ├── dashboard/             # Role-aware dashboard (student · teacher · admin)
│   ├── offline/               # PWA offline fallback page (no auth required)
│   ├── leaderboard/           # XP leaderboard — top 50 with podium
│   ├── notifications/         # Full notification center
│   ├── performance/           # Student GPA and progress view
│   ├── certificates/          # Earned certificate gallery
│   ├── messages/              # Threaded messaging
│   ├── guardian/              # Guardian progress view
│   └── announcements/         # Announcement feed + composer
├── components/
│   ├── learning/              # LearningShell, BlockPlayer, QuizPlayer, DiscussionPlayer
│   ├── layout/                # Sidebar, GlobalSearch, MobileBottomNav, NotificationBell
│   └── dashboard/             # StudentDashboard, InstructorDashboard, AdminDashboard
├── hooks/                     # useRealtimeChannel, useNotifications, useMessages
├── lib/
│   ├── auth/                  # Permission helpers (isAdmin, isStaff, …)
│   ├── formation/             # Formation Pulse — trajectory classifier
│   └── monitoring.ts          # captureError() — error ID generation
├── tests/                     # Vitest setup, e2e specs, test fixtures
├── types/                     # Shared TypeScript interfaces
└── utils/
    ├── supabase/              # client · server · service helpers + __mocks__
    ├── grading.ts             # calculateLetterGrade, calculatePercentage, isPassing
    └── certificate.ts         # generateCertificateData, formatCompletionDate
.github/
├── workflows/
│   ├── ci.yml                 # lint → typecheck → test → build
│   ├── e2e.yml                # edge function e2e (PR-only)
│   └── release.yml            # CI → manual approval → production
└── CODEOWNERS                 # migrations and workflows require maintainer review
docs/
├── decisions/                 # ADR-2025-001 through ADR-2025-007, COUNCIL-2025-008 through 011
├── github-setup.md            # Secrets, branch protection, CI/CD reference
├── testing.md                 # Unit tests, coverage, e2e guide
├── demo-data.md               # Demo reset documentation
└── academic-program-workflows.md  # Tracks, blueprints, terms, cohorts walkthrough
supabase/
├── functions/
│   ├── search-users/              # Edge Function — role-gated user search with audit log
│   ├── system-health-check/       # Edge Function — DB, embedding, RLS, and bridge checks
│   └── send-guardian-notifications/  # Edge Function — guardian email queue processor (pg_cron, every 5 min)
├── migrations/                # 50+ ordered SQL migrations
└── seed.test.sql              # Deterministic test seed (GoTrue-first, email-subquery pattern)
scripts/
├── reset-demo-data.mjs        # Guarded destructive demo reset
├── check-version.mjs          # CHANGELOG ↔ package.json version sync
├── ci-setup-test-env.mjs      # CI helper — sets test-user passwords via Admin API
├── generate-icons.js          # Generates PWA icons (icon-192.png, icon-512.png) — Node.js built-ins only
└── register-guardian-cron.sh  # Registers guardian-notify pg_cron job against linked Supabase project

Architecture Decisions

Full records in docs/decisions/. Key decisions:

Document Decision
ADR-2025-001 PostgreSQL RLS as sole authorization layer
ADR-2025-002 Academic structure: blueprints → sections → terms → cohorts
ADR-2025-003 Block-based course content model
ADR-2025-004 XP and leveling system
ADR-2025-005 Server Actions pattern for all mutations
ADR-2025-006 OpenAI embeddings for semantic course search
ADR-2025-007 AGPL-3.0 licensing model
ADR-2026-001 Multi-tenant isolation: platform_admins, organizations, RLS
ADR-2026-002 Stripe billing: webhook idempotency, Customer Portal, plan mapping
COUNCIL-2025-011 Formation Pulse — AI-generated weekly pastoral nudges
COUNCIL-2026-001 Guardian Email Bridge — queue, Edge Function, debounce, unsubscribe
COUNCIL-2026-002 Bulk CSV Import — parse, preview, GoTrue invite, per-row errors
COUNCIL-2026-003 Self-Serve Billing — Stripe Customer Portal, plan page, suspension UX
COUNCIL-2026-004 PWA + Offline Player — service worker, manifest, offline banner
COUNCIL-2026-005 Graded Discussion Block — participation score, inline teacher UI

The RLS Recursion Fix

The most common Supabase pitfall is writing RLS policies that query profiles directly — which causes infinite recursion because reading profiles triggers the policy that reads profiles. ChurchCore LMS solves this permanently via profile_roles:

-- Two SECURITY DEFINER functions read only from profile_roles (never profiles)
CREATE FUNCTION current_user_uid()  RETURNS uuid  AS $$ SELECT uid  FROM profile_roles WHERE auth_id = auth.uid() $$;
CREATE FUNCTION current_user_role() RETURNS text   AS $$ SELECT role FROM profile_roles WHERE auth_id = auth.uid() $$;

-- A trigger keeps profile_roles in sync automatically
CREATE TRIGGER trg_sync_profile_roles
  AFTER INSERT OR UPDATE OR DELETE ON profiles
  FOR EACH ROW EXECUTE FUNCTION sync_profile_roles();

All RLS policies use current_user_uid() and current_user_role() — never auth.uid() or direct profiles reads.


Governance

ChurchCore LMS uses a six-member Architecture Council for all significant technical decisions. The council evaluates proposals across six lenses — architecture, engineering, security, product, QA, and data — and produces formal decision records before implementation begins.

Decision documents live in docs/decisions/. The council system itself is described in docs/CODE-FACTORY-SYSTEM-PROMPT.md.


Contributing

See CONTRIBUTING.md for:

  • Commit conventions and branch naming
  • PR requirements (typecheck, tests, coverage thresholds)
  • Migration authoring rules (RLS required, no SET row_security = off)
  • Security constraints (no PII in AI prompts, service role server-side only)
  • Architecture Council procedures for significant changes

Licensing

Open Source (AGPL-3.0)

ChurchCore LMS is licensed under the GNU Affero General Public License v3.0.

This means:

  • ✅ Free to self-host for any church or organization
  • ✅ Free to study, modify, and contribute back
  • ✅ Free to use in ministry, education, and non-profit contexts
  • ⚠️ If you deploy a modified version as a service, you must open-source the changes
  • ❌ Commercial SaaS forks, white-label products, or embedded licensing require a commercial license

Commercial License

For denominations, church software vendors, or organizations that need:

  • White-label branding without AGPL disclosure requirements
  • Embedded use in a larger commercial product
  • Dedicated enterprise support

Contact: ricardojjulia@gmail.com


Changelog

See CHANGELOG.md for the full version history.

Latest: v0.22.0 — Self-Serve Billing, PWA + Offline, Graded Discussions
Previous: v0.21.0 — Guardian Email Bridge, Bulk CSV Import


Built with care for the global church.
AGPL-3.0 © 2026 Ricardo Julia

来源:GitHub · github.com