- Add Quiz entity with full domain logic (Create, Update, Activate, Deactivate) - Add QuizQuestion entity updated to use QuizId instead of LessonId - Add QuizRepository with all CRUD and query methods - Add QuizQuestionRepository with QuizId-based and LessonId-based (legacy) methods - Add QuizDto, CreateQuizDto, UpdateQuizDto, QuizWithQuestionsDto, QuizListItemDto - Add QuizQuestionDto updated with QuizId, LessonId, QuizTitle, Points fields - Add CreateQuizCommand, UpdateQuizCommand, DeleteQuizCommand, GetQuizWithQuestionsCommand - Add QuizService with full quiz management (CRUD, activate, deactivate, counts, queries) - Add QuizQuestionService updated with QuizId-based methods - Add QuizzesController with comprehensive endpoints (GET, POST, PUT, DELETE) - Add QuizzesController endpoints for quiz questions (by-quiz, random, active) - Update QuizQuestionsController with new QuizId-based endpoints (backward compatible) - Register QuizRepository and QuizService in DI container - Update IRepository interfaces with QuizId-based methods - Add SubmitQuizDto for quiz answer submission Clean Architecture layers maintained: - Domain: Quiz, QuizQuestion entities with business logic - Application: DTOs, Commands, Services - Infrastructure: Repositories, DbContext - Presentation: Controllers Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
221 lines
8.2 KiB
C#
221 lines
8.2 KiB
C#
using GermanApp.Domain.Entities;
|
|
using GermanApp.Domain.Interfaces;
|
|
using GermanApp.Infrastructure.Data.DbContext;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace GermanApp.Infrastructure.Data.Repositories;
|
|
|
|
/// <summary>
|
|
/// Entity Framework Core implementation of IQuizQuestionRepository.
|
|
/// This is part of the Infrastructure layer.
|
|
/// </summary>
|
|
public class QuizQuestionRepository : IQuizQuestionRepository
|
|
{
|
|
private readonly AppDbContext _context;
|
|
|
|
public QuizQuestionRepository(AppDbContext context)
|
|
{
|
|
_context = context;
|
|
}
|
|
|
|
public async Task<QuizQuestion?> GetByIdAsync(int id, CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.QuizQuestions
|
|
.Include(q => q.Quiz)
|
|
.ThenInclude(q => q.Lesson)
|
|
.Include(q => q.Options.OrderBy(o => o.Order))
|
|
.FirstOrDefaultAsync(q => q.Id == id, cancellationToken);
|
|
}
|
|
|
|
public async Task<IReadOnlyList<QuizQuestion>> GetAllAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.QuizQuestions
|
|
.Include(q => q.Quiz)
|
|
.ThenInclude(q => q.Lesson)
|
|
.Include(q => q.Options)
|
|
.AsNoTracking()
|
|
.ToListAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<QuizQuestion> AddAsync(QuizQuestion entity, CancellationToken cancellationToken = default)
|
|
{
|
|
await _context.QuizQuestions.AddAsync(entity, cancellationToken);
|
|
await _context.SaveChangesAsync(cancellationToken);
|
|
return entity;
|
|
}
|
|
|
|
public async Task UpdateAsync(QuizQuestion entity, CancellationToken cancellationToken = default)
|
|
{
|
|
_context.QuizQuestions.Update(entity);
|
|
await _context.SaveChangesAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task DeleteAsync(QuizQuestion entity, CancellationToken cancellationToken = default)
|
|
{
|
|
_context.QuizQuestions.Remove(entity);
|
|
await _context.SaveChangesAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<bool> ExistsAsync(int id, CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.QuizQuestions
|
|
.AnyAsync(q => q.Id == id, cancellationToken);
|
|
}
|
|
|
|
public async Task<IReadOnlyList<QuizQuestion>> GetByLessonAsync(int lessonId, CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.QuizQuestions
|
|
.Include(q => q.Quiz)
|
|
.ThenInclude(q => q.Lesson)
|
|
.Include(q => q.Options.OrderBy(o => o.Order))
|
|
.Where(q => q.Quiz != null && q.Quiz.LessonId == lessonId)
|
|
.OrderBy(q => q.Order)
|
|
.AsNoTracking()
|
|
.ToListAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<IReadOnlyList<QuizQuestion>> GetAllOrderedAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.QuizQuestions
|
|
.Include(q => q.Quiz)
|
|
.ThenInclude(q => q.Lesson)
|
|
.Include(q => q.Options.OrderBy(o => o.Order))
|
|
.OrderBy(q => q.Quiz.Lesson.Order)
|
|
.ThenBy(q => q.Quiz.Id)
|
|
.ThenBy(q => q.Order)
|
|
.AsNoTracking()
|
|
.ToListAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<IReadOnlyList<QuizQuestion>> GetActiveByLessonAsync(int lessonId, CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.QuizQuestions
|
|
.Include(q => q.Quiz)
|
|
.ThenInclude(q => q.Lesson)
|
|
.Include(q => q.Options.OrderBy(o => o.Order))
|
|
.Where(q => q.Quiz != null && q.Quiz.LessonId == lessonId && q.IsActive)
|
|
.OrderBy(q => q.Order)
|
|
.AsNoTracking()
|
|
.ToListAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<IReadOnlyList<QuizQuestion>> GetRandomQuestionsForLessonAsync(int lessonId, int count, CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.QuizQuestions
|
|
.Include(q => q.Quiz)
|
|
.ThenInclude(q => q.Lesson)
|
|
.Include(q => q.Options.OrderBy(o => o.Order))
|
|
.Where(q => q.Quiz != null && q.Quiz.LessonId == lessonId && q.IsActive)
|
|
.OrderBy(q => Guid.NewGuid()) // Random ordering
|
|
.Take(count)
|
|
.AsNoTracking()
|
|
.ToListAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<IReadOnlyList<QuizQuestion>> GetByDifficultyAsync(int difficulty, CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.QuizQuestions
|
|
.Include(q => q.Quiz)
|
|
.Include(q => q.Options)
|
|
.Where(q => q.Difficulty == difficulty)
|
|
.OrderBy(q => q.Order)
|
|
.AsNoTracking()
|
|
.ToListAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<IReadOnlyList<QuizQuestion>> GetByTypeAsync(QuestionType type, CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.QuizQuestions
|
|
.Include(q => q.Quiz)
|
|
.Include(q => q.Options)
|
|
.Where(q => q.Type == type)
|
|
.OrderBy(q => q.Order)
|
|
.AsNoTracking()
|
|
.ToListAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<bool> OrderExistsInLessonAsync(int lessonId, int order, int? excludeId = null, CancellationToken cancellationToken = default)
|
|
{
|
|
var query = _context.QuizQuestions
|
|
.Where(q => q.Quiz != null && q.Quiz.LessonId == lessonId && q.Order == order);
|
|
|
|
if (excludeId.HasValue)
|
|
{
|
|
query = query.Where(q => q.Id != excludeId.Value);
|
|
}
|
|
|
|
return await query.AnyAsync(cancellationToken);
|
|
}
|
|
|
|
// ============================================
|
|
// Quiz-based methods (QuizId)
|
|
// ============================================
|
|
|
|
public async Task<IReadOnlyList<QuizQuestion>> GetByQuizAsync(int quizId, CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.QuizQuestions
|
|
.Include(q => q.Quiz)
|
|
.Include(q => q.Options.OrderBy(o => o.Order))
|
|
.Where(q => q.QuizId == quizId)
|
|
.OrderBy(q => q.Order)
|
|
.AsNoTracking()
|
|
.ToListAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<IReadOnlyList<QuizQuestion>> GetActiveByQuizAsync(int quizId, CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.QuizQuestions
|
|
.Include(q => q.Quiz)
|
|
.Include(q => q.Options.OrderBy(o => o.Order))
|
|
.Where(q => q.QuizId == quizId && q.IsActive)
|
|
.OrderBy(q => q.Order)
|
|
.AsNoTracking()
|
|
.ToListAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<IReadOnlyList<QuizQuestion>> GetByQuizWithOptionsAsync(int quizId, CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.QuizQuestions
|
|
.Include(q => q.Quiz)
|
|
.Include(q => q.Options.OrderBy(o => o.Order))
|
|
.Where(q => q.QuizId == quizId)
|
|
.OrderBy(q => q.Order)
|
|
.AsNoTracking()
|
|
.ToListAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<IReadOnlyList<QuizQuestion>> GetRandomQuestionsForQuizAsync(int quizId, int count, CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.QuizQuestions
|
|
.Include(q => q.Quiz)
|
|
.Include(q => q.Options.OrderBy(o => o.Order))
|
|
.Where(q => q.QuizId == quizId && q.IsActive)
|
|
.OrderBy(q => Guid.NewGuid()) // Random ordering
|
|
.Take(count)
|
|
.AsNoTracking()
|
|
.ToListAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<int> GetTotalPointsForQuizAsync(int quizId, CancellationToken cancellationToken = default)
|
|
{
|
|
var questions = await _context.QuizQuestions
|
|
.Where(q => q.QuizId == quizId && q.IsActive)
|
|
.Select(q => q.Points)
|
|
.ToListAsync(cancellationToken);
|
|
|
|
return questions.Sum();
|
|
}
|
|
|
|
public async Task<bool> OrderExistsInQuizAsync(int quizId, int order, int? excludeId = null, CancellationToken cancellationToken = default)
|
|
{
|
|
var query = _context.QuizQuestions
|
|
.Where(q => q.QuizId == quizId && q.Order == order);
|
|
|
|
if (excludeId.HasValue)
|
|
{
|
|
query = query.Where(q => q.Id != excludeId.Value);
|
|
}
|
|
|
|
return await query.AnyAsync(cancellationToken);
|
|
}
|
|
}
|