feat: Fixed errors

This commit is contained in:
ereshk1gal
2025-09-30 02:16:37 +03:00
parent 8d68a719d4
commit 8c0809d620
68 changed files with 538 additions and 490 deletions

View File

@@ -1,4 +1,5 @@
using LctMonolith.Infrastructure.UnitOfWork;
using LctMonolith.Database.UnitOfWork;
using LctMonolith.Models;
using LctMonolith.Services.Models;
using Microsoft.EntityFrameworkCore;
@@ -16,7 +17,7 @@ public class AnalyticsService : IAnalyticsService
{
var totalUsers = await _uow.Users.Query().CountAsync(ct);
var totalMissions = await _uow.Missions.Query().CountAsync(ct);
var completedMissions = await _uow.UserMissions.Query(um => um.Status == Domain.Entities.MissionStatus.Completed).CountAsync(ct);
var completedMissions = await _uow.UserMissions.Query(um => um.Status == MissionStatus.Completed).CountAsync(ct);
var totalArtifacts = await _uow.Artifacts.Query().CountAsync(ct);
var totalStoreItems = await _uow.StoreItems.Query().CountAsync(ct);
var totalExperience = await _uow.Users.Query().SumAsync(u => (long)u.Experience, ct);
@@ -31,4 +32,3 @@ public class AnalyticsService : IAnalyticsService
};
}
}

View File

@@ -0,0 +1,10 @@
using LctMonolith.Services.Models;
using LctMonolith.Services.Models;
namespace LctMonolith.Services;
public interface IAnalyticsService
{
Task<AnalyticsSummary> GetSummaryAsync(CancellationToken ct = default);
}

View File

@@ -0,0 +1,15 @@
using LctMonolith.Models;
using LctMonolith.Services.Models;
namespace LctMonolith.Services.Contracts;
/// <summary>Gamification progression logic (progress, rewards, rank evaluation).</summary>
public interface IGamificationService
{
/// <summary>Get current user progression snapshot (xp, mana, next rank requirements).</summary>
Task<ProgressSnapshot> GetProgressAsync(Guid userId, CancellationToken ct = default);
/// <summary>Apply mission completion rewards (xp, mana, skills, artifacts) to user.</summary>
Task ApplyMissionCompletionAsync(Guid userId, Mission mission, CancellationToken ct = default);
/// <summary>Re-evaluate and apply rank upgrade if requirements are met.</summary>
Task EvaluateRankUpgradeAsync(Guid userId, CancellationToken ct = default);
}

View File

@@ -0,0 +1,9 @@
using LctMonolith.Models;
namespace LctMonolith.Services.Contracts;
public interface IInventoryService
{
Task<IEnumerable<UserInventoryItem>> GetStoreInventoryAsync(Guid userId, CancellationToken ct = default);
Task<IEnumerable<UserArtifact>> GetArtifactsAsync(Guid userId, CancellationToken ct = default);
}

View File

@@ -0,0 +1,11 @@
using LctMonolith.Models;
using LctMonolith.Services.Models;
namespace LctMonolith.Services.Contracts;
public interface IMissionService
{
Task<Mission> CreateMissionAsync(CreateMissionModel model, CancellationToken ct = default);
Task<IEnumerable<Mission>> GetAvailableMissionsAsync(Guid userId, CancellationToken ct = default);
Task<UserMission> UpdateStatusAsync(Guid userId, Guid missionId, MissionStatus status, string? submissionData, CancellationToken ct = default);
}

View File

@@ -0,0 +1,12 @@
using LctMonolith.Models;
namespace LctMonolith.Services.Contracts;
public interface INotificationService
{
Task<Notification> CreateAsync(Guid userId, string type, string title, string message, CancellationToken ct = default);
Task<IEnumerable<Notification>> GetUnreadAsync(Guid userId, CancellationToken ct = default);
Task<IEnumerable<Notification>> GetAllAsync(Guid userId, int take = 100, CancellationToken ct = default);
Task MarkReadAsync(Guid userId, Guid notificationId, CancellationToken ct = default);
Task<int> MarkAllReadAsync(Guid userId, CancellationToken ct = default);
}

View File

@@ -0,0 +1,9 @@
using LctMonolith.Models;
namespace LctMonolith.Services.Contracts;
public interface IStoreService
{
Task<IEnumerable<StoreItem>> GetActiveItemsAsync(CancellationToken ct = default);
Task<UserInventoryItem> PurchaseAsync(Guid userId, Guid itemId, int quantity, CancellationToken ct = default);
}

View File

@@ -0,0 +1,11 @@
using LctMonolith.Models;
using LctMonolith.Services.Models;
namespace LctMonolith.Services.Contracts;
public interface ITokenService
{
Task<TokenPair> IssueAsync(AppUser user, CancellationToken ct = default);
Task<TokenPair> RefreshAsync(string refreshToken, CancellationToken ct = default);
Task RevokeAsync(string refreshToken, CancellationToken ct = default);
}

View File

@@ -1,6 +1,7 @@
using System.Linq;
using LctMonolith.Domain.Entities;
using LctMonolith.Infrastructure.UnitOfWork;
using LctMonolith.Database.UnitOfWork;
using LctMonolith.Models;
using LctMonolith.Services.Contracts;
using LctMonolith.Services.Models;
using Microsoft.EntityFrameworkCore;
using Serilog;

View File

@@ -1,3 +0,0 @@
// Removed legacy Weather service placeholder.
// Intentionally left blank.

View File

@@ -1,58 +0,0 @@
using LctMonolith.Domain.Entities;
using LctMonolith.Services.Models;
namespace LctMonolith.Services;
/// <summary>Service for issuing JWT and refresh tokens.</summary>
public interface ITokenService
{
Task<TokenPair> IssueAsync(AppUser user, CancellationToken ct = default);
Task<TokenPair> RefreshAsync(string refreshToken, CancellationToken ct = default);
Task RevokeAsync(string refreshToken, CancellationToken ct = default);
}
/// <summary>Gamification progression logic (awards, rank upgrade).</summary>
public interface IGamificationService
{
Task<ProgressSnapshot> GetProgressAsync(Guid userId, CancellationToken ct = default);
Task ApplyMissionCompletionAsync(Guid userId, Mission mission, CancellationToken ct = default);
Task EvaluateRankUpgradeAsync(Guid userId, CancellationToken ct = default);
}
/// <summary>Mission management and user mission state transitions.</summary>
public interface IMissionService
{
Task<Mission> CreateMissionAsync(CreateMissionModel model, CancellationToken ct = default);
Task<IEnumerable<Mission>> GetAvailableMissionsAsync(Guid userId, CancellationToken ct = default);
Task<UserMission> UpdateStatusAsync(Guid userId, Guid missionId, MissionStatus status, string? submissionData, CancellationToken ct = default);
}
/// <summary>Store and inventory operations.</summary>
public interface IStoreService
{
Task<IEnumerable<StoreItem>> GetActiveItemsAsync(CancellationToken ct = default);
Task<UserInventoryItem> PurchaseAsync(Guid userId, Guid itemId, int quantity, CancellationToken ct = default);
}
/// <summary>User notifications (in-app) management.</summary>
public interface INotificationService
{
Task<Notification> CreateAsync(Guid userId, string type, string title, string message, CancellationToken ct = default);
Task<IEnumerable<Notification>> GetUnreadAsync(Guid userId, CancellationToken ct = default);
Task<IEnumerable<Notification>> GetAllAsync(Guid userId, int take = 100, CancellationToken ct = default);
Task MarkReadAsync(Guid userId, Guid notificationId, CancellationToken ct = default);
Task<int> MarkAllReadAsync(Guid userId, CancellationToken ct = default);
}
/// <summary>Inventory querying (owned artifacts, store items).</summary>
public interface IInventoryService
{
Task<IEnumerable<UserInventoryItem>> GetStoreInventoryAsync(Guid userId, CancellationToken ct = default);
Task<IEnumerable<UserArtifact>> GetArtifactsAsync(Guid userId, CancellationToken ct = default);
}
/// <summary>Basic analytics / aggregated metrics.</summary>
public interface IAnalyticsService
{
Task<AnalyticsSummary> GetSummaryAsync(CancellationToken ct = default);
}

View File

@@ -1,5 +1,6 @@
using LctMonolith.Domain.Entities;
using LctMonolith.Infrastructure.UnitOfWork;
using LctMonolith.Database.UnitOfWork;
using LctMonolith.Models;
using LctMonolith.Services.Contracts;
using Microsoft.EntityFrameworkCore;
namespace LctMonolith.Services;
@@ -32,4 +33,3 @@ public class InventoryService : IInventoryService
.ToListAsync(ct);
}
}

View File

@@ -1,10 +1,11 @@
using LctMonolith.Domain.Entities;
using LctMonolith.Infrastructure.UnitOfWork;
using LctMonolith.Models;
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;

View File

@@ -0,0 +1,12 @@
namespace LctMonolith.Services.Models;
public class AnalyticsSummary
{
public int TotalUsers { get; set; }
public int TotalMissions { get; set; }
public int CompletedMissions { get; set; }
public int TotalArtifacts { get; set; }
public int TotalStoreItems { get; set; }
public long TotalExperience { get; set; }
public DateTime GeneratedAtUtc { get; set; } = DateTime.UtcNow;
}

View File

@@ -0,0 +1,9 @@
namespace LctMonolith.Services.Models;
public class AuthRequest
{
public string Email { get; set; } = null!;
public string Password { get; set; } = null!;
public string? FirstName { get; set; }
public string? LastName { get; set; }
}

View File

@@ -0,0 +1,8 @@
namespace LctMonolith.Services.Models;
public class CompetencyRewardModel
{
public Guid CompetencyId { get; set; }
public int LevelDelta { get; set; }
public int ProgressPointsDelta { get; set; }
}

View File

@@ -0,0 +1,16 @@
using LctMonolith.Models;
namespace LctMonolith.Services.Models;
public class CreateMissionModel
{
public string Title { get; set; } = null!;
public string? Description { get; set; }
public string? Branch { get; set; }
public MissionCategory Category { get; set; }
public Guid? MinRankId { get; set; }
public int ExperienceReward { get; set; }
public int ManaReward { get; set; }
public List<CompetencyRewardModel> CompetencyRewards { get; set; } = new();
public List<Guid> ArtifactRewardIds { get; set; } = new();
}

View File

