diff --git a/LctMonolith/Application/Middleware/ErrorHandlingMiddleware.cs b/LctMonolith/Application/Middleware/ErrorHandlingMiddleware.cs index b26789c..2cad718 100644 --- a/LctMonolith/Application/Middleware/ErrorHandlingMiddleware.cs +++ b/LctMonolith/Application/Middleware/ErrorHandlingMiddleware.cs @@ -4,13 +4,14 @@ using Serilog; namespace LctMonolith.Application.Middleware; -/// -/// Global error handling middleware capturing unhandled exceptions and converting to standardized JSON response. -/// public class ErrorHandlingMiddleware { private readonly RequestDelegate _next; - public ErrorHandlingMiddleware(RequestDelegate next) => _next = next; + + public ErrorHandlingMiddleware(RequestDelegate next) + { + _next = next; + } public async Task Invoke(HttpContext ctx) { @@ -20,17 +21,18 @@ public class ErrorHandlingMiddleware } catch (OperationCanceledException) { - // Client aborted request (non-standard 499 code used by some proxies) if (!ctx.Response.HasStarted) { - ctx.Response.StatusCode = 499; // Client Closed Request (custom) + ctx.Response.StatusCode = 499; } } catch (Exception ex) { Log.Error(ex, "Unhandled exception"); - if (ctx.Response.HasStarted) throw; - + if (ctx.Response.HasStarted) + { + throw; + } ctx.Response.ContentType = "application/json"; ctx.Response.StatusCode = (int)HttpStatusCode.InternalServerError; var payload = new { error = new { message = ex.Message, traceId = ctx.TraceIdentifier } }; @@ -41,6 +43,8 @@ public class ErrorHandlingMiddleware public static class ErrorHandlingMiddlewareExtensions { - /// Adds global error handling middleware. - public static IApplicationBuilder UseErrorHandling(this IApplicationBuilder app) => app.UseMiddleware(); + public static IApplicationBuilder UseErrorHandling(this IApplicationBuilder app) + { + return app.UseMiddleware(); + } } diff --git a/LctMonolith/Application/Options/JwtOptions.cs b/LctMonolith/Application/Options/JwtOptions.cs index 1d2a544..e571d83 100644 --- a/LctMonolith/Application/Options/JwtOptions.cs +++ b/LctMonolith/Application/Options/JwtOptions.cs @@ -1,8 +1,5 @@ namespace LctMonolith.Application.Options; -/// -/// JWT issuing configuration loaded from appsettings (section Jwt). -/// public class JwtOptions { public string Key { get; set; } = string.Empty; @@ -11,4 +8,3 @@ public class JwtOptions public int AccessTokenMinutes { get; set; } = 60; public int RefreshTokenDays { get; set; } = 7; } - diff --git a/LctMonolith/Controllers/AnalyticsController.cs b/LctMonolith/Controllers/AnalyticsController.cs index fc978a3..2c6cbc9 100644 --- a/LctMonolith/Controllers/AnalyticsController.cs +++ b/LctMonolith/Controllers/AnalyticsController.cs @@ -4,21 +4,18 @@ using Microsoft.AspNetCore.Mvc; namespace LctMonolith.Controllers; -/// -/// Basic analytics endpoints. -/// [ApiController] [Route("api/analytics")] [Authorize] public class AnalyticsController : ControllerBase { private readonly IAnalyticsService _analytics; + public AnalyticsController(IAnalyticsService analytics) { _analytics = analytics; } - /// Get aggregate system summary metrics. [HttpGet("summary")] public async Task GetSummary(CancellationToken ct) { @@ -26,4 +23,3 @@ public class AnalyticsController : ControllerBase return Ok(summary); } } - diff --git a/LctMonolith/Controllers/AuthController.cs b/LctMonolith/Controllers/AuthController.cs index e6be810..8cf0ba4 100644 --- a/LctMonolith/Controllers/AuthController.cs +++ b/LctMonolith/Controllers/AuthController.cs @@ -11,11 +11,8 @@ using RefreshRequest = LctMonolith.Services.Models.RefreshRequest; namespace LctMonolith.Controllers; -/// -/// Authentication endpoints (mocked local identity + JWT issuing). -/// [ApiController] -[Route("api/auth")] +[Route("api/auth")] public class AuthController : ControllerBase { private readonly UserManager _userManager; @@ -29,34 +26,43 @@ public class AuthController : ControllerBase _tokenService = tokenService; } - /// Registers a new user (simplified). [HttpPost("register")] [AllowAnonymous] public async Task> Register(AuthRequest req, CancellationToken ct) { var existing = await _userManager.FindByEmailAsync(req.Email); - if (existing != null) return Conflict("Email already registered"); + if (existing != null) + { + return Conflict("Email already registered"); + } var user = new AppUser { UserName = req.Email, Email = req.Email, FirstName = req.FirstName, LastName = req.LastName }; var result = await _userManager.CreateAsync(user, req.Password); - if (!result.Succeeded) return BadRequest(result.Errors); + if (!result.Succeeded) + { + return BadRequest(result.Errors); + } var tokens = await _tokenService.IssueAsync(user, ct); return Ok(tokens); } - /// Login with email + password. [HttpPost("login")] [AllowAnonymous] public async Task> Login(AuthRequest req, CancellationToken ct) { var user = await _userManager.FindByEmailAsync(req.Email); - if (user == null) return Unauthorized(); - var passOk = await _signInManager.CheckPasswordSignInAsync(user, req.Password, lockoutOnFailure: false); - if (!passOk.Succeeded) return Unauthorized(); + if (user == null) + { + return Unauthorized(); + } + var passOk = await _signInManager.CheckPasswordSignInAsync(user, req.Password, false); + if (!passOk.Succeeded) + { + return Unauthorized(); + } var tokens = await _tokenService.IssueAsync(user, ct); return Ok(tokens); } - /// Refresh access token by refresh token. [HttpPost("refresh")] [AllowAnonymous] public async Task> Refresh(RefreshRequest req, CancellationToken ct) @@ -65,7 +71,6 @@ public class AuthController : ControllerBase return Ok(pair); } - /// Revoke refresh token (logout). [HttpPost("revoke")] [Authorize] public async Task Revoke(RevokeRequest req, CancellationToken ct) @@ -74,13 +79,11 @@ public class AuthController : ControllerBase return NoContent(); } - /// Returns current user id (debug). [HttpGet("me")] [Authorize] public ActionResult Me() { - var id = User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue(ClaimTypes.Name); + var id = User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue(ClaimTypes.Name); return Ok(new { userId = id }); } } - diff --git a/LctMonolith/Controllers/DialogueController.cs b/LctMonolith/Controllers/DialogueController.cs index 1dc990f..00157c1 100644 --- a/LctMonolith/Controllers/DialogueController.cs +++ b/LctMonolith/Controllers/DialogueController.cs @@ -20,22 +20,30 @@ public class DialogueController : ControllerBase [HttpGet("mission/{missionId:guid}")] public async Task GetByMission(Guid missionId) { - var d = await _dialogueService.GetDialogueByMissionIdAsync(missionId); - return d == null ? NotFound() : Ok(d); + var dialogue = await _dialogueService.GetDialogueByMissionIdAsync(missionId); + if (dialogue == null) + { + return NotFound(); + } + return Ok(dialogue); } [HttpGet("message/{messageId:guid}")] public async Task GetMessage(Guid messageId) { - var m = await _dialogueService.GetDialogueMessageByIdAsync(messageId); - return m == null ? NotFound() : Ok(m); + var message = await _dialogueService.GetDialogueMessageByIdAsync(messageId); + if (message == null) + { + return NotFound(); + } + return Ok(message); } [HttpGet("message/{messageId:guid}/options")] public async Task GetOptions(Guid messageId) { - var opts = await _dialogueService.GetResponseOptionsAsync(messageId); - return Ok(opts); + var options = await _dialogueService.GetResponseOptionsAsync(messageId); + return Ok(options); } public record DialogueResponseRequest(Guid ResponseOptionId, Guid PlayerId); @@ -44,7 +52,10 @@ public class DialogueController : ControllerBase public async Task Respond(Guid messageId, DialogueResponseRequest req) { var next = await _dialogueService.ProcessDialogueResponseAsync(messageId, req.ResponseOptionId, req.PlayerId); - if (next == null) return Ok(new { end = true }); + if (next == null) + { + return Ok(new { end = true }); + } return Ok(next); } @@ -60,16 +71,16 @@ public class DialogueController : ControllerBase [Authorize(Roles = "Admin")] public async Task Create(CreateDialogueRequest dto) { - var d = new Dialogue + var dialogue = new Dialogue { Id = Guid.NewGuid(), MissionId = dto.MissionId, InitialDialogueMessageId = dto.InitialDialogueMessageId, InterimDialogueMessageId = dto.InterimDialogueMessageId, EndDialogueMessageId = dto.EndDialogueMessageId, - Mission = null! // EF will populate if included + Mission = null! }; - d = await _dialogueService.CreateDialogueAsync(d); - return CreatedAtAction(nameof(GetByMission), new { missionId = d.MissionId }, d); + dialogue = await _dialogueService.CreateDialogueAsync(dialogue); + return CreatedAtAction(nameof(GetByMission), new { missionId = dialogue.MissionId }, dialogue); } } diff --git a/LctMonolith/Controllers/InventoryController.cs b/LctMonolith/Controllers/InventoryController.cs index 459f5ec..0c127f0 100644 --- a/LctMonolith/Controllers/InventoryController.cs +++ b/LctMonolith/Controllers/InventoryController.cs @@ -11,19 +11,25 @@ namespace LctMonolith.Controllers; public class InventoryController : ControllerBase { private readonly IInventoryService _inventoryService; - public InventoryController(IInventoryService inventoryService) => _inventoryService = inventoryService; - private Guid CurrentUserId() => Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!); + public InventoryController(IInventoryService inventoryService) + { + _inventoryService = inventoryService; + } + + private Guid CurrentUserId() + { + return Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!); + } - /// Get inventory for current authenticated user. [HttpGet] public async Task GetMine(CancellationToken ct) { var items = await _inventoryService.GetStoreInventoryAsync(CurrentUserId(), ct); - return Ok(items.Select(i => new { i.StoreItemId, i.Quantity, i.AcquiredAt })); + var shaped = items.Select(i => new { i.StoreItemId, i.Quantity, i.AcquiredAt }); + return Ok(shaped); } - /// Admin: get inventory for specific user. [HttpGet("user/{userId:guid}")] [Authorize(Roles = "Admin")] public async Task GetByUser(Guid userId, CancellationToken ct) diff --git a/LctMonolith/Controllers/MissionCategoriesController.cs b/LctMonolith/Controllers/MissionCategoriesController.cs index 5c92c23..a924d35 100644 --- a/LctMonolith/Controllers/MissionCategoriesController.cs +++ b/LctMonolith/Controllers/MissionCategoriesController.cs @@ -12,7 +12,11 @@ namespace LctMonolith.Controllers; public class MissionCategoriesController : ControllerBase { private readonly IMissionCategoryService _service; - public MissionCategoriesController(IMissionCategoryService service) => _service = service; + + public MissionCategoriesController(IMissionCategoryService service) + { + _service = service; + } [HttpGet] public async Task GetAll() @@ -24,35 +28,45 @@ public class MissionCategoriesController : ControllerBase [HttpGet("{id:guid}")] public async Task Get(Guid id) { - var c = await _service.GetCategoryByIdAsync(id); - return c == null ? NotFound() : Ok(c); + var category = await _service.GetCategoryByIdAsync(id); + if (category == null) + { + return NotFound(); + } + return Ok(category); } [HttpPost] [Authorize(Roles = "Admin")] public async Task Create(CreateMissionCategoryDto dto) { - var c = await _service.CreateCategoryAsync(new MissionCategory { Title = dto.Title }); - return CreatedAtAction(nameof(Get), new { id = c.Id }, c); + var created = await _service.CreateCategoryAsync(new MissionCategory { Title = dto.Title }); + return CreatedAtAction(nameof(Get), new { id = created.Id }, created); } [HttpPut("{id:guid}")] [Authorize(Roles = "Admin")] public async Task Update(Guid id, CreateMissionCategoryDto dto) { - var c = await _service.GetCategoryByIdAsync(id); - if (c == null) return NotFound(); - c.Title = dto.Title; - await _service.UpdateCategoryAsync(c); - return Ok(c); + var existing = await _service.GetCategoryByIdAsync(id); + if (existing == null) + { + return NotFound(); + } + existing.Title = dto.Title; + await _service.UpdateCategoryAsync(existing); + return Ok(existing); } [HttpDelete("{id:guid}")] [Authorize(Roles = "Admin")] public async Task Delete(Guid id) { - var ok = await _service.DeleteCategoryAsync(id); - return ok ? NoContent() : NotFound(); + var removed = await _service.DeleteCategoryAsync(id); + if (!removed) + { + return NotFound(); + } + return NoContent(); } } - diff --git a/LctMonolith/Controllers/MissionsController.cs b/LctMonolith/Controllers/MissionsController.cs index 7c7b561..96afb25 100644 --- a/LctMonolith/Controllers/MissionsController.cs +++ b/LctMonolith/Controllers/MissionsController.cs @@ -23,8 +23,12 @@ public class MissionsController : ControllerBase [HttpGet("{id:guid}")] public async Task Get(Guid id) { - var m = await _missions.GetMissionByIdAsync(id); - return m == null ? NotFound() : Ok(m); + var mission = await _missions.GetMissionByIdAsync(id); + if (mission == null) + { + return NotFound(); + } + return Ok(mission); } [HttpGet("category/{categoryId:guid}")] @@ -80,7 +84,10 @@ public class MissionsController : ControllerBase public async Task Update(Guid id, CreateMissionRequest dto) { var existing = await _missions.GetMissionByIdAsync(id); - if (existing == null) return NotFound(); + if (existing == null) + { + return NotFound(); + } existing.Title = dto.Title; existing.Description = dto.Description ?? string.Empty; existing.MissionCategoryId = dto.MissionCategoryId; @@ -95,8 +102,12 @@ public class MissionsController : ControllerBase [Authorize(Roles = "Admin")] public async Task Delete(Guid id) { - var ok = await _missions.DeleteMissionAsync(id); - return ok ? NoContent() : NotFound(); + var removed = await _missions.DeleteMissionAsync(id); + if (!removed) + { + return NotFound(); + } + return NoContent(); } public record CompleteMissionRequest(Guid PlayerId, object? Proof); @@ -105,8 +116,10 @@ public class MissionsController : ControllerBase public async Task Complete(Guid missionId, CompleteMissionRequest r) { var result = await _missions.CompleteMissionAsync(missionId, r.PlayerId, r.Proof); - if (!result.Success) return BadRequest(result); + if (!result.Success) + { + return BadRequest(result); + } return Ok(result); } } - diff --git a/LctMonolith/Controllers/NotificationsController.cs b/LctMonolith/Controllers/NotificationsController.cs index 83dfe14..680fa96 100644 --- a/LctMonolith/Controllers/NotificationsController.cs +++ b/LctMonolith/Controllers/NotificationsController.cs @@ -6,9 +6,6 @@ using Microsoft.AspNetCore.Mvc; namespace LctMonolith.Controllers; -/// -/// In-app user notifications API. -/// [ApiController] [Route("api/notifications")] [Authorize] @@ -21,17 +18,18 @@ public class NotificationsController : ControllerBase _notifications = notifications; } - private Guid GetUserId() => Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!); + private Guid GetUserId() + { + return Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!); + } - /// Get up to 100 unread notifications. - [HttpGet("unread")] + [HttpGet("unread")] public async Task GetUnread(CancellationToken ct) { var list = await _notifications.GetUnreadAsync(GetUserId(), ct); return Ok(list); } - /// Get recent notifications (paged by take). [HttpGet] public async Task GetAll([FromQuery] int take = 100, CancellationToken ct = default) { @@ -39,20 +37,17 @@ public class NotificationsController : ControllerBase return Ok(list); } - /// Mark a notification as read. - [HttpPost("mark/{id:guid}")] + [HttpPost("mark/{id:guid}")] public async Task MarkRead(Guid id, CancellationToken ct) { await _notifications.MarkReadAsync(GetUserId(), id, ct); return NoContent(); } - /// Mark all notifications as read. - [HttpPost("mark-all")] + [HttpPost("mark-all")] public async Task MarkAll(CancellationToken ct) { - var cnt = await _notifications.MarkAllReadAsync(GetUserId(), ct); - return Ok(new { updated = cnt }); + var updated = await _notifications.MarkAllReadAsync(GetUserId(), ct); + return Ok(new { updated }); } } - diff --git a/LctMonolith/Controllers/PlayersController.cs b/LctMonolith/Controllers/PlayersController.cs index a097243..3170f0f 100644 --- a/LctMonolith/Controllers/PlayersController.cs +++ b/LctMonolith/Controllers/PlayersController.cs @@ -18,47 +18,54 @@ public class PlayersController : ControllerBase _progressService = progressService; } - [HttpGet("{playerId:guid}")] + [HttpGet("{playerId:guid}")] public async Task GetPlayer(Guid playerId) { var player = await _playerService.GetPlayerWithProgressAsync(playerId); return Ok(player); } - [HttpGet("user/{userId:guid}")] + [HttpGet("user/{userId:guid}")] public async Task GetByUser(Guid userId) { - var p = await _playerService.GetPlayerByUserIdAsync(userId.ToString()); - if (p == null) return NotFound(); - return Ok(p); + var player = await _playerService.GetPlayerByUserIdAsync(userId.ToString()); + if (player == null) + { + return NotFound(); + } + return Ok(player); } - public class CreatePlayerRequest { public Guid UserId { get; set; } public string Username { get; set; } = string.Empty; } + public class CreatePlayerRequest + { + public Guid UserId { get; set; } + public string Username { get; set; } = string.Empty; + } [HttpPost] [Authorize(Roles = "Admin")] public async Task Create(CreatePlayerRequest req) { - var p = await _playerService.CreatePlayerAsync(req.UserId.ToString(), req.Username); - return CreatedAtAction(nameof(GetPlayer), new { playerId = p.Id }, p); + var player = await _playerService.CreatePlayerAsync(req.UserId.ToString(), req.Username); + return CreatedAtAction(nameof(GetPlayer), new { playerId = player.Id }, player); } public record AdjustValueRequest(int Value); [HttpPost("{playerId:guid}/experience")] - [Authorize(Roles = "Admin")] // manual adjust + [Authorize(Roles = "Admin")] public async Task AddExperience(Guid playerId, AdjustValueRequest r) { - var p = await _playerService.AddPlayerExperienceAsync(playerId, r.Value); - return Ok(new { p.Id, p.Experience }); + var player = await _playerService.AddPlayerExperienceAsync(playerId, r.Value); + return Ok(new { player.Id, player.Experience }); } [HttpPost("{playerId:guid}/mana")] - [Authorize(Roles = "Admin")] // manual adjust + [Authorize(Roles = "Admin")] public async Task AddMana(Guid playerId, AdjustValueRequest r) { - var p = await _playerService.AddPlayerManaAsync(playerId, r.Value); - return Ok(new { p.Id, p.Mana }); + var player = await _playerService.AddPlayerManaAsync(playerId, r.Value); + return Ok(new { player.Id, player.Mana }); } [HttpGet("top")] @@ -68,11 +75,10 @@ public class PlayersController : ControllerBase return Ok(list); } - [HttpGet("{playerId:guid}/progress")] + [HttpGet("{playerId:guid}/progress")] public async Task GetProgress(Guid playerId) { - var prog = await _progressService.GetPlayerOverallProgressAsync(playerId); - return Ok(prog); + var progress = await _progressService.GetPlayerOverallProgressAsync(playerId); + return Ok(progress); } } - diff --git a/LctMonolith/Controllers/ProfileController.cs b/LctMonolith/Controllers/ProfileController.cs index a59ca08..b25ce6a 100644 --- a/LctMonolith/Controllers/ProfileController.cs +++ b/LctMonolith/Controllers/ProfileController.cs @@ -12,9 +12,16 @@ namespace LctMonolith.Controllers; public class ProfileController : ControllerBase { private readonly IProfileService _profiles; - public ProfileController(IProfileService profiles) => _profiles = profiles; - private Guid CurrentUserId() => Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!); + public ProfileController(IProfileService profiles) + { + _profiles = profiles; + } + + private Guid CurrentUserId() + { + return Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!); + } public class UpdateProfileDto { @@ -28,40 +35,54 @@ public class ProfileController : ControllerBase [HttpGet("me")] public async Task GetMe(CancellationToken ct) { - var p = await _profiles.GetByUserIdAsync(CurrentUserId(), ct); - if (p == null) return NotFound(); - return Ok(p); + var profile = await _profiles.GetByUserIdAsync(CurrentUserId(), ct); + if (profile == null) + { + return NotFound(); + } + return Ok(profile); } [HttpGet("{userId:guid}")] [Authorize(Roles = "Admin")] public async Task GetByUser(Guid userId, CancellationToken ct) { - var p = await _profiles.GetByUserIdAsync(userId, ct); - return p == null ? NotFound() : Ok(p); + var profile = await _profiles.GetByUserIdAsync(userId, ct); + if (profile == null) + { + return NotFound(); + } + return Ok(profile); } [HttpPut] public async Task Upsert(UpdateProfileDto dto, CancellationToken ct) { - var p = await _profiles.UpsertAsync(CurrentUserId(), dto.FirstName, dto.LastName, dto.BirthDate, dto.About, dto.Location, ct); - return Ok(p); + var profile = await _profiles.UpsertAsync(CurrentUserId(), dto.FirstName, dto.LastName, dto.BirthDate, dto.About, dto.Location, ct); + return Ok(profile); } [HttpPost("avatar")] - [RequestSizeLimit(7_000_000)] // ~7MB + [RequestSizeLimit(7_000_000)] public async Task UploadAvatar(IFormFile file, CancellationToken ct) { - if (file == null || file.Length == 0) return BadRequest("File required"); + if (file == null || file.Length == 0) + { + return BadRequest("File required"); + } await using var stream = file.OpenReadStream(); - var p = await _profiles.UpdateAvatarAsync(CurrentUserId(), stream, file.ContentType, file.FileName, ct); - return Ok(new { p.AvatarUrl }); + var profile = await _profiles.UpdateAvatarAsync(CurrentUserId(), stream, file.ContentType, file.FileName, ct); + return Ok(new { profile.AvatarUrl }); } [HttpDelete("avatar")] public async Task DeleteAvatar(CancellationToken ct) { - var ok = await _profiles.DeleteAvatarAsync(CurrentUserId(), ct); - return ok ? NoContent() : NotFound(); + var removed = await _profiles.DeleteAvatarAsync(CurrentUserId(), ct); + if (!removed) + { + return NotFound(); + } + return NoContent(); } } diff --git a/LctMonolith/Controllers/RanksController.cs b/LctMonolith/Controllers/RanksController.cs index b5bfd73..3f10185 100644 --- a/LctMonolith/Controllers/RanksController.cs +++ b/LctMonolith/Controllers/RanksController.cs @@ -30,8 +30,12 @@ public class RanksController : ControllerBase [HttpGet("{id:guid}")] public async Task Get(Guid id) { - var r = await _rankService.GetRankByIdAsync(id); - return r == null ? NotFound() : Ok(r); + var rank = await _rankService.GetRankByIdAsync(id); + if (rank == null) + { + return NotFound(); + } + return Ok(rank); } [HttpPost] @@ -46,27 +50,33 @@ public class RanksController : ControllerBase [Authorize(Roles = "Admin")] public async Task Update(Guid id, CreateRankDto dto) { - var r = await _rankService.GetRankByIdAsync(id); - if (r == null) return NotFound(); - r.Title = dto.Title; - r.ExpNeeded = dto.ExpNeeded; - await _rankService.UpdateRankAsync(r); - return Ok(r); + var rank = await _rankService.GetRankByIdAsync(id); + if (rank == null) + { + return NotFound(); + } + rank.Title = dto.Title; + rank.ExpNeeded = dto.ExpNeeded; + await _rankService.UpdateRankAsync(rank); + return Ok(rank); } [HttpDelete("{id:guid}")] [Authorize(Roles = "Admin")] public async Task Delete(Guid id) { - var ok = await _rankService.DeleteRankAsync(id); - return ok ? NoContent() : NotFound(); + var removed = await _rankService.DeleteRankAsync(id); + if (!removed) + { + return NotFound(); + } + return NoContent(); } [HttpGet("validate-advance/{playerId:guid}/{targetRankId:guid}")] public async Task CanAdvance(Guid playerId, Guid targetRankId) { - var ok = await _ruleValidation.ValidateRankAdvancementRulesAsync(playerId, targetRankId); - return Ok(new { playerId, targetRankId, canAdvance = ok }); + var can = await _ruleValidation.ValidateRankAdvancementRulesAsync(playerId, targetRankId); + return Ok(new { playerId, targetRankId, canAdvance = can }); } } - diff --git a/LctMonolith/Controllers/RewardController.cs b/LctMonolith/Controllers/RewardController.cs index 40c216e..073744e 100644 --- a/LctMonolith/Controllers/RewardController.cs +++ b/LctMonolith/Controllers/RewardController.cs @@ -16,23 +16,22 @@ public class RewardController : ControllerBase _rewardService = rewardService; } - /// List skill rewards configured for a mission. [HttpGet("mission/{missionId:guid}/skills")] public async Task GetMissionSkillRewards(Guid missionId) { var rewards = await _rewardService.GetMissionSkillRewardsAsync(missionId); - return Ok(rewards.Select(r => new { r.SkillId, r.Value })); + var shaped = rewards.Select(r => new { r.SkillId, r.Value }); + return Ok(shaped); } - /// List item rewards configured for a mission. [HttpGet("mission/{missionId:guid}/items")] public async Task GetMissionItemRewards(Guid missionId) { var rewards = await _rewardService.GetMissionItemRewardsAsync(missionId); - return Ok(rewards.Select(r => new { r.ItemId })); + var shaped = rewards.Select(r => new { r.ItemId }); + return Ok(shaped); } - /// Check if mission rewards can be claimed by player (missionId used as rewardId). [HttpGet("mission/{missionId:guid}/can-claim/{playerId:guid}")] public async Task CanClaim(Guid missionId, Guid playerId) { @@ -42,19 +41,20 @@ public class RewardController : ControllerBase public record ClaimRewardRequest(Guid PlayerId); - /// Claim mission rewards if available (idempotent on already claimed). [HttpPost("mission/{missionId:guid}/claim")] public async Task Claim(Guid missionId, ClaimRewardRequest req) { var can = await _rewardService.CanClaimRewardAsync(missionId, req.PlayerId); - if (!can) return Conflict(new { message = "Rewards already claimed or mission not completed" }); + if (!can) + { + return Conflict(new { message = "Rewards already claimed or mission not completed" }); + } await _rewardService.DistributeMissionRewardsAsync(missionId, req.PlayerId); return Ok(new { missionId, req.PlayerId, status = "claimed" }); } public record ForceDistributeRequest(Guid PlayerId); - /// Admin: force distribute rewards regardless of previous state (may duplicate). Use cautiously. [HttpPost("mission/{missionId:guid}/force-distribute")] [Authorize(Roles = "Admin")] public async Task ForceDistribute(Guid missionId, ForceDistributeRequest req) diff --git a/LctMonolith/Controllers/SkillsController.cs b/LctMonolith/Controllers/SkillsController.cs index 515d392..b527f78 100644 --- a/LctMonolith/Controllers/SkillsController.cs +++ b/LctMonolith/Controllers/SkillsController.cs @@ -28,8 +28,12 @@ public class SkillsController : ControllerBase [HttpGet("{id:guid}")] public async Task Get(Guid id) { - var s = await _skillService.GetSkillByIdAsync(id); - return s == null ? NotFound() : Ok(s); + var skill = await _skillService.GetSkillByIdAsync(id); + if (skill == null) + { + return NotFound(); + } + return Ok(skill); } [HttpPost] @@ -44,19 +48,26 @@ public class SkillsController : ControllerBase [Authorize(Roles = "Admin")] public async Task Update(Guid id, CreateSkillDto dto) { - var s = await _skillService.GetSkillByIdAsync(id); - if (s == null) return NotFound(); - s.Title = dto.Title; - await _skillService.UpdateSkillAsync(s); - return Ok(s); + var skill = await _skillService.GetSkillByIdAsync(id); + if (skill == null) + { + return NotFound(); + } + skill.Title = dto.Title; + await _skillService.UpdateSkillAsync(skill); + return Ok(skill); } [HttpDelete("{id:guid}")] [Authorize(Roles = "Admin")] public async Task Delete(Guid id) { - var ok = await _skillService.DeleteSkillAsync(id); - return ok ? NoContent() : NotFound(); + var removed = await _skillService.DeleteSkillAsync(id); + if (!removed) + { + return NotFound(); + } + return NoContent(); } [HttpGet("player/{playerId:guid}")] @@ -76,4 +87,3 @@ public class SkillsController : ControllerBase return Ok(new { ps.PlayerId, ps.SkillId, ps.Score }); } } - diff --git a/LctMonolith/Controllers/StoreController.cs b/LctMonolith/Controllers/StoreController.cs index 8689328..f84356e 100644 --- a/LctMonolith/Controllers/StoreController.cs +++ b/LctMonolith/Controllers/StoreController.cs @@ -7,9 +7,6 @@ using Microsoft.AspNetCore.Mvc; namespace LctMonolith.Controllers; -/// -/// Store endpoints for listing items and purchasing. -/// [ApiController] [Route("api/store")] [Authorize] @@ -22,9 +19,11 @@ public class StoreController : ControllerBase _storeService = storeService; } - private Guid GetUserId() => Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!); + private Guid GetUserId() + { + return Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!); + } - /// List active store items. [HttpGet("items")] public async Task GetItems(CancellationToken ct) { @@ -32,7 +31,6 @@ public class StoreController : ControllerBase return Ok(items); } - /// Purchase an item for the authenticated user. [HttpPost("purchase")] public async Task Purchase(PurchaseRequest req, CancellationToken ct) { @@ -40,4 +38,3 @@ public class StoreController : ControllerBase return Ok(new { inv.StoreItemId, inv.Quantity, inv.AcquiredAt }); } } - diff --git a/LctMonolith/Database/Data/DbSeeder.cs b/LctMonolith/Database/Data/DbSeeder.cs index 91e8317..e69de29 100644 --- a/LctMonolith/Database/Data/DbSeeder.cs +++ b/LctMonolith/Database/Data/DbSeeder.cs @@ -1,47 +0,0 @@ -using LctMonolith.Models.Database; -using Microsoft.EntityFrameworkCore; -using Serilog; - -namespace LctMonolith.Database.Data; - -/// -/// Development database seeder for initial ranks, competencies, sample store items. -/// Idempotent: checks existence before inserting. -/// -public static class DbSeeder -{ - public static async Task SeedAsync(AppDbContext db, CancellationToken ct = default) - { - // await db.Database.EnsureCreatedAsync(ct); - // - // if (!await db.Ranks.AnyAsync(ct)) - // { - // var ranks = new List - // { - // new() { Title = "Искатель", ExpNeeded = 0 }, - // new() { Title = "Пилот-кандидат", ExpNeeded = 500 }, - // new() { Title = "Принятый в экипаж", ExpNeeded = 1500 } - // }; - // db.Ranks.AddRange(ranks); - // Log.Information("Seeded {Count} ranks", ranks.Count); - // } - // - // if (!await db.Skills.AnyAsync(ct)) - // { - // var comps = new[] - // { - // "Вера в дело","Стремление к большему","Общение","Аналитика","Командование","Юриспруденция","Трёхмерное мышление","Базовая экономика","Основы аэронавигации" - // }.Select(n => new Skill { Title = n }); - // db.Skills.AddRange(comps); - // Log.Information("Seeded competencies"); - // } - // - // if (!await db.StoreItems.AnyAsync(ct)) - // { - // db.StoreItems.AddRange(new StoreItem { Name = "Футболка Алабуга", Price = 100 }, new StoreItem { Name = "Брелок Буран", Price = 50 }); - // Log.Information("Seeded store items"); - // } - // - // await db.SaveChangesAsync(ct); - } -} diff --git a/LctMonolith/Database/Repositories/GenericRepository.cs b/LctMonolith/Database/Repositories/GenericRepository.cs index 2614516..c995432 100644 --- a/LctMonolith/Database/Repositories/GenericRepository.cs +++ b/LctMonolith/Database/Repositories/GenericRepository.cs @@ -4,9 +4,6 @@ using Microsoft.EntityFrameworkCore; namespace LctMonolith.Database.Repositories; -/// -/// Generic repository implementation for common CRUD and query composition. -/// public class GenericRepository : IGenericRepository where TEntity : class { protected readonly AppDbContext Context; @@ -24,33 +21,61 @@ public class GenericRepository : IGenericRepository where TEnt params Expression>[] includes) { IQueryable query = Set; - if (filter != null) query = query.Where(filter); + if (filter != null) + { + query = query.Where(filter); + } if (includes != null) { foreach (var include in includes) + { query = query.Include(include); + } + } + if (orderBy != null) + { + query = orderBy(query); } - if (orderBy != null) query = orderBy(query); return query; } - public async Task GetByIdAsync(object id) => await Set.FindAsync(id) ?? null; + public async Task GetByIdAsync(object id) + { + return await Set.FindAsync(id); + } - public ValueTask FindAsync(params object[] keyValues) => Set.FindAsync(keyValues); + public ValueTask FindAsync(params object[] keyValues) + { + return Set.FindAsync(keyValues); + } - public async Task AddAsync(TEntity entity, CancellationToken ct = default) => await Set.AddAsync(entity, ct); + public async Task AddAsync(TEntity entity, CancellationToken ct = default) + { + await Set.AddAsync(entity, ct); + } - public async Task AddRangeAsync(IEnumerable entities, CancellationToken ct = default) => await Set.AddRangeAsync(entities, ct); + public async Task AddRangeAsync(IEnumerable entities, CancellationToken ct = default) + { + await Set.AddRangeAsync(entities, ct); + } - public void Update(TEntity entity) => Set.Update(entity); + public void Update(TEntity entity) + { + Set.Update(entity); + } - public void Remove(TEntity entity) => Set.Remove(entity); + public void Remove(TEntity entity) + { + Set.Remove(entity); + } public async Task RemoveByIdAsync(object id, CancellationToken ct = default) { var entity = await Set.FindAsync([id], ct); - if (entity == null) throw new KeyNotFoundException($"Entity {typeof(TEntity).Name} id={id} not found"); + if (entity == null) + { + throw new KeyNotFoundException($"Entity {typeof(TEntity).Name} id={id} not found"); + } Set.Remove(entity); } } - diff --git a/LctMonolith/Database/Repositories/IGenericRepository.cs b/LctMonolith/Database/Repositories/IGenericRepository.cs index 67152a1..554d246 100644 --- a/LctMonolith/Database/Repositories/IGenericRepository.cs +++ b/LctMonolith/Database/Repositories/IGenericRepository.cs @@ -2,9 +2,6 @@ using System.Linq.Expressions; namespace LctMonolith.Database.Repositories; -/// -/// Generic repository abstraction for aggregate root / entity access. Read operations return IQueryable for composition. -/// public interface IGenericRepository where TEntity : class { IQueryable Query( @@ -22,4 +19,3 @@ public interface IGenericRepository where TEntity : class void Remove(TEntity entity); Task RemoveByIdAsync(object id, CancellationToken ct = default); } - diff --git a/LctMonolith/Models/Database/Player.cs b/LctMonolith/Models/Database/Player.cs index e44c8c4..1279adf 100644 --- a/LctMonolith/Models/Database/Player.cs +++ b/LctMonolith/Models/Database/Player.cs @@ -3,12 +3,11 @@ namespace LctMonolith.Models.Database; public class Player { public Guid Id { get; set; } - public Guid UserId { get; set; } // 1:1 to AppUser (retain linkage) + public Guid UserId { get; set; } public Guid RankId { get; set; } public Rank? Rank { get; set; } public int Experience { get; set; } public int Mana { get; set; } - public ICollection PlayerMissions { get; set; } = new List(); public ICollection PlayerSkills { get; set; } = new List(); } diff --git a/LctMonolith/Program.cs b/LctMonolith/Program.cs index d1ff2e6..c1881cc 100644 --- a/LctMonolith/Program.cs +++ b/LctMonolith/Program.cs @@ -4,26 +4,24 @@ using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using System.Text; -using LctMonolith.Models.Database; // replaced Domain.Entities +using LctMonolith.Models.Database; using Microsoft.AspNetCore.Identity; using LctMonolith.Application.Middleware; using LctMonolith.Services; using LctMonolith.Application.Options; using LctMonolith.Database.Data; using LctMonolith.Database.UnitOfWork; -using LctMonolith.Services.Contracts; // Added for JwtOptions -using LctMonolith.Application.Extensions; // added +using LctMonolith.Services.Contracts; +using LctMonolith.Application.Extensions; var builder = WebApplication.CreateBuilder(args); -// Serilog configuration builder.Host.UseSerilog((ctx, services, loggerConfig) => loggerConfig .ReadFrom.Configuration(ctx.Configuration) .Enrich.FromLogContext() .Enrich.WithProperty("Application", "LctMonolith")); -// Configuration values var connectionString = builder.Configuration.GetConnectionString("Default") ?? "Host=localhost;Port=5432;Database=lct2025;Username=postgres;Password=postgres"; var jwtSection = builder.Configuration.GetSection("Jwt"); var jwtKey = jwtSection["Key"] ?? "Dev_Insecure_Key_Change_Me"; @@ -42,11 +40,9 @@ builder.Services.Configure(o => o.RefreshTokenDays = refreshDays; }); -// DbContext builder.Services.AddDbContext(opt => opt.UseNpgsql(connectionString)); -// Identity Core builder.Services.AddIdentityCore(options => { options.Password.RequireDigit = false; @@ -60,7 +56,6 @@ builder.Services.AddIdentityCore(options => .AddSignInManager>() .AddDefaultTokenProviders(); -// Authentication & JWT builder.Services.AddAuthentication(o => { o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; @@ -81,38 +76,19 @@ builder.Services.AddAuthentication(o => }; }); -// Controllers + NewtonsoftJson -builder.Services.AddControllers() - .AddNewtonsoftJson(); +builder.Services.AddControllers().AddNewtonsoftJson(); -// OpenAPI builder.Services.AddOpenApi(); -// Health checks builder.Services.AddHealthChecks(); -// Remove individual service registrations and replace with extension -// builder.Services.AddScoped(); -// builder.Services.AddScoped(); -// builder.Services.AddScoped(); -// builder.Services.AddScoped(); -// builder.Services.AddScoped(); - builder.Services.AddApplicationServices(builder.Configuration); -// CORS builder.Services.AddCors(p => p.AddDefaultPolicy(policy => policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin())); var app = builder.Build(); -using (var scope = app.Services.CreateScope()) -{ - var db = scope.ServiceProvider.GetRequiredService(); - db.Database.Migrate(); - await DbSeeder.SeedAsync(db); // seed dev data -} - app.UseSerilogRequestLogging(); if (app.Environment.IsDevelopment()) diff --git a/LctMonolith/Services/AnalyticsService.cs b/LctMonolith/Services/AnalyticsService.cs index 40834a2..697b64c 100644 --- a/LctMonolith/Services/AnalyticsService.cs +++ b/LctMonolith/Services/AnalyticsService.cs @@ -6,13 +6,14 @@ using Serilog; namespace LctMonolith.Services; -/// -/// Provides aggregated analytics metrics for dashboards. -/// public class AnalyticsService : IAnalyticsService { private readonly IUnitOfWork _uow; - public AnalyticsService(IUnitOfWork uow) => _uow = uow; + + public AnalyticsService(IUnitOfWork uow) + { + _uow = uow; + } public async Task GetSummaryAsync(CancellationToken ct = default) { diff --git a/LctMonolith/Services/DialogueService.cs b/LctMonolith/Services/DialogueService.cs index ee7a0c5..643cb78 100644 --- a/LctMonolith/Services/DialogueService.cs +++ b/LctMonolith/Services/DialogueService.cs @@ -4,52 +4,98 @@ using LctMonolith.Services.Interfaces; using Microsoft.EntityFrameworkCore; using Serilog; -namespace LctMonolith.Services; - -public class DialogueService : IDialogueService +namespace LctMonolith.Services { - private readonly IUnitOfWork _uow; - public DialogueService(IUnitOfWork uow) => _uow = uow; - - public async Task GetDialogueByMissionIdAsync(Guid missionId) + public class DialogueService : IDialogueService { - try { return await _uow.Dialogues.Query(d => d.MissionId == missionId).FirstOrDefaultAsync(); } - catch (Exception ex) { Log.Error(ex, "GetDialogueByMissionIdAsync failed {MissionId}", missionId); throw; } - } + private readonly IUnitOfWork _uow; - public async Task CreateDialogueAsync(Dialogue dialogue) - { - try + public DialogueService(IUnitOfWork uow) { - dialogue.Id = Guid.NewGuid(); - await _uow.Dialogues.AddAsync(dialogue); - await _uow.SaveChangesAsync(); - return dialogue; + _uow = uow; } - catch (Exception ex) { Log.Error(ex, "CreateDialogueAsync failed {MissionId}", dialogue.MissionId); throw; } - } - public async Task GetDialogueMessageByIdAsync(Guid messageId) - { - try { return await _uow.DialogueMessages.GetByIdAsync(messageId); } - catch (Exception ex) { Log.Error(ex, "GetDialogueMessageByIdAsync failed {MessageId}", messageId); throw; } - } - - public async Task> GetResponseOptionsAsync(Guid messageId) - { - try { return await _uow.DialogueMessageResponseOptions.Query(o => o.ParentDialogueMessageId == messageId).ToListAsync(); } - catch (Exception ex) { Log.Error(ex, "GetResponseOptionsAsync failed {MessageId}", messageId); throw; } - } - - public async Task ProcessDialogueResponseAsync(Guid messageId, Guid responseOptionId, Guid playerId) - { - try + public async Task GetDialogueByMissionIdAsync(Guid missionId) { - var option = await _uow.DialogueMessageResponseOptions.Query(o => o.Id == responseOptionId && o.ParentDialogueMessageId == messageId).FirstOrDefaultAsync(); - if (option == null) return null; - if (option.DestinationDialogueMessageId == null) return null; // end branch - return await _uow.DialogueMessages.GetByIdAsync(option.DestinationDialogueMessageId); + try + { + return await _uow.Dialogues + .Query(d => d.MissionId == missionId) + .FirstOrDefaultAsync(); + } + catch (Exception ex) + { + Log.Error(ex, "GetDialogueByMissionIdAsync failed {MissionId}", missionId); + throw; + } + } + + public async Task CreateDialogueAsync(Dialogue dialogue) + { + try + { + dialogue.Id = Guid.NewGuid(); + await _uow.Dialogues.AddAsync(dialogue); + await _uow.SaveChangesAsync(); + return dialogue; + } + catch (Exception ex) + { + Log.Error(ex, "CreateDialogueAsync failed {MissionId}", dialogue.MissionId); + throw; + } + } + + public async Task GetDialogueMessageByIdAsync(Guid messageId) + { + try + { + return await _uow.DialogueMessages.GetByIdAsync(messageId); + } + catch (Exception ex) + { + Log.Error(ex, "GetDialogueMessageByIdAsync failed {MessageId}", messageId); + throw; + } + } + + public async Task> GetResponseOptionsAsync(Guid messageId) + { + try + { + return await _uow.DialogueMessageResponseOptions + .Query(o => o.ParentDialogueMessageId == messageId) + .ToListAsync(); + } + catch (Exception ex) + { + Log.Error(ex, "GetResponseOptionsAsync failed {MessageId}", messageId); + throw; + } + } + + public async Task ProcessDialogueResponseAsync(Guid messageId, Guid responseOptionId, Guid playerId) + { + try + { + var option = await _uow.DialogueMessageResponseOptions + .Query(o => o.Id == responseOptionId && o.ParentDialogueMessageId == messageId) + .FirstOrDefaultAsync(); + if (option == null) + { + return null; + } + if (option.DestinationDialogueMessageId == null) + { + return null; + } + return await _uow.DialogueMessages.GetByIdAsync(option.DestinationDialogueMessageId); + } + catch (Exception ex) + { + Log.Error(ex, "ProcessDialogueResponseAsync failed {MessageId} {ResponseId}", messageId, responseOptionId); + throw; + } } - catch (Exception ex) { Log.Error(ex, "ProcessDialogueResponseAsync failed {MessageId} {ResponseId}", messageId, responseOptionId); throw; } } } diff --git a/LctMonolith/Services/MissionCategoryService.cs b/LctMonolith/Services/MissionCategoryService.cs index d4f73a3..c98e931 100644 --- a/LctMonolith/Services/MissionCategoryService.cs +++ b/LctMonolith/Services/MissionCategoryService.cs @@ -4,46 +4,102 @@ using LctMonolith.Services.Interfaces; using Microsoft.EntityFrameworkCore; using Serilog; -namespace LctMonolith.Services; - -public class MissionCategoryService : IMissionCategoryService +namespace LctMonolith.Services { - private readonly IUnitOfWork _uow; - public MissionCategoryService(IUnitOfWork uow) => _uow = uow; - - public async Task GetCategoryByIdAsync(Guid categoryId) + public class MissionCategoryService : IMissionCategoryService { - try { return await _uow.MissionCategories.GetByIdAsync(categoryId); } - catch (Exception ex) { Log.Error(ex, "GetCategoryByIdAsync failed {CategoryId}", categoryId); throw; } - } + private readonly IUnitOfWork _uow; - public async Task GetCategoryByTitleAsync(string title) - { - try { return await _uow.MissionCategories.Query(c => c.Title == title).FirstOrDefaultAsync(); } - catch (Exception ex) { Log.Error(ex, "GetCategoryByTitleAsync failed {Title}", title); throw; } - } + public MissionCategoryService(IUnitOfWork uow) + { + _uow = uow; + } - public async Task> GetAllCategoriesAsync() - { - try { return await _uow.MissionCategories.Query().OrderBy(c => c.Title).ToListAsync(); } - catch (Exception ex) { Log.Error(ex, "GetAllCategoriesAsync failed"); throw; } - } + public async Task GetCategoryByIdAsync(Guid categoryId) + { + try + { + return await _uow.MissionCategories.GetByIdAsync(categoryId); + } + catch (Exception ex) + { + Log.Error(ex, "GetCategoryByIdAsync failed {CategoryId}", categoryId); + throw; + } + } - public async Task CreateCategoryAsync(MissionCategory category) - { - try { category.Id = Guid.NewGuid(); await _uow.MissionCategories.AddAsync(category); await _uow.SaveChangesAsync(); return category; } - catch (Exception ex) { Log.Error(ex, "CreateCategoryAsync failed {Title}", category.Title); throw; } - } + public async Task GetCategoryByTitleAsync(string title) + { + try + { + return await _uow.MissionCategories.Query(c => c.Title == title).FirstOrDefaultAsync(); + } + catch (Exception ex) + { + Log.Error(ex, "GetCategoryByTitleAsync failed {Title}", title); + throw; + } + } - public async Task UpdateCategoryAsync(MissionCategory category) - { - try { _uow.MissionCategories.Update(category); await _uow.SaveChangesAsync(); return category; } - catch (Exception ex) { Log.Error(ex, "UpdateCategoryAsync failed {Id}", category.Id); throw; } - } + public async Task> GetAllCategoriesAsync() + { + try + { + return await _uow.MissionCategories.Query().OrderBy(c => c.Title).ToListAsync(); + } + catch (Exception ex) + { + Log.Error(ex, "GetAllCategoriesAsync failed"); + throw; + } + } - public async Task DeleteCategoryAsync(Guid categoryId) - { - try { var c = await _uow.MissionCategories.GetByIdAsync(categoryId); if (c == null) return false; _uow.MissionCategories.Remove(c); await _uow.SaveChangesAsync(); return true; } - catch (Exception ex) { Log.Error(ex, "DeleteCategoryAsync failed {CategoryId}", categoryId); throw; } + public async Task CreateCategoryAsync(MissionCategory category) + { + try + { + category.Id = Guid.NewGuid(); + await _uow.MissionCategories.AddAsync(category); + await _uow.SaveChangesAsync(); + return category; + } + catch (Exception ex) + { + Log.Error(ex, "CreateCategoryAsync failed {Title}", category.Title); + throw; + } + } + + public async Task UpdateCategoryAsync(MissionCategory category) + { + try + { + _uow.MissionCategories.Update(category); + await _uow.SaveChangesAsync(); + return category; + } + catch (Exception ex) + { + Log.Error(ex, "UpdateCategoryAsync failed {Id}", category.Id); + throw; + } + } + + public async Task DeleteCategoryAsync(Guid categoryId) + { + try + { + var c = await _uow.MissionCategories.GetByIdAsync(categoryId); + if (c == null) { return false; } + _uow.MissionCategories.Remove(c); + await _uow.SaveChangesAsync(); + return true; + } + catch (Exception ex) + { + Log.Error(ex, "DeleteCategoryAsync failed {CategoryId}", categoryId); + throw; + } + } } } diff --git a/LctMonolith/Services/RankService.cs b/LctMonolith/Services/RankService.cs index c026c26..03a4fc7 100644 --- a/LctMonolith/Services/RankService.cs +++ b/LctMonolith/Services/RankService.cs @@ -9,50 +9,124 @@ namespace LctMonolith.Services; public class RankService : IRankService { private readonly IUnitOfWork _uow; - public RankService(IUnitOfWork uow) => _uow = uow; + + public RankService(IUnitOfWork uow) + { + _uow = uow; + } public async Task GetRankByIdAsync(Guid rankId) { - try { return await _uow.Ranks.GetByIdAsync(rankId); } - catch (Exception ex) { Log.Error(ex, "GetRankByIdAsync failed {RankId}", rankId); throw; } + try + { + return await _uow.Ranks.GetByIdAsync(rankId); + } + catch (Exception ex) + { + Log.Error(ex, "GetRankByIdAsync failed {RankId}", rankId); + throw; + } } + public async Task GetRankByTitleAsync(string title) { - try { return await _uow.Ranks.Query(r => r.Title == title).FirstOrDefaultAsync(); } - catch (Exception ex) { Log.Error(ex, "GetRankByTitleAsync failed {Title}", title); throw; } + try + { + return await _uow.Ranks.Query(r => r.Title == title).FirstOrDefaultAsync(); + } + catch (Exception ex) + { + Log.Error(ex, "GetRankByTitleAsync failed {Title}", title); + throw; + } } + public async Task> GetAllRanksAsync() { - try { return await _uow.Ranks.Query().OrderBy(r => r.ExpNeeded).ToListAsync(); } - catch (Exception ex) { Log.Error(ex, "GetAllRanksAsync failed"); throw; } + try + { + return await _uow.Ranks.Query().OrderBy(r => r.ExpNeeded).ToListAsync(); + } + catch (Exception ex) + { + Log.Error(ex, "GetAllRanksAsync failed"); + throw; + } } + public async Task CreateRankAsync(Rank rank) { - try { rank.Id = Guid.NewGuid(); await _uow.Ranks.AddAsync(rank); await _uow.SaveChangesAsync(); return rank; } - catch (Exception ex) { Log.Error(ex, "CreateRankAsync failed {Title}", rank.Title); throw; } + try + { + rank.Id = Guid.NewGuid(); + await _uow.Ranks.AddAsync(rank); + await _uow.SaveChangesAsync(); + return rank; + } + catch (Exception ex) + { + Log.Error(ex, "CreateRankAsync failed {Title}", rank.Title); + throw; + } } + public async Task UpdateRankAsync(Rank rank) { - try { _uow.Ranks.Update(rank); await _uow.SaveChangesAsync(); return rank; } - catch (Exception ex) { Log.Error(ex, "UpdateRankAsync failed {RankId}", rank.Id); throw; } + try + { + _uow.Ranks.Update(rank); + await _uow.SaveChangesAsync(); + return rank; + } + catch (Exception ex) + { + Log.Error(ex, "UpdateRankAsync failed {RankId}", rank.Id); + throw; + } } + public async Task DeleteRankAsync(Guid rankId) { - try { var r = await _uow.Ranks.GetByIdAsync(rankId); if (r == null) return false; _uow.Ranks.Remove(r); await _uow.SaveChangesAsync(); return true; } - catch (Exception ex) { Log.Error(ex, "DeleteRankAsync failed {RankId}", rankId); throw; } + try + { + var r = await _uow.Ranks.GetByIdAsync(rankId); + if (r == null) + { + return false; + } + + _uow.Ranks.Remove(r); + await _uow.SaveChangesAsync(); + return true; + } + catch (Exception ex) + { + Log.Error(ex, "DeleteRankAsync failed {RankId}", rankId); + throw; + } } + public async Task CanPlayerAdvanceToRankAsync(Guid playerId, Guid rankId) { - try { + try + { var player = await _uow.Players.GetByIdAsync(playerId) ?? throw new KeyNotFoundException("Player not found"); var rank = await _uow.Ranks.GetByIdAsync(rankId) ?? throw new KeyNotFoundException("Rank not found"); - if (player.Experience < rank.ExpNeeded) return false; + if (player.Experience < rank.ExpNeeded) + { + return false; + } + var missionReqs = await _uow.RankMissionRules.Query(rmr => rmr.RankId == rankId).Select(r => r.MissionId).ToListAsync(); if (missionReqs.Count > 0) { var completed = await _uow.PlayerMissions.Query(pm => pm.PlayerId == playerId && pm.Completed != null).Select(pm => pm.MissionId).ToListAsync(); - if (missionReqs.Except(completed).Any()) return false; + if (missionReqs.Except(completed).Any()) + { + return false; + } } + var skillReqs = await _uow.RankSkillRules.Query(rsr => rsr.RankId == rankId).ToListAsync(); if (skillReqs.Count > 0) { @@ -60,16 +134,38 @@ public class RankService : IRankService foreach (var req in skillReqs) { var ps = playerSkills.FirstOrDefault(s => s.SkillId == req.SkillId); - if (ps == null || ps.Score < req.Min) return false; + if (ps == null || ps.Score < req.Min) + { + return false; + } } } - return true; } - catch (Exception ex) { Log.Error(ex, "CanPlayerAdvanceToRankAsync failed {PlayerId}->{RankId}", playerId, rankId); throw; } + + return true; + } + catch (Exception ex) + { + Log.Error(ex, "CanPlayerAdvanceToRankAsync failed {PlayerId}->{RankId}", playerId, rankId); + throw; + } } + public async Task GetNextRankAsync(Guid currentRankId) { - try { - var current = await _uow.Ranks.GetByIdAsync(currentRankId); if (current == null) return null; return await _uow.Ranks.Query(r => r.ExpNeeded > current.ExpNeeded).OrderBy(r => r.ExpNeeded).FirstOrDefaultAsync(); } - catch (Exception ex) { Log.Error(ex, "GetNextRankAsync failed {RankId}", currentRankId); throw; } + try + { + var current = await _uow.Ranks.GetByIdAsync(currentRankId); + if (current == null) + { + return null; + } + + return await _uow.Ranks.Query(r => r.ExpNeeded > current.ExpNeeded).OrderBy(r => r.ExpNeeded).FirstOrDefaultAsync(); + } + catch (Exception ex) + { + Log.Error(ex, "GetNextRankAsync failed {RankId}", currentRankId); + throw; + } } } diff --git a/LctMonolith/Services/RewardService.cs b/LctMonolith/Services/RewardService.cs index 12bc576..0cabd4d 100644 --- a/LctMonolith/Services/RewardService.cs +++ b/LctMonolith/Services/RewardService.cs @@ -9,18 +9,36 @@ namespace LctMonolith.Services; public class RewardService : IRewardService { private readonly IUnitOfWork _uow; - public RewardService(IUnitOfWork uow) => _uow = uow; + + public RewardService(IUnitOfWork uow) + { + _uow = uow; + } public async Task> GetMissionSkillRewardsAsync(Guid missionId) { - try { return await _uow.MissionSkillRewards.Query(r => r.MissionId == missionId, null, r => r.Skill).ToListAsync(); } - catch (Exception ex) { Log.Error(ex, "GetMissionSkillRewardsAsync failed {MissionId}", missionId); throw; } + try + { + return await _uow.MissionSkillRewards.Query(r => r.MissionId == missionId, null, r => r.Skill).ToListAsync(); + } + catch (Exception ex) + { + Log.Error(ex, "GetMissionSkillRewardsAsync failed {MissionId}", missionId); + throw; + } } public async Task> GetMissionItemRewardsAsync(Guid missionId) { - try { return await _uow.MissionItemRewards.Query(r => r.MissionId == missionId).ToListAsync(); } - catch (Exception ex) { Log.Error(ex, "GetMissionItemRewardsAsync failed {MissionId}", missionId); throw; } + try + { + return await _uow.MissionItemRewards.Query(r => r.MissionId == missionId).ToListAsync(); + } + catch (Exception ex) + { + Log.Error(ex, "GetMissionItemRewardsAsync failed {MissionId}", missionId); + throw; + } } public async Task DistributeMissionRewardsAsync(Guid missionId, Guid playerId) @@ -33,7 +51,6 @@ public class RewardService : IRewardService player.Experience += mission.ExpReward; player.Mana += mission.ManaReward; - // Skill rewards var skillRewards = await _uow.MissionSkillRewards.Query(r => r.MissionId == missionId).ToListAsync(); foreach (var sr in skillRewards) { @@ -50,7 +67,6 @@ public class RewardService : IRewardService } } - // Item rewards (store items) one each var itemRewards = await _uow.MissionItemRewards.Query(r => r.MissionId == missionId).ToListAsync(); foreach (var ir in itemRewards) { @@ -60,10 +76,12 @@ public class RewardService : IRewardService inv = new UserInventoryItem { UserId = player.UserId, StoreItemId = ir.ItemId, Quantity = 1, AcquiredAt = DateTime.UtcNow }; await _uow.UserInventoryItems.AddAsync(inv); } - else inv.Quantity += 1; + else + { + inv.Quantity += 1; + } } - // Mark redeemed var pm = await _uow.PlayerMissions.Query(pm => pm.PlayerId == playerId && pm.MissionId == missionId).FirstOrDefaultAsync(); if (pm != null && pm.RewardsRedeemed == null) { @@ -82,11 +100,17 @@ public class RewardService : IRewardService { try { - // Interpret rewardId as missionId; claim if mission completed and rewards not yet redeemed var pm = await _uow.PlayerMissions.Query(pm => pm.PlayerId == playerId && pm.MissionId == rewardId).FirstOrDefaultAsync(); - if (pm == null || pm.Completed == null) return false; + if (pm == null || pm.Completed == null) + { + return false; + } return pm.RewardsRedeemed == null; } - catch (Exception ex) { Log.Error(ex, "CanClaimRewardAsync failed {RewardId} {PlayerId}", rewardId, playerId); throw; } + catch (Exception ex) + { + Log.Error(ex, "CanClaimRewardAsync failed {RewardId} {PlayerId}", rewardId, playerId); + throw; + } } } diff --git a/LctMonolith/Services/RuleValidationService.cs b/LctMonolith/Services/RuleValidationService.cs index e580ac1..e25fc91 100644 --- a/LctMonolith/Services/RuleValidationService.cs +++ b/LctMonolith/Services/RuleValidationService.cs @@ -9,19 +9,35 @@ namespace LctMonolith.Services; public class RuleValidationService : IRuleValidationService { private readonly IUnitOfWork _uow; - public RuleValidationService(IUnitOfWork uow) => _uow = uow; + + public RuleValidationService(IUnitOfWork uow) + { + _uow = uow; + } public async Task ValidateMissionRankRulesAsync(Guid missionId, Guid playerId) { try { var player = await _uow.Players.GetByIdAsync(playerId); - if (player == null) return false; + if (player == null) + { + return false; + } + var rankRules = await _uow.MissionRankRules.Query(r => r.MissionId == missionId).Select(r => r.RankId).ToListAsync(); - if (rankRules.Count == 0) return true; // no restriction + if (rankRules.Count == 0) + { + return true; + } + return rankRules.Contains(player.RankId); } - catch (Exception ex) { Log.Error(ex, "ValidateMissionRankRulesAsync failed {MissionId} {PlayerId}", missionId, playerId); throw; } + catch (Exception ex) + { + Log.Error(ex, "ValidateMissionRankRulesAsync failed {MissionId} {PlayerId}", missionId, playerId); + throw; + } } public async Task ValidateRankAdvancementRulesAsync(Guid playerId, Guid targetRankId) @@ -29,18 +45,32 @@ public class RuleValidationService : IRuleValidationService try { var player = await _uow.Players.GetByIdAsync(playerId); - if (player == null) return false; + if (player == null) + { + return false; + } + var rank = await _uow.Ranks.GetByIdAsync(targetRankId); - if (rank == null) return false; - if (player.Experience < rank.ExpNeeded) return false; - // required missions + if (rank == null) + { + return false; + } + + if (player.Experience < rank.ExpNeeded) + { + return false; + } + var missionReqs = await _uow.RankMissionRules.Query(r => r.RankId == targetRankId).Select(r => r.MissionId).ToListAsync(); if (missionReqs.Count > 0) { var completed = await _uow.PlayerMissions.Query(pm => pm.PlayerId == playerId && pm.Completed != null).Select(pm => pm.MissionId).ToListAsync(); - if (missionReqs.Except(completed).Any()) return false; + if (missionReqs.Except(completed).Any()) + { + return false; + } } - // required skills + var skillReqs = await _uow.RankSkillRules.Query(r => r.RankId == targetRankId).ToListAsync(); if (skillReqs.Count > 0) { @@ -48,17 +78,32 @@ public class RuleValidationService : IRuleValidationService foreach (var req in skillReqs) { var ps = playerSkills.FirstOrDefault(s => s.SkillId == req.SkillId); - if (ps == null || ps.Score < req.Min) return false; + if (ps == null || ps.Score < req.Min) + { + return false; + } } } + return true; } - catch (Exception ex) { Log.Error(ex, "ValidateRankAdvancementRulesAsync failed {PlayerId}->{RankId}", playerId, targetRankId); throw; } + catch (Exception ex) + { + Log.Error(ex, "ValidateRankAdvancementRulesAsync failed {PlayerId}->{RankId}", playerId, targetRankId); + throw; + } } public async Task> GetApplicableRankRulesAsync(Guid missionId) { - try { return await _uow.MissionRankRules.Query(r => r.MissionId == missionId, null, r => r.Rank).ToListAsync(); } - catch (Exception ex) { Log.Error(ex, "GetApplicableRankRulesAsync failed {MissionId}", missionId); throw; } + try + { + return await _uow.MissionRankRules.Query(r => r.MissionId == missionId, null, r => r.Rank).ToListAsync(); + } + catch (Exception ex) + { + Log.Error(ex, "GetApplicableRankRulesAsync failed {MissionId}", missionId); + throw; + } } } diff --git a/LctMonolith/Services/SkillService.cs b/LctMonolith/Services/SkillService.cs index 7296d0a..5cae90f 100644 --- a/LctMonolith/Services/SkillService.cs +++ b/LctMonolith/Services/SkillService.cs @@ -9,7 +9,7 @@ namespace LctMonolith.Services; public class SkillService : ISkillService { private readonly IUnitOfWork _uow; - public SkillService(IUnitOfWork uow) => _uow = uow; + public SkillService(IUnitOfWork uow) { _uow = uow; } public async Task GetSkillByIdAsync(Guid skillId) { @@ -38,7 +38,7 @@ public class SkillService : ISkillService } public async Task DeleteSkillAsync(Guid skillId) { - try { var skill = await _uow.Skills.GetByIdAsync(skillId); if (skill == null) return false; _uow.Skills.Remove(skill); await _uow.SaveChangesAsync(); return true; } + try { var skill = await _uow.Skills.GetByIdAsync(skillId); if (skill == null) { return false; } _uow.Skills.Remove(skill); await _uow.SaveChangesAsync(); return true; } catch (Exception ex) { Log.Error(ex, "DeleteSkillAsync failed {SkillId}", skillId); throw; } } public async Task UpdatePlayerSkillAsync(Guid playerId, Guid skillId, int level) diff --git a/LctMonolith/Services/StoreService.cs b/LctMonolith/Services/StoreService.cs index 3f15a55..82ed076 100644 --- a/LctMonolith/Services/StoreService.cs +++ b/LctMonolith/Services/StoreService.cs @@ -7,9 +7,6 @@ using LctMonolith.Services.Contracts; namespace LctMonolith.Services; -/// -/// Store purchase operations and inventory management. -/// public class StoreService : IStoreService { private readonly IUnitOfWork _uow; @@ -21,8 +18,15 @@ public class StoreService : IStoreService public async Task> GetActiveItemsAsync(CancellationToken ct = default) { - try { return await _uow.StoreItems.Query(i => i.IsActive).ToListAsync(ct); } - catch (Exception ex) { Log.Error(ex, "GetActiveItemsAsync failed"); throw; } + try + { + return await _uow.StoreItems.Query(i => i.IsActive).ToListAsync(ct); + } + catch (Exception ex) + { + Log.Error(ex, "GetActiveItemsAsync failed"); + throw; + } } public async Task PurchaseAsync(Guid userId, Guid itemId, int quantity, CancellationToken ct = default) @@ -45,7 +49,10 @@ public class StoreService : IStoreService inv = new UserInventoryItem { UserId = userId, StoreItemId = itemId, Quantity = quantity, AcquiredAt = DateTime.UtcNow }; await _uow.UserInventoryItems.AddAsync(inv, ct); } - else inv.Quantity += quantity; + else + { + inv.Quantity += quantity; + } await _uow.Transactions.AddAsync(new Transaction { UserId = userId, StoreItemId = itemId, Type = TransactionType.Purchase, ManaAmount = -totalPrice }, ct); await _uow.EventLogs.AddAsync(new EventLog { Type = EventType.ItemPurchased, UserId = userId, Data = JsonSerializer.Serialize(new { itemId, quantity, totalPrice }) }, ct);