feat(lessons): «Быстрый урок» — одиночный урок без ручного создания курса
Учитель жмёт «Быстрый урок» в каталоге (theory.html) → урок создаётся в скрытом личном курсе-контейнере и сразу открывается редактор. Возни с курсом нет. - Миграция 059: courses.is_personal (ADD COLUMN). - POST /api/lessons/quick (teacher/admin): get-or-create личный контейнер (is_personal=1, один на учителя, опубликован) + создаёт урок, возвращает lessonId. - Каталог курсов скрывает личные контейнеры от всех, кроме владельца (courseController.list). - Свои быстрые уроки учитель видит как курс «Мои материалы» (открыв его в каталоге). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -45,6 +45,10 @@ function list(req, res) {
|
||||
let where = role === 'student' ? 'WHERE c.is_published = 1' : 'WHERE 1=1';
|
||||
const args = [];
|
||||
if (subject) { where += ' AND c.subject_slug = ?'; args.push(subject); }
|
||||
// Personal "container" courses (quick lessons) are hidden from the catalog;
|
||||
// their owner still sees their own.
|
||||
if (role === 'student') { where += ' AND c.is_personal = 0'; }
|
||||
else { where += ' AND (c.is_personal = 0 OR c.created_by = ?)'; args.push(uid); }
|
||||
|
||||
const rows = db.prepare(`
|
||||
SELECT c.*,
|
||||
|
||||
@@ -302,4 +302,28 @@ function deleteComment(req, res) {
|
||||
res.json({ ok: true });
|
||||
}
|
||||
|
||||
module.exports = { get, create, update, remove, saveBlocks, markComplete, saveNote, listComments, addComment, deleteComment };
|
||||
/* ── POST /api/lessons/quick ──────────────────────────────────────────────
|
||||
Create a standalone "quick lesson" without manually building a course:
|
||||
reuse (or lazily create) the teacher's hidden personal container course,
|
||||
add one lesson to it, and return its id for the editor. */
|
||||
function quickLesson(req, res) {
|
||||
const uid = req.user.id;
|
||||
let container = db.prepare(
|
||||
'SELECT id FROM courses WHERE created_by = ? AND is_personal = 1 ORDER BY id LIMIT 1'
|
||||
).get(uid);
|
||||
if (!container) {
|
||||
const r = db.prepare(`
|
||||
INSERT INTO courses (subject_slug, title, description, cover_emoji, order_index, is_published, is_personal, created_by)
|
||||
VALUES ('personal', 'Мои материалы', 'Отдельные уроки без курса', '', 0, 1, 1, ?)
|
||||
`).run(uid);
|
||||
container = { id: Number(r.lastInsertRowid) };
|
||||
}
|
||||
const title = (req.body && req.body.title && String(req.body.title).trim()) || 'Новый урок';
|
||||
const n = db.prepare('SELECT COUNT(*) AS c FROM lessons WHERE course_id = ?').get(container.id).c;
|
||||
const r2 = db.prepare(
|
||||
'INSERT INTO lessons (course_id, title, order_index) VALUES (?, ?, ?)'
|
||||
).run(container.id, title, n);
|
||||
res.status(201).json({ lessonId: Number(r2.lastInsertRowid), courseId: container.id });
|
||||
}
|
||||
|
||||
module.exports = { get, create, update, remove, saveBlocks, markComplete, saveNote, listComments, addComment, deleteComment, quickLesson };
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
-- ═══════════════════════════════════════════════════════════════
|
||||
-- 059: Personal "container" course for standalone quick lessons
|
||||
--
|
||||
-- A teacher who just wants to author a single lesson (without manually
|
||||
-- creating a whole course) gets a hidden personal container course
|
||||
-- (is_personal = 1, one per teacher). Quick lessons are added there.
|
||||
-- The course catalog hides personal containers from everyone except their
|
||||
-- owner (see courseController.list).
|
||||
-- ═══════════════════════════════════════════════════════════════
|
||||
|
||||
ALTER TABLE courses ADD COLUMN is_personal INTEGER NOT NULL DEFAULT 0;
|
||||
@@ -13,6 +13,7 @@ router.post('/:id/comments', c.addComment);
|
||||
router.delete('/:id/comments/:cid', c.deleteComment);
|
||||
|
||||
// Teacher/admin only
|
||||
router.post('/quick', requireRole('teacher','admin'), c.quickLesson);
|
||||
router.post('/', requireRole('teacher','admin'), c.create);
|
||||
router.put('/:id', requireRole('teacher','admin'), c.update);
|
||||
router.delete('/:id', requireRole('teacher','admin'), c.remove);
|
||||
|
||||
@@ -304,6 +304,9 @@
|
||||
<button class="btn-new-course" id="btn-from-tpl" style="display:none;background:transparent;border:1.5px solid rgba(255,255,255,0.2);box-shadow:none;color:rgba(255,255,255,0.7)" onclick="openTplBrowser()">
|
||||
<i data-lucide="clipboard" style="width:16px;height:16px"></i> Из шаблона
|
||||
</button>
|
||||
<button class="btn-new-course" id="btn-quick-lesson" style="display:none;background:rgba(255,255,255,0.14);box-shadow:none;color:#fff" onclick="createQuickLesson()" title="Создать отдельный урок без курса">
|
||||
<i data-lucide="file-plus" style="width:16px;height:16px"></i> Быстрый урок
|
||||
</button>
|
||||
<button class="btn-new-course" id="btn-new-course" style="display:none" onclick="openNewCourseModal()">
|
||||
<i data-lucide="plus" style="width:16px;height:16px"></i> Новый курс
|
||||
</button>
|
||||
@@ -350,6 +353,7 @@
|
||||
document.getElementById('btn-classes').style.display = '';
|
||||
document.getElementById('btn-new-course').style.display = '';
|
||||
document.getElementById('btn-from-tpl').style.display = '';
|
||||
document.getElementById('btn-quick-lesson').style.display = '';
|
||||
}
|
||||
if (isAdmin) {
|
||||
document.getElementById('btn-admin').style.display = '';
|
||||
@@ -586,6 +590,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Быстрый урок: создаёт отдельный урок в личном курсе-контейнере и сразу
|
||||
открывает редактор — без ручного создания курса. */
|
||||
async function createQuickLesson() {
|
||||
const btn = document.getElementById('btn-quick-lesson');
|
||||
if (btn) btn.disabled = true;
|
||||
try {
|
||||
const res = await LS.api('/api/lessons/quick', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({}),
|
||||
});
|
||||
location.href = '/lesson-editor.html?id=' + res.lessonId;
|
||||
} catch (e) {
|
||||
LS.toast(e.message || 'Ошибка', 'error');
|
||||
if (btn) btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════
|
||||
TEMPLATE BROWSER
|
||||
══════════════════════════════════════════════════════════════════ */
|
||||
|
||||
Reference in New Issue
Block a user