Добавил слой Application

This commit is contained in:
2026-04-28 15:52:32 +03:00
parent df0e30a1ae
commit d64447f0be
25 changed files with 595 additions and 0 deletions
@@ -0,0 +1,36 @@
namespace UniVerse.Application.DTOs.Achievements;
public record AchievementDto(
int Id,
string Name,
string? Description,
string? IconUrl,
int XpReward,
int CoinReward,
string? Condition,
DateTime CreatedAt
);
public record UserAchievementDto(
int Id,
AchievementDto Achievement,
DateTime AwardedAt
);
public record CreateAchievementRequest(
string Name,
string? Description,
string? IconUrl,
int XpReward,
int CoinReward,
string? Condition
);
public record UpdateAchievementRequest(
string Name,
string? Description,
string? IconUrl,
int XpReward,
int CoinReward,
string? Condition
);
@@ -0,0 +1,11 @@
using UniVerse.Domain.Enums;
namespace UniVerse.Application.DTOs.Auth;
public record AuthResponse(string AccessToken, DateTime ExpiresAt, UserAuthDto User);
public record UserAuthDto(int Id, string Email, string? DisplayName, UserRole Role);
public record LoginMicrosoftRequest(string AuthorizationCode);
public record DevLoginRequest(string Email, string? DisplayName = null, UserRole Role = UserRole.Student);
@@ -0,0 +1,18 @@
namespace UniVerse.Application.DTOs.Common;
public record PaginationRequest(int Page = 1, int PageSize = 20);
public record PagedResult<T>(
List<T> Items,
int TotalCount,
int Page,
int PageSize,
int TotalPages
)
{
public static PagedResult<T> Create(List<T> items, int totalCount, int page, int pageSize)
{
var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
return new PagedResult<T>(items, totalCount, page, pageSize, totalPages);
}
}
@@ -0,0 +1,24 @@
using UniVerse.Application.DTOs.Tags;
namespace UniVerse.Application.DTOs.Courses;
public record CourseDto(
int Id,
string Name,
string? Description,
bool IsSynced,
List<TagDto> Tags,
DateTime CreatedAt
);
public record CreateCourseRequest(string Name, string? Description);
public record UpdateCourseRequest(string Name, string? Description);
public record CourseFilterRequest(
int? TagId,
string? Search,
bool? IsSynced,
int Page = 1,
int PageSize = 20
);
@@ -0,0 +1,13 @@
using UniVerse.Domain.Enums;
namespace UniVerse.Application.DTOs.Gamification;
public record CoinTransactionDto(
int Id,
int Amount,
CoinTransactionType Type,
int? ReviewId,
int? AchievementId,
string? Description,
DateTime CreatedAt
);
@@ -0,0 +1,86 @@
using UniVerse.Domain.Enums;
namespace UniVerse.Application.DTOs.Lectures;
public record LectureDto(
int Id,
int CourseId,
string CourseName,
int? TeacherId,
string? TeacherName,
int? LocationId,
string? LocationName,
string Title,
string? Description,
LectureFormat Format,
DateTime StartsAt,
DateTime EndsAt,
bool IsOpen,
int MaxEnrollments,
int EnrollmentsCount,
string? OnlineUrl,
DateTime CreatedAt
);
public record LectureDetailDto(
int Id,
int CourseId,
string CourseName,
int? TeacherId,
string? TeacherName,
int? LocationId,
string? LocationName,
string Title,
string? Description,
LectureFormat Format,
DateTime StartsAt,
DateTime EndsAt,
bool IsOpen,
int MaxEnrollments,
int EnrollmentsCount,
string? OnlineUrl,
DateTime CreatedAt,
bool IsEnrolled
);
public record CreateLectureRequest(
int CourseId,
int? TeacherId,
int? LocationId,
string Title,
string? Description,
LectureFormat Format,
DateTime StartsAt,
DateTime EndsAt,
bool IsOpen,
int MaxEnrollments,
string? OnlineUrl
);
public record UpdateLectureRequest(
int? TeacherId,
int? LocationId,
string Title,
string? Description,
LectureFormat Format,
DateTime StartsAt,
DateTime EndsAt,
bool IsOpen,
int MaxEnrollments,
string? OnlineUrl
);
public record LectureFilterRequest(
DateOnly? DateFrom,
DateOnly? DateTo,
int? CourseId,
int? TeacherId,
LectureFormat? Format,
bool? IsOpen,
int? TagId,
string? Search,
int Page = 1,
int PageSize = 20
);
public record EnrollmentDto(int Id, int UserId, string? UserName, string? UserEmail, bool Attended, DateTime CreatedAt);
@@ -0,0 +1,7 @@
namespace UniVerse.Application.DTOs.Locations;
public record LocationDto(int Id, string Name, string? Building, string? Room, string? Address, DateTime CreatedAt);
public record CreateLocationRequest(string Name, string? Building, string? Room, string? Address);
public record UpdateLocationRequest(string Name, string? Building, string? Room, string? Address);
@@ -0,0 +1,23 @@
using UniVerse.Domain.Enums;
namespace UniVerse.Application.DTOs.Reviews;
public record ReviewDto(
int Id,
int LectureId,
string? LectureTitle,
int UserId,
string? UserName,
ReviewRating Rating,
string? Text,
ReviewLlmStatus LlmStatus,
ReviewSentiment? Sentiment,
double? QualityScore,
bool? IsInformative,
string[]? LlmTags,
DateTime CreatedAt
);
public record CreateReviewRequest(int LectureId, ReviewRating Rating, string? Text);
public record UpdateReviewRequest(ReviewRating Rating, string? Text);
@@ -0,0 +1,14 @@
namespace UniVerse.Application.DTOs.Sync;
public record SyncScheduleRequest(
string? SpecialtyCode,
DateTime? TimeMin,
DateTime? TimeMax,
string? TypeId
);
public record SyncResultDto(int Created, int Updated, int Skipped, string? Error);
public record SyncStatusDto(DateTime? LastSyncAt, string Status, SyncResultDto? LastResult);
public record EmployeeDto(string? Id, string FullName, string? Department);
@@ -0,0 +1,11 @@
using UniVerse.Domain.Enums;
namespace UniVerse.Application.DTOs.Tags;
public record TagDto(int Id, string Name, TagType Type, int? ParentId, DateTime CreatedAt);
public record TagTreeDto(int Id, string Name, TagType Type, List<TagTreeDto> Children);
public record CreateTagRequest(string Name, TagType Type, int? ParentId);
public record UpdateTagRequest(string Name, TagType Type, int? ParentId);
@@ -0,0 +1,55 @@
using UniVerse.Domain.Enums;
namespace UniVerse.Application.DTOs.Users;
public record UserDto(
int Id,
string Email,
string? DisplayName,
string? AvatarUrl,
UserRole Role,
bool IsActive,
int Xp,
int Coins,
int Level,
DateTime CreatedAt
);
public record UserStatsDto(
int TotalLectures,
int AttendedLectures,
int TotalReviews,
int Xp,
int Coins,
int Level,
int AchievementsCount
);
public record UpdateUserRequest(
string? DisplayName,
string? AvatarUrl
);
public record UserFilterRequest(
string? Search,
UserRole? Role,
bool? IsActive,
int Page = 1,
int PageSize = 20
);
public record StudentProfileDto(
int Id,
string? StudentId,
string? GroupName,
int? EnrollmentYear,
string? Faculty,
string? Specialty
);
public record TeacherProfileDto(
int Id,
string? Department,
string? Title,
string? Bio
);
@@ -0,0 +1,12 @@
using UniVerse.Application.DTOs.Achievements;
namespace UniVerse.Application.Interfaces;
public interface IAchievementService
{
Task<List<AchievementDto>> GetAllAsync();
Task<AchievementDto> GetByIdAsync(int id);
Task<AchievementDto> CreateAsync(CreateAchievementRequest request);
Task<AchievementDto> UpdateAsync(int id, UpdateAchievementRequest request);
Task DeleteAsync(int id);
}
@@ -0,0 +1,13 @@
using UniVerse.Application.DTOs.Auth;
using UniVerse.Application.DTOs.Users;
namespace UniVerse.Application.Interfaces;
public interface IAuthService
{
Task<AuthResponse> LoginWithMicrosoftAsync(string authorizationCode);
Task<AuthResponse> DevLoginAsync(string email, string? displayName, Domain.Enums.UserRole role);
Task<AuthResponse> RefreshTokenAsync(string refreshToken);
Task RevokeRefreshTokenAsync(string refreshToken);
Task<UserDto> GetCurrentUserAsync(int userId);
}
@@ -0,0 +1,15 @@
using UniVerse.Application.DTOs.Common;
using UniVerse.Application.DTOs.Courses;
namespace UniVerse.Application.Interfaces;
public interface ICourseService
{
Task<PagedResult<CourseDto>> GetAllAsync(CourseFilterRequest filter);
Task<CourseDto> GetByIdAsync(int id);
Task<CourseDto> CreateAsync(CreateCourseRequest request);
Task<CourseDto> UpdateAsync(int id, UpdateCourseRequest request);
Task DeleteAsync(int id);
Task AddTagAsync(int courseId, int tagId);
Task RemoveTagAsync(int courseId, int tagId);
}
@@ -0,0 +1,16 @@
using UniVerse.Application.DTOs.Achievements;
using UniVerse.Application.DTOs.Common;
using UniVerse.Application.DTOs.Gamification;
using UniVerse.Domain.Enums;
namespace UniVerse.Application.Interfaces;
public interface IGamificationService
{
Task AwardCoinsAsync(int userId, int amount, CoinTransactionType type,
int? reviewId = null, int? achievementId = null, string? description = null);
Task CheckAndAwardAchievementsAsync(int userId);
int CalculateLevel(int xp);
Task<List<UserAchievementDto>> GetUserAchievementsAsync(int userId);
Task<PagedResult<CoinTransactionDto>> GetTransactionsAsync(int userId, PaginationRequest pagination);
}
@@ -0,0 +1,17 @@
using UniVerse.Application.DTOs.Common;
using UniVerse.Application.DTOs.Lectures;
namespace UniVerse.Application.Interfaces;
public interface ILectureService
{
Task<PagedResult<LectureDto>> GetAllAsync(LectureFilterRequest filter);
Task<LectureDetailDto> GetByIdAsync(int id, int? currentUserId = null);
Task<LectureDto> CreateAsync(CreateLectureRequest request);
Task<LectureDto> UpdateAsync(int id, UpdateLectureRequest request);
Task DeleteAsync(int id);
Task EnrollAsync(int lectureId, int userId);
Task UnenrollAsync(int lectureId, int userId);
Task MarkAttendanceAsync(int lectureId, int userId, bool attended);
Task<PagedResult<EnrollmentDto>> GetEnrollmentsAsync(int lectureId, PaginationRequest pagination);
}
@@ -0,0 +1,7 @@
namespace UniVerse.Application.Interfaces;
public interface ILlmAnalysisService
{
Task AnalyzeReviewAsync(int reviewId);
Task ProcessPendingReviewsAsync();
}
@@ -0,0 +1,13 @@
namespace UniVerse.Application.Interfaces;
public record LlmReviewAnalysis(
double QualityScore,
string Sentiment,
string[] Tags,
bool IsInformative
);
public interface ILlmClient
{
Task<LlmReviewAnalysis> AnalyzeReviewAsync(string reviewText, string lectureContext);
}
@@ -0,0 +1,12 @@
using UniVerse.Application.DTOs.Locations;
namespace UniVerse.Application.Interfaces;
public interface ILocationService
{
Task<List<LocationDto>> GetAllAsync();
Task<LocationDto> GetByIdAsync(int id);
Task<LocationDto> CreateAsync(CreateLocationRequest request);
Task<LocationDto> UpdateAsync(int id, UpdateLocationRequest request);
Task DeleteAsync(int id);
}
@@ -0,0 +1,16 @@
using UniVerse.Application.DTOs.Common;
using UniVerse.Application.DTOs.Reviews;
namespace UniVerse.Application.Interfaces;
public interface IReviewService
{
Task<ReviewDto> CreateAsync(int userId, CreateReviewRequest request);
Task<ReviewDto> GetByIdAsync(int id);
Task<ReviewDto> UpdateAsync(int id, int userId, UpdateReviewRequest request);
Task DeleteAsync(int id, int userId, bool isAdmin = false);
Task<PagedResult<ReviewDto>> GetByLectureAsync(int lectureId, PaginationRequest pagination);
Task<PagedResult<ReviewDto>> GetByUserAsync(int userId, PaginationRequest pagination);
Task<PagedResult<ReviewDto>> GetPendingAsync(PaginationRequest pagination);
Task ReanalyzeAsync(int id);
}
@@ -0,0 +1,25 @@
using UniVerse.Application.DTOs.Sync;
namespace UniVerse.Application.Interfaces;
public interface IScheduleSyncService
{
Task<SyncResultDto> SyncScheduleAsync(SyncScheduleRequest request);
Task<SyncResultDto> SyncRoomsAsync();
Task<List<EmployeeDto>> SearchEmployeesAsync(string fullname);
Task<SyncStatusDto> GetLastSyncStatusAsync();
}
public interface IModeusApiClient
{
Task<ModeusEventsResponse> SearchEventsAsync(SyncScheduleRequest request);
Task<ModeusRoomsResponse> SearchRoomsAsync();
Task<List<ModeusEmployee>> SearchEmployeeAsync(string fullname);
}
// Modeus API response models
public record ModeusEvent(string Id, string Name, DateTime StartsAt, DateTime EndsAt, string? RoomId, string? TeacherId, string? TypeId);
public record ModeusEventsResponse(List<ModeusEvent> Events);
public record ModeusRoom(string Id, string Name, string? Building);
public record ModeusRoomsResponse(List<ModeusRoom> Rooms);
public record ModeusEmployee(string? Id, string FullName, string? Department);
@@ -0,0 +1,15 @@
using UniVerse.Application.DTOs.Common;
using UniVerse.Application.DTOs.Tags;
using UniVerse.Domain.Enums;
namespace UniVerse.Application.Interfaces;
public interface ITagService
{
Task<List<TagDto>> GetAllAsync(TagType? type = null, int? parentId = null);
Task<TagDto> GetByIdAsync(int id);
Task<TagDto> CreateAsync(CreateTagRequest request);
Task<TagDto> UpdateAsync(int id, UpdateTagRequest request);
Task DeleteAsync(int id);
Task<List<TagTreeDto>> GetTreeAsync();
}
@@ -0,0 +1,15 @@
using UniVerse.Application.DTOs.Common;
using UniVerse.Application.DTOs.Users;
using UniVerse.Domain.Enums;
namespace UniVerse.Application.Interfaces;
public interface IUserService
{
Task<UserDto> GetByIdAsync(int id);
Task<UserDto> UpdateProfileAsync(int id, UpdateUserRequest request);
Task<UserStatsDto> GetStatsAsync(int id);
Task<PagedResult<UserDto>> GetAllAsync(UserFilterRequest filter);
Task SetRoleAsync(int id, UserRole role);
Task SetActiveAsync(int id, bool isActive);
}
@@ -0,0 +1,98 @@
using UniVerse.Application.DTOs.Achievements;
using UniVerse.Application.DTOs.Auth;
using UniVerse.Application.DTOs.Courses;
using UniVerse.Application.DTOs.Gamification;
using UniVerse.Application.DTOs.Lectures;
using UniVerse.Application.DTOs.Locations;
using UniVerse.Application.DTOs.Reviews;
using UniVerse.Application.DTOs.Tags;
using UniVerse.Application.DTOs.Users;
using UniVerse.Domain.Entities;
namespace UniVerse.Application.Mappings;
public static class MappingExtensions
{
// --- User ---
public static UserDto ToDto(this User user, int level) => new(
user.Id, user.Email, user.DisplayName, user.AvatarUrl,
user.Role, user.IsActive, user.Xp, user.Coins, level, user.CreatedAt
);
public static UserAuthDto ToAuthDto(this User user) => new(
user.Id, user.Email, user.DisplayName, user.Role
);
// --- Tag ---
public static TagDto ToDto(this Tag tag) => new(
tag.Id, tag.Name, tag.Type, tag.ParentId, tag.CreatedAt
);
public static TagTreeDto ToTreeDto(this Tag tag) => new(
tag.Id, tag.Name, tag.Type,
tag.Children.Select(c => c.ToTreeDto()).ToList()
);
// --- Location ---
public static LocationDto ToDto(this Location loc) => new(
loc.Id, loc.Name, loc.Building, loc.Room, loc.Address, loc.CreatedAt
);
// --- Course ---
public static CourseDto ToDto(this Course course) => new(
course.Id, course.Name, course.Description, course.IsSynced,
course.CourseTags.Select(ct => ct.Tag.ToDto()).ToList(),
course.CreatedAt
);
// --- Lecture ---
public static LectureDto ToDto(this Lecture lecture) => new(
lecture.Id, lecture.CourseId, lecture.Course?.Name ?? "",
lecture.TeacherId, lecture.Teacher?.DisplayName,
lecture.LocationId, lecture.Location?.Name,
lecture.Title, lecture.Description, lecture.Format,
lecture.StartsAt, lecture.EndsAt, lecture.IsOpen,
lecture.MaxEnrollments, lecture.Enrollments.Count,
lecture.OnlineUrl, lecture.CreatedAt
);
public static LectureDetailDto ToDetailDto(this Lecture lecture, bool isEnrolled) => new(
lecture.Id, lecture.CourseId, lecture.Course?.Name ?? "",
lecture.TeacherId, lecture.Teacher?.DisplayName,
lecture.LocationId, lecture.Location?.Name,
lecture.Title, lecture.Description, lecture.Format,
lecture.StartsAt, lecture.EndsAt, lecture.IsOpen,
lecture.MaxEnrollments, lecture.Enrollments.Count,
lecture.OnlineUrl, lecture.CreatedAt, isEnrolled
);
// --- Enrollment ---
public static EnrollmentDto ToDto(this LectureEnrollment enrollment) => new(
enrollment.Id, enrollment.UserId,
enrollment.User?.DisplayName, enrollment.User?.Email,
enrollment.Attended, enrollment.CreatedAt
);
// --- Review ---
public static ReviewDto ToDto(this Review review) => new(
review.Id, review.LectureId, review.Lecture?.Title,
review.UserId, review.User?.DisplayName,
review.Rating, review.Text, review.LlmStatus,
review.Sentiment, review.QualityScore, review.IsInformative,
review.LlmTags, review.CreatedAt
);
// --- Achievement ---
public static AchievementDto ToDto(this Achievement a) => new(
a.Id, a.Name, a.Description, a.IconUrl, a.XpReward, a.CoinReward, a.Condition, a.CreatedAt
);
public static UserAchievementDto ToDto(this UserAchievement ua) => new(
ua.Id, ua.Achievement.ToDto(), ua.AwardedAt
);
// --- CoinTransaction ---
public static CoinTransactionDto ToDto(this CoinTransaction ct) => new(
ct.Id, ct.Amount, ct.Type, ct.ReviewId, ct.AchievementId, ct.Description, ct.CreatedAt
);
}
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>UniVerse.Application</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="12.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.7.0" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.7.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\UniVerse.Domain\UniVerse.Domain.csproj" />
</ItemGroup>
</Project>