Skip to content

About

Self-hosted AI-powered Learning & Training Management System for Singapore's SkillsFuture ecosystem. Features SSG/TPGateway API integration, multi-role access, OTP login, QuickBooks finance workflows, and AI agent automation.

Topics

Resources

Stars

19 stars

Watchers

0 watching

Forks

Latest commit

 

History

2,217 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

AI-LMS-TMS

A comprehensive Learning Management System (LMS) and Training Management System (TMS) with AI capabilities, designed for Singapore's SkillsFuture training ecosystem.

Next.js TypeScript React PostgreSQL Tailwind CSS Google AI

Live Demo Documentation


AI-LMS-TMS Login Screen


AI OAuth provider selection

AI Provider settings lets an Admin or Training Provider connect OpenAI and Claude OAuth, test the connections, and select the provider used by chat, courseware, assessments, audits, SEO and supporting-document processing. OpenAI uses the Codex SDK with gpt-5.6-sol; Claude uses the Claude SDK with claude-opus-5. Optional Claude fallback retries failed OpenAI requests. Credentials are encrypted on the server. See setup and verification.

AI provider settings

Overview

AI-LMS-TMS is a full-stack, enterprise-grade web application that manages the complete training lifecycle - from course creation and scheduling to learner enrollment, progress tracking, assessments, and certification. Built with modern technologies and deep integration with Singapore's SkillsFuture ecosystem, it provides a seamless experience for training providers, trainers, and learners.

Why AI-LMS-TMS?

  • AI-Powered Learning: AI chatbot powered by an external OpenClaw or Hermes agent, reached over WhatsApp/Telegram, plus a SEO metadata generator and GenAI authoring tools
  • SSG Integration: Full SkillsFuture Singapore API support for course runs, enrolments, assessments, grants, and claims
  • Multi-Role System: 7 roles with dedicated dashboards — Learner, Trainer, Admin, Developer, Finance, Payroll, Training Provider
  • Multi-Tenant: Same codebase deployed for multiple tenants on Coolify — Tertiary builds via Dockerfile, other tenants (Chariot, Intellisoft) build via Docker Compose
  • Financial Operations: QuickBooks integration, personal/company invoice workflows, grant calculators, claim tracking, billing history
  • Singapore-Ready: NRIC/FIN validation, UEN verification, WSQ/CASL/IBF course support, funding calculations
  • Automation: Task Scheduler, webhooks, n8n workflows for trainer invitations, certificate generation, enrollment sync, and more
  • Support System: Built-in ticket system for learner, trainer, and admin support
  • Workflow Guides: Interactive documentation for all system workflows (Training, Admin, Finance)
  • External API: 200+ REST endpoints including external integrations for third-party bots
  • Modern Stack: Next.js 16, TypeScript, Tailwind CSS, PostgreSQL, Docker, deployed on Coolify

Table of Contents

Features

Learner Features

  • Course browsing and enrollment
  • Courseware access — Learner Guide, Learner Slides, Activities/Lab and SkillsFuture link cards, unlocked from 8:30 AM SGT on the course start date
  • Progress tracking through learning units and subtopics
  • Assessment submission and grade viewing — the course page re-reads the trainer's publish state every minute while visible and on tab focus, so a Written/Practical exam published mid-class appears without a reload
  • AI chatbot for personalized course assistance (external OpenClaw/Hermes agent via WhatsApp/Telegram)
  • Certificate download after completion — issued only once the class has ended (from 6:00 PM SGT on the last day, or earlier if the trainer marks the learner Competent) and the minimum attendance is met; admins/trainers can still issue manually
  • Job search integration
  • My Calendar — personal month calendar of the learner's scheduled classes
  • Header quick nav — My Classes, My Calendar and Ed Tools links in the top header (Billing remains in the sidebar as Billing History)
  • Tools — the full trainer tools catalogue mirrored in the learner sidebar (Ed Tools, Project Mgt, Six Sigma, Cyber Security, Networking, Finance, HR, Data Analytics, ML, Statistical, DOE, SPC, Sustainability, K8s, Blockchain, Quantum, Design, GenAI, Agentic AI, Virtual Tools), shared via components/toolsData.ts; clicking a tools group opens the same tool card page as the trainer role (GenAI Tools stays link-only for learners)

Trainer Features

  • My Classes — View assigned classes (upcoming, ongoing, completed). Trainer mode is scoped server-side to classes the trainer is assigned to (course_run_trainer or the legacy assigned-trainer columns, matched by user id or any of the trainer's emails) — listing, opening, publishing assessments and grading all return 403 for unassigned classes
  • Trainer + Learner dual role — trainers who enrol in the provider's own courses sign in and pick Learner mode (their enrolled classes, lessons, assessment submissions) or Trainer mode (only their assigned classes), switchable from the header's View As menu. Gated per tenant by training_provider.trainer_learner_dual_role (default off); enabling it backfills the Learner role onto every trainer and a user_role_map trigger grants it to every new trainer. A trainer can never grade their own enrolment
  • My Calendar — personal month calendar of the trainer's assigned classes
  • E-Attendance — Digital attendance tracking
  • Assessment Grading — Rubric-based grading with Assessment Summary Record support; each learner row shows per-method submission status (WA Written, PP Practical, CS Case Study, RP Role Play, OQ Oral Questioning) ticked when the learner has submitted, alongside a manually-ticked TQ (TRAQOM survey) box — SSG publishes no per-learner survey completion feed, so the trainer confirms it — with per-method and TRAQOM completion counts (e.g. WA 3/7, TRAQOM 5/7) and a refresh button in the roster header, plus Mark All Competent and bulk certificate sending. A learner who holds several enrolment rows in the same run (a manual LMS signup plus the SSG-synced record) appears once, listing every email they enrolled under; the SSG row is primary (NRIC, certificate), submissions are unioned across rows, and competency/TRAQOM toggles update every row. Beside TQ sits a SIGN (assessor signature) box: the trainer records their assessor block once — name, NRIC, date and a mouse/touch-drawn signature (Trainer Profile → Assessor Signature, or the dialog on the roster) — and ticking SIGN stamps it into the "Assessor Name / NRIC / Date / Signature" block of every PDF, Word (DOCX) or OpenDocument (ODT) file the learner submitted, uploading the stamped copy next to the original in Google Drive; unticking restores the originals. A Mac Pages (.pages) submission is converted to PDF automatically and signed as <name> (signed).pdf (the .pages file stays the original): headless LibreOffice does the conversion, and lib/assessment/pagesToPdf.ts repairs the import first — answer boxes grow to fit instead of clipping, pasted screenshots (including HEIC) are put back in the boxes they were pasted into, in document order read from the Pages file itself (lib/assessment/pagesIwa.ts), and Mac font names are mapped to metric-compatible Linux fonts so the layout holds. The roster header carries per-method, TRAQOM, Assessor Sign and Competent counts plus a prominent Refresh, a Watch Demo link opens a 70-second narrated walkthrough of the sign-off flow (public/videos/assessor-sign-off-demo.mp4, learner details and NRIC masked), and each learner's email links to their Assessment Records folder in Drive (Course → Assessment Records → Session → Learner; the folder id is stored on upload and back-filled from the file's Drive parent for older submissions). Opening Assessment Grading auto-collapses the trainer's left sidebar to its icon rail so the wide roster has room; it re-expands on leaving the page (unless the trainer toggled it meanwhile)
  • Critical Reminders — a red-bordered block pinned under the course page's left menu. In Trainer mode (after Grading) it lists the four duties and rules that gate payout: TRAQOM is mandatory (a learner who misses the survey delays the trainer payout), mark each learner Competent in Assessment Grading once all assessments are in, digitally sign each learner's assessment there, and no trainer payment is made for learners who give an overall course review rating of 2 out of 5 or below; the grading items jump straight to the Grading section. In Learner mode (after Certificate) it lists the learner's own must-dos: TRAQOM is mandatory (learners must complete it to be marked COMPETENT), download the assessment questions as a Word document, and answer every question then submit the assessment online, watching for the green "submitted successfully" notification; each item jumps to the TRAQOM Survey or Assessment section
  • Assessment Summary Record e-signing — the ASR (Virtual Class) card on the course page is signed inside the LMS instead of printed and scanned. The learner records their name, NRIC and a drawn signature once (Learner Signature dialog, /api/learner/signature) and clicks Sign Assessment Summary Record; the trainer reuses their assessor block (the same one the grading roster stamps) and signs each learner from a roster of the class — or Sign All — with per-learner Learner/Trainer signed dates and a link to the PDF. Every signature regenerates the PDF from the course's ASR template (assessment_summary_record_url — PDF, Google Doc or Word) with all blocks signed so far: the Candidate and Assessor name, NRIC (last 3 digits + letter, as the form asks), signature and date are drawn into the sign-off table on the last page, whose cells are located from the label text so any course's template works, and the file is placed in the learner's Assessment Records folder in Drive (replaced in place, so the link stays stable). Unsigning removes that party's block and regenerates; a manually signed scan can still be uploaded as a fallback. A Watch Demo link on the trainer's card opens a 100-second narrated, captioned walkthrough of the online sign-off (public/videos/asr-sign-off-demo.mp4 + .vtt, NRIC and learner details masked) — the digital sign-off applies to virtual classes only; physical classes keep the printed record. Tables: learner_signature, assessment_summary_record (database/migrations/add_assessment_summary_signature.sql)
  • Collapsed course page in Trainer mode — the Lesson topics (Labs, Topic 1…n) and the Assessment Summary Record (Virtual Class) card mount collapsed for trainers, so the course page opens as a compact list of headings that expand on click; learner, admin, developer and training-provider views keep them open by default. The Announcements and Google Meet / Zoom / Teams cards start collapsed for every role
  • Announcements & class materials — sits right after E-Attendance on the class page (and in the left menu), collapsed by default with a post count in its header. An assigned trainer of the class (or staff) gets an always-open composer: type a message, paste a web link (the https:// is added if missing), attach a document with the button or by drag-and-drop (max 50 MB), then Post (or Cmd/Ctrl+Enter). Web addresses in the message become clickable, so a reference list fits in one post. Posts list in chronological order (oldest first, toggle to newest first) with the date, time and poster on each, and every learner of that class sees them on their LMS class page. Posting and deleting are limited to staff and the class's assigned trainers (requireCourseRunTrainer); attachments are stored under public/uploads/announcements/ and served through the /uploads/* → /api/uploads rewrite
  • Course Feedback & TRAQOM Survey card — the class page (Learner and Trainer modes) shows two QR codes side by side: the SSG TRAQOM survey (static link) and the in-house Course Feedback form at /feedback/<course_run_uuid>, which pre-fills the course title, code, trainer name and class start/end dates from that run. The QR is derived from the loaded class detail (the same source as the title and dates on the page), so it always points at the class in session; the Attendance and Grading pages' Back to Class reload the whole class rather than only swapping the selection. A Training Provider can override the QR with an external feedback URL from the Training Provider profile. A single zoom icon in the section header (or a click on either QR) opens both codes side by side in a full-window view sized to the screen, so a trainer can project them to the class without browser zoom; the X icon, Esc or a click on the backdrop returns to the class page.
  • Training Hours — Trainer training hours tracking
  • Past Attendance / Past Assessment — Historical records
  • Lesson Delivery Guide / Assessment Guide
  • Trainer Payout History — View past payouts and tier breakdown

Trainer Tools

Admin Features

  • Admin Dashboard — KPI cards: Ongoing Classes, Upcoming Classes, Completed Classes, Assigned Trainers (Local), Missing Trainers (Local/TPG)
  • Workflow Guides — Interactive documentation organized by category (Training, Admin, Finance) with 10+ workflow guides including SSG Process Steps
  • Support Tickets — Ticket management system for handling learner, trainer, and user support requests
  • Sign-in Requests — Login-page self-registrations (auto-enrolled, needs attention, resolved, dismissed); correct a Course Run ID and Enrol & notify in one click
  • Calendar & Scheduler — View and manage training calendar (top-level View Calendar), search by course code/title/name/date; WSQ class-days are live-matched against Google Calendar and any day with no matching event is highlighted (red dashed ⚠) with a "not on GCal" filter
  • Course Management — View courses, trainers, funding validity. The course card view shows the funding validity end date colour-coded by urgency (green: 3+ months away, orange: under 3 months, red: expired), an Active/Expired Status row, and an "(Expired)" prefix on the title of any course whose funding has lapsed
  • Class Management — View learners, manage upcoming/ongoing/completed classes, assign trainers and learners, search past learners
  • Direct Application — Upload, view, and manage direct applications
  • TPG Management (SkillsFuture)
    • Course Run: Create, search, view, upload, delete course runs; manage sessions and attendance
    • Enrolment: Enroll learners, bulk upload, search/view/update/cancel enrolments, manage fees
    • Assessment: Submit, update, search, and view SSG assessments
    • Grant: Search grants and view grant status
  • Certificate Management — Create, delete, and send certificates (SG and GH variants) with configurable CC email lists
  • Reference Links — Configurable external links (Master List, TMS, FMS, MMS, TPMS)
  • n8n Links — Quick access to n8n workflow hosts
  • Useful Links — Quick access to Magento backend and other tools

Finance Features

  • Financial Dashboard — Grants and Claims overview with KPIs: Total Grants, Total Estimated, Total Approved
  • FMS (n8n) Automation — View All Course Runs, Process Enrolments, Manual Enrolment, Create Enrolments For Error Status, Employer Enrolment, Append Cancelled Class Trainees, Update Assessment, Process Grants, Update Grant Status & Total Grant, Grant Query, Direct Application, Check Duplicates for DA, Update Claim ID
  • TPG Management — Full SSG course run, enrolment, session, attendance, assessment, grant, and claim management
  • Claim Management — Search, view, upload, and cancel SSG claims; track outstanding claims
  • QuickBooks Integration — OAuth2 proxy supporting query, create, send, delete, void, pdf, read actions on estimates, invoices, and payments
  • Bizfile Integration — Company profile, key dates, address, SSIC, capital, shareholders lookup
  • Workflow Guides — Billing History, Proforma Invoice, Personal Invoice, Company Invoice, Receipt workflows
  • Useful Links — Quickbooks, Vendors@gov, GeBiz, Bizfile quick access

Invoice Workflows

  • Personal Invoice Workflow
    1. Create enrollment and grant on SSG
    2. Retrieve Enrolment ID and Grant ID from SSG
    3. Trigger QuickBooks to generate invoice and send to learner
    4. Download PDF to Google Drive
    5. Record in learner's billing history
  • Company Invoice Workflow
    1. Send Excel template to company contact
    2. Receive completed Excel file
    3. Upload Excel file to TMS
    4. Create enrollment and grant for each record on SSG
    5. Retrieve Enrolment ID and Grant ID from SSG
    6. Trigger QuickBooks consolidated invoice and send to company contact
    7. Download PDF to Google Drive
    8. Record in company contact's billing history

Developer Features

  • Course content creation and editing
  • Course Image Generator — AI course banner generation (shared with Admin, backed by Cloudflare R2)
  • Course Funding Validity — WSQ/CASL/IBF course validity dates with funding-type totals (WSQ, CASL and IBF each tiled separately), cumulative expiry-window dashboards (3 months / 2 months / 1 month / 1 week), renewal tracking and whitelist management (shared with Admin). Per course the list shows the Earliest Renewal Date (computed: 3 months before the validity end date, red once reachable) plus stored Actual Renew Date and Renewal Application No, editable inline via the row's Edit button. Excel round-trip: full-list export, a pre-filled upload template, and bulk update by uploading the edited file (matched by course ref code; blank cells leave values unchanged) — the renewal date and application number round-trip too. Two pending-renewal watchlists: Expiring Within 1 Month (the set the daily reminder email sends) and Expiring Within 3 Months (every course already inside its renewal window)
  • Expired Course List — courses whose funding validity end date has already passed (any course type, including those re-typed to Non-WSQ when funding lapsed), oldest first, with course code, expiry date (and days ago) and renew status; searchable and filterable by renew status, with renewed/not-renewed totals tiled
  • Assessment authoring with multiple assessment methods (Written, Practical, Case Study, Role Play, Oral Questioning, Project, Assignment)
  • Learning unit and subtopic management
  • Course material uploads (lesson plans, slides, guides)
  • Courseware link cards — in course view mode, courseware documents (lesson plan, guides, slides, brochure, SkillsFuture link, etc.) render as click-to-open cards; empty links are hidden
  • "On This Page" section menu — course view mode shows a jump menu (Learning Outcomes, Courseware, Lesson, Assessment Links, Approved Trainers) that smooth-scrolls to the matching card; topics mount collapsed for a compact overview
  • SEO Metadata Generator — AI-powered SEO metadata generation for WSQ and non-WSQ courses using Claude Agent SDK
    • WSQ: Meta title (with WSQ prefix), keywords, description with 70% funding subsidy, course description, and 20 job roles
    • Non-WSQ: Region-specific meta titles (Singapore, Malaysia with HRD Corp, International), keywords, description, course description, and 20 job roles

Training Provider Features

  • Training Dashboard — Organization overview and statistics
  • Workflow Guides — Interactive workflow documentation organized by category (Training, Admin, Finance)
  • Course Management — Full course catalog and course run management
  • User Management — Add, remove, and manage users; assign/update roles; enable/disable accounts
  • Role Management — Manage admin-level accounts and role assignments
  • Finance Management — Access to all finance features (grants, claims, QuickBooks, billing)
  • Company Setting
    • Company profile (name, UEN, address, logo, color scheme)
    • Contact person management
    • Integrations — Virtual class meeting provider selection (Google Meet, Zoom, Microsoft Teams), course-run meeting links override the company default, Google (Calendar, OAuth, Certificate Folder), Zoom OAuth meeting generation, Microsoft Teams meeting generation, n8n, Magento, MailerLite, AI Agent (external WhatsApp/Telegram chat links — separate ops and trainer groups), Reference Links
    • SSG Authentication — SSG certificate, private key, and encryption key (with support for App1, App3, App4 credentials)
    • LLM Credentials — Anthropic, OpenAI, Gemini, MiniMax, Kimi, DeepSeek with default and fallback provider selection
    • Security Settings — OTP login, default OTP, force first password change, default password, sensitive data masking
    • Admin Settings — Auto-send pro-forma invoices, confirmation emails, invoices on grant success, receipts, certificates, thank-you emails
    • Gaming Settings — Leaderboard and points system toggles
    • Funding Settings — Normal/enhanced funding rates, GST configuration
    • Document Templates — Proforma invoice, invoice, receipt, and certificate templates (text IDs or file uploads)
  • SSG API Summary — Full summary of SSG API endpoints and their status
  • API Endpoints — Built-in interactive API documentation covering all 200+ endpoints
  • Task Scheduler — Schedule and manage automated tasks (auto-create certificates, auto-send emails, sync operations)
  • Webhooks — Configure and monitor webhooks with webhook logs for debugging
  • Email Templates — OTP, Certificate, Feedback, Password Reset, Learner Welcome (self-registration), Trainer Invitation, Trainer Accept/Decline, Final Class Confirm, Class Confirm, Privacy Policy, Acceptable Use Policy
  • Useful Links — SSG API Portal, SSG Developer quick access

Learner Support Features

  • Self-service sign-in fix — On the login page, a learner who can't get their OTP or can't see their class enters Name, Email and Course Run ID. The system creates their account if needed, enrols them, emails a welcome (OTP-first, with a must-change backup password for new accounts) and sends the OTP straight away. Cases it can't fix (unknown/expired run, inactive account, withdrawn enrolment) go to Admin → Support Tickets → Sign-in Requests with one-click Enrol & notify
  • Help & Support — Built-in support ticket system where learners can raise new tickets for course, connectivity, session, or payment issues
  • Contact Information — Direct access to company address, hotline (+65 6100 0613), support email (enquiry@tertiaryinfotech.com), and opening hours

AI-Powered Features

  • AI Chatbot (external agent) — The operational assistant is not built into the platform. The app hands off to an external agent — OpenClaw (Kael/Jarvis/Orion) or Hermes — reached over WhatsApp or Telegram, which acts on the platform through the authenticated /api/external/* HTTPS API. Keeping the agent outside the app means it upgrades independently of a deploy and never needs direct database access. See the chat launcher below for how requests are composed.
  • SEO Metadata Generator — Claude-powered SEO content generation for WSQ and non-WSQ courses
  • External Agent Chat Launcher — Floating WhatsApp/Telegram widget that hands off to an external agent such as OpenClaw/Kael or Hermes. Two role-scoped variants, each pointing at its own group:
    • Ops widget (green — Admin, Finance, Training Provider, Developer, Payroll): 5 starter suggestions, with search or "Browse all" revealing the full catalogue of 40 fill-in-the-blank templates across Trainers, Learners, Classes, Sessions, SSG/TPGateway, Finance and Reports
    • Trainer widget (blue — Trainer role only): a deliberately narrow set of 4 requests — cannot find my class, assign me to this class, send me the Google Meet link, send me the e-attendance link. Classes are identified by course title + start date, never by a course run ID, and the trainer catalogue is a separate array rather than a filter, so no admin action (schedules, run IDs, enrolments, SSG, finance) is reachable from the trainer widget
    • Field names mirror the agent's tools and the /api/external/* parameters, so a completed template maps onto a real operation
    • Selecting one opens an editable message bubble; the message is copied to the clipboard and the chat opens. WhatsApp only accepts a pre-filled body on wa.me one-to-one links — group invite links cannot be pre-filled, so for a group the user pastes the copied message
    • Destinations are set per tenant under Company Settings → Integrations → AI Agent (whatsapp_chat_url and trainer_whatsapp_chat_url); each widget auto-detects WhatsApp vs Telegram and hides itself when its link is blank
  • GenAI Authoring — AI content generation for course development
  • Quiz Generator — AI-generated quizzes based on course content

Automation Features

  • Task Scheduler — Schedule and run automated tasks: Auto Create Certificates, Auto Send Course Confirmation, Auto Create Learners, Auto Create Assessment Records, Auto Send Class Evaluation to Trainers (compiles learner feedback for ended classes and emails trainers daily at 6:30 PM SGT), etc.
  • Webhook System — Configure webhooks with full logging for external integrations
  • Trainer Invitation Workflow — Weekly cycle for classes without a trainer: Monday sweep invites the next trainer in the course's approved-list order, Thursday sends a reminder (same accept/decline links) to anyone still pending, and the following Monday expires no-response invitations and moves to the next trainer. On accept the class is Confirmed, the trainer is synced to SSG/TPG, added to the Google Calendar event (real invite, Course Run ID stamped in the description, Google Meet auto-generated for Virtual classes), and sent a confirmation email with the class details. Declines auto-cascade immediately; admins can pause per class or reset the cycle back to the first approved trainer
  • Certificate Workflow — Auto-generate certificates after class completion, upload to Google Drive, and email to learners
  • Auto-Create Learners — Automatically creates learner accounts for course runs starting today (via n8n)
  • Auto-Assign Trainers — Bot-driven trainer assignment with fallback logic
  • Course Run Date Sync — Automatic synchronization of dates with SSG
  • Enrolment Backfill — Batch sync of enrollment data from SSG
  • Trainer WhatsApp Notifications — Rate-gated queue consumed by the external WhatsApp Business agent: invitation/reminder nudges (max 5/day, 10:00–13:00 SGT) and upcoming-class reminders composed from the LMS record 3 days ahead (max 7/day, 13:00–17:00 SGT). An accepted trainer RSVP excludes every reminder with the same TGS and class date, and acceptance is checked again immediately before release. The channels use non-overlapping windows, all messages are ≥15 minutes apart, and a read-only confirmed-classes feed supplies authoritative class data.
  • Funding Renewal Reminder — Daily 8:00 AM SGT email listing funded courses whose funding validity has expired or expires within 1 month and are not yet marked renewed on the Course Funding Validity page (recipients via FUNDING_REMINDER_RECIPIENTS; nothing pending → no email)
  • Auto-Send Emails — Configurable auto-send for proforma invoices, confirmation emails, invoices, receipts, certificates, and thank-you emails

Singapore-Specific Features

  • SSG API Integration - Full SkillsFuture Singapore API support
    • Course run management (create, view, edit, delete, publish)
    • Session and attendance management
    • Enrolment synchronization
    • Assessment submission and updates
    • Grant search and status tracking
    • SSG data encryption/decryption
  • WSQ/CASL/IBF Course Support - Support for WSQ, CASL, IBF, and non-funded courses; CASL courses behave identically to WSQ across enrollment, grants, funding validity and SSG/TPGateway operations
  • Funding Calculations - Normal (50%/70%), enhanced (MCES), and GST calculations
  • Singapore ID Validation - NRIC/FIN and UEN verification

User Roles

Role Description Key Permissions
Learner Course participants Enroll in courses, track progress, submit assessments, download certificates, raise support tickets
Trainer Course instructors View, publish assessments for, and grade only their assigned classes; take attendance; access Ed Tools and GenAI tools. Can also hold Learner (dual-role flag) to attend courses
Developer Course developers Create and edit course content, assessments, learning materials, SEO metadata generation
Admin System administrators Full class management, trainer/learner assignment, TPG management, certificate generation, ticket system, workflow guides
Finance Financial operations Financial dashboard, FMS automation, QuickBooks, claims, grants, personal/company invoice workflows, Bizfile lookup
Payroll Trainer payout operations Manage payout tiers, per-class trainer payouts (WSQ auto-materialised + hand-entered non-WSQ classes), QuickBooks bill numbers, approval workflow, payout history, and a compact per-class Review Score column showing the class average of learners' overall course-feedback scores (each the mean of the 1–5 star questions; 2 and below in red, above in green) with a link that pops up each learner's score and flags how many scored 2 or below, to apply the no-payout-for-low-rating rule (gated by payroll_enabled feature flag)
Training Provider Organization admins Company settings, user/role management, finance management, scheduler, webhooks, email templates, API documentation

Technology Stack

Category Technology
Framework Next.js 16 (Pages Router, TypeScript)
Frontend React 18, Tailwind CSS
Database PostgreSQL 17
Authentication bcryptjs password hashing + OTP flow; server-side session tokens (hashed at rest in user_session) with role-based authorization on every API route
AI Integration Claude Agent SDK, Anthropic SDK, Google Generative AI (Gemini), OpenAI, MiniMax, Kimi, DeepSeek
Finance Integration QuickBooks Online (OAuth2), Bizfile API
File Uploads Multer, Google Drive API
Document Conversion LibreOffice 26.2 headless (official build, installed in the Dockerfile) + libheif heif-convert — Mac Pages assessment submissions → PDF for assessor signing
Email Nodemailer (Gmail OAuth2)
Password Hashing bcryptjs
Icons Lucide React
Database Client pg (node-postgres)
Container Docker, Docker Compose
Deployment Coolify (self-hosted)
Automation n8n workflows, Task Scheduler, Webhooks
Email Marketing MailerLite (daily learner-email subscriber sync)
E-commerce Magento integration

API Documentation

All API routes require authentication. Every endpoint below is guarded except a small public allowlist (login/OTP, OAuth callbacks, static asset serving, the tokenised webhook route and the public feedback form). See API Authentication for how to authenticate.

External APIs (x-api-key authenticated)

POST /api/external/assign-trainer         # Assign trainer to course run
POST /api/external/unassign-trainer       # Remove trainer from course run
GET  /api/external/get-course-run         # Get course run details
GET  /api/external/list-course-runs       # List course runs (with filters)
GET  /api/external/list-trainers          # List trainers (with filters)
POST /api/external/auto-create-learners   # Auto-create learner accounts
POST /api/external/sync-course-run-dates  # Sync dates with SSG
GET  /api/external/backfill-enrollments   # Preview enrollment backfill
POST /api/external/backfill-enrollments   # Execute enrollment backfill
POST /api/external/wsq-submit-runs        # Create specific course runs in SSG (previews unless confirm:true)
GET  /api/external/wsq-schedule-gap       # What course runs are missing, and why
GET  /api/external/wsq-sync-status        # Outcome of the last schedule sync
POST /api/external/create-course-run      # BROKEN (27 Aug 2026) — SSG rejects the payload shape. Use wsq-submit-runs.

Authentication

POST /api/auth/login              # Login (password or OTP) → returns a session token
POST /api/auth/send-otp           # Send OTP to email
POST /api/auth/self-register      # Public: learner self-enrols with Name, Email + Course Run ID (+ optional mobile)
GET  /api/auth/verify             # Validate the current session token
POST /api/auth/logout             # Revoke the current session token
PUT  /api/auth/update-password    # Update password (own account, or admin for any)

API Authentication

Two ways to authenticate, both required as of the August 2026 lockdown:

1. User sessions (browser/UI). POST /api/auth/login returns an opaque lms_… session token. Send it as a bearer token on every request:

Authorization: Bearer lms_<token>

Tokens are server-side records — only a SHA-256 hash is stored, they expire after 30 days, and they are revoked on logout, password change and admin reset. The browser app does this automatically via the global fetch interceptor in lib/clientAuthFetch.ts, so UI code needs no per-call auth handling.

2. Service key (machines). Schedulers, agents and other back-end systems send a server-side API key instead, valid on any route:

x-api-key: $EXTERNAL_API_KEY_FOR_CLAWDBOT

Authorization is role-based (Admin, Training Provider, Finance, Payroll, Trainer, Developer, Learner); a request with a valid session but the wrong role gets 403. Unauthenticated requests get 401.

scripts/check-api-auth.js runs as part of npm run lint and fails the build if a new route is added without authentication — new public routes must be added to its allowlist deliberately.

Courses & Course Runs

GET  /api/courses/list            # List all courses
GET  /api/courses/detail          # Get course details
POST /api/courses/create-course   # Create course (multipart)
POST /api/admin/save-course-run   # Create/update course run
GET  /api/admin/search-course-runs # Search course runs
POST /api/admin/import-course-run # Import from SSG
GET  /api/admin/course-code-history # Every course reference code a course has carried
GET  /api/admin/course-change-log   # Chronological audit of course field changes
PUT  /api/admin/update-course-title # Bulk rename by course_code (title only; supports dryRun)

Course code history. SSG funding renewal issues a new course reference code for what is still the same course, so a course accumulates codes over time. course_code_history records every one of them, which keeps past records retrievable by the code they were created under. Use lib/courseCode.ts — resolveCourseIdByCode() accepts any code, current or superseded, and returns the single course it belongs to; resolving with a bare WHERE course_code = $1 sees only the pre-renewal code and will miss.

Two code columns on course. course_code is the ORIGINAL registration code and never changes — past enrolments and SSG records were created against it. new_course_code is the code currently in force (kept equal to the original until a renewal issues a new one, so it is always populated). The course editor edits them as separate fields; a blank submitted value never clears the stored one. Both codes are shown as separate fields in view mode as well as edit mode — including when the course was never renewed and the two match — so the read-only pane and the edit form present the same set of fields.

Records show the code of their own era. The SQL function course_code_at(course_id, at) resolves the code in force at any moment. Class and enrolment listings use it with the record's creation date, so an enrolment submitted under a superseded code keeps displaying that code while records created after the renewal show the new one — all attached to the same course throughout.

Course Change Control. course_change_log is an append-only audit of key course field changes (title, both codes, type, hours, fee, funding validity, TSC title/code) as old → new rows with timestamp and author, written inside the course-save transaction (lib/courseChangeLog.ts). The Developer role's Course Change Control page renders it as a chronological table filterable by course title.

Enrolments

POST /api/enrolment/create        # Create enrolment
GET  /api/enrolment/search        # Search enrolments
GET  /api/enrolment/view          # View enrolment details
PUT  /api/enrolment/update        # Update enrolment
POST /api/enrolment/cancel        # Cancel enrolment
POST /api/enrolments/bulk-create  # Bulk create enrolments

Assessments & Grading

PUT  /api/assessments/publish     # Publish/unpublish assessment (staff or assigned trainer)
PUT  /api/assessments/publish-link # Publish/unpublish an assessment method link (staff or assigned trainer)
POST /api/submissions/submit      # Submit assessment
POST /api/grading/update-grading  # Grade submission
POST /api/trainer/grade-student   # Toggle learner competency (single enrolmentId or enrolmentIds[] for a merged learner)
POST /api/trainer/traqom-status   # Tick TRAQOM survey completion (single or batch)
GET/POST /api/trainer/assessor-signature  # Trainer's assessor block (name, NRIC, date, drawn signature)
POST /api/trainer/sign-assessments        # Stamp / unstamp the assessor block on a learner's submitted files (learnerUserIds[] covers a merged learner's accounts)
POST /api/assessments/ssg-create  # Create SSG assessment
PUT  /api/assessments/ssg-update  # Update SSG assessment

User Management

POST /api/admin/create-learner-account       # Create learner
POST /api/admin/add-trainer                  # Add trainer
POST /api/admin/assign-all-roles             # Assign roles to user
POST /api/training-provider/update-user-roles # Update user roles
POST /api/training-provider/delete-user      # Disable user
POST /api/admin/bulk-upload-courses          # Bulk import courses
POST /api/admin/bulk-upload-trainers         # Bulk import trainers

Full interactive API documentation with example requests/responses is available in-app under Training Provider > API Endpoints.

Project Structure

ai-lms-tms/
├── components/                 # React components
│   ├── admin/                  # Admin dashboard & management
│   ├── trainer/                # Trainer views & tools
│   ├── training-provider/      # Training provider management
│   ├── ssg/                    # SSG API integration views
│   ├── ui/                     # Reusable UI components
│   ├── common/                 # Shared components
│   ├── LoginScreen.tsx         # Two-column, one-screen login: learner self-registration + sign-in (phones: sign-in first)
│   ├── AiChatbot.tsx           # Floating WhatsApp/Telegram chat launcher + template picker
│   ├── chatTemplates.ts        # 44 TMS request templates for the chat launcher
│   ├── CourseDetail.tsx         # Course detail view
│   ├── GradingView.tsx         # Assessment grading
│   └── ...
│
├── pages/                      # Next.js pages & API routes
│   ├── api/
│   │   ├── admin/              # Admin operations (~215 endpoints)
│   │   ├── external/           # External/machine APIs (~79 endpoints)
│   │   ├── auth/               # Authentication (9 endpoints)
│   │   ├── courses/            # Course management (25 endpoints)
│   │   ├── enrolment/          # Enrolment operations
│   │   ├── assessments/        # Assessment CRUD & SSG
│   │   ├── grading/            # Grading system
│   │   ├── trainer/            # Trainer operations
│   │   ├── training-provider/  # TP management (~38 endpoints)
│   │   ├── profile/            # User profiles
│   │   ├── ssg/                # SSG API proxies
│   │   ├── ai/                 # AI endpoints
│   │   ├── upload/             # File uploads
│   │   └── grants/             # Grant management
│   ├── _app.tsx
│   └── index.tsx               # Main SPA entry
│
├── lib/                        # Core libraries
│   ├── assessment/             # Assessor sign-off stamping (PDF via pdfjs/pdf-lib, DOCX via PizZip) + Drive round trip
│   ├── auth/                   # API authentication & authorization
│   │   ├── withAuth.ts         # Route guard wrapper (roles + service key)
│   │   ├── requireRole.ts      # Session/role resolution
│   │   ├── session.ts          # Session token issue/revoke (hashed at rest)
│   │   ├── courseRunAccess.ts  # Trainer-mode class authorization (assigned trainer / staff)
│   │   └── serviceKey.ts       # Machine (x-api-key) authentication
│   ├── services/               # Business logic services
│   │   ├── authService.ts      # Client-side auth state
│   │   ├── certificateService.ts # Certificate generation
│   │   ├── geminiService.ts    # AI integration
│   │   └── ...
│   ├── clientAuthFetch.ts      # Browser fetch interceptor (bearer + 401 logout)
│   ├── ssg/                    # SSG API utilities
│   ├── config.ts               # Environment configuration
│   └── db.ts                   # PostgreSQL connection pool
│
├── contexts/                   # React context (LmsContext)
├── types/                      # TypeScript definitions
├── database/                   # SQL schema & migrations
├── scripts/                    # Utility scripts
├── styles/                     # Global CSS
└── public/                     # Static assets & uploads

Database Schema

Entity Relationship Overview

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│    app_user     │────>│  user_role_map  │     │ training_provider│
│  (Core User)    │     │   (Roles)       │     │   (Tenant)      │
└────────┬────────┘     └─────────────────┘     └────────┬────────┘
         │                                               │
    ┌────┴────┬────────────┬────────────┐               │
    v         v            v            v               │
┌────────┐┌────────┐┌───────────┐┌───────────┐         │
│learner ││trainer ││ developer ││   admin   │         │
│_profile││_profile││ _profile  ││ _profile  │         │
└───┬────┘└───┬────┘└───────────┘└───────────┘         │
    │         │                                         │
    v         v                                         │
┌─────────────────┐     ┌─────────────────┐            │
│   enrollment    │────>│    course_run   │<───────────┘
│ (User<>Course)  │     │  (Scheduled)    │
└────────┬────────┘     └────────┬────────┘
         │                       │
         │              ┌────────┴────────┐
         │              v                 v
         │       ┌─────────────┐   ┌─────────────┐
         │       │   course    │   │  assessment │
         │       │ (Template)  │   │   (Tests)   │
         │       └──────┬──────┘   └──────┬──────┘
         │              │                 │
         │       ┌──────┴──────┐         │
         │       v             v         │
         │ ┌──────────┐ ┌──────────┐     │
         │ │ learning │ │  subtopic│     │
         │ │  _unit   │ │          │     │
         │ └──────────┘ └──────────┘     │
         │                               │
         └───────────────────────────────┘
                        │
                 ┌──────┴──────┐
                 v             v
          ┌───────────┐ ┌───────────┐
          │submission │ │assessment │
          │  (Files)  │ │  _grade   │
          └───────────┘ └───────────┘

Core Tables

Table Description
app_user Core user accounts with email, password hash, and profile
user_role_map Maps users to roles (Learner, Trainer, Admin, Developer, Finance, Payroll, Training Provider)
training_provider Organization settings, templates, integrations, and security config
training_provider_member User membership in training provider organizations
course Course templates with metadata, materials, funding info, and assessment methods
course_code_history Every course reference code a course has carried, with validity windows — funding renewal issues a new code for the same course
course_title_history Every title a course has carried — a renamed course stays findable by its former titles
course_change_log Append-only audit of key course field changes (old → new, when, by whom) behind the Course Change Control page
course_run Scheduled course instances with dates, trainers, and digital attendance
enrollment Learner enrollments with progress, payment, and sponsorship tracking
assessment Course assessments (Written, Practical, Case Study, Role Play, etc.)
submission Learner assessment submissions
learning_unit Course chapters/modules
subtopic Individual learning topics within units

Getting Started

Prerequisites

  • Node.js 18+
  • PostgreSQL 17+
  • npm or yarn
  • (optional) LibreOffice — only needed locally to sign Mac Pages (.pages) submissions; the binary is auto-detected (set SOFFICE_PATH to override). The Docker image already includes it.

Installation

  1. Clone the repository

    git clone https://github.com/alfredang/AI-LMS-TMS.git
    cd AI-LMS-TMS
  2. Install dependencies

    npm install
  3. Configure environment variables

    cp .env.example .env.local
    # Edit .env.local with your configuration
  4. Set up the database

    # Run schema migration
    psql -f database/01-schema.sql
    
    # Seed initial data (optional)
    psql -f database/02-data-clean-fixed.sql
  5. Start development server

    npm run dev

    The application will be available at http://localhost:3000.

    npm run dev disables the startup cron scheduler by setting ENABLE_APP_SCHEDULER=false. This prevents local development servers from automatically executing production-like scheduled jobs when developers are connected to shared, production, or production-clone databases.

    To test scheduled jobs locally, run the scheduler-enabled dev script instead:

    npm run dev:scheduler

    Only one app instance connected to a shared database should run with ENABLE_APP_SCHEDULER=true. Manual "Run Now" actions in the admin UI and direct API-triggered jobs can still run even when the startup scheduler is disabled.

Environment Variables

Create a .env.local file with the following variables:

# Database Connection
DATABASE_URL=postgresql://user:password@host:port/database

# Application URLs
NEXT_PUBLIC_BASE_URL=http://localhost:3000
BASE_URL=http://localhost:3000

# Google Gemini AI
NEXT_PUBLIC_GOOGLE_GEMINI_API_KEY=your-gemini-api-key

# SSG API Integration
SSG_API_BASE_URL=https://api.ssg-wsg.sg

# External / machine API Authentication
# Required for schedulers, agents and back-end systems to call the API.
# Sent as the `x-api-key` header; must be a server-side secret (never NEXT_PUBLIC_*).
EXTERNAL_API_KEY_FOR_CLAWDBOT=your-external-api-key
SCHEDULER_SECRET=your-scheduler-secret
DIRECT_APPLICATION_EMAIL_INGEST_TOKEN=your-direct-application-email-ingest-token

# Funding renewal reminder — comma-separated recipient emails (tenants without it skip the job)
FUNDING_REMINDER_RECIPIENTS=ops@example.com,finance@example.com

Deployment

Coolify (Multi-Tenant)

The same codebase is deployed across multiple tenants on Coolify (self-hosted PaaS — not Vercel, not Nixpacks). Pushes to main trigger automatic redeployment for each tenant.

Tenant Build Pack URL
Tertiary Infotech Academy Dockerfile https://lms-tms.tertiaryinfotech.com
Chariot / Intellisoft Docker Compose (per-tenant)

Tenant-specific behavior is gated via DB config, env vars, or feature flags (e.g. payroll_enabled in lib/payroll/featureFlag.ts) — never by forking shared code.

Local Docker

A docker-compose.yml is provided for local development (Next.js app + Postgres 17, ports 3003 / 6434).

# Build for production
npm run build

# Start production server
npm start

Documentation

Full documentation is available at https://alfredang.github.io/AI-LMS-TMS/

Role Guide
Learner Learner User Guide
Trainer Trainer User Guide
Developer Developer User Guide
Admin Admin User Guide
Finance Finance User Guide
Training Provider Training Provider Guide

License

This project is proprietary software developed by Tertiary Infotech Pte Ltd. All rights reserved.


Developed by Tertiary Infotech Pte Ltd
Empowering Training Excellence in Singapore

Native iOS companion

The learner/trainer-only Tertiary Learning iPhone and iPad app uses the scoped mobile API, email OTP, courseware, a class calendar and APNs class reminders. See iOS backend setup.

Native Android learner and trainer client

The Android companion supports email OTP, scoped courseware, class calendars, and optional 3-day/1-day Firebase reminders. See Android API and deployment notes for configuration and validation.

About

Self-hosted AI-powered Learning & Training Management System for Singapore's SkillsFuture ecosystem. Features SSG/TPGateway API integration, multi-role access, OTP login, QuickBooks finance workflows, and AI agent automation.

Topics

Resources

Stars

19 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages