Files
UniVerse/backend/UniVerse.Infrastructure/Services/UserService.cs
serega404 a8d51df3f1
Backend CI / build-and-test (push) Successful in 40s
Frontend CI / build-and-check (push) Failing after 19s
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Successful in 6s
🚀 Create and publish a Docker image / Build & publish backend image (push) Successful in 24s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Successful in 27s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Successful in 5s
feat: добавил эндпоинт для получения статистики админского дашборда
2026-05-30 01:23:57 +03:00

209 lines
7.8 KiB
C#

using Microsoft.EntityFrameworkCore;
using UniVerse.Application.DTOs.Common;
using UniVerse.Application.DTOs.Lectures;
using UniVerse.Application.DTOs.Users;
using UniVerse.Application.Interfaces;
using UniVerse.Application.Mappings;
using UniVerse.Domain.Enums;
using UniVerse.Domain.Exceptions;
using UniVerse.Domain.Services;
using UniVerse.Infrastructure.Data;
namespace UniVerse.Infrastructure.Services;
public class UserService : IUserService
{
private readonly AppDbContext _db;
private readonly IGamificationService _gamification;
public UserService(AppDbContext db, IGamificationService gamification)
{
_db = db;
_gamification = gamification;
}
public async Task<UserDto> GetByIdAsync(int id)
{
var user = await _db.Users
.Include(u => u.Roles)
.FirstOrDefaultAsync(u => u.Id == id)
?? throw new NotFoundException("User", id);
return user.ToDto(await _gamification.CalculateLevelAsync(user.Xp));
}
public async Task<UserDto> UpdateProfileAsync(int id, UpdateUserRequest request)
{
var user = await _db.Users
.Include(u => u.Roles)
.FirstOrDefaultAsync(u => u.Id == id)
?? throw new NotFoundException("User", id);
if (request.DisplayName != null) user.DisplayName = request.DisplayName;
if (request.AvatarUrl != null) user.AvatarUrl = request.AvatarUrl;
user.UpdatedAt = DateTime.UtcNow;
await _db.SaveChangesAsync();
await _gamification.CheckAndAwardAchievementsAsync(id);
return user.ToDto(await _gamification.CalculateLevelAsync(user.Xp));
}
public async Task<UserStatsDto> GetStatsAsync(int id)
{
var user = await _db.Users.FindAsync(id)
?? throw new NotFoundException("User", id);
var totalLectures = await _db.LectureEnrollments.CountAsync(e => e.UserId == id);
var attended = await _db.LectureEnrollments.CountAsync(e => e.UserId == id && e.Attended);
var reviews = await _db.Reviews.CountAsync(r => r.UserId == id);
var achievements = await _db.UserAchievements.CountAsync(ua => ua.UserId == id);
var activeEnrollments = await _db.LectureEnrollments
.CountAsync(e => e.UserId == id && !e.Attended);
var level = await _gamification.CalculateLevelAsync(user.Xp);
var levelProgress = await _gamification.GetLevelProgressAsync(user.Xp);
var slotLimit = EnrollmentSlotPolicy.GetLimitForLevel(level);
var slotRules = EnrollmentSlotPolicy.Rules
.Select(rule => new EnrollmentSlotRuleDto(rule.Level, rule.Slots))
.ToList();
return new UserStatsDto(
totalLectures, attended, reviews,
user.Xp, user.Coins, level, achievements,
levelProgress.CurrentLevelXp, levelProgress.NextLevelXp,
activeEnrollments, slotLimit, slotRules
);
}
public async Task<AdminDashboardStatsDto> GetAdminDashboardStatsAsync()
{
var usersCount = await _db.Users
.CountAsync(user => !user.Roles.Any(role => role.Role == UserRole.Teacher));
var lecturesCount = await _db.Lectures.CountAsync();
var enrollmentsCount = await _db.LectureEnrollments.CountAsync();
var pendingReviewsCount = await _db.Reviews.CountAsync(review => review.LlmStatus == ReviewLlmStatus.Pending);
return new AdminDashboardStatsDto(usersCount, lecturesCount, enrollmentsCount, pendingReviewsCount);
}
public async Task<PagedResult<LectureDto>> GetEnrollmentsAsync(int id, PaginationRequest pagination)
{
if (!await _db.Users.AnyAsync(u => u.Id == id))
throw new NotFoundException("User", id);
var query = _db.LectureEnrollments
.Where(e => e.UserId == id)
.Include(e => e.Lecture)
.ThenInclude(l => l.Course)
.Include(e => e.Lecture)
.ThenInclude(l => l.Teacher)
.Include(e => e.Lecture)
.ThenInclude(l => l.Location)
.Include(e => e.Lecture)
.ThenInclude(l => l.Enrollments);
var total = await query.CountAsync();
var enrollments = await query
.OrderBy(e => e.Lecture.StartsAt)
.Skip((pagination.Page - 1) * pagination.PageSize)
.Take(pagination.PageSize)
.ToListAsync();
return PagedResult<LectureDto>.Create(
enrollments.Select(e => e.Lecture.ToDto(isEnrolled: true)).ToList(),
total,
pagination.Page,
pagination.PageSize);
}
public async Task<PagedResult<UserDto>> GetAllAsync(UserFilterRequest filter)
{
var query = _db.Users.AsQueryable();
if (!string.IsNullOrEmpty(filter.Search))
{
var search = filter.Search.ToLower();
query = query.Where(u =>
u.Email.ToLower().Contains(search) ||
(u.DisplayName != null && u.DisplayName.ToLower().Contains(search)));
}
query = query.Include(u => u.Roles);
if (filter.Role.HasValue)
{
var role = filter.Role.Value;
query = query.Where(u =>
u.Roles.Count == 1 &&
u.Roles.Any(ur => ur.Role == role));
}
if (filter.IsActive.HasValue)
query = query.Where(u => u.IsActive == filter.IsActive.Value);
var total = await query.CountAsync();
var users = await query
.OrderByDescending(u => u.CreatedAt)
.Skip((filter.Page - 1) * filter.PageSize)
.Take(filter.PageSize)
.ToListAsync();
var items = new List<UserDto>(users.Count);
foreach (var user in users)
items.Add(user.ToDto(await _gamification.CalculateLevelAsync(user.Xp)));
return PagedResult<UserDto>.Create(items, total, filter.Page, filter.PageSize);
}
public async Task SetRolesAsync(int id, IReadOnlyCollection<UserRole> roles)
{
var normalizedRoles = roles.Distinct().ToList();
if (normalizedRoles.Count == 0)
throw new ForbiddenException("At least one role is required.");
var user = await _db.Users
.Include(u => u.Roles)
.FirstOrDefaultAsync(u => u.Id == id)
?? throw new NotFoundException("User", id);
var existing = user.Roles.Select(r => r.Role).ToHashSet();
var toRemove = user.Roles.Where(r => !normalizedRoles.Contains(r.Role)).ToList();
foreach (var item in toRemove)
user.Roles.Remove(item);
var toAdd = normalizedRoles.Where(r => !existing.Contains(r)).ToList();
foreach (var role in toAdd)
user.Roles.Add(new Domain.Entities.UserRoleAssignment { UserId = user.Id, Role = role });
await EnsureProfilesForRolesAsync(user.Id, normalizedRoles);
user.UpdatedAt = DateTime.UtcNow;
await _db.SaveChangesAsync();
}
public async Task SetActiveAsync(int id, bool isActive)
{
var user = await _db.Users.FindAsync(id)
?? throw new NotFoundException("User", id);
user.IsActive = isActive;
user.UpdatedAt = DateTime.UtcNow;
await _db.SaveChangesAsync();
}
private async Task EnsureProfilesForRolesAsync(int userId, IReadOnlyCollection<UserRole> roles)
{
if (roles.Contains(UserRole.Student))
{
var hasStudentProfile = await _db.StudentProfiles.AnyAsync(p => p.UserId == userId);
if (!hasStudentProfile)
_db.StudentProfiles.Add(new Domain.Entities.StudentProfile { UserId = userId });
}
if (roles.Contains(UserRole.Teacher))
{
var hasTeacherProfile = await _db.TeacherProfiles.AnyAsync(p => p.UserId == userId);
if (!hasTeacherProfile)
_db.TeacherProfiles.Add(new Domain.Entities.TeacherProfile { UserId = userId });
}
}
}