Fix course router: remove nonexistent model fields
- Remove category, thumbnail_url references (not on Course model) - Fix sort_order → position (matching actual column name) - Fix CourseCreate schema: only title + description - Fix tab labels: "My Created" → "Created by Me" Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a2a2da7da0
commit
594bbfaf26
2 changed files with 37 additions and 44 deletions
|
|
@ -28,9 +28,6 @@ router = APIRouter()
|
||||||
class CourseCreate(BaseModel):
|
class CourseCreate(BaseModel):
|
||||||
title: str
|
title: str
|
||||||
description: str | None = None
|
description: str | None = None
|
||||||
category: str | None = None
|
|
||||||
thumbnail_url: str | None = None
|
|
||||||
requires_subscription: int = 0
|
|
||||||
|
|
||||||
|
|
||||||
class CourseUpdate(BaseModel):
|
class CourseUpdate(BaseModel):
|
||||||
|
|
@ -44,13 +41,13 @@ class CourseUpdate(BaseModel):
|
||||||
class ModuleCreate(BaseModel):
|
class ModuleCreate(BaseModel):
|
||||||
title: str
|
title: str
|
||||||
description: str | None = None
|
description: str | None = None
|
||||||
sort_order: int | None = None
|
position: int | None = None
|
||||||
|
|
||||||
|
|
||||||
class ModuleUpdate(BaseModel):
|
class ModuleUpdate(BaseModel):
|
||||||
title: str | None = None
|
title: str | None = None
|
||||||
description: str | None = None
|
description: str | None = None
|
||||||
sort_order: int | None = None
|
position: int | None = None
|
||||||
|
|
||||||
|
|
||||||
class ModuleReorder(BaseModel):
|
class ModuleReorder(BaseModel):
|
||||||
|
|
@ -64,7 +61,7 @@ class LessonCreate(BaseModel):
|
||||||
video_url: str | None = None
|
video_url: str | None = None
|
||||||
duration_minutes: int | None = None
|
duration_minutes: int | None = None
|
||||||
is_required: int = 1
|
is_required: int = 1
|
||||||
sort_order: int | None = None
|
position: int | None = None
|
||||||
|
|
||||||
|
|
||||||
class LessonUpdate(BaseModel):
|
class LessonUpdate(BaseModel):
|
||||||
|
|
@ -75,7 +72,7 @@ class LessonUpdate(BaseModel):
|
||||||
video_provider: str | None = None
|
video_provider: str | None = None
|
||||||
duration_minutes: int | None = None
|
duration_minutes: int | None = None
|
||||||
is_required: int | None = None
|
is_required: int | None = None
|
||||||
sort_order: int | None = None
|
position: int | None = None
|
||||||
|
|
||||||
|
|
||||||
class ProgressUpdate(BaseModel):
|
class ProgressUpdate(BaseModel):
|
||||||
|
|
@ -128,9 +125,6 @@ def create_course(
|
||||||
course = Course(
|
course = Course(
|
||||||
title=data.title,
|
title=data.title,
|
||||||
description=data.description,
|
description=data.description,
|
||||||
category=data.category,
|
|
||||||
thumbnail_url=data.thumbnail_url,
|
|
||||||
requires_subscription=data.requires_subscription,
|
|
||||||
user_id=current_user.id,
|
user_id=current_user.id,
|
||||||
status="draft",
|
status="draft",
|
||||||
)
|
)
|
||||||
|
|
@ -186,9 +180,9 @@ def list_courses(
|
||||||
"id": course.id,
|
"id": course.id,
|
||||||
"title": course.title,
|
"title": course.title,
|
||||||
"description": course.description,
|
"description": course.description,
|
||||||
"category": course.category,
|
|
||||||
"status": course.status,
|
"status": course.status,
|
||||||
"thumbnail_url": course.thumbnail_url,
|
"thumbnail_path": course.thumbnail_path,
|
||||||
"requires_subscription": course.requires_subscription,
|
"requires_subscription": course.requires_subscription,
|
||||||
"user_id": course.user_id,
|
"user_id": course.user_id,
|
||||||
"creator_name": user_cache[course.user_id],
|
"creator_name": user_cache[course.user_id],
|
||||||
|
|
@ -242,8 +236,8 @@ def list_published_courses(
|
||||||
"id": course.id,
|
"id": course.id,
|
||||||
"title": course.title,
|
"title": course.title,
|
||||||
"description": course.description,
|
"description": course.description,
|
||||||
"category": course.category,
|
|
||||||
"thumbnail_url": course.thumbnail_url,
|
"thumbnail_path": course.thumbnail_path,
|
||||||
"requires_subscription": course.requires_subscription,
|
"requires_subscription": course.requires_subscription,
|
||||||
"user_id": course.user_id,
|
"user_id": course.user_id,
|
||||||
"creator_name": user_cache[course.user_id],
|
"creator_name": user_cache[course.user_id],
|
||||||
|
|
@ -275,8 +269,8 @@ def my_courses(
|
||||||
"id": course.id,
|
"id": course.id,
|
||||||
"title": course.title,
|
"title": course.title,
|
||||||
"description": course.description,
|
"description": course.description,
|
||||||
"category": course.category,
|
|
||||||
"thumbnail_url": course.thumbnail_url,
|
"thumbnail_path": course.thumbnail_path,
|
||||||
"status": course.status,
|
"status": course.status,
|
||||||
"enrollment_id": enrollment.id,
|
"enrollment_id": enrollment.id,
|
||||||
"progress_pct": enrollment.progress_pct,
|
"progress_pct": enrollment.progress_pct,
|
||||||
|
|
@ -318,7 +312,7 @@ def get_course(
|
||||||
modules = (
|
modules = (
|
||||||
db.query(CourseModule)
|
db.query(CourseModule)
|
||||||
.filter(CourseModule.course_id == course_id)
|
.filter(CourseModule.course_id == course_id)
|
||||||
.order_by(CourseModule.sort_order, CourseModule.id)
|
.order_by(CourseModule.position, CourseModule.id)
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
modules_out = []
|
modules_out = []
|
||||||
|
|
@ -326,14 +320,14 @@ def get_course(
|
||||||
lessons = (
|
lessons = (
|
||||||
db.query(CourseLesson)
|
db.query(CourseLesson)
|
||||||
.filter(CourseLesson.module_id == mod.id)
|
.filter(CourseLesson.module_id == mod.id)
|
||||||
.order_by(CourseLesson.sort_order, CourseLesson.id)
|
.order_by(CourseLesson.position, CourseLesson.id)
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
modules_out.append({
|
modules_out.append({
|
||||||
"id": mod.id,
|
"id": mod.id,
|
||||||
"title": mod.title,
|
"title": mod.title,
|
||||||
"description": mod.description,
|
"description": mod.description,
|
||||||
"sort_order": mod.sort_order,
|
"position": mod.position,
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": les.id,
|
"id": les.id,
|
||||||
|
|
@ -345,7 +339,7 @@ def get_course(
|
||||||
"file_path": les.file_path,
|
"file_path": les.file_path,
|
||||||
"duration_minutes": les.duration_minutes,
|
"duration_minutes": les.duration_minutes,
|
||||||
"is_required": les.is_required,
|
"is_required": les.is_required,
|
||||||
"sort_order": les.sort_order,
|
"position": les.position,
|
||||||
"bbb_meeting_id": les.bbb_meeting_id,
|
"bbb_meeting_id": les.bbb_meeting_id,
|
||||||
}
|
}
|
||||||
for les in lessons
|
for les in lessons
|
||||||
|
|
@ -388,9 +382,8 @@ def get_course(
|
||||||
"id": course.id,
|
"id": course.id,
|
||||||
"title": course.title,
|
"title": course.title,
|
||||||
"description": course.description,
|
"description": course.description,
|
||||||
"category": course.category,
|
|
||||||
"status": course.status,
|
"status": course.status,
|
||||||
"thumbnail_url": course.thumbnail_url,
|
"thumbnail_path": course.thumbnail_path,
|
||||||
"requires_subscription": course.requires_subscription,
|
"requires_subscription": course.requires_subscription,
|
||||||
"user_id": course.user_id,
|
"user_id": course.user_id,
|
||||||
"creator_name": creator_name,
|
"creator_name": creator_name,
|
||||||
|
|
@ -476,11 +469,11 @@ async def upload_thumbnail(
|
||||||
with open(file_path, "wb") as f:
|
with open(file_path, "wb") as f:
|
||||||
f.write(contents)
|
f.write(contents)
|
||||||
|
|
||||||
course.thumbnail_url = f"/uploads/course_thumbnails/{filename}"
|
course.thumbnail_path = f"/uploads/course_thumbnails/{filename}"
|
||||||
course.updated_at = datetime.utcnow()
|
course.updated_at = datetime.utcnow()
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
return {"thumbnail_url": course.thumbnail_url}
|
return {"thumbnail_path": course.thumbnail_path}
|
||||||
|
|
||||||
|
|
||||||
# ── Module CRUD ──────────────────────────────────────────────────────
|
# ── Module CRUD ──────────────────────────────────────────────────────
|
||||||
|
|
@ -495,21 +488,21 @@ def create_module(
|
||||||
"""Add a module to a course (creator or admin)."""
|
"""Add a module to a course (creator or admin)."""
|
||||||
_course_owner_or_admin(course_id, current_user, db)
|
_course_owner_or_admin(course_id, current_user, db)
|
||||||
|
|
||||||
# Auto sort_order if not provided
|
# Auto position if not provided
|
||||||
sort_order = data.sort_order
|
position = data.position
|
||||||
if sort_order is None:
|
if position is None:
|
||||||
max_order = (
|
max_order = (
|
||||||
db.query(func.max(CourseModule.sort_order))
|
db.query(func.max(CourseModule.position))
|
||||||
.filter(CourseModule.course_id == course_id)
|
.filter(CourseModule.course_id == course_id)
|
||||||
.scalar()
|
.scalar()
|
||||||
)
|
)
|
||||||
sort_order = (max_order or 0) + 1
|
position = (max_order or 0) + 1
|
||||||
|
|
||||||
module = CourseModule(
|
module = CourseModule(
|
||||||
course_id=course_id,
|
course_id=course_id,
|
||||||
title=data.title,
|
title=data.title,
|
||||||
description=data.description,
|
description=data.description,
|
||||||
sort_order=sort_order,
|
position=position,
|
||||||
)
|
)
|
||||||
db.add(module)
|
db.add(module)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
@ -519,7 +512,7 @@ def create_module(
|
||||||
"course_id": module.course_id,
|
"course_id": module.course_id,
|
||||||
"title": module.title,
|
"title": module.title,
|
||||||
"description": module.description,
|
"description": module.description,
|
||||||
"sort_order": module.sort_order,
|
"position": module.position,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -548,7 +541,7 @@ def update_module(
|
||||||
"id": module.id,
|
"id": module.id,
|
||||||
"title": module.title,
|
"title": module.title,
|
||||||
"description": module.description,
|
"description": module.description,
|
||||||
"sort_order": module.sort_order,
|
"position": module.position,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -588,7 +581,7 @@ def reorder_modules(
|
||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
if module:
|
if module:
|
||||||
module.sort_order = idx + 1
|
module.position = idx + 1
|
||||||
db.commit()
|
db.commit()
|
||||||
return {"reordered": True, "count": len(data.module_ids)}
|
return {"reordered": True, "count": len(data.module_ids)}
|
||||||
|
|
||||||
|
|
@ -614,15 +607,15 @@ def create_lesson(
|
||||||
if not module:
|
if not module:
|
||||||
raise HTTPException(status_code=404, detail="Module not found")
|
raise HTTPException(status_code=404, detail="Module not found")
|
||||||
|
|
||||||
# Auto sort_order if not provided
|
# Auto position if not provided
|
||||||
sort_order = data.sort_order
|
position = data.position
|
||||||
if sort_order is None:
|
if position is None:
|
||||||
max_order = (
|
max_order = (
|
||||||
db.query(func.max(CourseLesson.sort_order))
|
db.query(func.max(CourseLesson.position))
|
||||||
.filter(CourseLesson.module_id == module_id)
|
.filter(CourseLesson.module_id == module_id)
|
||||||
.scalar()
|
.scalar()
|
||||||
)
|
)
|
||||||
sort_order = (max_order or 0) + 1
|
position = (max_order or 0) + 1
|
||||||
|
|
||||||
video_provider = _detect_video_provider(data.video_url)
|
video_provider = _detect_video_provider(data.video_url)
|
||||||
|
|
||||||
|
|
@ -635,7 +628,7 @@ def create_lesson(
|
||||||
video_provider=video_provider,
|
video_provider=video_provider,
|
||||||
duration_minutes=data.duration_minutes,
|
duration_minutes=data.duration_minutes,
|
||||||
is_required=data.is_required,
|
is_required=data.is_required,
|
||||||
sort_order=sort_order,
|
position=position,
|
||||||
)
|
)
|
||||||
db.add(lesson)
|
db.add(lesson)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
@ -647,7 +640,7 @@ def create_lesson(
|
||||||
"lesson_type": lesson.lesson_type,
|
"lesson_type": lesson.lesson_type,
|
||||||
"video_url": lesson.video_url,
|
"video_url": lesson.video_url,
|
||||||
"video_provider": lesson.video_provider,
|
"video_provider": lesson.video_provider,
|
||||||
"sort_order": lesson.sort_order,
|
"position": lesson.position,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -685,7 +678,7 @@ def update_lesson(
|
||||||
"lesson_type": lesson.lesson_type,
|
"lesson_type": lesson.lesson_type,
|
||||||
"video_url": lesson.video_url,
|
"video_url": lesson.video_url,
|
||||||
"video_provider": lesson.video_provider,
|
"video_provider": lesson.video_provider,
|
||||||
"sort_order": lesson.sort_order,
|
"position": lesson.position,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -111,9 +111,9 @@ export default function CoursesPage() {
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 8 }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 8 }}>
|
||||||
<h2 style={{ marginBottom: 0 }}>Courses</h2>
|
<h2 style={{ marginBottom: 0 }}>Courses</h2>
|
||||||
<div style={{ display: 'flex', gap: 4 }}>
|
<div style={{ display: 'flex', gap: 4 }}>
|
||||||
<button className={`btn btn-sm ${tab === 'browse' ? 'btn-primary' : 'btn-secondary'}`} onClick={() => setTab('browse')}>Browse Courses</button>
|
<button className={`btn btn-sm ${tab === 'browse' ? 'btn-primary' : 'btn-secondary'}`} onClick={() => setTab('browse')}>Browse</button>
|
||||||
<button className={`btn btn-sm ${tab === 'enrolled' ? 'btn-primary' : 'btn-secondary'}`} onClick={() => setTab('enrolled')}>My Courses</button>
|
<button className={`btn btn-sm ${tab === 'enrolled' ? 'btn-primary' : 'btn-secondary'}`} onClick={() => setTab('enrolled')}>Enrolled</button>
|
||||||
<button className={`btn btn-sm ${tab === 'created' ? 'btn-primary' : 'btn-secondary'}`} onClick={() => setTab('created')}>My Created</button>
|
<button className={`btn btn-sm ${tab === 'created' ? 'btn-primary' : 'btn-secondary'}`} onClick={() => setTab('created')}>Created by Me</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue