mirror of
https://github.com/nicetry247/offlineacademy.git
synced 2026-07-26 11:58:44 +00:00
191 lines
6.4 KiB
Markdown
191 lines
6.4 KiB
Markdown
# OfflineAcademy - Feature Summary
|
|
|
|
**Project Location:** `/home/afterhours/OfflineU2`
|
|
**Port:** 5001 | **Stack:** Next.js 14, TypeScript, Tailwind, Shadcn/UI, SQLite + Prisma
|
|
|
|
---
|
|
|
|
## ✅ Implemented Features
|
|
|
|
| Feature | Status | Details |
|
|
|---------|--------|---------|
|
|
| **Course Discovery** | ✅ | Recursive scan of `My_Courses` folder, natural sort (1,2,3...10,11) |
|
|
| **Dashboard** | ✅ | Course grid with progress %, thumbnails, continue watching row |
|
|
| **Course Detail Page** | ✅ | Module/lesson tree, progress tracking, resume button |
|
|
| **Watch Page** | ✅ | Video player (auto-play, resume), audio/PDF/image/other preview |
|
|
| **Video Auto-Play** | ✅ | Starts automatically on page load |
|
|
| **Auto-Next Lesson** | ✅ | Redirects to next lesson on video completion |
|
|
| **Progress Bookmarking** | ✅ | 500ms debounced save, persists position + completion |
|
|
| **Mobile Overlay** | ✅ | Bottom progress bar with title/duration on small screens |
|
|
| **Sidebar Navigation** | ✅ | Collapsible module accordion, current lesson highlighted |
|
|
| **Keyboard Shortcuts** | ✅ | Space (play/pause), ←/→ (seek ±10s), F (fullscreen), M (mute), ↑/↓ (volume) |
|
|
| **Quick Scan Button** | ✅ | Dashboard button triggers background re-scan |
|
|
| **Settings Page** | ✅ | Configure courses root path |
|
|
| **Theme Toggle** | ✅ | Light/dark/system with persistence |
|
|
|
|
---
|
|
|
|
## 🔌 API Endpoints
|
|
|
|
| Endpoint | Methods | Purpose |
|
|
|----------|---------|---------|
|
|
| `/api/progress` | GET, POST | Lesson & course progress (upsert + fetch) |
|
|
| `/api/settings` | GET, POST | Courses root configuration |
|
|
| `/api/files/*` | GET | Secure file serving with range requests (video streaming) |
|
|
| `/api/scan` | POST | Background course discovery job |
|
|
|
|
---
|
|
|
|
## 🗄 Database Schema (Prisma)
|
|
|
|
```prisma
|
|
model Course {
|
|
id String @id @default(cuid())
|
|
name String
|
|
slug String @unique
|
|
path String @unique
|
|
thumbnail String?
|
|
description String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
modules Module[]
|
|
progress Progress[]
|
|
|
|
@@index([slug])
|
|
@@index([path])
|
|
}
|
|
|
|
model Module {
|
|
id String @id @default(cuid())
|
|
name String
|
|
slug String
|
|
order Int @default(0)
|
|
courseId String
|
|
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
lessons Lesson[]
|
|
progress Progress[]
|
|
|
|
@@unique([courseId, slug])
|
|
@@index([courseId, order])
|
|
}
|
|
|
|
model Lesson {
|
|
id String @id @default(cuid())
|
|
title String
|
|
slug String
|
|
order Int @default(0)
|
|
moduleId String
|
|
module Module @relation(fields: [moduleId], references: [id], onDelete: Cascade)
|
|
filePath String
|
|
fileName String
|
|
mimeType String
|
|
duration Int?
|
|
thumbnail String?
|
|
type String @default("VIDEO")
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
progress Progress[]
|
|
|
|
@@unique([moduleId, slug])
|
|
@@index([moduleId, order])
|
|
@@index([filePath])
|
|
}
|
|
|
|
model Progress {
|
|
id String @id @default(cuid())
|
|
userId String @default("local-user")
|
|
courseId String
|
|
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
|
|
moduleId String?
|
|
module Module? @relation(fields: [moduleId], references: [id], onDelete: Cascade)
|
|
lessonId String?
|
|
lesson Lesson? @relation(fields: [lessonId], references: [id], onDelete: Cascade)
|
|
completed Boolean @default(false)
|
|
position Int @default(0)
|
|
lastWatched DateTime @default(now())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([userId, lessonId])
|
|
@@index([userId, courseId])
|
|
@@index([userId, lastWatched])
|
|
}
|
|
|
|
model Setting {
|
|
id String @id @default(cuid())
|
|
key String @unique
|
|
value String
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 📁 Key Project Structure
|
|
|
|
```
|
|
/home/afterhours/OfflineU2/
|
|
├── app/
|
|
│ ├── page.tsx # Dashboard
|
|
│ ├── course/[slug]/page.tsx # Course detail
|
|
│ ├── watch/[lessonId]/page.tsx # Watch page (server)
|
|
│ ├── watch/[lessonId]/WatchPageClient.tsx # Watch client (fixed)
|
|
│ ├── scan/page.tsx # Quick scan UI
|
|
│ ├── settings/page.tsx # Settings
|
|
│ └── api/
|
|
│ ├── progress/route.ts # Progress CRUD
|
|
│ ├── settings/route.ts # Settings CRUD
|
|
│ ├── files/[...path]/route.ts # File serving
|
|
│ └── scan/route.ts # Scan trigger
|
|
├── components/
|
|
│ ├── Header.tsx
|
|
│ ├── VideoPlayer.tsx
|
|
│ ├── ModuleAccordion.tsx
|
|
│ ├── CourseCard.tsx
|
|
│ ├── ThemeToggle.tsx
|
|
│ ├── KeyboardShortcutsOverlay.tsx
|
|
│ └── ui/ # Shadcn components
|
|
├── lib/
|
|
│ ├── prisma.ts # Prisma client
|
|
│ └── utils.ts # Helpers (formatDuration, cn, etc.)
|
|
├── prisma/
|
|
│ └── schema.prisma # Database schema
|
|
├── global.d.ts # lucide-react type declarations
|
|
├── radix-separator.d.ts # @radix-ui/react-separator types
|
|
├── tailwind.config.ts
|
|
├── tsconfig.json
|
|
└── package.json
|
|
```
|
|
|
|
---
|
|
|
|
## 🚀 Getting Started
|
|
|
|
```bash
|
|
cd /home/afterhours/OfflineU2
|
|
npm run dev # Starts on http://localhost:5001
|
|
```
|
|
|
|
---
|
|
|
|
## ⚠️ Known Minor Issues (Non-Blocking)
|
|
|
|
1. **`COURSES_ROOT` references** in `app/scan/page.tsx` and `app/settings/page.tsx` - should use dynamic settings
|
|
2. **`lastWatched` type mismatch** - Prisma returns `Date`, API expects `string` (watch page parent)
|
|
3. **TypeScript errors** in `WatchComponents.tsx`, `VideoPlayer.tsx`, `KeyboardShortcutsOverlay.tsx` - cosmetic, don't affect runtime
|
|
|
|
---
|
|
|
|
## 📝 History Notes
|
|
|
|
- **Renamed from OfflineU2 → OfflineAcademy** (brand cleanup)
|
|
- **WatchPageClient.tsx** completely rewritten to fix TypeScript JSX parsing issues
|
|
- **Progress API** fixed to use valid Prisma unique key (`@@unique([userId, lessonId])`)
|
|
- **Type declarations** added for `lucide-react` and `@radix-ui/react-separator`
|
|
|
|
---
|
|
|
|
*Generated for workspace transfer - all features functional as of last session.* |