@@ -1,99 +0,0 @@
using LctMonolith.Domain.Entities;
namespace LctMonolith.Services.Models;
/// <summary>Returned access+refresh token pair with expiry metadata.</summary>
public record TokenPair(string AccessToken, DateTime AccessTokenExpiresAt, string RefreshToken, DateTime RefreshTokenExpiresAt);
/// <summary>Mission creation request model.</summary>
public class CreateMissionModel
{
public string Title { get; set; } = null!;
public string? Description { get; set; }
public string? Branch { get; set; }
public MissionCategory Category { get; set; }
public Guid? MinRankId { get; set; }
public int ExperienceReward { get; set; }
public int ManaReward { get; set; }
public List<CompetencyRewardModel> CompetencyRewards { get; set; } = new();
public List<Guid> ArtifactRewardIds { get; set; } = new();
}
/// <summary>Competency reward definition for mission creation.</summary>
public class CompetencyRewardModel
{
public Guid CompetencyId { get; set; }
public int LevelDelta { get; set; }
public int ProgressPointsDelta { get; set; }
}
/// <summary>Progress snapshot for UI: rank, xp, remaining requirements.</summary>
public class ProgressSnapshot
{
public int Experience { get; set; }
public int Mana { get; set; }
public Guid? CurrentRankId { get; set; }
public string? CurrentRankName { get; set; }
public Guid? NextRankId { get; set; }
public string? NextRankName { get; set; }
public int? RequiredExperienceForNextRank { get; set; }
public int? ExperienceRemaining => RequiredExperienceForNextRank.HasValue ? Math.Max(0, RequiredExperienceForNextRank.Value - Experience) : null;
public List<Guid> OutstandingMissionIds { get; set; } = new();
public List<OutstandingCompetency> OutstandingCompetencies { get; set; } = new();
}
/// <summary>Competency requirement still unmet for next rank.</summary>
public class OutstandingCompetency
{
public Guid CompetencyId { get; set; }
public string? CompetencyName { get; set; }
public int RequiredLevel { get; set; }
public int CurrentLevel { get; set; }
}
/// <summary>Request to update mission status with optional submission data.</summary>
public class UpdateMissionStatusRequest
{
public MissionStatus Status { get; set; }
public string? SubmissionData { get; set; }
}
/// <summary>Store purchase request.</summary>
public class PurchaseRequest
{
public Guid ItemId { get; set; }
public int Quantity { get; set; } = 1;
}
/// <summary>Authentication request (login/register) simple mock.</summary>
public class AuthRequest
{
public string Email { get; set; } = null!;
public string Password { get; set; } = null!;
public string? FirstName { get; set; }
public string? LastName { get; set; }
}
/// <summary>Refresh token request.</summary>
public class RefreshRequest
{
public string RefreshToken { get; set; } = null!;
}
/// <summary>Revoke refresh token request.</summary>
public class RevokeRequest
{
public string RefreshToken { get; set; } = null!;
}
/// <summary>Analytics summary for admin dashboard.</summary>
public class AnalyticsSummary
{
public int TotalUsers { get; set; }
public int TotalMissions { get; set; }
public int CompletedMissions { get; set; }
public int TotalArtifacts { get; set; }
public int TotalStoreItems { get; set; }
public long TotalExperience { get; set; }
public DateTime GeneratedAtUtc { get; set; } = DateTime.UtcNow;
}

View File

@@ -0,0 +1,23 @@
namespace LctMonolith.Services.Models;
public class ProgressSnapshot
{
public int Experience { get; set; }
public int Mana { get; set; }
public Guid? CurrentRankId { get; set; }
public string? CurrentRankName { get; set; }
public Guid? NextRankId { get; set; }
public string? NextRankName { get; set; }
public int? RequiredExperienceForNextRank { get; set; }
public int? ExperienceRemaining => RequiredExperienceForNextRank.HasValue ? Math.Max(0, RequiredExperienceForNextRank.Value - Experience) : null;
public List<Guid> OutstandingMissionIds { get; set; } = new();
public List<OutstandingCompetency> OutstandingCompetencies { get; set; } = new();
}
public class OutstandingCompetency
{
public Guid CompetencyId { get; set; }
public string? CompetencyName { get; set; }
public int RequiredLevel { get; set; }
public int CurrentLevel { get; set; }
}

View File

@@ -0,0 +1,7 @@
namespace LctMonolith.Services.Models;
public class PurchaseRequest
{
public Guid ItemId { get; set; }
public int Quantity { get; set; } = 1;
}

View File

@@ -0,0 +1,6 @@
namespace LctMonolith.Services.Models;
public class RefreshRequest
{
public string RefreshToken { get; set; } = null!;
}

View File

@@ -0,0 +1,6 @@
namespace LctMonolith.Services.Models;
public class RevokeRequest
{
public string RefreshToken { get; set; } = null!;
}

View File

@@ -0,0 +1,3 @@
namespace LctMonolith.Services.Models;
public record TokenPair(string AccessToken, DateTime AccessTokenExpiresAt, string RefreshToken, DateTime RefreshTokenExpiresAt);

View File

