Commit Graph

7 Commits

Author SHA1 Message Date
Harwinder Sekhon 090d7fa94c Seed the vendor catalogue into SQL and add an optional AI course advisor
Course metadata: a scan now gives every course a course_meta row from the
downloader's sidecar JSON when the folder has one, else from catalog_courses
matched by the folder name the downloader would create (folderKeyFor, a port
of ine.py's sanitize_name). tools/build-catalog-seed.php turns the INE export
into seeds/ine_catalog.sql, an idempotent patch that creates its own table and
upserts by id, so it can be applied to the live server before or after the
code and re-run freely. Course pages show the blurb, difficulty, vendor and
tags; search matches tags and blurbs; Settings reports how each course got its
details. Against the live library 163 of 179 courses match.

Course advisor, off by default: api/advisor.php answers "which course should I
take to learn X / what comes before Y" with courses from the library, in
order, plus vendor courses not yet downloaded. includes/ai_client.php is a
small curl wrapper for any OpenAI-format chat endpoint (DeepSeek V4 Flash by
default, no composer); the prompt is a byte-stable catalogue of the library so
the provider's prefix cache covers it. Every course id the model returns is
validated server-side, a bad JSON reply gets one retry then a plain-text
fallback, and a per-user daily cap with token counts lives in
advisor_requests. With AI_ENABLED false the app makes no outbound requests
and the box does not appear; users can also hide it per browser.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012enquL2RZdjy3D5AcKP5yx
2026-09-08 18:36:43 -04:00
root 6a09ae3833 Detect course roots per branch and add a category layer above them
The scanner no longer assumes courses sit at a fixed depth. It walks
each branch and marks a folder is_course when it holds media directly
or its children read as chapters (01_, 1. , Module 2, Week 3);
everything above that is a category, and every folder still gets a
course_id pointing at its nearest course for cheap rollups. An empty
.course/.category marker file overrides the heuristic per folder.

Also:
- Roll up progress for the whole library in two grouped queries
  instead of one prefix-matched query per course/folder card.
- Store folders/videos/quizzes paths as utf8mb4_bin so folders that
  differ only in case (CCNP Notes vs CCNP NOtes) aren't merged by a
  case-insensitive UNIQUE key.
- migrateLibrarySchema() brings an older install's tables up to date
  at scan time, so there's no separate migration step.
- Library UI gains a categories view alongside shelves/catalog; the
  continue-watching strip's play button now seeks to the saved
  position on open.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FfMPJK7TQV18JhFv6yTKev
2026-09-08 18:51:04 +00:00
Harwinder Sekhon 17a401c313 Harden auth, add subtitles, vendor all assets locally
- Session cookies are HttpOnly + SameSite=Lax (Secure on HTTPS), with
  strict session-ID mode; the CSRF token from GET api/auth.php must now
  accompany every state-changing request (X-CSRF-Token header, or a csrf
  body field for the sendBeacon progress save), rotated at login.
- Failed sign-ins are throttled per username and IP via login_attempts
  (created lazily, so existing installs need no migration): 5 failures a
  username / 20 an IP inside 15 minutes answer 429 with Retry-After.
- A video's sibling .srt now appears as a CC button in the player;
  captions.php converts it to WebVTT (comma timestamps, legacy encodings,
  and the already-VTT files with out-of-spec 6-digit fractions).
- Plyr and Inter ship in assets/vendor/ instead of coming from CDNs, and
  anime.js is gone along with all scripted animations and the motion
  setting — the app now runs with no internet access at all.
- .htaccess denies direct access to videos/, includes/, config.php and
  schema.sql, so video URLs cannot bypass login on Apache.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SePf61iazhCLJBefdRLBDm
2026-07-30 21:14:07 -04:00
Harwinder Sekhon ff9a351091 Remove course thumbnails and the ffmpeg dependency
Course cards go back to the colour-block art that previously sat under the
captured frame, so the UI is unchanged apart from losing the photo.

- Drop thumb.php, generateThumbnails(), captureThumbnail() and
  ensureThumbnailColumns(), plus the first-video tracking the scanner only
  kept to pick a frame source.
- Drop the ffprobe fallback in probeDuration(); the pure-PHP mvhd parse is
  now the only scan-time probe, with the player's report on first play as
  the remaining fallback.
- Drop THUMBNAILS_DIR/THUMBNAIL_SEEK_PERCENT from config, the
  thumbnail_* columns from schema.sql, the thumbnail counters from scan
  output, .thumb img from the stylesheet, and the matching README parts.

Existing databases keep two now-unused folders.thumbnail_* columns; they
are inert and can be dropped by hand if wanted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oyi9DMdhALrGz989MNNZJ
2026-07-27 14:06:06 -04:00
Harwinder Sekhon 31b01a1a18 UI improved 2026-07-27 13:58:47 -04:00
Harwinder Sekhon deda7c3c18 Add quiz support: parse *_quiz_solved.txt files, take and grade quizzes
Scanner indexes quiz files found next to videos ([&] questions, "- "
options, "-- Correct" markers; _notsolved twins ignored). Quizzes appear
in the folder content list interleaved with videos by filename order.
New api/quiz.php serves questions without answers and grades attempts
server-side; per-user best/last score and attempts stored in
quiz_progress. Folder/course progress now aggregates videos + quizzes
with partial credit for partial scores.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 11:24:37 -04:00
Harwinder Sekhon b9c92540d4 CourseHub: AJAX video course player (PHP + MySQL + Bootstrap + Plyr)
Scans nested course folders for mp4s, tracks per-user resume position
and per-folder progress. SPA frontend with hash routing over a JSON API;
range-request streaming via stream.php.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 10:25:35 -04:00