diff --git a/LctMonolith/Services/Contracts/IGamificationService.cs b/LctMonolith/Services/Contracts/IGamificationService.cs
deleted file mode 100644
index 3f34cd9..0000000
--- a/LctMonolith/Services/Contracts/IGamificationService.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using LctMonolith.Models.Database;
-using LctMonolith.Services.Models;
-
-namespace LctMonolith.Services.Contracts;
-
-/// Gamification progression logic (progress, rewards, rank evaluation).
-public interface IGamificationService
-{
- /// Get current user progression snapshot (xp, mana, next rank requirements).
- Task GetProgressAsync(Guid userId, CancellationToken ct = default);
- /// Apply mission completion rewards (xp, mana, skills, artifacts) to user.
- Task ApplyMissionCompletionAsync(Guid userId, Mission mission, CancellationToken ct = default);
- /// Re-evaluate and apply rank upgrade if requirements are met.
- Task EvaluateRankUpgradeAsync(Guid userId, CancellationToken ct = default);
-}
diff --git a/LctMonolith/Services/Contracts/IMissionService.cs b/LctMonolith/Services/Contracts/IMissionService.cs
deleted file mode 100644
index 0e5a236..0000000
--- a/LctMonolith/Services/Contracts/IMissionService.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using LctMonolith.Models.Database;
-using LctMonolith.Services.Models;
-
-namespace LctMonolith.Services.Contracts;
-
-public interface IMissionService
-{
- Task CreateMissionAsync(CreateMissionModel model, CancellationToken ct = default);
- Task> GetAvailableMissionsAsync(Guid userId, CancellationToken ct = default);
- Task UpdateStatusAsync(Guid userId, Guid missionId, MissionStatus status, string? submissionData, CancellationToken ct = default);
-}
diff --git a/LctMonolith/Services/GamificationService.cs b/LctMonolith/Services/GamificationService.cs
deleted file mode 100644
index 5653a87..0000000
--- a/LctMonolith/Services/GamificationService.cs
+++ /dev/null
@@ -1,162 +0,0 @@
-using System.Linq;
-using LctMonolith.Database.UnitOfWork;
-using LctMonolith.Models.Database;
-using LctMonolith.Services.Contracts;
-using LctMonolith.Services.Models;
-using Microsoft.EntityFrameworkCore;
-using Serilog;
-
-namespace LctMonolith.Services;
-
-///
-/// Handles progression logic: mission completion rewards and rank advancement evaluation.
-///
-public class GamificationService : IGamificationService
-{
- private readonly IUnitOfWork _uow;
- private readonly INotificationService _notifications;
-
- public GamificationService(IUnitOfWork uow, INotificationService notifications)
- {
- _uow = uow;
- _notifications = notifications;
- }
-
- public async Task GetProgressAsync(Guid userId, CancellationToken ct = default)
- {
- var user = await _uow.Users
- .Query(u => u.Id == userId, null, u => u.Rank, u => u.Competencies)
- .FirstOrDefaultAsync(ct) ?? throw new KeyNotFoundException("User not found");
-
- var ranks = await _uow.Ranks
- .Query(null, q => q.OrderBy(r => r.Order), r => r.RequiredMissions, r => r.RequiredCompetencies)
- .ToListAsync(ct);
-
- var currentOrder = user.Rank?.Order ?? -1;
- var nextRank = ranks.FirstOrDefault(r => r.Order == currentOrder + 1);
- var snapshot = new ProgressSnapshot
- {
- Experience = user.Experience,
- Mana = user.Mana,
- CurrentRankId = user.RankId,
- CurrentRankName = user.Rank?.Name,
- NextRankId = nextRank?.Id,
- NextRankName = nextRank?.Name,
- RequiredExperienceForNextRank = nextRank?.RequiredExperience
- };
- if (nextRank != null)
- {
- // Outstanding missions
- var userMissionIds = await _uow.UserMissions.Query(um => um.UserId == userId).Select(um => um.MissionId).ToListAsync(ct);
- foreach (var rm in nextRank.RequiredMissions)
- {
- if (!userMissionIds.Contains(rm.MissionId))
- snapshot.OutstandingMissionIds.Add(rm.MissionId);
- }
- // Outstanding competencies
- foreach (var rc in nextRank.RequiredCompetencies)
- {
- var userComp = user.Competencies.FirstOrDefault(c => c.CompetencyId == rc.CompetencyId);
- var level = userComp?.Level ?? 0;
- if (level < rc.MinLevel)
- {
- snapshot.OutstandingCompetencies.Add(new OutstandingCompetency
- {
- CompetencyId = rc.CompetencyId,
- CompetencyName = rc.Competency?.Name,
- RequiredLevel = rc.MinLevel,
- CurrentLevel = level
- });
- }
- }
- }
- return snapshot;
- }
-
- public async Task ApplyMissionCompletionAsync(Guid userId, Mission mission, CancellationToken ct = default)
- {
- var user = await _uow.Users
- .Query(u => u.Id == userId, null, u => u.Competencies, u => u.Rank)
- .FirstOrDefaultAsync(ct) ?? throw new KeyNotFoundException("User not found");
-
- user.Experience += mission.ExperienceReward;
- user.Mana += mission.ManaReward;
- user.UpdatedAt = DateTime.UtcNow;
-
- // Competency rewards
- var compRewards = await _uow.MissionCompetencyRewards.Query(m => m.MissionId == mission.Id).ToListAsync(ct);
- foreach (var reward in compRewards)
- {
- var uc = user.Competencies.FirstOrDefault(c => c.CompetencyId == reward.CompetencyId);
- if (uc == null)
- {
- uc = new UserCompetency
- {
- UserId = userId,
- CompetencyId = reward.CompetencyId,
- Level = reward.LevelDelta,
- ProgressPoints = reward.ProgressPointsDelta
- };
- await _uow.UserCompetencies.AddAsync(uc, ct);
- }
- else
- {
- uc.Level += reward.LevelDelta;
- uc.ProgressPoints += reward.ProgressPointsDelta;
- }
- }
-
- // Artifacts
- var artRewards = await _uow.MissionArtifactRewards.Query(m => m.MissionId == mission.Id).ToListAsync(ct);
- foreach (var ar in artRewards)
- {
- var existing = await _uow.UserArtifacts.FindAsync(userId, ar.ArtifactId);
- if (existing == null)
- {
- await _uow.UserArtifacts.AddAsync(new UserArtifact
- {
- UserId = userId,
- ArtifactId = ar.ArtifactId,
- ObtainedAt = DateTime.UtcNow
- }, ct);
- }
- }
-
- await _uow.SaveChangesAsync(ct);
- await EvaluateRankUpgradeAsync(userId, ct);
- }
-
- public async Task EvaluateRankUpgradeAsync(Guid userId, CancellationToken ct = default)
- {
- var user = await _uow.Users
- .Query(u => u.Id == userId, null, u => u.Rank, u => u.Competencies)
- .FirstOrDefaultAsync(ct) ?? throw new KeyNotFoundException("User not found");
-
- var ranks = await _uow.Ranks
- .Query(null, q => q.OrderBy(r => r.Order), r => r.RequiredMissions, r => r.RequiredCompetencies)
- .ToListAsync(ct);
-
- var currentOrder = user.Rank?.Order ?? -1;
- var nextRank = ranks.FirstOrDefault(r => r.Order == currentOrder + 1);
- if (nextRank == null) return;
-
- if (user.Experience < nextRank.RequiredExperience) return;
- var completedMissionIds = await _uow.UserMissions
- .Query(um => um.UserId == userId && um.Status == MissionStatus.Completed)
- .Select(x => x.MissionId)
- .ToListAsync(ct);
- if (nextRank.RequiredMissions.Any(rm => !completedMissionIds.Contains(rm.MissionId))) return;
- foreach (var rc in nextRank.RequiredCompetencies)
- {
- var uc = user.Competencies.FirstOrDefault(c => c.CompetencyId == rc.CompetencyId);
- if (uc == null || uc.Level < rc.MinLevel)
- return;
- }
- user.RankId = nextRank.Id;
- user.Rank = nextRank;
- user.UpdatedAt = DateTime.UtcNow;
- await _uow.SaveChangesAsync(ct);
- Log.Information("User {UserId} promoted to rank {Rank}", userId, nextRank.Name);
- await _notifications.CreateAsync(userId, "rank", "Повышение ранга", $"Вы получили ранг '{nextRank.Name}'", ct);
- }
-}
diff --git a/LctMonolith/Services/InventoryService.cs b/LctMonolith/Services/InventoryService.cs
deleted file mode 100644
index 5a84d21..0000000
--- a/LctMonolith/Services/InventoryService.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using LctMonolith.Database.UnitOfWork;
-using LctMonolith.Models.Database;
-using LctMonolith.Services.Contracts;
-using Microsoft.EntityFrameworkCore;
-
-namespace LctMonolith.Services;
-
-///
-/// Provides read-only access to user-owned inventory (store items and artifacts).
-///
-public class InventoryService : IInventoryService
-{
- private readonly IUnitOfWork _uow;
-
- public InventoryService(IUnitOfWork uow)
- {
- _uow = uow;
- }
-
- public async Task> GetStoreInventoryAsync(Guid userId, CancellationToken ct = default)
- {
- return await _uow.UserInventoryItems
- .Query(ii => ii.UserId == userId, null, ii => ii.StoreItem)
- .OrderByDescending(i => i.AcquiredAt)
- .ToListAsync(ct);
- }
-
- public async Task> GetArtifactsAsync(Guid userId, CancellationToken ct = default)
- {
- return await _uow.UserArtifacts
- .Query(a => a.UserId == userId, null, a => a.Artifact)
- .OrderByDescending(a => a.ObtainedAt)
- .ToListAsync(ct);
- }
-}
diff --git a/LctMonolith/Services/MissionService.cs b/LctMonolith/Services/MissionService.cs
deleted file mode 100644
index feaa2d4..0000000
--- a/LctMonolith/Services/MissionService.cs
+++ /dev/null
@@ -1,113 +0,0 @@
-using LctMonolith.Models.Database;
-using LctMonolith.Services.Models;
-using Microsoft.EntityFrameworkCore;
-using Serilog;
-using System.Text.Json;
-using System.Linq;
-using LctMonolith.Database.UnitOfWork;
-using LctMonolith.Services.Contracts;
-
-namespace LctMonolith.Services;
-
-///
-/// Mission management and user mission state transitions.
-///
-public class MissionService : IMissionService
-{
- private readonly IUnitOfWork _uow;
- private readonly IGamificationService _gamification;
-
- public MissionService(IUnitOfWork uow, IGamificationService gamification)
- {
- _uow = uow;
- _gamification = gamification;
- }
-
- public async Task CreateMissionAsync(CreateMissionModel model, CancellationToken ct = default)
- {
- var mission = new Mission
- {
- Title = model.Title,
- Description = model.Description,
- Branch = model.Branch,
- Category = model.Category,
- MinRankId = model.MinRankId,
- ExperienceReward = model.ExperienceReward,
- ManaReward = model.ManaReward,
- IsActive = true
- };
- await _uow.Missions.AddAsync(mission, ct);
- foreach (var cr in model.CompetencyRewards)
- {
- await _uow.MissionCompetencyRewards.AddAsync(new MissionCompetencyReward
- {
- MissionId = mission.Id,
- CompetencyId = cr.CompetencyId,
- LevelDelta = cr.LevelDelta,
- ProgressPointsDelta = cr.ProgressPointsDelta
- }, ct);
- }
- foreach (var artId in model.ArtifactRewardIds.Distinct())
- {
- await _uow.MissionArtifactRewards.AddAsync(new MissionArtifactReward
- {
- MissionId = mission.Id,
- ArtifactId = artId
- }, ct);
- }
- await _uow.SaveChangesAsync(ct);
- await LogEventAsync(EventType.MissionStatusChanged, null, new { action = "created", missionId = mission.Id }, ct);
- return mission;
- }
-
- public async Task> GetAvailableMissionsAsync(Guid userId, CancellationToken ct = default)
- {
- var user = await _uow.Users.Query(u => u.Id == userId, null, u => u.Rank).FirstOrDefaultAsync(ct)
- ?? throw new KeyNotFoundException("User not found");
- var missions = await _uow.Missions.Query(m => m.IsActive, null, m => m.MinRank).ToListAsync(ct);
- var userOrder = user.Rank?.Order ?? int.MinValue;
- return missions.Where(m => m.MinRank == null || m.MinRank.Order <= userOrder + 1);
- }
-
- public async Task UpdateStatusAsync(Guid userId, Guid missionId, MissionStatus status, string? submissionData, CancellationToken ct = default)
- {
- var mission = await _uow.Missions.Query(m => m.Id == missionId).FirstOrDefaultAsync(ct)
- ?? throw new KeyNotFoundException("Mission not found");
- var userMission = await _uow.UserMissions.FindAsync(userId, missionId);
- if (userMission == null)
- {
- userMission = new UserMission
- {
- UserId = userId,
- MissionId = missionId,
- Status = MissionStatus.Available
- };
- await _uow.UserMissions.AddAsync(userMission, ct);
- }
- userMission.Status = status;
- userMission.SubmissionData = submissionData;
- userMission.UpdatedAt = DateTime.UtcNow;
- await _uow.SaveChangesAsync(ct);
- await LogEventAsync(EventType.MissionStatusChanged, userId, new { missionId, status }, ct);
- if (status == MissionStatus.Completed)
- {
- await _gamification.ApplyMissionCompletionAsync(userId, mission, ct);
- await LogEventAsync(EventType.RewardGranted, userId, new { missionId, mission.ExperienceReward, mission.ManaReward }, ct);
- }
- return userMission;
- }
-
- private async Task LogEventAsync(EventType type, Guid? userId, object data, CancellationToken ct)
- {
- if (userId == null && type != EventType.MissionStatusChanged) return;
- var evt = new EventLog
- {
- Type = type,
- UserId = userId ?? Guid.Empty,
- Data = JsonSerializer.Serialize(data)
- };
- await _uow.EventLogs.AddAsync(evt, ct);
- await _uow.SaveChangesAsync(ct);
- Log.Debug("Event {Type} logged {Data}", type, evt.Data);
- }
-}