- 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>
277 lines
10 KiB
C#
277 lines
10 KiB
C#
using GermanApp.Domain.Entities;
|
|
|
|
namespace GermanApp.Domain.Interfaces;
|
|
|
|
/// <summary>
|
|
/// Generic repository interface for domain entities.
|
|
/// All repositories should implement this interface.
|
|
/// </summary>
|
|
/// <typeparam name="TEntity">The entity type</typeparam>
|
|
/// <typeparam name="TId">The identifier type (usually int or Guid)</typeparam>
|
|
public interface IRepository<TEntity, TId> where TEntity : class
|
|
{
|
|
/// <summary>
|
|
/// Gets an entity by its identifier.
|
|
/// </summary>
|
|
Task<TEntity?> GetByIdAsync(TId id, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Gets all entities.
|
|
/// </summary>
|
|
Task<IReadOnlyList<TEntity>> GetAllAsync(CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Adds a new entity.
|
|
/// </summary>
|
|
Task<TEntity> AddAsync(TEntity entity, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Updates an existing entity.
|
|
/// </summary>
|
|
Task UpdateAsync(TEntity entity, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Deletes an entity.
|
|
/// </summary>
|
|
Task DeleteAsync(TEntity entity, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Checks if an entity with the given identifier exists.
|
|
/// </summary>
|
|
Task<bool> ExistsAsync(TId id, CancellationToken cancellationToken = default);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Repository interface for Level entities.
|
|
/// </summary>
|
|
public interface ILevelRepository : IRepository<Level, int>
|
|
{
|
|
/// <summary>
|
|
/// Gets a level by its code (e.g., "A1", "B2").
|
|
/// </summary>
|
|
Task<Level?> GetByCodeAsync(string code, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Gets all levels ordered by their sort order.
|
|
/// </summary>
|
|
Task<IReadOnlyList<Level>> GetAllOrderedAsync(CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Gets the first level (lowest order number).
|
|
/// </summary>
|
|
Task<Level?> GetFirstLevelAsync(CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Gets the next level after the specified one.
|
|
/// </summary>
|
|
Task<Level?> GetNextLevelAsync(int currentLevelId, CancellationToken cancellationToken = default);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Repository interface for Lesson entities.
|
|
/// </summary>
|
|
public interface ILessonRepository : IRepository<Lesson, int>
|
|
{
|
|
/// <summary>
|
|
/// Gets lessons by level.
|
|
/// </summary>
|
|
Task<IReadOnlyList<Lesson>> GetByLevelAsync(int levelId, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Gets all lessons ordered by level and lesson order.
|
|
/// </summary>
|
|
Task<IReadOnlyList<Lesson>> GetAllOrderedAsync(CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Gets the first lesson in a level.
|
|
/// </summary>
|
|
Task<Lesson?> GetFirstLessonInLevelAsync(int levelId, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Gets the next lesson after the specified one (in the same level).
|
|
/// </summary>
|
|
Task<Lesson?> GetNextLessonAsync(int currentLessonId, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Gets lessons that the user can access (completed previous lessons or first in level).
|
|
/// </summary>
|
|
Task<IReadOnlyList<Lesson>> GetAccessibleLessonsAsync(int userId, CancellationToken cancellationToken = default);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Repository interface for UserProgress entities.
|
|
/// </summary>
|
|
public interface IUserProgressRepository : IRepository<UserProgress, int>
|
|
{
|
|
/// <summary>
|
|
/// Gets user progress for a specific user and lesson.
|
|
/// </summary>
|
|
Task<UserProgress?> GetByUserAndLessonAsync(int userId, int lessonId, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Gets all progress records for a user.
|
|
/// </summary>
|
|
Task<IReadOnlyList<UserProgress>> GetByUserAsync(int userId, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Gets progress for all lessons in a specific level for a user.
|
|
/// </summary>
|
|
Task<IReadOnlyList<UserProgress>> GetByUserAndLevelAsync(int userId, int levelId, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Checks if a user has completed a lesson (with 80% or higher score).
|
|
/// </summary>
|
|
Task<bool> HasUserCompletedLessonAsync(int userId, int lessonId, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Gets the user's average score for a level.
|
|
/// </summary>
|
|
Task<double> GetAverageScoreForLevelAsync(int userId, int levelId, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Gets the percentage of lessons completed in a level.
|
|
/// </summary>
|
|
Task<double> GetLevelCompletionPercentageAsync(int userId, int levelId, CancellationToken cancellationToken = default);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Repository interface for QuizQuestion entities.
|
|
/// </summary>
|
|
public interface IQuizQuestionRepository : IRepository<QuizQuestion, int>
|
|
{
|
|
/// <summary>
|
|
/// Gets quiz questions by lesson ID.
|
|
/// </summary>
|
|
Task<IReadOnlyList<QuizQuestion>> GetByLessonAsync(int lessonId, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Gets all quiz questions ordered by lesson and question order.
|
|
/// </summary>
|
|
Task<IReadOnlyList<QuizQuestion>> GetAllOrderedAsync(CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Gets active quiz questions by lesson ID.
|
|
/// </summary>
|
|
Task<IReadOnlyList<QuizQuestion>> GetActiveByLessonAsync(int lessonId, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Gets a random set of quiz questions for a lesson.
|
|
/// </summary>
|
|
Task<IReadOnlyList<QuizQuestion>> GetRandomQuestionsForLessonAsync(int lessonId, int count, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Gets quiz questions by difficulty level.
|
|
/// </summary>
|
|
Task<IReadOnlyList<QuizQuestion>> GetByDifficultyAsync(int difficulty, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Gets quiz questions by type.
|
|
/// </summary>
|
|
Task<IReadOnlyList<QuizQuestion>> GetByTypeAsync(QuestionType type, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Checks if a quiz question with the given order exists in a lesson.
|
|
/// </summary>
|
|
Task<bool> OrderExistsInLessonAsync(int lessonId, int order, int? excludeId = null, CancellationToken cancellationToken = default);
|
|
|
|
// ============================================
|
|
// Quiz-based methods (QuizId)
|
|
// ============================================
|
|
|
|
/// <summary>
|
|
/// Gets quiz questions by quiz ID.
|
|
/// </summary>
|
|
Task<IReadOnlyList<QuizQuestion>> GetByQuizAsync(int quizId, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Gets active quiz questions by quiz ID.
|
|
/// </summary>
|
|
Task<IReadOnlyList<QuizQuestion>> GetActiveByQuizAsync(int quizId, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Gets quiz questions by quiz with options loaded.
|
|
/// </summary>
|
|
Task<IReadOnlyList<QuizQuestion>> GetByQuizWithOptionsAsync(int quizId, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Gets a random set of quiz questions for a quiz.
|
|
/// </summary>
|
|
Task<IReadOnlyList<QuizQuestion>> GetRandomQuestionsForQuizAsync(int quizId, int count, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Gets the total points for a quiz.
|
|
/// </summary>
|
|
Task<int> GetTotalPointsForQuizAsync(int quizId, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Checks if a quiz question with the given order exists in a quiz.
|
|
/// </summary>
|
|
Task<bool> OrderExistsInQuizAsync(int quizId, int order, int? excludeId = null, CancellationToken cancellationToken = default);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Repository interface for Quiz entities.
|
|
/// </summary>
|
|
public interface IQuizRepository : IRepository<Quiz, int>
|
|
{
|
|
/// <summary>
|
|
/// Gets quizzes by lesson ID.
|
|
/// </summary>
|
|
Task<IReadOnlyList<Quiz>> GetByLessonAsync(int lessonId, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Gets all quizzes ordered by lesson and quiz order.
|
|
/// </summary>
|
|
Task<IReadOnlyList<Quiz>> GetAllOrderedAsync(CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Gets active quizzes by lesson ID.
|
|
/// </summary>
|
|
Task<IReadOnlyList<Quiz>> GetActiveByLessonAsync(int lessonId, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Gets a quiz with its questions and options.
|
|
/// </summary>
|
|
Task<Quiz?> GetWithQuestionsAsync(int id, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Gets the first quiz for a lesson.
|
|
/// </summary>
|
|
Task<Quiz?> GetFirstByLessonAsync(int lessonId, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Checks if a quiz exists for a lesson.
|
|
/// </summary>
|
|
Task<bool> ExistsByLessonAsync(int lessonId, CancellationToken cancellationToken = default);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Repository interface for QuizOption entities.
|
|
/// </summary>
|
|
public interface IQuizOptionRepository : IRepository<QuizOption, int>
|
|
{
|
|
/// <summary>
|
|
/// Gets quiz options by question ID.
|
|
/// </summary>
|
|
Task<IReadOnlyList<QuizOption>> GetByQuestionAsync(int questionId, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Gets the correct option for a question.
|
|
/// </summary>
|
|
Task<QuizOption?> GetCorrectOptionAsync(int questionId, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Gets all correct options for a question (for MultipleSelect type).
|
|
/// </summary>
|
|
Task<IReadOnlyList<QuizOption>> GetCorrectOptionsAsync(int questionId, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Deletes all options for a question.
|
|
/// </summary>
|
|
Task DeleteByQuestionAsync(int questionId, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Checks if the order exists for a question.
|
|
/// </summary>
|
|
Task<bool> OrderExistsForQuestionAsync(int questionId, int order, int? excludeId = null, CancellationToken cancellationToken = default);
|
|
}
|