fix: sync admin UI schema with plugin (UUID keys, lesson_media table)
Schema now matches plugin Data/LessonStore.cs exactly: - id TEXT PRIMARY KEY (UUIDs) - lesson_media table (not media_items) - Full column set with NOT NULL constraints matching plugin Addresses PR #14 review feedback.
This commit is contained in:
parent
6e7d2372e7
commit
e4967e1cda
1 changed files with 45 additions and 37 deletions
|
|
@ -16,26 +16,35 @@ fs.mkdirSync(path.dirname(DB_PATH), { recursive: true });
|
|||
|
||||
const db = new Database(DB_PATH);
|
||||
|
||||
// Initialize schema if needed
|
||||
// UUID helper
|
||||
function generateId() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
||||
const r = Math.random() * 16 | 0;
|
||||
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize schema — must match plugin Data/LessonStore.cs
|
||||
const now = () => new Date().toISOString();
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS lessons (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
id TEXT PRIMARY KEY,
|
||||
title TEXT NOT NULL,
|
||||
grade INTEGER,
|
||||
subject TEXT,
|
||||
lesson_date TEXT,
|
||||
grade INTEGER NOT NULL,
|
||||
subject TEXT NOT NULL,
|
||||
lesson_date TEXT NOT NULL,
|
||||
description TEXT,
|
||||
source_path TEXT,
|
||||
source_path TEXT NOT NULL,
|
||||
compiled_html TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS media_items (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
lesson_id INTEGER NOT NULL,
|
||||
CREATE TABLE IF NOT EXISTS lesson_media (
|
||||
lesson_id TEXT NOT NULL,
|
||||
jellyfin_item_id TEXT NOT NULL,
|
||||
sort_order INTEGER DEFAULT 0,
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (lesson_id, jellyfin_item_id),
|
||||
FOREIGN KEY (lesson_id) REFERENCES lessons(id) ON DELETE CASCADE
|
||||
);
|
||||
`);
|
||||
|
|
@ -68,7 +77,7 @@ app.get('/api/lessons/:id', (req, res) => {
|
|||
try {
|
||||
const lesson = db.prepare('SELECT * FROM lessons WHERE id = ?').get(req.params.id);
|
||||
if (!lesson) return res.status(404).json({ error: 'Lesson not found' });
|
||||
const mediaItems = db.prepare('SELECT * FROM media_items WHERE lesson_id = ? ORDER BY sort_order').all(req.params.id);
|
||||
const mediaItems = db.prepare('SELECT * FROM lesson_media WHERE lesson_id = ? ORDER BY sort_order').all(req.params.id);
|
||||
res.json({ ...lesson, mediaItems });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
|
|
@ -83,31 +92,30 @@ app.post('/api/lessons', (req, res) => {
|
|||
if (!title) return res.status(400).json({ error: 'Title is required' });
|
||||
|
||||
const upsertLesson = db.transaction(() => {
|
||||
let lessonId = id;
|
||||
if (lessonId) {
|
||||
// Update existing
|
||||
db.prepare(`
|
||||
UPDATE lessons
|
||||
SET title = ?, grade = ?, subject = ?, lesson_date = ?, description = ?,
|
||||
source_path = ?, compiled_html = ?, updated_at = datetime('now')
|
||||
WHERE id = ?
|
||||
`).run(title, grade || null, subject || null, lessonDate || null, description || null,
|
||||
sourcePath || null, compiledHtml || null, lessonId);
|
||||
// Replace media items
|
||||
db.prepare('DELETE FROM media_items WHERE lesson_id = ?').run(lessonId);
|
||||
} else {
|
||||
// Create new
|
||||
const result = db.prepare(`
|
||||
INSERT INTO lessons (title, grade, subject, lesson_date, description, source_path, compiled_html)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(title, grade || null, subject || null, lessonDate || null, description || null,
|
||||
sourcePath || null, compiledHtml || null);
|
||||
lessonId = result.lastInsertRowid;
|
||||
}
|
||||
let lessonId = id || generateId();
|
||||
const ts = now();
|
||||
const existing = id ? db.prepare('SELECT created_at FROM lessons WHERE id = ?').get(id) : null;
|
||||
const createdAt = existing ? existing.created_at : ts;
|
||||
|
||||
// Insert media items
|
||||
db.prepare(`
|
||||
INSERT INTO lessons (id, title, grade, subject, lesson_date, description, source_path, compiled_html, created_at, updated_at)
|
||||
VALUES (@id, @title, @grade, @subject, @lesson_date, @description, @source_path, @compiled_html, @created_at, @updated_at)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
title = excluded.title, grade = excluded.grade, subject = excluded.subject,
|
||||
lesson_date = excluded.lesson_date, description = excluded.description,
|
||||
source_path = excluded.source_path, compiled_html = excluded.compiled_html,
|
||||
updated_at = excluded.updated_at
|
||||
`).run({
|
||||
id: lessonId, title, grade: grade || 0,
|
||||
subject: subject || '', lesson_date: lessonDate || new Date().toISOString().split('T')[0],
|
||||
description: description || null, source_path: sourcePath || '',
|
||||
compiled_html: compiledHtml || null, created_at: createdAt, updated_at: ts
|
||||
});
|
||||
|
||||
// Replace media items
|
||||
db.prepare('DELETE FROM lesson_media WHERE lesson_id = ?').run(lessonId);
|
||||
if (mediaItems && Array.isArray(mediaItems)) {
|
||||
const insertMedia = db.prepare('INSERT INTO media_items (lesson_id, jellyfin_item_id, sort_order) VALUES (?, ?, ?)');
|
||||
const insertMedia = db.prepare('INSERT INTO lesson_media (lesson_id, jellyfin_item_id, sort_order) VALUES (?, ?, ?)');
|
||||
for (const item of mediaItems) {
|
||||
insertMedia.run(lessonId, item.jellyfinItemId, item.sortOrder || 0);
|
||||
}
|
||||
|
|
@ -118,7 +126,7 @@ app.post('/api/lessons', (req, res) => {
|
|||
|
||||
const lessonId = upsertLesson();
|
||||
const lesson = db.prepare('SELECT * FROM lessons WHERE id = ?').get(lessonId);
|
||||
const media = db.prepare('SELECT * FROM media_items WHERE lesson_id = ? ORDER BY sort_order').all(lessonId);
|
||||
const media = db.prepare('SELECT * FROM lesson_media WHERE lesson_id = ? ORDER BY sort_order').all(lessonId);
|
||||
res.json({ ...lesson, mediaItems: media });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue