Implement story integration foundation: - Create Domain/Entities/StorySegment.cs with full CRUD methods - Create Domain/Entities/StoryProgress.cs for user story progress tracking - Create Application/DTOs/StorySegmentDto.cs with multiple DTO types - Create Domain/Interfaces/IStoryRepository.cs - Create Domain/Interfaces/IStoryProgressRepository.cs - Create Infrastructure/Data/Repositories/StoryRepository.cs - Create Infrastructure/Data/Repositories/StoryProgressRepository.cs - Add DbSets and entity configurations to AppDbContext - Add navigation properties to Level, Lesson, and User entities Next: Phase 2 - Backend Services (StoryService, StoryGenerationService) Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
127 lines
4.2 KiB
C#
127 lines
4.2 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
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 IStoryProgressRepository.
|
|
/// This is part of the Infrastructure layer.
|
|
/// </summary>
|
|
public class StoryProgressRepository : IStoryProgressRepository
|
|
{
|
|
private readonly AppDbContext _context;
|
|
|
|
public StoryProgressRepository(AppDbContext context)
|
|
{
|
|
_context = context;
|
|
}
|
|
|
|
public async Task<IReadOnlyList<StoryProgress>> GetByUserAndLevelAsync(
|
|
int userId,
|
|
int levelId,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.StoryProgress
|
|
.Where(p => p.UserId == userId && p.LevelId == levelId)
|
|
.Include(p => p.StorySegment)
|
|
.OrderBy(p => p.StorySegment != null ? p.StorySegment.Order : 0)
|
|
.AsNoTracking()
|
|
.ToListAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<StoryProgress?> GetByUserAndSegmentAsync(
|
|
int userId,
|
|
int storySegmentId,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.StoryProgress
|
|
.FirstOrDefaultAsync(
|
|
p => p.UserId == userId && p.StorySegmentId == storySegmentId,
|
|
cancellationToken);
|
|
}
|
|
|
|
public async Task<int> GetHighestUnlockedOrderAsync(
|
|
int userId,
|
|
int levelId,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var highestOrder = await _context.StoryProgress
|
|
.Where(p => p.UserId == userId && p.LevelId == levelId)
|
|
.Include(p => p.StorySegment)
|
|
.Select(p => p.StorySegment != null ? p.StorySegment.Order : 0)
|
|
.MaxAsync(cancellationToken);
|
|
|
|
return highestOrder == null ? 0 : highestOrder;
|
|
}
|
|
|
|
public async Task<bool> IsSegmentUnlockedAsync(
|
|
int userId,
|
|
int storySegmentId,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.StoryProgress
|
|
.AnyAsync(
|
|
p => p.UserId == userId && p.StorySegmentId == storySegmentId,
|
|
cancellationToken);
|
|
}
|
|
|
|
public async Task<bool> IsSegmentCompletedAsync(
|
|
int userId,
|
|
int storySegmentId,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.StoryProgress
|
|
.AnyAsync(
|
|
p => p.UserId == userId
|
|
&& p.StorySegmentId == storySegmentId
|
|
&& p.IsCompleted,
|
|
cancellationToken);
|
|
}
|
|
|
|
public async Task<StoryProgress> AddAsync(StoryProgress progress, CancellationToken cancellationToken = default)
|
|
{
|
|
await _context.StoryProgress.AddAsync(progress, cancellationToken);
|
|
await _context.SaveChangesAsync(cancellationToken);
|
|
return progress;
|
|
}
|
|
|
|
public async Task UpdateAsync(StoryProgress progress, CancellationToken cancellationToken = default)
|
|
{
|
|
_context.StoryProgress.Update(progress);
|
|
await _context.SaveChangesAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<int> GetTotalSegmentsForLevelAsync(int levelId, CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.StorySegments
|
|
.CountAsync(s => s.LevelId == levelId && s.IsActive, cancellationToken);
|
|
}
|
|
|
|
public async Task<int> GetUnlockedCountAsync(
|
|
int userId,
|
|
int levelId,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.StoryProgress
|
|
.CountAsync(
|
|
p => p.UserId == userId && p.LevelId == levelId,
|
|
cancellationToken);
|
|
}
|
|
|
|
public async Task<int> GetCompletedCountAsync(
|
|
int userId,
|
|
int levelId,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.StoryProgress
|
|
.CountAsync(
|
|
p => p.UserId == userId && p.LevelId == levelId && p.IsCompleted,
|
|
cancellationToken);
|
|
}
|
|
}
|