@@ -0,0 +1,9 @@
using LctMonolith.Models;
namespace LctMonolith.Services.Models;
public class UpdateMissionStatusRequest
{
public MissionStatus Status { get; set; }
public string? SubmissionData { get; set; }
}

View File

@@ -1 +1,79 @@
using LctMonolith.Database.UnitOfWork;
using LctMonolith.Models;
using LctMonolith.Services.Contracts;
using Microsoft.EntityFrameworkCore;
namespace LctMonolith.Services;
/// <summary>
/// In-app user notifications CRUD / read-state operations.
/// </summary>
public class NotificationService : INotificationService
{
private readonly IUnitOfWork _uow;
public NotificationService(IUnitOfWork uow)
{
_uow = uow;
}
public async Task<Notification> CreateAsync(Guid userId, string type, string title, string message, CancellationToken ct = default)
{
if (string.IsNullOrWhiteSpace(type)) throw new ArgumentException("Required", nameof(type));
if (string.IsNullOrWhiteSpace(title)) throw new ArgumentException("Required", nameof(title));
if (string.IsNullOrWhiteSpace(message)) throw new ArgumentException("Required", nameof(message));
var n = new Notification
{
UserId = userId,
Type = type.Trim(),
Title = title.Trim(),
Message = message.Trim(),
CreatedAt = DateTime.UtcNow,
IsRead = false
};
await _uow.Notifications.AddAsync(n, ct);
await _uow.SaveChangesAsync(ct);
return n;
}
public async Task<IEnumerable<Notification>> GetUnreadAsync(Guid userId, CancellationToken ct = default)
{
var query = _uow.Notifications.Query(n => n.UserId == userId && !n.IsRead, q => q.OrderByDescending(x => x.CreatedAt));
return await query.Take(100).ToListAsync(ct);
}
public async Task<IEnumerable<Notification>> GetAllAsync(Guid userId, int take = 100, CancellationToken ct = default)
{
if (take <= 0) take = 1;
if (take > 500) take = 500;
var query = _uow.Notifications.Query(n => n.UserId == userId, q => q.OrderByDescending(x => x.CreatedAt));
return await query.Take(take).ToListAsync(ct);
}
public async Task MarkReadAsync(Guid userId, Guid notificationId, CancellationToken ct = default)
{
var notif = await _uow.Notifications.Query(n => n.Id == notificationId && n.UserId == userId).FirstOrDefaultAsync(ct)
?? throw new KeyNotFoundException("Notification not found");
if (!notif.IsRead)
{
notif.IsRead = true;
notif.ReadAt = DateTime.UtcNow;
await _uow.SaveChangesAsync(ct);
}
}
public async Task<int> MarkAllReadAsync(Guid userId, CancellationToken ct = default)
{
var unread = await _uow.Notifications.Query(n => n.UserId == userId && !n.IsRead).ToListAsync(ct);
if (unread.Count == 0) return 0;
var now = DateTime.UtcNow;
foreach (var n in unread)
{
n.IsRead = true;
n.ReadAt = now;
}
await _uow.SaveChangesAsync(ct);
return unread.Count;
}
}

View File

@@ -1,8 +1,9 @@
using LctMonolith.Domain.Entities;
using LctMonolith.Infrastructure.UnitOfWork;
using LctMonolith.Models;
using Microsoft.EntityFrameworkCore;
using Serilog;
using System.Text.Json;
using LctMonolith.Database.UnitOfWork;
using LctMonolith.Services.Contracts;
namespace LctMonolith.Services;
@@ -72,4 +73,3 @@ public class StoreService : IStoreService
return inv;
}
}

View File

@@ -3,8 +3,9 @@ using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using LctMonolith.Application.Options;
using LctMonolith.Domain.Entities;
using LctMonolith.Infrastructure.UnitOfWork;
using LctMonolith.Database.UnitOfWork;
using LctMonolith.Models;
using LctMonolith.Services.Contracts;
using LctMonolith.Services.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;