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,10 +1,13 @@
using System.Security.Claims;
using LctMonolith.Domain.Entities;
using LctMonolith.Models;
using LctMonolith.Services;
using LctMonolith.Services.Contracts;
using LctMonolith.Services.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.Data;
using Microsoft.AspNetCore.Mvc;
using RefreshRequest = LctMonolith.Services.Models.RefreshRequest;
namespace LctMonolith.Controllers;

View File

@@ -1,5 +1,6 @@
using System.Security.Claims;
using LctMonolith.Services;
using LctMonolith.Services.Contracts;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

View File

@@ -1,5 +1,6 @@
using System.Security.Claims;
using LctMonolith.Services;
using LctMonolith.Services.Contracts;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

View File

@@ -1,6 +1,7 @@
using System.Security.Claims;
using LctMonolith.Domain.Entities;
using LctMonolith.Models;
using LctMonolith.Services;
using LctMonolith.Services.Contracts;
using LctMonolith.Services.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@@ -50,4 +51,3 @@ public class MissionsController : ControllerBase
return Ok(new { result.MissionId, result.Status, result.UpdatedAt });
}
}

View File

@@ -1,5 +1,6 @@
using System.Security.Claims;
using LctMonolith.Services;
using LctMonolith.Services.Contracts;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

View File

@@ -1,5 +1,6 @@
using System.Security.Claims;
using LctMonolith.Services;
using LctMonolith.Services.Contracts;
using LctMonolith.Services.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

View File

@@ -1,15 +1,18 @@
using LctMonolith.Domain.Entities;
using System.Diagnostics;
using LctMonolith.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using EventLog = LctMonolith.Models.EventLog;
namespace LctMonolith.Infrastructure.Data;
namespace LctMonolith.Database.Data;
/// <summary>
/// Main EF Core database context for gamification module (PostgreSQL provider expected).
/// </summary>
public class AppDbContext : IdentityDbContext<AppUser, IdentityRole<Guid>, Guid>
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
public DbSet<Rank> Ranks => Set<Rank>();

View File

@@ -1,9 +1,8 @@
using LctMonolith.Domain.Entities;
using LctMonolith.Models;
using Microsoft.EntityFrameworkCore;
using Serilog;
using System.Collections.Generic;
namespace LctMonolith.Infrastructure.Data;
namespace LctMonolith.Database.Data;
/// <summary>
/// Development database seeder for initial ranks, competencies, sample store items.

View File

@@ -0,0 +1,19 @@
namespace LctMonolith.Models;
public enum EventType
{
SkillProgress = 1,
MissionStatusChanged = 2,
RankChanged = 3,
ItemPurchased = 4,
ArtifactObtained = 5,
RewardGranted = 6,
ProfileChanged = 7,
AuthCredentialsChanged = 8,
ItemReturned = 9,
ItemSold = 10
}
#if false
// Moved to Models/EventType.cs
#endif

View File

@@ -1,8 +1,8 @@
using System.Linq.Expressions;
using LctMonolith.Database.Data;
using Microsoft.EntityFrameworkCore;
using LctMonolith.Infrastructure.Data;
namespace LctMonolith.Infrastructure.Repositories;
namespace LctMonolith.Database.Repositories;
/// <summary>
/// Generic repository implementation for common CRUD and query composition.

View File

@@ -1,6 +1,6 @@
using System.Linq.Expressions;
namespace LctMonolith.Infrastructure.Repositories;
namespace LctMonolith.Database.Repositories;
/// <summary>
/// Generic repository abstraction for aggregate root / entity access. Read operations return IQueryable for composition.

View File

@@ -1,7 +1,9 @@
using LctMonolith.Domain.Entities;
using LctMonolith.Infrastructure.Repositories;
using System.Diagnostics;
using LctMonolith.Database.Repositories;
using LctMonolith.Models;
using EventLog = LctMonolith.Models.EventLog;
namespace LctMonolith.Infrastructure.UnitOfWork;
namespace LctMonolith.Database.UnitOfWork;
/// <summary>
/// Unit of Work aggregates repositories and transaction boundary.

View File

@@ -1,9 +1,11 @@
using LctMonolith.Domain.Entities;
using LctMonolith.Infrastructure.Data;
using LctMonolith.Infrastructure.Repositories;
using Microsoft.EntityFrameworkCore.Storage;
namespace LctMonolith.Infrastructure.UnitOfWork;
using LctMonolith.Database.Data;
using LctMonolith.Database.Repositories;
using LctMonolith.Models;
using Microsoft.EntityFrameworkCore.Storage;
using EventLog = LctMonolith.Models.EventLog;
namespace LctMonolith.Database.UnitOfWork;
/// <summary>
/// Unit of Work implementation encapsulating repositories and DB transaction scope.

View File

@@ -1,95 +0,0 @@
namespace LctMonolith.Domain.Entities;
/// <summary>Artifact definition (unique reward objects).</summary>
public class Artifact
{
public Guid Id { get; set; } = Guid.NewGuid();
public string Name { get; set; } = null!;
public string? Description { get; set; }
public string? ImageUrl { get; set; }
public ArtifactRarity Rarity { get; set; }
public ICollection<UserArtifact> Users { get; set; } = new List<UserArtifact>();
public ICollection<MissionArtifactReward> MissionRewards { get; set; } = new List<MissionArtifactReward>();
}
/// <summary>Mapping artifact to user ownership.</summary>
public class UserArtifact
{
public Guid UserId { get; set; }
public AppUser User { get; set; } = null!;
public Guid ArtifactId { get; set; }
public Artifact Artifact { get; set; } = null!;
public DateTime ObtainedAt { get; set; } = DateTime.UtcNow;
}
/// <summary>Reward mapping: mission grants artifact(s).</summary>
public class MissionArtifactReward
{
public Guid MissionId { get; set; }
public Mission Mission { get; set; } = null!;
public Guid ArtifactId { get; set; }
public Artifact Artifact { get; set; } = null!;
}
/// <summary>Item in store that can be purchased with mana.</summary>
public class StoreItem
{
public Guid Id { get; set; } = Guid.NewGuid();
public string Name { get; set; } = null!;
public string? Description { get; set; }
public int Price { get; set; }
public bool IsActive { get; set; } = true;
public int? Stock { get; set; }
public ICollection<UserInventoryItem> UserInventory { get; set; } = new List<UserInventoryItem>();
}
/// <summary>User owned store item record.</summary>
public class UserInventoryItem
{
public Guid UserId { get; set; }
public AppUser User { get; set; } = null!;
public Guid StoreItemId { get; set; }
public StoreItem StoreItem { get; set; } = null!;
public int Quantity { get; set; } = 1;
public DateTime AcquiredAt { get; set; } = DateTime.UtcNow;
public bool IsReturned { get; set; }
}
/// <summary>Transaction record for purchases/returns/sales.</summary>
public class Transaction
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid UserId { get; set; }
public AppUser User { get; set; } = null!;
public TransactionType Type { get; set; }
public Guid? StoreItemId { get; set; }
public StoreItem? StoreItem { get; set; }
public int ManaAmount { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
/// <summary>System event log for auditing user actions and progression.</summary>
public class EventLog
{
public Guid Id { get; set; } = Guid.NewGuid();
public EventType Type { get; set; }
public Guid UserId { get; set; }
public AppUser User { get; set; } = null!;
public string? Data { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
/// <summary>Refresh token for JWT auth.</summary>
public class RefreshToken
{
public Guid Id { get; set; } = Guid.NewGuid();
public string Token { get; set; } = null!;
public DateTime ExpiresAt { get; set; }
public bool IsRevoked { get; set; }
public Guid UserId { get; set; }
public AppUser User { get; set; } = null!;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}

View File

@@ -1,41 +0,0 @@
namespace LctMonolith.Domain.Entities;
/// <summary>
/// Competency (skill) that can be progressed by completing missions.
/// </summary>
public class Competency
{
public Guid Id { get; set; } = Guid.NewGuid();
public string Name { get; set; } = null!;
public string? Description { get; set; }
public ICollection<UserCompetency> UserCompetencies { get; set; } = new List<UserCompetency>();
public ICollection<MissionCompetencyReward> MissionRewards { get; set; } = new List<MissionCompetencyReward>();
public ICollection<RankRequiredCompetency> RankRequirements { get; set; } = new List<RankRequiredCompetency>();
}
/// <summary>Per-user competency level.</summary>
public class UserCompetency
{
public Guid UserId { get; set; }
public AppUser User { get; set; } = null!;
public Guid CompetencyId { get; set; }
public Competency Competency { get; set; } = null!;
/// <summary>Current level (integer simple scale).</summary>
public int Level { get; set; }
/// <summary>Optional numeric progress inside level (e.g., partial points).</summary>
public int ProgressPoints { get; set; }
}
/// <summary>Reward mapping: mission increases competency level points.</summary>
public class MissionCompetencyReward
{
public Guid MissionId { get; set; }
public Mission Mission { get; set; } = null!;
public Guid CompetencyId { get; set; }
public Competency Competency { get; set; } = null!;
/// <summary>Increment value in levels (could be 0 or 1) or points depending on design.</summary>
public int LevelDelta { get; set; }
public int ProgressPointsDelta { get; set; }
}

View File

@@ -1,54 +0,0 @@
namespace LctMonolith.Domain.Entities;
/// <summary>Mission category taxonomy.</summary>
public enum MissionCategory
{
Quest = 0,
Recruiting = 1,
Lecture = 2,
Simulator = 3
}
/// <summary>Status of a mission for a specific user.</summary>
public enum MissionStatus
{
Locked = 0,
Available = 1,
InProgress = 2,
Submitted = 3,
Completed = 4,
Rejected = 5
}
/// <summary>Rarity level of an artifact.</summary>
public enum ArtifactRarity
{
Common = 0,
Rare = 1,
Epic = 2,
Legendary = 3
}
/// <summary>Type of transactional operation in store.</summary>
public enum TransactionType
{
Purchase = 0,
Return = 1,
Sale = 2
}
/// <summary>Auditable event types enumerated in requirements.</summary>
public enum EventType
{
SkillProgress = 1,
MissionStatusChanged = 2,
RankChanged = 3,
ItemPurchased = 4,
ArtifactObtained = 5,
RewardGranted = 6,
ProfileChanged = 7,
AuthCredentialsChanged = 8,
ItemReturned = 9,
ItemSold = 10
}

View File

@@ -1,44 +0,0 @@
namespace LctMonolith.Domain.Entities;
/// <summary>
/// Mission (task) definition configured by HR.
/// </summary>
public class Mission
{
public Guid Id { get; set; } = Guid.NewGuid();
public string Title { get; set; } = null!;
public string? Description { get; set; }
/// <summary>Optional branch (path) name for grouping / visualization.</summary>
public string? Branch { get; set; }
public MissionCategory Category { get; set; }
/// <summary>Minimum rank required to access the mission (nullable = available from start).</summary>
public Guid? MinRankId { get; set; }
public Rank? MinRank { get; set; }
/// <summary>Experience reward on completion.</summary>
public int ExperienceReward { get; set; }
/// <summary>Mana reward on completion.</summary>
public int ManaReward { get; set; }
public bool IsActive { get; set; } = true;
public ICollection<MissionCompetencyReward> CompetencyRewards { get; set; } = new List<MissionCompetencyReward>();
public ICollection<MissionArtifactReward> ArtifactRewards { get; set; } = new List<MissionArtifactReward>();
public ICollection<UserMission> UserMissions { get; set; } = new List<UserMission>();
public ICollection<RankRequiredMission> RanksRequiring { get; set; } = new List<RankRequiredMission>();
}
/// <summary>Per-user mission status and progression.</summary>
public class UserMission
{
public Guid UserId { get; set; }
public AppUser User { get; set; } = null!;
public Guid MissionId { get; set; }
public Mission Mission { get; set; } = null!;
public MissionStatus Status { get; set; } = MissionStatus.Available;
/// <summary>Date/time of last status change.</summary>
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
/// <summary>Optional submission payload (e.g., link, text, attachments pointer).</summary>
public string? SubmissionData { get; set; }
}

View File

@@ -1,40 +0,0 @@
namespace LctMonolith.Domain.Entities;
/// <summary>
/// Linear rank in progression ladder. User must meet XP, key mission and competency requirements.
/// </summary>
public class Rank
{
public Guid Id { get; set; } = Guid.NewGuid();
/// <summary>Display name (e.g., "Искатель", "Пилот-кандидат").</summary>
public string Name { get; set; } = null!;
/// <summary>Ordering position. Lower value = earlier rank.</summary>
public int Order { get; set; }
/// <summary>Required cumulative experience to attain this rank.</summary>
public int RequiredExperience { get; set; }
public ICollection<RankRequiredMission> RequiredMissions { get; set; } = new List<RankRequiredMission>();
public ICollection<RankRequiredCompetency> RequiredCompetencies { get; set; } = new List<RankRequiredCompetency>();
public ICollection<AppUser> Users { get; set; } = new List<AppUser>();
}
/// <summary>Mapping of rank to required mission.</summary>
public class RankRequiredMission
{
public Guid RankId { get; set; }
public Rank Rank { get; set; } = null!;
public Guid MissionId { get; set; }
public Mission Mission { get; set; } = null!;
}
/// <summary>Mapping of rank to required competency minimum level.</summary>
public class RankRequiredCompetency
{
public Guid RankId { get; set; }
public Rank Rank { get; set; } = null!;
public Guid CompetencyId { get; set; }
public Competency Competency { get; set; } = null!;
/// <summary>Minimum level required for the competency.</summary>
public int MinLevel { get; set; }
}

View File

@@ -1,32 +1,19 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Identity;
namespace LctMonolith.Domain.Entities;
namespace LctMonolith.Models;
/// <summary>
/// Application user (candidate or employee) participating in gamification.
/// Extends IdentityUser with Guid primary key.
/// </summary>
public class AppUser : IdentityUser<Guid>
{
/// <summary>User given (first) name.</summary>
public string? FirstName { get; set; }
/// <summary>User family (last) name.</summary>
public string? LastName { get; set; }
/// <summary>Date of birth.</summary>
public DateOnly? BirthDate { get; set; }
/// <summary>Current accumulated experience points.</summary>
public int Experience { get; set; }
/// <summary>Current mana (in-game currency).</summary>
public int Mana { get; set; }
/// <summary>Current rank reference.</summary>
public Guid? RankId { get; set; }
public Rank? Rank { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
public ICollection<UserCompetency> Competencies { get; set; } = new List<UserCompetency>();
public ICollection<UserMission> Missions { get; set; } = new List<UserMission>();
public ICollection<UserInventoryItem> Inventory { get; set; } = new List<UserInventoryItem>();

View File

@@ -0,0 +1,13 @@
namespace LctMonolith.Models;
public class Artifact
{
public Guid Id { get; set; } = Guid.NewGuid();
public string Name { get; set; } = null!;
public string? Description { get; set; }
public string? ImageUrl { get; set; }
public ArtifactRarity Rarity { get; set; }
public ICollection<UserArtifact> Users { get; set; } = new List<UserArtifact>();
public ICollection<MissionArtifactReward> MissionRewards { get; set; } = new List<MissionArtifactReward>();
}

View File

@@ -0,0 +1,3 @@
namespace LctMonolith.Models;
public enum ArtifactRarity { Common = 0, Rare = 1, Epic = 2, Legendary = 3 }

View File

@@ -0,0 +1,11 @@
namespace LctMonolith.Models;
public class Competency
{
public Guid Id { get; set; } = Guid.NewGuid();
public string Name { get; set; } = null!;
public string? Description { get; set; }
public ICollection<UserCompetency> UserCompetencies { get; set; } = new List<UserCompetency>();
public ICollection<MissionCompetencyReward> MissionRewards { get; set; } = new List<MissionCompetencyReward>();
public ICollection<RankRequiredCompetency> RankRequirements { get; set; } = new List<RankRequiredCompetency>();
}

View File

@@ -0,0 +1,11 @@
namespace LctMonolith.Models;
public class EventLog
{
public Guid Id { get; set; } = Guid.NewGuid();
public EventType Type { get; set; }
public Guid UserId { get; set; }
public AppUser User { get; set; } = null!;
public string? Data { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}

View File

@@ -0,0 +1,19 @@
namespace LctMonolith.Models;
public class Mission
{
public Guid Id { get; set; } = Guid.NewGuid();
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 Rank? MinRank { get; set; }
public int ExperienceReward { get; set; }
public int ManaReward { get; set; }
public bool IsActive { get; set; } = true;
public ICollection<MissionCompetencyReward> CompetencyRewards { get; set; } = new List<MissionCompetencyReward>();
public ICollection<MissionArtifactReward> ArtifactRewards { get; set; } = new List<MissionArtifactReward>();
public ICollection<UserMission> UserMissions { get; set; } = new List<UserMission>();
public ICollection<RankRequiredMission> RanksRequiring { get; set; } = new List<RankRequiredMission>();
}

View File

@@ -0,0 +1,9 @@
namespace LctMonolith.Models;
public class MissionArtifactReward
{
public Guid MissionId { get; set; }
public Mission Mission { get; set; } = null!;
public Guid ArtifactId { get; set; }
public Artifact Artifact { get; set; } = null!;
}

View File

@@ -0,0 +1,3 @@
namespace LctMonolith.Models;
public enum MissionCategory { Quest = 0, Recruiting = 1, Lecture = 2, Simulator = 3 }

View File

@@ -0,0 +1,11 @@
namespace LctMonolith.Models;
public class MissionCompetencyReward
{
public Guid MissionId { get; set; }
public Mission Mission { get; set; } = null!;
public Guid CompetencyId { get; set; }
public Competency Competency { get; set; } = null!;
public int LevelDelta { get; set; }
public int ProgressPointsDelta { get; set; }
}

View File

@@ -0,0 +1,11 @@
namespace LctMonolith.Models;
public enum MissionStatus
{
Locked = 0,
Available = 1,
InProgress = 2,
Submitted = 3,
Completed = 4,
Rejected = 5
}

View File

@@ -1,12 +1,10 @@
namespace LctMonolith.Domain.Entities;
namespace LctMonolith.Models;
/// <summary>User notification (in-app).</summary>
public class Notification
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid UserId { get; set; }
public AppUser User { get; set; } = null!;
/// <summary>Short classification tag (e.g., rank, mission, store).</summary>
public string Type { get; set; } = null!;
public string Title { get; set; } = null!;
public string Message { get; set; } = null!;

View File

@@ -0,0 +1,12 @@
namespace LctMonolith.Models;
public class Rank
{
public Guid Id { get; set; } = Guid.NewGuid();
public string Name { get; set; } = null!;
public int Order { get; set; }
public int RequiredExperience { get; set; }
public ICollection<RankRequiredMission> RequiredMissions { get; set; } = new List<RankRequiredMission>();
public ICollection<RankRequiredCompetency> RequiredCompetencies { get; set; } = new List<RankRequiredCompetency>();
public ICollection<AppUser> Users { get; set; } = new List<AppUser>();
}

View File

@@ -0,0 +1,10 @@
namespace LctMonolith.Models;
public class RankRequiredCompetency
{
public Guid RankId { get; set; }
public Rank Rank { get; set; } = null!;
public Guid CompetencyId { get; set; }
public Competency Competency { get; set; } = null!;
public int MinLevel { get; set; }
}

View File

@@ -0,0 +1,9 @@
namespace LctMonolith.Models;
public class RankRequiredMission
{
public Guid RankId { get; set; }
public Rank Rank { get; set; } = null!;
public Guid MissionId { get; set; }
public Mission Mission { get; set; } = null!;
}

View File

@@ -0,0 +1,12 @@
namespace LctMonolith.Models;
public class RefreshToken
{
public Guid Id { get; set; } = Guid.NewGuid();
public string Token { get; set; } = null!;
public DateTime ExpiresAt { get; set; }
public bool IsRevoked { get; set; }
public Guid UserId { get; set; }
public AppUser User { get; set; } = null!;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}

View File

@@ -0,0 +1,12 @@
namespace LctMonolith.Models;
public class StoreItem
{
public Guid Id { get; set; } = Guid.NewGuid();
public string Name { get; set; } = null!;
public string? Description { get; set; }
public int Price { get; set; }
public bool IsActive { get; set; } = true;
public int? Stock { get; set; }
public ICollection<UserInventoryItem> UserInventory { get; set; } = new List<UserInventoryItem>();
}

View File

@@ -0,0 +1,13 @@
namespace LctMonolith.Models;
public class Transaction
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid UserId { get; set; }
public AppUser User { get; set; } = null!;
public TransactionType Type { get; set; }
public Guid? StoreItemId { get; set; }
public StoreItem? StoreItem { get; set; }
public int ManaAmount { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}

View File

@@ -0,0 +1,3 @@
namespace LctMonolith.Models;
public enum TransactionType { Purchase = 0, Return = 1, Sale = 2 }

View File

@@ -0,0 +1,10 @@
namespace LctMonolith.Models;
public class UserArtifact
{
public Guid UserId { get; set; }
public AppUser User { get; set; } = null!;
public Guid ArtifactId { get; set; }
public Artifact Artifact { get; set; } = null!;
public DateTime ObtainedAt { get; set; } = DateTime.UtcNow;
}

View File

@@ -0,0 +1,11 @@
namespace LctMonolith.Models;
public class UserCompetency
{
public Guid UserId { get; set; }
public AppUser User { get; set; } = null!;
public Guid CompetencyId { get; set; }
public Competency Competency { get; set; } = null!;
public int Level { get; set; }
public int ProgressPoints { get; set; }
}

View File

@@ -0,0 +1,12 @@
namespace LctMonolith.Models;
public class UserInventoryItem
{
public Guid UserId { get; set; }
public AppUser User { get; set; } = null!;
public Guid StoreItemId { get; set; }
public StoreItem StoreItem { get; set; } = null!;
public int Quantity { get; set; } = 1;
public DateTime AcquiredAt { get; set; } = DateTime.UtcNow;
public bool IsReturned { get; set; }
}

View File

@@ -0,0 +1,12 @@
namespace LctMonolith.Models;
public class UserMission
{
public Guid UserId { get; set; }
public AppUser User { get; set; } = null!;
public Guid MissionId { get; set; }
public Mission Mission { get; set; } = null!;
public MissionStatus Status { get; set; } = MissionStatus.Available;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
public string? SubmissionData { get; set; }
}

View File

@@ -4,13 +4,14 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using LctMonolith.Infrastructure.Data;
using LctMonolith.Domain.Entities;
using LctMonolith.Models; // replaced Domain.Entities
using Microsoft.AspNetCore.Identity;
using LctMonolith.Infrastructure.UnitOfWork;
using LctMonolith.Application.Middleware;
using LctMonolith.Services;
using LctMonolith.Application.Options; // Added for JwtOptions
using LctMonolith.Application.Options;
using LctMonolith.Database.Data;
using LctMonolith.Database.UnitOfWork;
using LctMonolith.Services.Contracts; // Added for JwtOptions
var builder = WebApplication.CreateBuilder(args);

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;