180 lines
6.1 KiB
Python
180 lines
6.1 KiB
Python
from fastapi import APIRouter, Depends, HTTPException
|
|
from sqlalchemy.orm import Session
|
|
from typing import Optional
|
|
from .. import crud, schemas, auth
|
|
from ..database import get_db
|
|
from ..models import User
|
|
|
|
router = APIRouter(prefix="/learn", tags=["learning"])
|
|
|
|
@router.get("/steps/{step_id}", response_model=schemas.SuccessResponse)
|
|
def get_step_content(
|
|
step_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_user: Optional[User] = Depends(auth.get_optional_current_user)
|
|
):
|
|
step = crud.get_step_by_id(db, step_id=step_id)
|
|
if not step:
|
|
raise HTTPException(status_code=404, detail="Step not found")
|
|
|
|
lesson = crud.get_lesson_by_id(db, step.lesson_id)
|
|
if not lesson:
|
|
raise HTTPException(status_code=404, detail="Lesson not found")
|
|
|
|
module = crud.get_module_by_id(db, lesson.module_id)
|
|
if not module:
|
|
raise HTTPException(status_code=404, detail="Module not found")
|
|
|
|
from ..models import Course
|
|
course = db.query(Course).filter(Course.id == module.course_id).first()
|
|
if not course:
|
|
raise HTTPException(status_code=404, detail="Course not found")
|
|
|
|
if course.status != 'published':
|
|
if current_user is None or course.author_id != current_user.id:
|
|
raise HTTPException(status_code=403, detail="Not authorized")
|
|
|
|
return schemas.SuccessResponse(data={
|
|
"step": {
|
|
"id": step.id,
|
|
"step_type": step.step_type,
|
|
"title": step.title,
|
|
"content": step.content,
|
|
"content_html": step.content_html,
|
|
"file_url": step.file_url,
|
|
"test_settings": step.test_settings,
|
|
"order_index": step.order_index
|
|
}
|
|
})
|
|
|
|
@router.get("/steps/{step_id}/test", response_model=schemas.SuccessResponse)
|
|
def get_test_for_viewing(
|
|
step_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(auth.get_current_user)
|
|
):
|
|
"""
|
|
Get test questions for viewing (without correct answers)
|
|
"""
|
|
step = crud.get_step_by_id(db, step_id)
|
|
if not step:
|
|
raise HTTPException(status_code=404, detail="Step not found")
|
|
|
|
if step.step_type != 'test':
|
|
raise HTTPException(status_code=400, detail="Step is not a test")
|
|
|
|
questions = crud.get_test_questions(db, step_id)
|
|
|
|
# Prepare questions without correct answers
|
|
questions_for_viewer = []
|
|
for q in questions:
|
|
question_data = {
|
|
"id": q.id,
|
|
"question_text": q.question_text,
|
|
"question_type": q.question_type,
|
|
"points": q.points,
|
|
"order_index": q.order_index
|
|
}
|
|
|
|
if q.question_type in ['single_choice', 'multiple_choice']:
|
|
# Return answers without is_correct flag
|
|
question_data["answers"] = [
|
|
{"id": a.id, "answer_text": a.answer_text, "order_index": a.order_index}
|
|
for a in q.answers
|
|
]
|
|
elif q.question_type == 'match':
|
|
# Return match items shuffled
|
|
question_data["match_items"] = [
|
|
{"id": m.id, "left_text": m.left_text, "right_text": m.right_text, "order_index": m.order_index}
|
|
for m in q.match_items
|
|
]
|
|
|
|
questions_for_viewer.append(question_data)
|
|
|
|
# Get user's previous attempts
|
|
attempts = crud.get_test_attempts(db, current_user.id, step_id)
|
|
attempts_data = [
|
|
{
|
|
"id": a.id,
|
|
"score": float(a.score),
|
|
"max_score": float(a.max_score),
|
|
"passed": a.passed,
|
|
"completed_at": a.completed_at.isoformat() if a.completed_at else None
|
|
}
|
|
for a in attempts
|
|
]
|
|
|
|
return schemas.SuccessResponse(data={
|
|
"questions": questions_for_viewer,
|
|
"settings": step.test_settings,
|
|
"attempts": attempts_data
|
|
})
|
|
|
|
@router.post("/steps/{step_id}/test/submit", response_model=schemas.SuccessResponse)
|
|
def submit_test(
|
|
step_id: int,
|
|
attempt: schemas.TestAttemptCreate,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(auth.get_current_user)
|
|
):
|
|
"""
|
|
Submit test answers and get results
|
|
"""
|
|
step = crud.get_step_by_id(db, step_id)
|
|
if not step:
|
|
raise HTTPException(status_code=404, detail="Step not found")
|
|
|
|
if step.step_type != 'test':
|
|
raise HTTPException(status_code=400, detail="Step is not a test")
|
|
|
|
# Check max attempts limit
|
|
settings = step.test_settings or {}
|
|
max_attempts = settings.get('max_attempts', 0)
|
|
|
|
if max_attempts > 0:
|
|
attempts = crud.get_test_attempts(db, current_user.id, step_id)
|
|
if len(attempts) >= max_attempts:
|
|
raise HTTPException(status_code=400, detail="Maximum attempts reached")
|
|
|
|
# Create attempt and calculate score
|
|
test_attempt = crud.create_test_attempt(db, current_user.id, step_id, attempt.answers)
|
|
if not test_attempt:
|
|
raise HTTPException(status_code=500, detail="Failed to create test attempt")
|
|
|
|
return schemas.SuccessResponse(data={
|
|
"attempt": {
|
|
"id": test_attempt.id,
|
|
"score": float(test_attempt.score),
|
|
"max_score": float(test_attempt.max_score),
|
|
"passed": test_attempt.passed,
|
|
"completed_at": test_attempt.completed_at.isoformat() if test_attempt.completed_at else None
|
|
}
|
|
})
|
|
|
|
@router.get("/steps/{step_id}/test/attempts", response_model=schemas.SuccessResponse)
|
|
def get_test_attempts(
|
|
step_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(auth.get_current_user)
|
|
):
|
|
"""
|
|
Get user's test attempts history
|
|
"""
|
|
step = crud.get_step_by_id(db, step_id)
|
|
if not step:
|
|
raise HTTPException(status_code=404, detail="Step not found")
|
|
|
|
attempts = crud.get_test_attempts(db, current_user.id, step_id)
|
|
attempts_data = [
|
|
{
|
|
"id": a.id,
|
|
"score": float(a.score),
|
|
"max_score": float(a.max_score),
|
|
"passed": a.passed,
|
|
"completed_at": a.completed_at.isoformat() if a.completed_at else None
|
|
}
|
|
for a in attempts
|
|
]
|
|
|
|
return schemas.SuccessResponse(data={"attempts": attempts_data})
|