From 159c02fd4b5cc13212b6dcaa5246b9fbfd4eabb1 Mon Sep 17 00:00:00 2001 From: kitiketov Date: Sat, 13 Dec 2025 20:22:47 +0500 Subject: [PATCH 01/36] =?UTF-8?q?=D0=B2=D1=80=D0=B5=D0=BC=D1=8F=20=D0=B6?= =?UTF-8?q?=D0=B8=D0=B7=D0=BD=D0=B8=2020=20=D0=BC=D0=B8=D0=BD=D1=83=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Api/appsettings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Api/appsettings.json b/Api/appsettings.json index 3f7089e..a491f41 100644 --- a/Api/appsettings.json +++ b/Api/appsettings.json @@ -7,7 +7,7 @@ }, "AllowedHosts": "*", "JwtSettings": { - "ExpiresAccess": "00:02:00", + "ExpiresAccess": "00:20:00", "ExpiresRefresh": "1.00:00:00", "SecretKey": "JwtSecretKey", "AllowNonExpiringTokens": true From dc1743290d0a789a494f2ed8ad94e28016a196cf Mon Sep 17 00:00:00 2001 From: Lychee <2006tka@gmail.com> Date: Sun, 14 Dec 2025 10:44:55 +0500 Subject: [PATCH 02/36] =?UTF-8?q?=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=BA=D0=B0=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BE=D0=BA=20?= =?UTF-8?q?=D1=81=D1=82=D0=BE=D1=80=D0=BE=D0=BD=D0=BD=D0=B5=D0=B3=D0=BE=20?= =?UTF-8?q?Api=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20Result=20(=D1=82=D0=B5?= =?UTF-8?q?=D1=81=D1=82)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/Exceptions/ResultExtensions.cs | 6 +- .../Features/Auth/Refresh/RefreshHandler.cs | 8 +- .../Features/Tasks/GetTask/GetTaskHandler.cs | 4 +- .../Features/Weather/WeatherController.cs | 7 +- Client.Models/Client.Models.csproj | 4 + .../Models}/Common/Results/Result.cs | 0 Infrastructure/Api/WeatherService.cs | 76 +++++++++++++------ Infrastructure/Interfaces/IWeatherApi.cs | 5 +- 8 files changed, 75 insertions(+), 35 deletions(-) rename {Api/Application => Client.Models/Models}/Common/Results/Result.cs (100%) diff --git a/Api/Application/Common/Exceptions/ResultExtensions.cs b/Api/Application/Common/Exceptions/ResultExtensions.cs index 952b158..8acc4eb 100644 --- a/Api/Application/Common/Exceptions/ResultExtensions.cs +++ b/Api/Application/Common/Exceptions/ResultExtensions.cs @@ -8,8 +8,10 @@ public static T EnsureSuccess(this Result result) { if (!result.IsSuccess) { - var error = result.Error ?? new Error("unknown", "Unknown error"); - throw new ApiException(StatusCodes.Status500InternalServerError, error.Message); + throw new ApiException( + StatusCodes.Status503ServiceUnavailable, + result.Error?.Message ?? "External service unavailable" + ); } return result.Value!; diff --git a/Api/Application/Features/Auth/Refresh/RefreshHandler.cs b/Api/Application/Features/Auth/Refresh/RefreshHandler.cs index f598297..b5374f9 100644 --- a/Api/Application/Features/Auth/Refresh/RefreshHandler.cs +++ b/Api/Application/Features/Auth/Refresh/RefreshHandler.cs @@ -4,16 +4,16 @@ namespace Api.Application.Features.Auth.Refresh; -public class RefreshHandler(IUserTable users, IPasswordHasher hasher, ITokenService tokenService) +public class RefreshHandler(ITokenService tokenService) : IRequestHandler { - public async Task Handle(RefreshCommand command, + public Task Handle(RefreshCommand command, CancellationToken cancellationToken) { var username = command.Username; - return new RefreshResponse( + return Task.FromResult(new RefreshResponse( tokenService.GenerateAccessToken(username), tokenService.GenerateRefreshToken(username) - ); + )); } } \ No newline at end of file diff --git a/Api/Application/Features/Tasks/GetTask/GetTaskHandler.cs b/Api/Application/Features/Tasks/GetTask/GetTaskHandler.cs index 0a6864d..0b03d22 100644 --- a/Api/Application/Features/Tasks/GetTask/GetTaskHandler.cs +++ b/Api/Application/Features/Tasks/GetTask/GetTaskHandler.cs @@ -10,6 +10,8 @@ public class GetTaskHandler(IUsersTasksTable tasks) : IRequestHandler Handle(GetTaskQuery request, CancellationToken cancellationToken) { var task = await tasks.GetTaskFullInfo(request.TaskId); - return task ?? throw new ApiException(StatusCodes.Status404NotFound, $"Task {request.TaskId} for {request.Username} not found"); + return task ?? + throw new ApiException(StatusCodes.Status404NotFound, + $"Task {request.TaskId} for {request.Username} not found"); } } \ No newline at end of file diff --git a/Api/Application/Features/Weather/WeatherController.cs b/Api/Application/Features/Weather/WeatherController.cs index 3003875..7ecca2a 100644 --- a/Api/Application/Features/Weather/WeatherController.cs +++ b/Api/Application/Features/Weather/WeatherController.cs @@ -1,4 +1,5 @@ -using Infrastructure.Interfaces; +using Api.Application.Common.Exceptions; +using Infrastructure.Interfaces; using Microsoft.AspNetCore.Mvc; namespace Api.Application.Features.Weather; @@ -13,7 +14,9 @@ public class WeatherController(IWeatherApi weatherService) : ControllerBase [HttpGet("current")] public async Task GetCurrentWeather(CancellationToken cancellationToken) { - var weather = await weatherService.GetCurrentWeatherAsync(cancellationToken); + var weather = (await weatherService + .GetCurrentWeatherAsync(cancellationToken)).EnsureSuccess(); + return Ok(weather); } } \ No newline at end of file diff --git a/Client.Models/Client.Models.csproj b/Client.Models/Client.Models.csproj index c1f1305..c406dd0 100644 --- a/Client.Models/Client.Models.csproj +++ b/Client.Models/Client.Models.csproj @@ -7,4 +7,8 @@ Shared + + + + diff --git a/Api/Application/Common/Results/Result.cs b/Client.Models/Models/Common/Results/Result.cs similarity index 100% rename from Api/Application/Common/Results/Result.cs rename to Client.Models/Models/Common/Results/Result.cs diff --git a/Infrastructure/Api/WeatherService.cs b/Infrastructure/Api/WeatherService.cs index ef32427..64cf61e 100644 --- a/Infrastructure/Api/WeatherService.cs +++ b/Infrastructure/Api/WeatherService.cs @@ -1,4 +1,5 @@ -using Infrastructure.Interfaces; +using Api.Application.Common.Results; +using Infrastructure.Interfaces; using Microsoft.Extensions.Options; using Shared.Models.Configs; using Shared.Models.ExternalApi; @@ -32,22 +33,26 @@ public WeatherService(HttpClient httpClient, IOptions weatherConf longitude = config.Longitude; } - public async Task GetCurrentWeatherAsync(CancellationToken ct = default) + public async Task> GetCurrentWeatherAsync(CancellationToken ct = default) { if (cached != null && cacheExpiresAt > DateTimeOffset.UtcNow) - return cached; + return Result.Ok(cached); await cacheLock.WaitAsync(ct); try { if (cached != null && cacheExpiresAt > DateTimeOffset.UtcNow) - return cached; + return Result.Ok(cached); var info = await FetchFromApiAsync(ct); - cached = info; - cacheExpiresAt = DateTimeOffset.UtcNow.Add(cacheDuration); + if (info.IsSuccess) + { + cached = info.Value!; + cacheExpiresAt = DateTimeOffset.UtcNow.Add(cacheDuration); + } + return info; } @@ -57,30 +62,53 @@ public async Task GetCurrentWeatherAsync(CancellationToken ct = def } } - private async Task FetchFromApiAsync(CancellationToken ct) + private async Task> FetchFromApiAsync(CancellationToken ct) { - if (apiKey is null) - throw new InvalidOperationException("API key is not configured for weather service."); + if (string.IsNullOrEmpty(apiKey)) + return Result.Fail( + "weather.config", + "API key is not configured for weather service."); + + try + { + var url = $"{BaseUrl}?lat={latitude}&lon={longitude}&appid={apiKey}&units=metric&lang=ru"; + + using var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.AcceptLanguage.ParseAdd("ru"); - var url = $"{BaseUrl}?lat={latitude}&lon={longitude}&appid={apiKey}&units=metric&lang=ru"; + using var response = await httpClient.SendAsync(request, ct); - using var request = new HttpRequestMessage(HttpMethod.Get, url); - request.Headers.AcceptLanguage.ParseAdd("ru"); + if (!response.IsSuccessStatusCode) + return Result.Fail( + "weather.http", + " $\"Weather service returned {(int)response.StatusCode}\""); - using var response = await httpClient.SendAsync(request, ct); - response.EnsureSuccessStatusCode(); + var payload = await response.Content.ReadFromJsonAsync(cancellationToken: ct); - var payload = await response.Content.ReadFromJsonAsync(cancellationToken: ct); - if (payload?.Weather is null || payload.Weather.Count == 0 || payload.Main is null) - throw new InvalidOperationException("Weather response is missing required fields."); + if (payload?.Weather is null || payload.Weather.Count == 0 || payload.Main is null) + return Result.Fail( + "weather.payload", + "Invalid weather data received"); - var w = payload.Weather[0]; - return new WeatherInfo + var w = payload.Weather[0]; + + return Result.Ok(new WeatherInfo + { + TemperatureCelsius = payload.Main.Temp, + Condition = w.Main ?? "Unknown", + Description = w.Description ?? string.Empty, + RetrievedAt = DateTimeOffset.UtcNow + }); + } + catch (OperationCanceledException) { - TemperatureCelsius = payload.Main.Temp, - Condition = w.Main ?? "Unknown", - Description = w.Description ?? string.Empty, - RetrievedAt = DateTimeOffset.UtcNow - }; + throw; + } + catch (Exception ex) + { + return Result.Fail( + "weather.exception", + ex.Message); + } } } \ No newline at end of file diff --git a/Infrastructure/Interfaces/IWeatherApi.cs b/Infrastructure/Interfaces/IWeatherApi.cs index 36d942a..d9b2b00 100644 --- a/Infrastructure/Interfaces/IWeatherApi.cs +++ b/Infrastructure/Interfaces/IWeatherApi.cs @@ -1,8 +1,9 @@ -using Shared.Models.ExternalApi; +using Api.Application.Common.Results; +using Shared.Models.ExternalApi; namespace Infrastructure.Interfaces; public interface IWeatherApi { - Task GetCurrentWeatherAsync(CancellationToken ct = default); + Task> GetCurrentWeatherAsync(CancellationToken ct = default); } \ No newline at end of file From aa9021d8341b7d7ec32874e62c012576b5218d28 Mon Sep 17 00:00:00 2001 From: Lychee <2006tka@gmail.com> Date: Sun, 14 Dec 2025 10:44:55 +0500 Subject: [PATCH 03/36] =?UTF-8?q?=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=BA=D0=B0=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BE=D0=BA=20?= =?UTF-8?q?=D1=81=D1=82=D0=BE=D1=80=D0=BE=D0=BD=D0=BD=D0=B5=D0=B3=D0=BE=20?= =?UTF-8?q?Api=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20Result=20(=D1=82=D0=B5?= =?UTF-8?q?=D1=81=D1=82)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/Exceptions/ResultExtensions.cs | 6 +- .../Features/Auth/Refresh/RefreshHandler.cs | 8 +- .../Features/Tasks/GetTask/GetTaskHandler.cs | 4 +- .../Features/Weather/WeatherController.cs | 7 +- Client.Models/Client.Models.csproj | 4 + .../Models}/Common/Results/Result.cs | 0 Infrastructure/Api/WeatherService.cs | 76 +++++++++++++------ Infrastructure/Interfaces/IWeatherApi.cs | 5 +- 8 files changed, 75 insertions(+), 35 deletions(-) rename {Api/Application => Client.Models/Models}/Common/Results/Result.cs (100%) diff --git a/Api/Application/Common/Exceptions/ResultExtensions.cs b/Api/Application/Common/Exceptions/ResultExtensions.cs index 952b158..8acc4eb 100644 --- a/Api/Application/Common/Exceptions/ResultExtensions.cs +++ b/Api/Application/Common/Exceptions/ResultExtensions.cs @@ -8,8 +8,10 @@ public static T EnsureSuccess(this Result result) { if (!result.IsSuccess) { - var error = result.Error ?? new Error("unknown", "Unknown error"); - throw new ApiException(StatusCodes.Status500InternalServerError, error.Message); + throw new ApiException( + StatusCodes.Status503ServiceUnavailable, + result.Error?.Message ?? "External service unavailable" + ); } return result.Value!; diff --git a/Api/Application/Features/Auth/Refresh/RefreshHandler.cs b/Api/Application/Features/Auth/Refresh/RefreshHandler.cs index f598297..b5374f9 100644 --- a/Api/Application/Features/Auth/Refresh/RefreshHandler.cs +++ b/Api/Application/Features/Auth/Refresh/RefreshHandler.cs @@ -4,16 +4,16 @@ namespace Api.Application.Features.Auth.Refresh; -public class RefreshHandler(IUserTable users, IPasswordHasher hasher, ITokenService tokenService) +public class RefreshHandler(ITokenService tokenService) : IRequestHandler { - public async Task Handle(RefreshCommand command, + public Task Handle(RefreshCommand command, CancellationToken cancellationToken) { var username = command.Username; - return new RefreshResponse( + return Task.FromResult(new RefreshResponse( tokenService.GenerateAccessToken(username), tokenService.GenerateRefreshToken(username) - ); + )); } } \ No newline at end of file diff --git a/Api/Application/Features/Tasks/GetTask/GetTaskHandler.cs b/Api/Application/Features/Tasks/GetTask/GetTaskHandler.cs index 0a6864d..0b03d22 100644 --- a/Api/Application/Features/Tasks/GetTask/GetTaskHandler.cs +++ b/Api/Application/Features/Tasks/GetTask/GetTaskHandler.cs @@ -10,6 +10,8 @@ public class GetTaskHandler(IUsersTasksTable tasks) : IRequestHandler Handle(GetTaskQuery request, CancellationToken cancellationToken) { var task = await tasks.GetTaskFullInfo(request.TaskId); - return task ?? throw new ApiException(StatusCodes.Status404NotFound, $"Task {request.TaskId} for {request.Username} not found"); + return task ?? + throw new ApiException(StatusCodes.Status404NotFound, + $"Task {request.TaskId} for {request.Username} not found"); } } \ No newline at end of file diff --git a/Api/Application/Features/Weather/WeatherController.cs b/Api/Application/Features/Weather/WeatherController.cs index 3003875..7ecca2a 100644 --- a/Api/Application/Features/Weather/WeatherController.cs +++ b/Api/Application/Features/Weather/WeatherController.cs @@ -1,4 +1,5 @@ -using Infrastructure.Interfaces; +using Api.Application.Common.Exceptions; +using Infrastructure.Interfaces; using Microsoft.AspNetCore.Mvc; namespace Api.Application.Features.Weather; @@ -13,7 +14,9 @@ public class WeatherController(IWeatherApi weatherService) : ControllerBase [HttpGet("current")] public async Task GetCurrentWeather(CancellationToken cancellationToken) { - var weather = await weatherService.GetCurrentWeatherAsync(cancellationToken); + var weather = (await weatherService + .GetCurrentWeatherAsync(cancellationToken)).EnsureSuccess(); + return Ok(weather); } } \ No newline at end of file diff --git a/Client.Models/Client.Models.csproj b/Client.Models/Client.Models.csproj index c1f1305..c406dd0 100644 --- a/Client.Models/Client.Models.csproj +++ b/Client.Models/Client.Models.csproj @@ -7,4 +7,8 @@ Shared + + + + diff --git a/Api/Application/Common/Results/Result.cs b/Client.Models/Models/Common/Results/Result.cs similarity index 100% rename from Api/Application/Common/Results/Result.cs rename to Client.Models/Models/Common/Results/Result.cs diff --git a/Infrastructure/Api/WeatherService.cs b/Infrastructure/Api/WeatherService.cs index ef32427..956303e 100644 --- a/Infrastructure/Api/WeatherService.cs +++ b/Infrastructure/Api/WeatherService.cs @@ -1,4 +1,5 @@ -using Infrastructure.Interfaces; +using Api.Application.Common.Results; +using Infrastructure.Interfaces; using Microsoft.Extensions.Options; using Shared.Models.Configs; using Shared.Models.ExternalApi; @@ -32,22 +33,26 @@ public WeatherService(HttpClient httpClient, IOptions weatherConf longitude = config.Longitude; } - public async Task GetCurrentWeatherAsync(CancellationToken ct = default) + public async Task> GetCurrentWeatherAsync(CancellationToken ct = default) { if (cached != null && cacheExpiresAt > DateTimeOffset.UtcNow) - return cached; + return Result.Ok(cached); await cacheLock.WaitAsync(ct); try { if (cached != null && cacheExpiresAt > DateTimeOffset.UtcNow) - return cached; + return Result.Ok(cached); var info = await FetchFromApiAsync(ct); - cached = info; - cacheExpiresAt = DateTimeOffset.UtcNow.Add(cacheDuration); + if (info.IsSuccess) + { + cached = info.Value!; + cacheExpiresAt = DateTimeOffset.UtcNow.Add(cacheDuration); + } + return info; } @@ -57,30 +62,53 @@ public async Task GetCurrentWeatherAsync(CancellationToken ct = def } } - private async Task FetchFromApiAsync(CancellationToken ct) + private async Task> FetchFromApiAsync(CancellationToken ct) { - if (apiKey is null) - throw new InvalidOperationException("API key is not configured for weather service."); + if (string.IsNullOrEmpty(apiKey)) + return Result.Fail( + "weather.config", + "API key is not configured for weather service."); + + try + { + var url = $"{BaseUrl}?lat={latitude}&lon={longitude}&appid={apiKey}&units=metric&lang=ru"; + + using var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.AcceptLanguage.ParseAdd("ru"); - var url = $"{BaseUrl}?lat={latitude}&lon={longitude}&appid={apiKey}&units=metric&lang=ru"; + using var response = await httpClient.SendAsync(request, ct); - using var request = new HttpRequestMessage(HttpMethod.Get, url); - request.Headers.AcceptLanguage.ParseAdd("ru"); + if (!response.IsSuccessStatusCode) + return Result.Fail( + "weather.http", + $"Weather service returned {(int)response.StatusCode}"); - using var response = await httpClient.SendAsync(request, ct); - response.EnsureSuccessStatusCode(); + var payload = await response.Content.ReadFromJsonAsync(cancellationToken: ct); - var payload = await response.Content.ReadFromJsonAsync(cancellationToken: ct); - if (payload?.Weather is null || payload.Weather.Count == 0 || payload.Main is null) - throw new InvalidOperationException("Weather response is missing required fields."); + if (payload?.Weather is null || payload.Weather.Count == 0 || payload.Main is null) + return Result.Fail( + "weather.payload", + "Invalid weather data received"); - var w = payload.Weather[0]; - return new WeatherInfo + var w = payload.Weather[0]; + + return Result.Ok(new WeatherInfo + { + TemperatureCelsius = payload.Main.Temp, + Condition = w.Main ?? "Unknown", + Description = w.Description ?? string.Empty, + RetrievedAt = DateTimeOffset.UtcNow + }); + } + catch (OperationCanceledException) { - TemperatureCelsius = payload.Main.Temp, - Condition = w.Main ?? "Unknown", - Description = w.Description ?? string.Empty, - RetrievedAt = DateTimeOffset.UtcNow - }; + throw; + } + catch (Exception ex) + { + return Result.Fail( + "weather.exception", + ex.Message); + } } } \ No newline at end of file diff --git a/Infrastructure/Interfaces/IWeatherApi.cs b/Infrastructure/Interfaces/IWeatherApi.cs index 36d942a..d9b2b00 100644 --- a/Infrastructure/Interfaces/IWeatherApi.cs +++ b/Infrastructure/Interfaces/IWeatherApi.cs @@ -1,8 +1,9 @@ -using Shared.Models.ExternalApi; +using Api.Application.Common.Results; +using Shared.Models.ExternalApi; namespace Infrastructure.Interfaces; public interface IWeatherApi { - Task GetCurrentWeatherAsync(CancellationToken ct = default); + Task> GetCurrentWeatherAsync(CancellationToken ct = default); } \ No newline at end of file From ea937e604c7deb18c751655bd64bbbd29baebca4 Mon Sep 17 00:00:00 2001 From: fan4cz Date: Sun, 14 Dec 2025 13:25:38 +0500 Subject: [PATCH 04/36] =?UTF-8?q?=D1=84=D1=8B=D0=B2=D0=B0=D0=BF=D0=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ApproveTask/ChangeTaskStatusHandler.cs | 17 +++-- .../Moderation/ModerationController.cs | 2 +- .../Tasks/ChangeTask/ChangeTaskHandler.cs | 4 +- Infrastructure/DataAccess/UserTable.cs | 62 ++++++++++++------- Infrastructure/DataAccess/UsersTasksTable.cs | 56 +++++++---------- Infrastructure/Interfaces/IUserTable.cs | 7 ++- Infrastructure/Interfaces/IUsersTasksTable.cs | 2 +- 7 files changed, 80 insertions(+), 70 deletions(-) diff --git a/Api/Application/Features/Moderation/ApproveTask/ChangeTaskStatusHandler.cs b/Api/Application/Features/Moderation/ApproveTask/ChangeTaskStatusHandler.cs index 7159121..ee71425 100644 --- a/Api/Application/Features/Moderation/ApproveTask/ChangeTaskStatusHandler.cs +++ b/Api/Application/Features/Moderation/ApproveTask/ChangeTaskStatusHandler.cs @@ -1,3 +1,4 @@ +using Infrastructure.DataAccess; using Infrastructure.Interfaces; using MediatR; using Shared.Models.Enums; @@ -8,17 +9,15 @@ public class ChangeTaskStatusHandler(IUsersTasksTable tasks, IUserTable users) : { public async Task Handle(ChangeTaskStatusQuery request, CancellationToken cancellationToken) { - var userTaskId = request.UserTaskId; - + var task = await tasks.GetTaskNoExtendedInfo(request.UserTaskId); + Console.WriteLine($"{task.Name}, {task.UserTaskId}"); if (request.Status == ModerationStatus.Approved) { - var reward = await tasks.GetReward(userTaskId); - - await tasks.SetCompleted(userTaskId); - await users.ChangeExperience(userTaskId, 1); - await users.TryChangeLevel(userTaskId); - await users.ChangeMoney(userTaskId, reward); + + await users.AddExperience(task.Name, 1); + await users.AddLevel(task.Name, 1); + await users.AddMoney(task.Name, task.Reward); } - return await tasks.ChangeModerationStatus(userTaskId, request.Status); + return await tasks.ChangeModerationStatus(request.UserTaskId, request.Status); } } \ No newline at end of file diff --git a/Api/Application/Features/Moderation/ModerationController.cs b/Api/Application/Features/Moderation/ModerationController.cs index 412b1d6..dbf944f 100644 --- a/Api/Application/Features/Moderation/ModerationController.cs +++ b/Api/Application/Features/Moderation/ModerationController.cs @@ -44,7 +44,7 @@ public async Task CheckModerator(long tgUserId) /// approve task /// [HttpPost("{userTaskId:int}/approve")] - [Authorize(Roles = UserRoleNames.TgBot)] + // [Authorize(Roles = UserRoleNames.TgBot)] public async Task ApproveTask(int userTaskId) { if (userTaskId == -1) diff --git a/Api/Application/Features/Tasks/ChangeTask/ChangeTaskHandler.cs b/Api/Application/Features/Tasks/ChangeTask/ChangeTaskHandler.cs index 578672e..6efd78a 100644 --- a/Api/Application/Features/Tasks/ChangeTask/ChangeTaskHandler.cs +++ b/Api/Application/Features/Tasks/ChangeTask/ChangeTaskHandler.cs @@ -14,10 +14,10 @@ public async Task Handle(ChangeTaskQuery request, CancellationToken c { var username = request.Username; var taskId = request.TaskId; - + //TODO потом перепишу и это надо в service var newTask = await tasks.ChangeUserTask(taskId); var reward = tasks.GetReward(taskId); - await users.ChangeMoney(taskId, -(int)(reward.Result * Coefficient)); + // await users.AddMoney(taskId, -(int)(reward.Result * Coefficient)); await metrics.AddRecord(username, MetricType.Change); return newTask; diff --git a/Infrastructure/DataAccess/UserTable.cs b/Infrastructure/DataAccess/UserTable.cs index f801f08..5ba1a52 100644 --- a/Infrastructure/DataAccess/UserTable.cs +++ b/Infrastructure/DataAccess/UserTable.cs @@ -92,6 +92,27 @@ users.money AS Money return await builder.QuerySingleAsync(); } + public async Task GetUser(int userTaskId) + { + await using var conn = await dataSource.OpenConnectionAsync(); + var builder = conn.QueryBuilder( + $""" + SELECT + users.username AS Username, + users.display_name AS DisplayName, + users.exp AS Experience, + users.lvl AS Level, + users.money AS Money + FROM + users_tasks + JOIN users ON users.username = users_tasks.username + WHERE + users_tasks.id = {userTaskId} + """ + ); + return await builder.QuerySingleAsync(); + } + public async Task ChangeDisplayName(string username, string newDisplayName) { await using var conn = await dataSource.OpenConnectionAsync(); @@ -109,17 +130,17 @@ public async Task ChangeDisplayName(string username, string newDisplayName return rowsChanged == 1; } - public async Task ChangeMoney(int userTaskId, int money) + public async Task AddMoney(string username, int money) { await using var conn = await dataSource.OpenConnectionAsync(); - var builder = conn.QueryBuilder( $""" - UPDATE users - SET money = money + {money} - FROM users_tasks - WHERE users.username = users_tasks.username - AND users_tasks.id = {userTaskId} + UPDATE + users + Set + money = money + {money} + WHERE + username = {username} """ ); @@ -127,34 +148,33 @@ FROM users_tasks return rowsChanged == 1; } - - public async Task ChangeExperience(int userTaskId, int exp) + + public async Task AddExperience(string username, int exp) { await using var conn = await dataSource.OpenConnectionAsync(); var builder = conn.QueryBuilder( $""" - UPDATE users - SET exp = users.exp + {exp} - FROM users_tasks - WHERE users.username = users_tasks.username - AND users_tasks.id = {userTaskId}; + UPDATE + users + Set + exp = exp + {exp} + WHERE + username = {username} """ ); var rowsChanged = await builder.ExecuteAsync(); return rowsChanged == 1; } - - public async Task TryChangeLevel(int userTaskId) + + public async Task AddLevel(string username, int lvl) { await using var conn = await dataSource.OpenConnectionAsync(); var builder = conn.QueryBuilder( $""" UPDATE users - SET lvl = users.lvl + 1 - FROM users_tasks - WHERE users.username = users_tasks.username - AND users_tasks.id = {userTaskId} - AND users.exp % 5 = 0; + SET lvl = users.lvl + 1 + FROM users + WHERE users.username = {username} """ ); var rowsChanged = await builder.ExecuteAsync(); diff --git a/Infrastructure/DataAccess/UsersTasksTable.cs b/Infrastructure/DataAccess/UsersTasksTable.cs index f592d51..5dbd78e 100644 --- a/Infrastructure/DataAccess/UsersTasksTable.cs +++ b/Infrastructure/DataAccess/UsersTasksTable.cs @@ -31,7 +31,7 @@ tasks.reward AS Reward JOIN tasks ON tasks.id = users_tasks.task_id WHERE users_tasks.username = {username} - AND users_tasks.is_completed = '0' + AND users_tasks.moderation_status != {ModerationStatus.Approved.ToString().ToLower()}::moderation_status """); return (await builder.QueryAsync()).ToList(); } @@ -47,13 +47,12 @@ INSERT INTO task_id, username, moderation_status, - is_completed, start_time, photos_path, photos_count ) VALUES - ({task.TaskId}, {username}, {ModerationStatus.Default.ToString().ToLower()}::moderation_status, '0', NOW(), NULL, 0) + ({task.TaskId}, {username}, {ModerationStatus.Default.ToString().ToLower()}::moderation_status, NOW(), NULL, 0) """ ); var rowsChanged = await builder.ExecuteAsync(); @@ -71,7 +70,6 @@ UPDATE users_tasks SET task_id = {task.TaskId}, moderation_status = {ModerationStatus.Default.ToString().ToLower()}::moderation_status, - is_completed = '0', start_time = NOW(), photos_path = NULL, photos_count = 0 @@ -96,7 +94,6 @@ UPDATE users_tasks SET task_id = {task.TaskId}, moderation_status = {ModerationStatus.Default.ToString().ToLower()}::moderation_status, - is_completed = '0', start_time = NOW(), photos_path = NULL, photos_count = 0 @@ -115,7 +112,7 @@ private async Task GetRandomTask() var builder = conn.QueryBuilder( $""" SELECT - tasks.id AS TaskId, + tasks.task_id AS TaskId, now()::timestamp AS StartTime, tasks.name AS Name, tasks.reward AS Reward @@ -179,6 +176,15 @@ ORDER BY random() } public async Task GetTaskFullInfo(int id) + { + var task = await GetTaskNoExtendedInfo(id); + if (task is null) + return null; + task.ExtendedInfo = await GetTaskExtendedInfo(id); + return task; + } + + public async Task GetTaskNoExtendedInfo(int id) { await using var conn = await dataSource.OpenConnectionAsync(); var builder = conn.QueryBuilder( @@ -196,9 +202,7 @@ tasks.reward AS Reward users_tasks.id = {id} """" ); - var task = (await builder.QueryAsync()).First(); - task.ExtendedInfo = await GetTaskExtendedInfo(id); - return task; + return await builder.QueryFirstOrDefaultAsync(); } public async Task GetTaskFullInfo(string username, string taskId) @@ -272,7 +276,7 @@ tasks.reward AS Reward JOIN tasks ON tasks.id = users_tasks.task_id WHERE users_tasks.username = {username} - AND is_completed = '1' + AND users_tasks.moderation_status = {ModerationStatus.Approved.ToString().ToLower()}::moderation_status ORDER BY users_tasks.start_time DESC """); return (await builder.QueryAsync()).ToList(); @@ -337,7 +341,7 @@ ORDER BY users_tasks.id task.ExtendedInfo = await GetTaskExtendedInfo(task.UserTaskId); return task; } - + public async Task GetModerationStatus(int id) { await using var conn = await dataSource.OpenConnectionAsync(); @@ -352,25 +356,8 @@ public async Task GetModerationStatus(int id) id = {id} """ ); - - return await builder.QueryFirstOrDefaultAsync(); - } - - public async Task SetCompleted(int id) - { - await using var conn = await dataSource.OpenConnectionAsync(); - var builder = conn.QueryBuilder( - $""" - UPDATE users_tasks - SET is_completed = '1' - WHERE - id = {id} - - """ - ); - - return await builder.QueryFirstOrDefaultAsync(); + return await builder.QueryFirstOrDefaultAsync(); } public async Task GetReward(int userTaskId) @@ -379,10 +366,13 @@ public async Task GetReward(int userTaskId) var builder = conn.QueryBuilder( $""" - SELECT tasks.reward - FROM users_tasks - JOIN tasks ON users_tasks.task_id = tasks.id - WHERE users_tasks.id = {userTaskId} + SELECT + tasks.reward + FROM + users_tasks + JOIN tasks ON users_tasks.task_id = tasks.id + WHERE + users_tasks.id = {userTaskId} """ ); diff --git a/Infrastructure/Interfaces/IUserTable.cs b/Infrastructure/Interfaces/IUserTable.cs index 4d867c9..2f03978 100644 --- a/Infrastructure/Interfaces/IUserTable.cs +++ b/Infrastructure/Interfaces/IUserTable.cs @@ -7,8 +7,9 @@ public interface IUserTable public Task RegisterUser(string username, string hashPassword); public Task LoginUser(string username, string hashPassword); public Task GetUser(string username); + public Task GetUser(int userTaskId); public Task ChangeDisplayName(string username, string newDisplayName); - public Task ChangeExperience(int userTaskId, int exp); - public Task TryChangeLevel(int userTaskId); - public Task ChangeMoney(int userTaskId, int money); + public Task AddExperience(string username, int exp); + public Task AddLevel(string username, int lvl); + public Task AddMoney(string username, int money); } \ No newline at end of file diff --git a/Infrastructure/Interfaces/IUsersTasksTable.cs b/Infrastructure/Interfaces/IUsersTasksTable.cs index c1635c9..4fc298c 100644 --- a/Infrastructure/Interfaces/IUsersTasksTable.cs +++ b/Infrastructure/Interfaces/IUsersTasksTable.cs @@ -11,6 +11,7 @@ public interface IUsersTasksTable public Task GetTaskExtendedInfo(int id); public Task GetTaskFullInfo(string taskId, string username); public Task GetTaskFullInfo(int taskId); + public Task GetTaskNoExtendedInfo(int id); public Task ChangeModerationStatus(string username, string taskId, ModerationStatus moderationStatus); public Task ChangeModerationStatus(int id, ModerationStatus moderationStatus); public Task> GetUserSubmissionHistory(string username); @@ -21,6 +22,5 @@ public interface IUsersTasksTable public Task ChangeUserTask(string username, string taskId); public Task ChangeUserTask(int id); public Task GetModerationStatus(int id); - public Task SetCompleted(int id); public Task GetReward(int id); } \ No newline at end of file From 486626059d1d79fa2de6ebbdc7c26199a1d8c834 Mon Sep 17 00:00:00 2001 From: fan4cz Date: Mon, 15 Dec 2025 10:19:16 +0500 Subject: [PATCH 05/36] dsa --- .../ApproveTask/ChangeTaskStatusHandler.cs | 20 ++++++++++++------- Client.Models/Models/Entities/User.cs | 6 +++--- Infrastructure/DataAccess/UsersTasksTable.cs | 4 ++-- Infrastructure/Interfaces/IUsersTasksTable.cs | 2 +- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/Api/Application/Features/Moderation/ApproveTask/ChangeTaskStatusHandler.cs b/Api/Application/Features/Moderation/ApproveTask/ChangeTaskStatusHandler.cs index ee71425..8ed5943 100644 --- a/Api/Application/Features/Moderation/ApproveTask/ChangeTaskStatusHandler.cs +++ b/Api/Application/Features/Moderation/ApproveTask/ChangeTaskStatusHandler.cs @@ -5,19 +5,25 @@ namespace Api.Application.Features.Moderation.ApproveTask; -public class ChangeTaskStatusHandler(IUsersTasksTable tasks, IUserTable users) : IRequestHandler +public class ChangeTaskStatusHandler(IUsersTasksTable tasks, IUserTable users) + : IRequestHandler { public async Task Handle(ChangeTaskStatusQuery request, CancellationToken cancellationToken) { - var task = await tasks.GetTaskNoExtendedInfo(request.UserTaskId); - Console.WriteLine($"{task.Name}, {task.UserTaskId}"); + var user = await users.GetUser(request.UserTaskId); + var reward = await tasks.GetReward(request.UserTaskId); if (request.Status == ModerationStatus.Approved) { - - await users.AddExperience(task.Name, 1); - await users.AddLevel(task.Name, 1); - await users.AddMoney(task.Name, task.Reward); + await users.AddMoney(user.Username, reward); + if (user.Experience % 5 == 0) + { + await users.AddLevel(user.Username, 1); + await users.AddExperience(user.Username, -4); + } + else + await users.AddExperience(user.Username, 1); } + return await tasks.ChangeModerationStatus(request.UserTaskId, request.Status); } } \ No newline at end of file diff --git a/Client.Models/Models/Entities/User.cs b/Client.Models/Models/Entities/User.cs index b5ad7dd..5604134 100644 --- a/Client.Models/Models/Entities/User.cs +++ b/Client.Models/Models/Entities/User.cs @@ -4,7 +4,7 @@ public class User { public required string Username { get; set; } public required string DisplayName { get; set; } - public int Level { get; set; } = 0; - public int Experience { get; set; } = 0; - public int Money { get; set; } = 0; + public int Level { get; set; } + public int Experience { get; set; } + public int Money { get; set; } } \ No newline at end of file diff --git a/Infrastructure/DataAccess/UsersTasksTable.cs b/Infrastructure/DataAccess/UsersTasksTable.cs index 5dbd78e..452bdce 100644 --- a/Infrastructure/DataAccess/UsersTasksTable.cs +++ b/Infrastructure/DataAccess/UsersTasksTable.cs @@ -177,14 +177,14 @@ ORDER BY random() public async Task GetTaskFullInfo(int id) { - var task = await GetTaskNoExtendedInfo(id); + var task = await GetUser(id); if (task is null) return null; task.ExtendedInfo = await GetTaskExtendedInfo(id); return task; } - public async Task GetTaskNoExtendedInfo(int id) + public async Task GetUser(int id) { await using var conn = await dataSource.OpenConnectionAsync(); var builder = conn.QueryBuilder( diff --git a/Infrastructure/Interfaces/IUsersTasksTable.cs b/Infrastructure/Interfaces/IUsersTasksTable.cs index 4fc298c..9f266f4 100644 --- a/Infrastructure/Interfaces/IUsersTasksTable.cs +++ b/Infrastructure/Interfaces/IUsersTasksTable.cs @@ -11,7 +11,7 @@ public interface IUsersTasksTable public Task GetTaskExtendedInfo(int id); public Task GetTaskFullInfo(string taskId, string username); public Task GetTaskFullInfo(int taskId); - public Task GetTaskNoExtendedInfo(int id); + public Task GetUser(int id); public Task ChangeModerationStatus(string username, string taskId, ModerationStatus moderationStatus); public Task ChangeModerationStatus(int id, ModerationStatus moderationStatus); public Task> GetUserSubmissionHistory(string username); From 0184e6f6d3fc30f193b2ab0fda9f5e1247f74864 Mon Sep 17 00:00:00 2001 From: fan4cz Date: Mon, 15 Dec 2025 11:27:09 +0500 Subject: [PATCH 06/36] dasfdf --- .../Common/ServiceCollectionExtensions.cs | 2 + Infrastructure/DataAccess/RandomTaskEvent.cs | 87 +++++++++++++++++++ Infrastructure/DataAccess/UsersTasksTable.cs | 10 +-- Infrastructure/Interfaces/ITaskEvent.cs | 9 ++ Infrastructure/Interfaces/IUsersTasksTable.cs | 2 +- 5 files changed, 104 insertions(+), 6 deletions(-) create mode 100644 Infrastructure/DataAccess/RandomTaskEvent.cs create mode 100644 Infrastructure/Interfaces/ITaskEvent.cs diff --git a/Api/Application/Common/ServiceCollectionExtensions.cs b/Api/Application/Common/ServiceCollectionExtensions.cs index 7aff491..5a2f000 100644 --- a/Api/Application/Common/ServiceCollectionExtensions.cs +++ b/Api/Application/Common/ServiceCollectionExtensions.cs @@ -125,6 +125,8 @@ public static WebApplicationBuilder AddInfrastructureServices(this WebApplicatio builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); + builder.Services.AddScoped(); + return builder; } diff --git a/Infrastructure/DataAccess/RandomTaskEvent.cs b/Infrastructure/DataAccess/RandomTaskEvent.cs new file mode 100644 index 0000000..f63fb9f --- /dev/null +++ b/Infrastructure/DataAccess/RandomTaskEvent.cs @@ -0,0 +1,87 @@ +using Infrastructure.Interfaces; +using Npgsql; +using Shared.Models.Entities; +using InterpolatedSql.Dapper; +using Shared.Models.Enums; + +namespace Infrastructure.DataAccess; + +public class RandomTaskEvent(NpgsqlDataSource dataSource) : ITaskEvent +{ + public async Task AddUserTask(string username) + { + var taskId = await GetRandomTask(); + await using var conn = await dataSource.OpenConnectionAsync(); + var builder = conn.QueryBuilder( + $""" + INSERT INTO + users_tasks ( + task_id, + username, + moderation_status, + start_time, + photos_path, + photos_count + ) + VALUES + ({taskId}, {username}, {ModerationStatus.Default.ToString().ToLower()}::moderation_status, NOW(), NULL, 0) + RETURNING + users_tasks.id AS UserTaskId, + users_tasks.task_id AS TaskId, + users_tasks.start_time AS StartTime, + (SELECT name FROM tasks WHERE id = users_tasks.task_id) AS Name, + (SELECT reward FROM tasks WHERE id = users_tasks.task_id) AS Reward + """ + ); + var task = await builder.QueryFirstAsync(); + return task; + } + + public async Task ChangeUserTask(int id) + { + var taskId = await GetRandomTask(); + + await using var conn = await dataSource.OpenConnectionAsync(); + var builder = conn.QueryBuilder( + $""" + UPDATE + users_tasks + SET + task_id = {taskId}, + moderation_status = {ModerationStatus.Default.ToString().ToLower()}::moderation_status, + start_time = NOW(), + photos_path = NULL, + photos_count = 0 + WHERE + id = {id} + RETURNING + users_tasks.id AS UserTaskId, + users_tasks.task_id AS TaskId, + users_tasks.start_time AS StartTime, + (SELECT name FROM tasks WHERE id = users_tasks.task_id) AS Name, + (SELECT reward FROM tasks WHERE id = users_tasks.task_id) AS Reward + """ + ); + + var task = await builder.QueryFirstAsync(); + return task; + } + + private async Task GetRandomTask() + { + await using var conn = await dataSource.OpenConnectionAsync(); + var builder = conn.QueryBuilder( + $""" + SELECT + id + FROM + tasks + ORDER BY random() + LIMIT 1; + """ + ); + var taskId = await builder.QuerySingleAsync(); + + return taskId; + } +} \ No newline at end of file diff --git a/Infrastructure/DataAccess/UsersTasksTable.cs b/Infrastructure/DataAccess/UsersTasksTable.cs index 452bdce..661a37c 100644 --- a/Infrastructure/DataAccess/UsersTasksTable.cs +++ b/Infrastructure/DataAccess/UsersTasksTable.cs @@ -168,7 +168,7 @@ ORDER BY random() WHERE users_tasks.id = {id} """); - + var result = await builder.QueryFirstOrDefaultAsync(); if (result is null) return null; @@ -177,14 +177,14 @@ ORDER BY random() public async Task GetTaskFullInfo(int id) { - var task = await GetUser(id); + var task = await GetTaskNoExtendedInfo(id); if (task is null) return null; task.ExtendedInfo = await GetTaskExtendedInfo(id); return task; } - public async Task GetUser(int id) + public async Task GetTaskNoExtendedInfo(int id) { await using var conn = await dataSource.OpenConnectionAsync(); var builder = conn.QueryBuilder( @@ -193,8 +193,8 @@ ORDER BY random() users_tasks.id AS UserTaskId, users_tasks.task_id AS TaskId, users_tasks.start_time::timestamp AS StartTime, - tasks.name AS Name, - tasks.reward AS Reward + tasks.name AS Name, + tasks.reward AS Reward FROM users_tasks JOIN tasks ON tasks.id = users_tasks.task_id diff --git a/Infrastructure/Interfaces/ITaskEvent.cs b/Infrastructure/Interfaces/ITaskEvent.cs new file mode 100644 index 0000000..39687a0 --- /dev/null +++ b/Infrastructure/Interfaces/ITaskEvent.cs @@ -0,0 +1,9 @@ +using Shared.Models.Entities; + +namespace Infrastructure.Interfaces; + +public interface ITaskEvent +{ + public Task AddUserTask(string username); + public Task ChangeUserTask(int id); +} \ No newline at end of file diff --git a/Infrastructure/Interfaces/IUsersTasksTable.cs b/Infrastructure/Interfaces/IUsersTasksTable.cs index 9f266f4..4fc298c 100644 --- a/Infrastructure/Interfaces/IUsersTasksTable.cs +++ b/Infrastructure/Interfaces/IUsersTasksTable.cs @@ -11,7 +11,7 @@ public interface IUsersTasksTable public Task GetTaskExtendedInfo(int id); public Task GetTaskFullInfo(string taskId, string username); public Task GetTaskFullInfo(int taskId); - public Task GetUser(int id); + public Task GetTaskNoExtendedInfo(int id); public Task ChangeModerationStatus(string username, string taskId, ModerationStatus moderationStatus); public Task ChangeModerationStatus(int id, ModerationStatus moderationStatus); public Task> GetUserSubmissionHistory(string username); From 07fa94c7d8299336e145db055e1f6f31596f7a07 Mon Sep 17 00:00:00 2001 From: fan4cz Date: Mon, 15 Dec 2025 11:55:37 +0500 Subject: [PATCH 07/36] qwerewtre --- .../Tasks/ChangeTask/ChangeTaskHandler.cs | 4 +- .../Tasks/GetTasks/GetTasksHandler.cs | 4 +- Infrastructure/DataAccess/UsersTasksTable.cs | 91 ------------------- Infrastructure/Interfaces/IUsersTasksTable.cs | 3 - 4 files changed, 4 insertions(+), 98 deletions(-) diff --git a/Api/Application/Features/Tasks/ChangeTask/ChangeTaskHandler.cs b/Api/Application/Features/Tasks/ChangeTask/ChangeTaskHandler.cs index 6efd78a..13f71ff 100644 --- a/Api/Application/Features/Tasks/ChangeTask/ChangeTaskHandler.cs +++ b/Api/Application/Features/Tasks/ChangeTask/ChangeTaskHandler.cs @@ -5,7 +5,7 @@ namespace Api.Application.Features.Tasks.ChangeTask; -public class ChangeTaskHandler(IUsersTasksTable tasks, IUserTable users, IMetricsTable metrics) +public class ChangeTaskHandler(IUsersTasksTable tasks, IUserTable users, IMetricsTable metrics, ITaskEvent taskEvent) : IRequestHandler { private const double Coefficient = 0.2; @@ -15,7 +15,7 @@ public async Task Handle(ChangeTaskQuery request, CancellationToken c var username = request.Username; var taskId = request.TaskId; //TODO потом перепишу и это надо в service - var newTask = await tasks.ChangeUserTask(taskId); + var newTask = await taskEvent.ChangeUserTask(taskId); var reward = tasks.GetReward(taskId); // await users.AddMoney(taskId, -(int)(reward.Result * Coefficient)); diff --git a/Api/Application/Features/Tasks/GetTasks/GetTasksHandler.cs b/Api/Application/Features/Tasks/GetTasks/GetTasksHandler.cs index 2f2c2e8..3bfa2c2 100644 --- a/Api/Application/Features/Tasks/GetTasks/GetTasksHandler.cs +++ b/Api/Application/Features/Tasks/GetTasks/GetTasksHandler.cs @@ -4,7 +4,7 @@ namespace Api.Application.Features.Tasks.GetTasks; -public class GetTasksHandler(IUsersTasksTable tasks) : IRequestHandler> +public class GetTasksHandler(IUsersTasksTable tasks, ITaskEvent taskEvent) : IRequestHandler> { public async Task> Handle(GetTasksQuery request, CancellationToken cancellationToken) @@ -14,7 +14,7 @@ public async Task> Handle(GetTasksQuery request, while (tasksList.Count != 4) { - var task = await tasks.AddUserTask(username); + var task = await taskEvent.AddUserTask(username); tasksList.Add(task); } diff --git a/Infrastructure/DataAccess/UsersTasksTable.cs b/Infrastructure/DataAccess/UsersTasksTable.cs index 661a37c..29ecad4 100644 --- a/Infrastructure/DataAccess/UsersTasksTable.cs +++ b/Infrastructure/DataAccess/UsersTasksTable.cs @@ -36,97 +36,6 @@ tasks.reward AS Reward return (await builder.QueryAsync()).ToList(); } - public async Task AddUserTask(string username) - { - var task = await GetRandomTask(); - await using var conn = await dataSource.OpenConnectionAsync(); - var builder = conn.QueryBuilder( - $""" - INSERT INTO - users_tasks ( - task_id, - username, - moderation_status, - start_time, - photos_path, - photos_count - ) - VALUES - ({task.TaskId}, {username}, {ModerationStatus.Default.ToString().ToLower()}::moderation_status, NOW(), NULL, 0) - """ - ); - var rowsChanged = await builder.ExecuteAsync(); - return rowsChanged == 1 ? task : null; - } - - public async Task ChangeUserTask(string username, string taskId) - { - var task = await GetRandomTask(); - - await using var conn = await dataSource.OpenConnectionAsync(); - var builder = conn.QueryBuilder( - $""" - UPDATE users_tasks - SET - task_id = {task.TaskId}, - moderation_status = {ModerationStatus.Default.ToString().ToLower()}::moderation_status, - start_time = NOW(), - photos_path = NULL, - photos_count = 0 - WHERE - username = {username} - AND task_id = {taskId} - """ - ); - - var rowsChanged = await builder.ExecuteAsync(); - return rowsChanged == 1 ? task : null; - } - - public async Task ChangeUserTask(int id) - { - var task = await GetRandomTask(); - - await using var conn = await dataSource.OpenConnectionAsync(); - var builder = conn.QueryBuilder( - $""" - UPDATE users_tasks - SET - task_id = {task.TaskId}, - moderation_status = {ModerationStatus.Default.ToString().ToLower()}::moderation_status, - start_time = NOW(), - photos_path = NULL, - photos_count = 0 - WHERE - id = {id} - """ - ); - - var rowsChanged = await builder.ExecuteAsync(); - return rowsChanged == 1 ? task : null; - } - - private async Task GetRandomTask() - { - await using var conn = await dataSource.OpenConnectionAsync(); - var builder = conn.QueryBuilder( - $""" - SELECT - tasks.task_id AS TaskId, - now()::timestamp AS StartTime, - tasks.name AS Name, - tasks.reward AS Reward - FROM - tasks - ORDER BY random() - LIMIT 1; - """ - ); - var taskId = await builder.QuerySingleAsync(); - - return taskId; - } - public async Task GetTaskExtendedInfo(string username, string taskId) { logger.LogInformation("вызов GetTaskExtendedInfo для username: {username} task: {taskId} ", username, taskId); diff --git a/Infrastructure/Interfaces/IUsersTasksTable.cs b/Infrastructure/Interfaces/IUsersTasksTable.cs index 4fc298c..4df82b3 100644 --- a/Infrastructure/Interfaces/IUsersTasksTable.cs +++ b/Infrastructure/Interfaces/IUsersTasksTable.cs @@ -6,7 +6,6 @@ namespace Infrastructure.Interfaces; public interface IUsersTasksTable { public Task> GetListActiveUserTasks(string username); - public Task AddUserTask(string username); public Task GetTaskExtendedInfo(string username, string taskId); public Task GetTaskExtendedInfo(int id); public Task GetTaskFullInfo(string taskId, string username); @@ -19,8 +18,6 @@ public interface IUsersTasksTable public Task AddPhoto(string username, string taskId, string photoName); public Task AddPhoto(int id, string photoName); public Task GetModerationTask(); - public Task ChangeUserTask(string username, string taskId); - public Task ChangeUserTask(int id); public Task GetModerationStatus(int id); public Task GetReward(int id); } \ No newline at end of file From 5cd6eb8c91ba3487b9c8b9be6954bed9b0465488 Mon Sep 17 00:00:00 2001 From: fan4cz Date: Mon, 15 Dec 2025 12:07:47 +0500 Subject: [PATCH 08/36] fix --- Infrastructure/DataAccess/RandomTaskEvent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Infrastructure/DataAccess/RandomTaskEvent.cs b/Infrastructure/DataAccess/RandomTaskEvent.cs index f63fb9f..ee58567 100644 --- a/Infrastructure/DataAccess/RandomTaskEvent.cs +++ b/Infrastructure/DataAccess/RandomTaskEvent.cs @@ -28,7 +28,7 @@ INSERT INTO RETURNING users_tasks.id AS UserTaskId, users_tasks.task_id AS TaskId, - users_tasks.start_time AS StartTime, + users_tasks.start_time::timestamp AS StartTime, (SELECT name FROM tasks WHERE id = users_tasks.task_id) AS Name, (SELECT reward FROM tasks WHERE id = users_tasks.task_id) AS Reward """ From bd9b31268774cd3f1091925c91ff55db4bf66e5a Mon Sep 17 00:00:00 2001 From: Lychee <2006tka@gmail.com> Date: Mon, 15 Dec 2025 17:47:11 +0500 Subject: [PATCH 09/36] =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B1=D1=83=D0=B5?= =?UTF-8?q?=D0=BC=20=D1=81=D0=B6=D0=B8=D0=BC=D0=B0=D1=82=D1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Api/Api.csproj | 1 + .../Photos/UploadPhoto/UploadPhotoHandler.cs | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Api/Api.csproj b/Api/Api.csproj index 1716764..9984a91 100644 --- a/Api/Api.csproj +++ b/Api/Api.csproj @@ -14,6 +14,7 @@ + diff --git a/Api/Application/Features/Photos/UploadPhoto/UploadPhotoHandler.cs b/Api/Application/Features/Photos/UploadPhoto/UploadPhotoHandler.cs index 440876e..2b5c58b 100644 --- a/Api/Application/Features/Photos/UploadPhoto/UploadPhotoHandler.cs +++ b/Api/Application/Features/Photos/UploadPhoto/UploadPhotoHandler.cs @@ -1,5 +1,6 @@ using Amazon.S3; using Amazon.S3.Model; +using ImageMagick; using MediatR; using Microsoft.Extensions.Options; using Shared.Models.Configs; @@ -22,9 +23,19 @@ public UploadPhotoHandler(IAmazonS3 s3Client, IOptions config, ILogge public async Task Handle(UploadPhotoCommand request, CancellationToken cancellationToken) { var file = request.File; + + await using var inputStream = file.OpenReadStream(); + using var image = new MagickImage(inputStream); + + image.Quality = 75; + image.Strip(); + + await using var compressedStream = new MemoryStream(); + await image.WriteAsync(compressedStream, cancellationToken); + compressedStream.Position = 0; var fileName = $"{Guid.NewGuid()}{Path.GetExtension(file.FileName)}"; - var contentType = file.ContentType; + const string contentType = "image/jpeg"; var buckets = await s3Client.ListBucketsAsync(cancellationToken); if (buckets.Buckets.All(b => b.BucketName != bucket)) @@ -41,7 +52,7 @@ public async Task Handle(UploadPhotoCommand request, CancellationToken c { BucketName = bucket, Key = fileName, - InputStream = stream, + InputStream = compressedStream, ContentType = contentType }; From a513143e2bb2adde4b4bf85b17a92910cfda6019 Mon Sep 17 00:00:00 2001 From: Lychee <2006tka@gmail.com> Date: Mon, 15 Dec 2025 18:36:13 +0500 Subject: [PATCH 10/36] =?UTF-8?q?=D1=8B=D0=BE=D0=B2=D0=BB=D0=B0=D0=BE?= =?UTF-8?q?=D1=8B=D0=B2=D0=BB=D0=B0=D1=84=D0=BE=D1=8B=D0=B2=D0=B4=D0=B6?= =?UTF-8?q?=D0=BB=D0=B0=D1=84=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Photos/UploadPhoto/UploadPhotoHandler.cs | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/Api/Application/Features/Photos/UploadPhoto/UploadPhotoHandler.cs b/Api/Application/Features/Photos/UploadPhoto/UploadPhotoHandler.cs index 2b5c58b..7bc727b 100644 --- a/Api/Application/Features/Photos/UploadPhoto/UploadPhotoHandler.cs +++ b/Api/Application/Features/Photos/UploadPhoto/UploadPhotoHandler.cs @@ -23,13 +23,30 @@ public UploadPhotoHandler(IAmazonS3 s3Client, IOptions config, ILogge public async Task Handle(UploadPhotoCommand request, CancellationToken cancellationToken) { var file = request.File; - + await using var inputStream = file.OpenReadStream(); using var image = new MagickImage(inputStream); image.Quality = 75; + + const int maxWidth = 1920; + const int maxHeight = 1080; + + if (image.Width > maxWidth || image.Height > maxHeight) + { + var coefH = (double)maxHeight / image.Height; + var coefW = (double)maxWidth / image.Width; + + var ratio = Math.Min(coefH, coefW); + + var newWidth = (uint)(image.Width * ratio); + var newHeight = (uint)(image.Height * ratio); + + image.Resize(new MagickGeometry(newWidth, newHeight) { IgnoreAspectRatio = false }); + } + image.Strip(); - + await using var compressedStream = new MemoryStream(); await image.WriteAsync(compressedStream, cancellationToken); compressedStream.Position = 0; From 7aa709ae2dce0cd5c7ecffd6487385eb3b5d1cef Mon Sep 17 00:00:00 2001 From: fan4cz Date: Mon, 15 Dec 2025 18:48:35 +0500 Subject: [PATCH 11/36] asgdg --- Infrastructure/DataAccess/RandomTaskEvent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Infrastructure/DataAccess/RandomTaskEvent.cs b/Infrastructure/DataAccess/RandomTaskEvent.cs index ee58567..43132b0 100644 --- a/Infrastructure/DataAccess/RandomTaskEvent.cs +++ b/Infrastructure/DataAccess/RandomTaskEvent.cs @@ -57,7 +57,7 @@ INSERT INTO RETURNING users_tasks.id AS UserTaskId, users_tasks.task_id AS TaskId, - users_tasks.start_time AS StartTime, + users_tasks.start_time::timestamp AS StartTime, (SELECT name FROM tasks WHERE id = users_tasks.task_id) AS Name, (SELECT reward FROM tasks WHERE id = users_tasks.task_id) AS Reward """ From f128e240e2b89e18114c2c62d2fff724c975619c Mon Sep 17 00:00:00 2001 From: Lychee <2006tka@gmail.com> Date: Mon, 15 Dec 2025 19:35:46 +0500 Subject: [PATCH 12/36] =?UTF-8?q?=D1=8D=D1=82=D0=BE=20=D0=BD=D0=B0=20?= =?UTF-8?q?=D1=84=D1=80=D0=BE=D0=BD=D1=82=D0=B5=20=D0=B1=D1=83=D0=B4=D0=B5?= =?UTF-8?q?=D1=82,=20=D0=BF=D0=BE=D1=84=D0=B8=D0=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Photos/UploadPhoto/UploadPhotoHandler.cs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/Api/Application/Features/Photos/UploadPhoto/UploadPhotoHandler.cs b/Api/Application/Features/Photos/UploadPhoto/UploadPhotoHandler.cs index 7bc727b..ad0a6e5 100644 --- a/Api/Application/Features/Photos/UploadPhoto/UploadPhotoHandler.cs +++ b/Api/Application/Features/Photos/UploadPhoto/UploadPhotoHandler.cs @@ -28,23 +28,6 @@ public async Task Handle(UploadPhotoCommand request, CancellationToken c using var image = new MagickImage(inputStream); image.Quality = 75; - - const int maxWidth = 1920; - const int maxHeight = 1080; - - if (image.Width > maxWidth || image.Height > maxHeight) - { - var coefH = (double)maxHeight / image.Height; - var coefW = (double)maxWidth / image.Width; - - var ratio = Math.Min(coefH, coefW); - - var newWidth = (uint)(image.Width * ratio); - var newHeight = (uint)(image.Height * ratio); - - image.Resize(new MagickGeometry(newWidth, newHeight) { IgnoreAspectRatio = false }); - } - image.Strip(); await using var compressedStream = new MemoryStream(); From 2b5468fac79e05dd194215151db6b559a14d95a5 Mon Sep 17 00:00:00 2001 From: fan4cz Date: Mon, 15 Dec 2025 19:50:22 +0500 Subject: [PATCH 13/36] fsdgsdh --- Infrastructure/DataAccess/UserTable.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Infrastructure/DataAccess/UserTable.cs b/Infrastructure/DataAccess/UserTable.cs index 5ba1a52..963079d 100644 --- a/Infrastructure/DataAccess/UserTable.cs +++ b/Infrastructure/DataAccess/UserTable.cs @@ -171,10 +171,12 @@ public async Task AddLevel(string username, int lvl) await using var conn = await dataSource.OpenConnectionAsync(); var builder = conn.QueryBuilder( $""" - UPDATE users - SET lvl = users.lvl + 1 - FROM users - WHERE users.username = {username} + UPDATE + users + SET + lvl = users.lvl + 1 + WHERE + users.username = {username} """ ); var rowsChanged = await builder.ExecuteAsync(); From 69db6e1c0d14874d82296eb59ca96b8161547c74 Mon Sep 17 00:00:00 2001 From: fan4cz Date: Mon, 15 Dec 2025 19:55:27 +0500 Subject: [PATCH 14/36] dfghj --- Infrastructure/DataAccess/UserTable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Infrastructure/DataAccess/UserTable.cs b/Infrastructure/DataAccess/UserTable.cs index 963079d..c9f016c 100644 --- a/Infrastructure/DataAccess/UserTable.cs +++ b/Infrastructure/DataAccess/UserTable.cs @@ -176,7 +176,7 @@ public async Task AddLevel(string username, int lvl) SET lvl = users.lvl + 1 WHERE - users.username = {username} + username = {username} """ ); var rowsChanged = await builder.ExecuteAsync(); From 0d55d9ade876831b2f6fb706ddb688e7d8c2d7fd Mon Sep 17 00:00:00 2001 From: fan4cz Date: Mon, 15 Dec 2025 20:00:33 +0500 Subject: [PATCH 15/36] fgdhfjgh --- Infrastructure/DataAccess/UserTable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Infrastructure/DataAccess/UserTable.cs b/Infrastructure/DataAccess/UserTable.cs index c9f016c..b7eb58c 100644 --- a/Infrastructure/DataAccess/UserTable.cs +++ b/Infrastructure/DataAccess/UserTable.cs @@ -174,7 +174,7 @@ public async Task AddLevel(string username, int lvl) UPDATE users SET - lvl = users.lvl + 1 + lvl = vl + 1 WHERE username = {username} """ From 8e1b39740935dbda1126462b084e50bd3b2c7cac Mon Sep 17 00:00:00 2001 From: fan4cz Date: Mon, 15 Dec 2025 20:06:24 +0500 Subject: [PATCH 16/36] sfdftg --- Infrastructure/DataAccess/UserTable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Infrastructure/DataAccess/UserTable.cs b/Infrastructure/DataAccess/UserTable.cs index b7eb58c..c7c51bf 100644 --- a/Infrastructure/DataAccess/UserTable.cs +++ b/Infrastructure/DataAccess/UserTable.cs @@ -174,7 +174,7 @@ public async Task AddLevel(string username, int lvl) UPDATE users SET - lvl = vl + 1 + lvl = lvl + 1 WHERE username = {username} """ From 3a00c25a269296006d7bda9d83dbea0480f37854 Mon Sep 17 00:00:00 2001 From: fan4cz Date: Mon, 15 Dec 2025 20:27:21 +0500 Subject: [PATCH 17/36] gdfzhd --- Infrastructure/DataAccess/UserTable.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Infrastructure/DataAccess/UserTable.cs b/Infrastructure/DataAccess/UserTable.cs index c7c51bf..efb8107 100644 --- a/Infrastructure/DataAccess/UserTable.cs +++ b/Infrastructure/DataAccess/UserTable.cs @@ -174,11 +174,14 @@ public async Task AddLevel(string username, int lvl) UPDATE users SET - lvl = lvl + 1 + lvl = lvl + {lvl} WHERE - username = {username} + """ ); + + builder.AppendLine($"""username = {username}"""); + var rowsChanged = await builder.ExecuteAsync(); return rowsChanged == 1; } From d465e0eee4c3a46498bc239fc054197a5a0bcf7c Mon Sep 17 00:00:00 2001 From: fan4cz Date: Mon, 15 Dec 2025 20:32:50 +0500 Subject: [PATCH 18/36] erwdfh --- Infrastructure/DataAccess/UserTable.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Infrastructure/DataAccess/UserTable.cs b/Infrastructure/DataAccess/UserTable.cs index efb8107..ad565be 100644 --- a/Infrastructure/DataAccess/UserTable.cs +++ b/Infrastructure/DataAccess/UserTable.cs @@ -176,12 +176,10 @@ public async Task AddLevel(string username, int lvl) SET lvl = lvl + {lvl} WHERE - + username = @username """ - ); - - builder.AppendLine($"""username = {username}"""); - + ).AddParameter(username); + var rowsChanged = await builder.ExecuteAsync(); return rowsChanged == 1; } From c40994c138082bed668e84c283eeb9496a736bc9 Mon Sep 17 00:00:00 2001 From: fan4cz Date: Mon, 15 Dec 2025 20:38:03 +0500 Subject: [PATCH 19/36] qewretrtyu --- Infrastructure/DataAccess/UserTable.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Infrastructure/DataAccess/UserTable.cs b/Infrastructure/DataAccess/UserTable.cs index ad565be..07eec7e 100644 --- a/Infrastructure/DataAccess/UserTable.cs +++ b/Infrastructure/DataAccess/UserTable.cs @@ -171,15 +171,14 @@ public async Task AddLevel(string username, int lvl) await using var conn = await dataSource.OpenConnectionAsync(); var builder = conn.QueryBuilder( $""" - UPDATE - users - SET - lvl = lvl + {lvl} - WHERE - username = @username + UPDATE + users + Set + lvl = lvl + {lvl} + WHERE + username = {username} """ - ).AddParameter(username); - + ); var rowsChanged = await builder.ExecuteAsync(); return rowsChanged == 1; } From 3a94131b81f6e0f17afa3eb1f529c54539178586 Mon Sep 17 00:00:00 2001 From: fan4cz Date: Mon, 15 Dec 2025 20:42:27 +0500 Subject: [PATCH 20/36] =?UTF-8?q?erjnefdfbnsnhbsljkdbjlfdk.nvbk=20jkrtblgn?= =?UTF-8?q?bjsdlhkbnlajtiudhgfnarjeiudfhgnbguigojrgdfhiljkernadfdjigjsnriu?= =?UTF-8?q?tdgjkbnstildghnablhrdhgnrkdfjbfgfgkiobnilnkfdjlgnbailbhnkbaejlf?= =?UTF-8?q?knlbsiglznbzl.gzjk=D0=B8=D0=B4=D0=B8=20=D0=BD=D0=B0=D1=85=D1=83?= =?UTF-8?q?=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Features/Moderation/ApproveTask/ChangeTaskStatusHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Api/Application/Features/Moderation/ApproveTask/ChangeTaskStatusHandler.cs b/Api/Application/Features/Moderation/ApproveTask/ChangeTaskStatusHandler.cs index 8ed5943..0381354 100644 --- a/Api/Application/Features/Moderation/ApproveTask/ChangeTaskStatusHandler.cs +++ b/Api/Application/Features/Moderation/ApproveTask/ChangeTaskStatusHandler.cs @@ -17,7 +17,7 @@ public async Task Handle(ChangeTaskStatusQuery request, CancellationToken await users.AddMoney(user.Username, reward); if (user.Experience % 5 == 0) { - await users.AddLevel(user.Username, 1); + // await users.AddLevel(user.Username, 1); await users.AddExperience(user.Username, -4); } else From 36cb94ac9253f723c92d36dfff91ddb674b06464 Mon Sep 17 00:00:00 2001 From: fan4cz Date: Mon, 15 Dec 2025 21:20:33 +0500 Subject: [PATCH 21/36] hxjfhkfujtysrthgfhgmdktsrutjsgfdhj6srtygfxdysyrjfg --- .../Features/Moderation/ApproveTask/ChangeTaskStatusHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Api/Application/Features/Moderation/ApproveTask/ChangeTaskStatusHandler.cs b/Api/Application/Features/Moderation/ApproveTask/ChangeTaskStatusHandler.cs index 0381354..8ed5943 100644 --- a/Api/Application/Features/Moderation/ApproveTask/ChangeTaskStatusHandler.cs +++ b/Api/Application/Features/Moderation/ApproveTask/ChangeTaskStatusHandler.cs @@ -17,7 +17,7 @@ public async Task Handle(ChangeTaskStatusQuery request, CancellationToken await users.AddMoney(user.Username, reward); if (user.Experience % 5 == 0) { - // await users.AddLevel(user.Username, 1); + await users.AddLevel(user.Username, 1); await users.AddExperience(user.Username, -4); } else From db57e0248a530f662fa6f99dd155df8c6be65194 Mon Sep 17 00:00:00 2001 From: Lychee <2006tka@gmail.com> Date: Mon, 15 Dec 2025 21:29:06 +0500 Subject: [PATCH 22/36] =?UTF-8?q?=D0=B7=D0=B0=D0=B1=D0=B8=D1=80=D0=B0?= =?UTF-8?q?=D1=8E=20=D0=BC=D0=BE=D0=BD=D0=B5=D1=82=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Api/Application/Features/Tasks/ChangeTask/ChangeTaskHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Api/Application/Features/Tasks/ChangeTask/ChangeTaskHandler.cs b/Api/Application/Features/Tasks/ChangeTask/ChangeTaskHandler.cs index 13f71ff..3f2a014 100644 --- a/Api/Application/Features/Tasks/ChangeTask/ChangeTaskHandler.cs +++ b/Api/Application/Features/Tasks/ChangeTask/ChangeTaskHandler.cs @@ -17,7 +17,7 @@ public async Task Handle(ChangeTaskQuery request, CancellationToken c //TODO потом перепишу и это надо в service var newTask = await taskEvent.ChangeUserTask(taskId); var reward = tasks.GetReward(taskId); - // await users.AddMoney(taskId, -(int)(reward.Result * Coefficient)); + await users.AddMoney(username, -(int)(reward.Result * Coefficient)); await metrics.AddRecord(username, MetricType.Change); return newTask; From 91112fbebef7e463e4f8c50436f8cbac21f96de3 Mon Sep 17 00:00:00 2001 From: Lychee <2006tka@gmail.com> Date: Mon, 15 Dec 2025 21:35:14 +0500 Subject: [PATCH 23/36] add lvl --- .../Features/Moderation/ApproveTask/ChangeTaskStatusHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Api/Application/Features/Moderation/ApproveTask/ChangeTaskStatusHandler.cs b/Api/Application/Features/Moderation/ApproveTask/ChangeTaskStatusHandler.cs index 0381354..8ed5943 100644 --- a/Api/Application/Features/Moderation/ApproveTask/ChangeTaskStatusHandler.cs +++ b/Api/Application/Features/Moderation/ApproveTask/ChangeTaskStatusHandler.cs @@ -17,7 +17,7 @@ public async Task Handle(ChangeTaskStatusQuery request, CancellationToken await users.AddMoney(user.Username, reward); if (user.Experience % 5 == 0) { - // await users.AddLevel(user.Username, 1); + await users.AddLevel(user.Username, 1); await users.AddExperience(user.Username, -4); } else From c803840a269e5bc5db8e2213e2b2af56d2cff975 Mon Sep 17 00:00:00 2001 From: Lychee <2006tka@gmail.com> Date: Mon, 15 Dec 2025 21:44:05 +0500 Subject: [PATCH 24/36] =?UTF-8?q?=D0=BD=D0=B5,=20=D0=BD=D1=83=20=D0=BD?= =?UTF-8?q?=D0=B0=D0=B2=D0=B5=D1=80=D0=BD=D0=BE=D0=B5=20=D0=B2=D0=BE=D1=82?= =?UTF-8?q?=20=D1=82=D0=B0=D0=BA=20=D0=B2=D1=81=D0=B5-=D1=82=D0=B0=D0=BA?= =?UTF-8?q?=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Features/Moderation/ApproveTask/ChangeTaskStatusHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Api/Application/Features/Moderation/ApproveTask/ChangeTaskStatusHandler.cs b/Api/Application/Features/Moderation/ApproveTask/ChangeTaskStatusHandler.cs index 8ed5943..0b0249f 100644 --- a/Api/Application/Features/Moderation/ApproveTask/ChangeTaskStatusHandler.cs +++ b/Api/Application/Features/Moderation/ApproveTask/ChangeTaskStatusHandler.cs @@ -15,7 +15,7 @@ public async Task Handle(ChangeTaskStatusQuery request, CancellationToken if (request.Status == ModerationStatus.Approved) { await users.AddMoney(user.Username, reward); - if (user.Experience % 5 == 0) + if ((user.Experience + 1) % 5 == 0) { await users.AddLevel(user.Username, 1); await users.AddExperience(user.Username, -4); From 461ba223d04adcdcd7de55bf10483e19d6a082bb Mon Sep 17 00:00:00 2001 From: fan4cz Date: Mon, 15 Dec 2025 23:18:36 +0500 Subject: [PATCH 25/36] fgrthghfdf --- Api/Application/Features/Tasks/GetTasks/GetTasksHandler.cs | 2 +- Infrastructure/DataAccess/UserTable.cs | 2 +- db_init/001_schema.sql | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Api/Application/Features/Tasks/GetTasks/GetTasksHandler.cs b/Api/Application/Features/Tasks/GetTasks/GetTasksHandler.cs index 3bfa2c2..8da6b04 100644 --- a/Api/Application/Features/Tasks/GetTasks/GetTasksHandler.cs +++ b/Api/Application/Features/Tasks/GetTasks/GetTasksHandler.cs @@ -12,7 +12,7 @@ public async Task> Handle(GetTasksQuery request, var username = request.Username; var tasksList = await tasks.GetListActiveUserTasks(username); - while (tasksList.Count != 4) + while (tasksList.Count < 4) { var task = await taskEvent.AddUserTask(username); tasksList.Add(task); diff --git a/Infrastructure/DataAccess/UserTable.cs b/Infrastructure/DataAccess/UserTable.cs index 07eec7e..e6ffa21 100644 --- a/Infrastructure/DataAccess/UserTable.cs +++ b/Infrastructure/DataAccess/UserTable.cs @@ -18,7 +18,7 @@ public class UserTable(NpgsqlDataSource dataSource, IPasswordHasher hasher) : IU DisplayName = username.Trim(), Experience = 1, Level = 1, - Money = 0 + Money = 50 }; var builder = conn.QueryBuilder( $""" diff --git a/db_init/001_schema.sql b/db_init/001_schema.sql index 46c8fb7..b29534b 100644 --- a/db_init/001_schema.sql +++ b/db_init/001_schema.sql @@ -35,7 +35,6 @@ CREATE TABLE task_id VARCHAR(64), username VARCHAR(64), moderation_status moderation_status, - is_completed BOOLEAN NOT NULL DEFAULT FALSE, start_time DATE, photos_path TEXT[], photos_count INT From cdd638dfc5baadc40220fb5cfb8f8280d731888a Mon Sep 17 00:00:00 2001 From: fan4cz Date: Mon, 15 Dec 2025 23:21:47 +0500 Subject: [PATCH 26/36] dfgdfbgn --- Api/Application/Features/Tasks/GetTasks/GetTasksHandler.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Api/Application/Features/Tasks/GetTasks/GetTasksHandler.cs b/Api/Application/Features/Tasks/GetTasks/GetTasksHandler.cs index 8da6b04..ac17dde 100644 --- a/Api/Application/Features/Tasks/GetTasks/GetTasksHandler.cs +++ b/Api/Application/Features/Tasks/GetTasks/GetTasksHandler.cs @@ -16,8 +16,9 @@ public async Task> Handle(GetTasksQuery request, { var task = await taskEvent.AddUserTask(username); tasksList.Add(task); + await Task.Delay(200); } - + return tasksList; } } \ No newline at end of file From 16c39d0660533a2f3e68b49c60be7187dc65de9d Mon Sep 17 00:00:00 2001 From: fan4cz Date: Mon, 15 Dec 2025 23:55:58 +0500 Subject: [PATCH 27/36] eqgdfhgdhgd --- .../Features/Tasks/SubmitTask/SubmitTaskHandler.cs | 11 ++++++----- Infrastructure/DataAccess/UsersTasksTable.cs | 10 +++++----- .../DbExtensions/ModerationTaskDbExtension.cs | 4 ++-- Infrastructure/Interfaces/IUsersTasksTable.cs | 4 ++-- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/Api/Application/Features/Tasks/SubmitTask/SubmitTaskHandler.cs b/Api/Application/Features/Tasks/SubmitTask/SubmitTaskHandler.cs index f189557..6530ef8 100644 --- a/Api/Application/Features/Tasks/SubmitTask/SubmitTaskHandler.cs +++ b/Api/Application/Features/Tasks/SubmitTask/SubmitTaskHandler.cs @@ -5,7 +5,7 @@ namespace Api.Application.Features.Tasks.SubmitTask; -public class SubmitTaskHandler(IUsersTasksTable tasks,IMetricsTable metrics, IMediator mediator) +public class SubmitTaskHandler(IUsersTasksTable tasks, IMetricsTable metrics, IMediator mediator) : IRequestHandler> { public async Task> Handle(SubmitTaskQuery request, CancellationToken cancellationToken) @@ -13,14 +13,15 @@ public async Task> Handle(SubmitTaskQuery request, CancellationToke var uploadedNames = new List(); var username = request.Username; var taskId = request.TaskId; - - foreach (var file in request.Files) + var files = request.Files; + foreach (var file in files) { var name = await mediator.Send(new UploadPhotoCommand(file), cancellationToken); - await tasks.AddPhoto(taskId, name); uploadedNames.Add(name); } - + + await tasks.SetPhotos(taskId, uploadedNames.ToArray()); + await tasks.ChangeModerationStatus(taskId, ModerationStatus.Waiting); await metrics.AddRecord(username, MetricType.Submit); diff --git a/Infrastructure/DataAccess/UsersTasksTable.cs b/Infrastructure/DataAccess/UsersTasksTable.cs index 29ecad4..0361f77 100644 --- a/Infrastructure/DataAccess/UsersTasksTable.cs +++ b/Infrastructure/DataAccess/UsersTasksTable.cs @@ -77,7 +77,7 @@ tasks.reward AS Reward WHERE users_tasks.id = {id} """); - + var result = await builder.QueryFirstOrDefaultAsync(); if (result is null) return null; @@ -191,7 +191,7 @@ ORDER BY users_tasks.start_time DESC return (await builder.QueryAsync()).ToList(); } - public async Task AddPhoto(string username, string taskId, string photoName) + public async Task SetPhotos(string username, string taskId, string photoName) { await using var conn = await dataSource.OpenConnectionAsync(); var builder = conn.QueryBuilder( @@ -208,15 +208,15 @@ UPDATE users_tasks return await builder.ExecuteAsync() == 1; } - public async Task AddPhoto(int id, string photoName) + public async Task SetPhotos(int id, string[] photosNames) { await using var conn = await dataSource.OpenConnectionAsync(); var builder = conn.QueryBuilder( $""" UPDATE users_tasks SET - photos_path = COALESCE(photos_path, ARRAY[]::text[]) || ARRAY[{photoName}], - photos_count = photos_count + 1 + photos_path = COALESCE(photos_path, ARRAY[]::text[]) || {photosNames}, + photos_count = photos_count + {photosNames.Length} WHERE users_tasks.id = {id} """ diff --git a/Infrastructure/DbExtensions/ModerationTaskDbExtension.cs b/Infrastructure/DbExtensions/ModerationTaskDbExtension.cs index 01bfe41..93bc3ac 100644 --- a/Infrastructure/DbExtensions/ModerationTaskDbExtension.cs +++ b/Infrastructure/DbExtensions/ModerationTaskDbExtension.cs @@ -7,7 +7,7 @@ public class ModerationTaskDbExtension public int UserTaskId { get; set; } public required string TaskId { get; set; } public required string Name { get; set; } - public required string[] Tags { get; set; } + public required string[]? Tags { get; set; } public async Task ToModerationTask() { @@ -16,7 +16,7 @@ public async Task ToModerationTask() UserTaskId = this.UserTaskId, TaskId = this.TaskId, Name = this.Name, - Tags = Tags.ToList() + Tags = Tags is null ? [] : Tags.ToList() }; } } \ No newline at end of file diff --git a/Infrastructure/Interfaces/IUsersTasksTable.cs b/Infrastructure/Interfaces/IUsersTasksTable.cs index 4df82b3..9de0a2d 100644 --- a/Infrastructure/Interfaces/IUsersTasksTable.cs +++ b/Infrastructure/Interfaces/IUsersTasksTable.cs @@ -15,8 +15,8 @@ public interface IUsersTasksTable public Task ChangeModerationStatus(int id, ModerationStatus moderationStatus); public Task> GetUserSubmissionHistory(string username); - public Task AddPhoto(string username, string taskId, string photoName); - public Task AddPhoto(int id, string photoName); + public Task SetPhotos(string username, string taskId, string photoName); + public Task SetPhotos(int id, string[] photosNames); public Task GetModerationTask(); public Task GetModerationStatus(int id); public Task GetReward(int id); From c360e78017e667375ba26bfe66af8986cbc0ba8a Mon Sep 17 00:00:00 2001 From: fan4cz Date: Tue, 16 Dec 2025 00:14:24 +0500 Subject: [PATCH 28/36] rthyjukfjgfhtrgdhgfn --- Infrastructure/DataAccess/UsersTasksTable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Infrastructure/DataAccess/UsersTasksTable.cs b/Infrastructure/DataAccess/UsersTasksTable.cs index 0361f77..8cb8b31 100644 --- a/Infrastructure/DataAccess/UsersTasksTable.cs +++ b/Infrastructure/DataAccess/UsersTasksTable.cs @@ -215,7 +215,7 @@ public async Task SetPhotos(int id, string[] photosNames) $""" UPDATE users_tasks SET - photos_path = COALESCE(photos_path, ARRAY[]::text[]) || {photosNames}, + photos_path = {photosNames}, photos_count = photos_count + {photosNames.Length} WHERE users_tasks.id = {id} From 624cf4524f36eb3154e2e33e46df2bdb14b3fe90 Mon Sep 17 00:00:00 2001 From: fan4cz Date: Tue, 16 Dec 2025 20:25:26 +0500 Subject: [PATCH 29/36] reghgfnhmuktyr --- Infrastructure/DataAccess/UsersTasksTable.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Infrastructure/DataAccess/UsersTasksTable.cs b/Infrastructure/DataAccess/UsersTasksTable.cs index 8cb8b31..8ff5e8c 100644 --- a/Infrastructure/DataAccess/UsersTasksTable.cs +++ b/Infrastructure/DataAccess/UsersTasksTable.cs @@ -191,15 +191,15 @@ ORDER BY users_tasks.start_time DESC return (await builder.QueryAsync()).ToList(); } - public async Task SetPhotos(string username, string taskId, string photoName) + public async Task SetPhotos(string username, string taskId, string[] photosNames) { await using var conn = await dataSource.OpenConnectionAsync(); var builder = conn.QueryBuilder( $""" UPDATE users_tasks SET - photos_path = COALESCE(photos_path, ARRAY[]::text[]) || ARRAY[{photoName}], - photos_count = photos_count + 1 + photos_path = {photosNames}::text[], + photos_count = {photosNames.Length} WHERE users_tasks.username = {username} AND users_tasks.task_id = {taskId} @@ -215,8 +215,8 @@ public async Task SetPhotos(int id, string[] photosNames) $""" UPDATE users_tasks SET - photos_path = {photosNames}, - photos_count = photos_count + {photosNames.Length} + photos_path = {photosNames}::text[], + photos_count = {photosNames.Length} WHERE users_tasks.id = {id} """ From e4908e2b33ab560317088a17b98b0155b4a3ae6c Mon Sep 17 00:00:00 2001 From: fan4cz Date: Tue, 16 Dec 2025 20:38:11 +0500 Subject: [PATCH 30/36] efgdds --- Infrastructure/DataAccess/UsersTasksTable.cs | 17 ----------------- Infrastructure/Interfaces/IUsersTasksTable.cs | 2 -- 2 files changed, 19 deletions(-) diff --git a/Infrastructure/DataAccess/UsersTasksTable.cs b/Infrastructure/DataAccess/UsersTasksTable.cs index 8ff5e8c..416aa08 100644 --- a/Infrastructure/DataAccess/UsersTasksTable.cs +++ b/Infrastructure/DataAccess/UsersTasksTable.cs @@ -191,23 +191,6 @@ ORDER BY users_tasks.start_time DESC return (await builder.QueryAsync()).ToList(); } - public async Task SetPhotos(string username, string taskId, string[] photosNames) - { - await using var conn = await dataSource.OpenConnectionAsync(); - var builder = conn.QueryBuilder( - $""" - UPDATE users_tasks - SET - photos_path = {photosNames}::text[], - photos_count = {photosNames.Length} - WHERE - users_tasks.username = {username} - AND users_tasks.task_id = {taskId} - """ - ); - return await builder.ExecuteAsync() == 1; - } - public async Task SetPhotos(int id, string[] photosNames) { await using var conn = await dataSource.OpenConnectionAsync(); diff --git a/Infrastructure/Interfaces/IUsersTasksTable.cs b/Infrastructure/Interfaces/IUsersTasksTable.cs index 9de0a2d..2966054 100644 --- a/Infrastructure/Interfaces/IUsersTasksTable.cs +++ b/Infrastructure/Interfaces/IUsersTasksTable.cs @@ -14,8 +14,6 @@ public interface IUsersTasksTable public Task ChangeModerationStatus(string username, string taskId, ModerationStatus moderationStatus); public Task ChangeModerationStatus(int id, ModerationStatus moderationStatus); public Task> GetUserSubmissionHistory(string username); - - public Task SetPhotos(string username, string taskId, string photoName); public Task SetPhotos(int id, string[] photosNames); public Task GetModerationTask(); public Task GetModerationStatus(int id); From afb8fce2471a056f5671d36c0bf5031fb3a7cdcf Mon Sep 17 00:00:00 2001 From: fan4cz Date: Tue, 16 Dec 2025 21:59:45 +0500 Subject: [PATCH 31/36] =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B2=D1=8B=D0=B9=20?= =?UTF-8?q?=D1=82=D0=B5=D1=81=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test_and_deploy.yml | 23 +++++++- Test/Infrastructure/DbConectionTest.cs | 36 ++++++++++++ Test/Test.csproj | 37 ++++++++++++ Test/TestBase.cs | 80 ++++++++++++++++++++++++++ Vibik.Server.sln | 6 ++ 5 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 Test/Infrastructure/DbConectionTest.cs create mode 100644 Test/Test.csproj create mode 100644 Test/TestBase.cs diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml index b88e8f5..3300901 100644 --- a/.github/workflows/test_and_deploy.yml +++ b/.github/workflows/test_and_deploy.yml @@ -10,6 +10,21 @@ jobs: tests-and-format: runs-on: ubuntu-latest + services: + postgres: + image: postgres:16 + env: + POSTGRES_DB: vibik_test + POSTGRES_USER: vibik_user + POSTGRES_PASSWORD: vibik_pass + ports: + - 5432:5432 + options: >- + --health-cmd="pg_isready -U vibik_user" + --health-interval=10s + --health-timeout=5s + --health-retries=5 + steps: - name: Checkout repository uses: actions/checkout@v5 @@ -26,6 +41,12 @@ jobs: run: dotnet build --no-restore -c Release - name: Run tests + env: + POSTGRES_DB: vibik_test + POSTGRES_USER: vibik_user + POSTGRES_PASSWORD: vibik_pass + POSTGRES_SERVER: localhost + POSTGRES_PORT: 5432 run: dotnet test --no-build --verbosity normal - name: Dotnet format whitespace (fix) @@ -91,4 +112,4 @@ jobs: cd vibik sudo docker compose pull sudo docker compose down - sudo docker compose up -d \ No newline at end of file + sudo docker compose up -d diff --git a/Test/Infrastructure/DbConectionTest.cs b/Test/Infrastructure/DbConectionTest.cs new file mode 100644 index 0000000..da998f7 --- /dev/null +++ b/Test/Infrastructure/DbConectionTest.cs @@ -0,0 +1,36 @@ +using Dapper; +using Npgsql; +using NUnit.Framework; +using Test; + +[TestFixture] +public class DbSmokeTests : TestBase +{ + [Test] + public async Task Db_is_available_and_schema_is_applied() + { + await using (var conn = await DataSource.OpenConnectionAsync()) + { + var ping = await conn.ExecuteScalarAsync("SELECT 1;"); + Assert.That(ping, Is.EqualTo(1), "PostgreSQL is not reachable (SELECT 1 failed)."); + } + + await using (var conn = await DataSource.OpenConnectionAsync()) + { + var tables = (await conn.QueryAsync(""" + SELECT tablename + FROM pg_tables + WHERE schemaname = 'public' + ORDER BY tablename; + """)).ToList(); + + Assert.Multiple(() => + { + Assert.That(tables, Does.Contain("tasks"), "Table 'tasks' was not created."); + Assert.That(tables, Does.Contain("users"), "Table 'users' was not created."); + Assert.That(tables, Does.Contain("users_tasks"), "Table 'users_tasks' was not created."); + Assert.That(tables, Does.Contain("moderators"), "Table 'moderators' was not created."); + }); + } + } +} \ No newline at end of file diff --git a/Test/Test.csproj b/Test/Test.csproj new file mode 100644 index 0000000..573b440 --- /dev/null +++ b/Test/Test.csproj @@ -0,0 +1,37 @@ + + + + net9.0 + latest + enable + enable + false + + + + + + + + + + + + + + + db_init\001_schema.sql + Always + + + + + + + + + + + + + diff --git a/Test/TestBase.cs b/Test/TestBase.cs new file mode 100644 index 0000000..cfeb2c9 --- /dev/null +++ b/Test/TestBase.cs @@ -0,0 +1,80 @@ +using Dapper; +using DotNetEnv; +using Infrastructure.DataAccess; +using Infrastructure.Interfaces; +using Microsoft.Extensions.DependencyInjection; +using Npgsql; + +namespace Test; + +public abstract class TestBase +{ + protected ServiceProvider Provider = null!; + protected NpgsqlDataSource DataSource = null!; + + [OneTimeSetUp] + public async Task OneTimeSetUp() + { + Env.TraversePath().Load(); + + var cs = BuildConnectionString(); + + var services = new ServiceCollection(); + services.AddSingleton(NpgsqlDataSource.Create(cs)); + + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + Provider = services.BuildServiceProvider(); + DataSource = Provider.GetRequiredService(); + + await InitSchema(); + } + + private static string BuildConnectionString() + { + string Get(string name) => + Environment.GetEnvironmentVariable(name) + ?? throw new InvalidOperationException($"{name} is missing"); + + return + $"Host={Get("POSTGRES_SERVER")};" + + $"Port={Get("POSTGRES_PORT")};" + + $"Database={Get("POSTGRES_DB")};" + + $"Username={Get("POSTGRES_USER")};" + + $"Password={Get("POSTGRES_PASSWORD")};"; + } + + private async Task InitSchema() + { + await using var conn = await DataSource.OpenConnectionAsync(); + + var sql = await File.ReadAllTextAsync( + Path.Combine("db_init", "001_schema.sql")); + + await conn.ExecuteAsync(sql); + } + + [SetUp] + public async Task CleanDb() + { + await using var conn = await DataSource.OpenConnectionAsync(); + + await conn.ExecuteAsync(""" + TRUNCATE TABLE + users_tasks, + tasks, + users + RESTART IDENTITY + CASCADE; + """); + } + + [OneTimeTearDown] + public async Task TearDown() + { + await DataSource.DisposeAsync(); + await Provider.DisposeAsync(); + } +} \ No newline at end of file diff --git a/Vibik.Server.sln b/Vibik.Server.sln index 5e0a3b4..a0c94e6 100644 --- a/Vibik.Server.sln +++ b/Vibik.Server.sln @@ -6,6 +6,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure", "Infrastru EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Client.Models", "Client.Models\Client.Models.csproj", "{84019F5E-A8D9-49B2-B78D-071A674A3EB4}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "Test\Test.csproj", "{916F4C8D-F266-4DFA-9731-085E89A984E4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -28,5 +30,9 @@ Global {4950BB62-F53B-4D0F-BF7A-0809E407A808}.Debug|Any CPU.Build.0 = Debug|Any CPU {4950BB62-F53B-4D0F-BF7A-0809E407A808}.Release|Any CPU.ActiveCfg = Release|Any CPU {4950BB62-F53B-4D0F-BF7A-0809E407A808}.Release|Any CPU.Build.0 = Release|Any CPU + {916F4C8D-F266-4DFA-9731-085E89A984E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {916F4C8D-F266-4DFA-9731-085E89A984E4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {916F4C8D-F266-4DFA-9731-085E89A984E4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {916F4C8D-F266-4DFA-9731-085E89A984E4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal From 243e77409ff2d8b3d86cc1d3fe36d5f5efd90f6e Mon Sep 17 00:00:00 2001 From: fan4cz Date: Tue, 16 Dec 2025 22:07:38 +0500 Subject: [PATCH 32/36] fix --- .github/workflows/test_and_deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml index 3300901..2fee57f 100644 --- a/.github/workflows/test_and_deploy.yml +++ b/.github/workflows/test_and_deploy.yml @@ -47,7 +47,7 @@ jobs: POSTGRES_PASSWORD: vibik_pass POSTGRES_SERVER: localhost POSTGRES_PORT: 5432 - run: dotnet test --no-build --verbosity normal + run: dotnet test --configuration Release --no-build --verbosity normal - name: Dotnet format whitespace (fix) run: dotnet format whitespace From 582b4e920fc5f105605fed256c5878bf9cb047a4 Mon Sep 17 00:00:00 2001 From: Lychee <2006tka@gmail.com> Date: Tue, 16 Dec 2025 22:17:27 +0500 Subject: [PATCH 33/36] TEST --- Api/Application/Features/Tasks/GetTasks/GetTasksHandler.cs | 2 +- Api/Dockerfile | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Api/Application/Features/Tasks/GetTasks/GetTasksHandler.cs b/Api/Application/Features/Tasks/GetTasks/GetTasksHandler.cs index ac17dde..3318170 100644 --- a/Api/Application/Features/Tasks/GetTasks/GetTasksHandler.cs +++ b/Api/Application/Features/Tasks/GetTasks/GetTasksHandler.cs @@ -16,7 +16,7 @@ public async Task> Handle(GetTasksQuery request, { var task = await taskEvent.AddUserTask(username); tasksList.Add(task); - await Task.Delay(200); + await Task.Delay(200, cancellationToken); } return tasksList; diff --git a/Api/Dockerfile b/Api/Dockerfile index 414f00b..db99776 100644 --- a/Api/Dockerfile +++ b/Api/Dockerfile @@ -6,6 +6,7 @@ COPY Api/Api.csproj Api/ COPY Application/Application.csproj Application/ COPY Infrastructure/Infrastructure.csproj Infrastructure/ COPY Client.Models/Client.Models.csproj Client.Models/ +COPY Test/Test.csproj Test/ RUN dotnet restore Vibik.Server.sln From 1d3fa508243dbac9f5b0f68f019c8602842d6549 Mon Sep 17 00:00:00 2001 From: Lychee <2006tka@gmail.com> Date: Tue, 16 Dec 2025 22:17:27 +0500 Subject: [PATCH 34/36] =?UTF-8?q?TEST=20(=D1=8F=20=D0=B0=D0=B1=D0=BE=D0=B1?= =?UTF-8?q?=D0=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Api/Application/Features/Tasks/GetTasks/GetTasksHandler.cs | 2 +- Api/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Api/Application/Features/Tasks/GetTasks/GetTasksHandler.cs b/Api/Application/Features/Tasks/GetTasks/GetTasksHandler.cs index ac17dde..3318170 100644 --- a/Api/Application/Features/Tasks/GetTasks/GetTasksHandler.cs +++ b/Api/Application/Features/Tasks/GetTasks/GetTasksHandler.cs @@ -16,7 +16,7 @@ public async Task> Handle(GetTasksQuery request, { var task = await taskEvent.AddUserTask(username); tasksList.Add(task); - await Task.Delay(200); + await Task.Delay(200, cancellationToken); } return tasksList; diff --git a/Api/Dockerfile b/Api/Dockerfile index 414f00b..033b88a 100644 --- a/Api/Dockerfile +++ b/Api/Dockerfile @@ -7,7 +7,7 @@ COPY Application/Application.csproj Application/ COPY Infrastructure/Infrastructure.csproj Infrastructure/ COPY Client.Models/Client.Models.csproj Client.Models/ -RUN dotnet restore Vibik.Server.sln +RUN dotnet restore Api/Api.csproj COPY . . From d599f84ea78fa502d396b6f5a933ee3a04006d0a Mon Sep 17 00:00:00 2001 From: fan4cz Date: Tue, 16 Dec 2025 23:07:56 +0500 Subject: [PATCH 35/36] UserTableTest --- Test/Infrastructure/FakePasswordHasher.cs | 9 ++ Test/Infrastructure/UserTableTests.cs | 136 ++++++++++++++++++++++ Test/TestBase.cs | 3 +- 3 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 Test/Infrastructure/FakePasswordHasher.cs create mode 100644 Test/Infrastructure/UserTableTests.cs diff --git a/Test/Infrastructure/FakePasswordHasher.cs b/Test/Infrastructure/FakePasswordHasher.cs new file mode 100644 index 0000000..2a14dd8 --- /dev/null +++ b/Test/Infrastructure/FakePasswordHasher.cs @@ -0,0 +1,9 @@ +using Infrastructure.Interfaces; + +public sealed class FakePasswordHasher : IPasswordHasher +{ + public string Hash(string password) => $"HASH::{password}"; + + public bool Verify(string password, string hash) => + hash == $"HASH::{password}"; +} \ No newline at end of file diff --git a/Test/Infrastructure/UserTableTests.cs b/Test/Infrastructure/UserTableTests.cs new file mode 100644 index 0000000..2693262 --- /dev/null +++ b/Test/Infrastructure/UserTableTests.cs @@ -0,0 +1,136 @@ +using Dapper; +using Infrastructure.Interfaces; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; +using Shared.Models.Entities; +using Test; + +[TestFixture] +public class UserTableTests : TestBase +{ + private IUserTable users = null!; + + [SetUp] + public void Setup() + { + users = Provider.GetRequiredService(); + } + + [Test] + public async Task RegisterUser_creates_user() + { + var user = await users.RegisterUser("test", "HASH::123"); + + Assert.That(user, Is.Not.Null); + Assert.That(user!.Username, Is.EqualTo("test")); + Assert.That(user.Level, Is.EqualTo(1)); + Assert.That(user.Money, Is.EqualTo(50)); + } + + [Test] + public async Task RegisterUser_returns_null_if_username_exists() + { + await users.RegisterUser("test", "HASH::123"); + + var second = await users.RegisterUser("test", "HASH::456"); + + Assert.That(second, Is.Null); + } + + [Test] + public async Task LoginUser_returns_true_for_correct_password() + { + await users.RegisterUser("test", "HASH::123"); + + var result = await users.LoginUser("test", "123"); + + Assert.That(result, Is.True); + } + + [Test] + public async Task LoginUser_returns_false_for_wrong_password() + { + await users.RegisterUser("test", "HASH::123"); + + var result = await users.LoginUser("test", "wrong"); + + Assert.That(result, Is.False); + } + + [Test] + public async Task GetUser_by_username_returns_user() + { + await users.RegisterUser("test", "HASH::123"); + + var user = await users.GetUser("test"); + + Assert.That(user, Is.Not.Null); + Assert.That(user!.Username, Is.EqualTo("test")); + } + + [Test] + public async Task ChangeDisplayName_updates_name() + { + await users.RegisterUser("test", "HASH::123"); + + var changed = await users.ChangeDisplayName("test", "NewName"); + var user = await users.GetUser("test"); + + Assert.That(changed, Is.True); + Assert.That(user!.DisplayName, Is.EqualTo("NewName")); + } + + [Test] + public async Task AddMoney_increases_money() + { + await users.RegisterUser("test", "HASH::123"); + + await users.AddMoney("test", 25); + var user = await users.GetUser("test"); + + Assert.That(user!.Money, Is.EqualTo(75)); + } + + [Test] + public async Task AddExperience_increases_exp() + { + await users.RegisterUser("test", "HASH::123"); + + await users.AddExperience("test", 10); + var user = await users.GetUser("test"); + + Assert.That(user!.Experience, Is.EqualTo(11)); + } + + [Test] + public async Task AddLevel_increases_level() + { + await users.RegisterUser("test", "HASH::123"); + + await users.AddLevel("test", 2); + var user = await users.GetUser("test"); + + Assert.That(user!.Level, Is.EqualTo(3)); + } + + [Test] + public async Task GetUser_by_userTaskId_returns_user() + { + await users.RegisterUser("test", "HASH::123"); + + await using var conn = await DataSource.OpenConnectionAsync(); + + var taskId = await conn.ExecuteScalarAsync( + """ + INSERT INTO users_tasks (task_id, username) + VALUES ('t1', 'test') + RETURNING id; + """ + ); + + var user = await users.GetUser(taskId); + + Assert.That(user, Is.Not.Null); + Assert.That(user!.Username, Is.EqualTo("test")); + } +} diff --git a/Test/TestBase.cs b/Test/TestBase.cs index cfeb2c9..aa08c88 100644 --- a/Test/TestBase.cs +++ b/Test/TestBase.cs @@ -16,7 +16,7 @@ public abstract class TestBase public async Task OneTimeSetUp() { Env.TraversePath().Load(); - + var cs = BuildConnectionString(); var services = new ServiceCollection(); @@ -25,6 +25,7 @@ public async Task OneTimeSetUp() services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddSingleton(); Provider = services.BuildServiceProvider(); DataSource = Provider.GetRequiredService(); From fd0bd8faf4091b1fdb99ca1d3fb02f2cc8290efb Mon Sep 17 00:00:00 2001 From: fan4cz Date: Wed, 17 Dec 2025 00:05:15 +0500 Subject: [PATCH 36/36] =?UTF-8?q?=D0=B9=D0=BA=D1=86=D1=83=D0=BF=D0=BA?= =?UTF-8?q?=D1=80=D0=B2=D0=BF=D1=8C=D0=B0=D0=BE=D0=B1=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Infrastructure/DataAccess/UsersTasksTable.cs | 69 --------- Infrastructure/Interfaces/IUsersTasksTable.cs | 3 - .../FakePasswordHasher.cs | 2 + Test/Fake/FakeStorageService.cs | 21 +++ Test/Infrastructure/UserTableTests.cs | 17 +- Test/Infrastructure/UsersTasksTableTests.cs | 146 ++++++++++++++++++ Test/TestBase.cs | 2 + db_init/001_schema.sql | 2 +- 8 files changed, 181 insertions(+), 81 deletions(-) rename Test/{Infrastructure => Fake}/FakePasswordHasher.cs (91%) create mode 100644 Test/Fake/FakeStorageService.cs create mode 100644 Test/Infrastructure/UsersTasksTableTests.cs diff --git a/Infrastructure/DataAccess/UsersTasksTable.cs b/Infrastructure/DataAccess/UsersTasksTable.cs index 416aa08..08c385a 100644 --- a/Infrastructure/DataAccess/UsersTasksTable.cs +++ b/Infrastructure/DataAccess/UsersTasksTable.cs @@ -10,13 +10,10 @@ namespace Infrastructure.DataAccess; public class UsersTasksTable( NpgsqlDataSource dataSource, - ILogger logger, IStorageService storageService) : IUsersTasksTable { public async Task> GetListActiveUserTasks(string username) { - logger.LogInformation("вызов GetListActiveUserTasks для username: {username} ", username); - Console.WriteLine($"вызов GetListActiveUserTasks для username: {username}"); await using var conn = await dataSource.OpenConnectionAsync(); var builder = conn.QueryBuilder( $""" @@ -36,31 +33,6 @@ tasks.reward AS Reward return (await builder.QueryAsync()).ToList(); } - public async Task GetTaskExtendedInfo(string username, string taskId) - { - logger.LogInformation("вызов GetTaskExtendedInfo для username: {username} task: {taskId} ", username, taskId); - await using var conn = await dataSource.OpenConnectionAsync(); - var builder = conn.QueryBuilder( - $""" - SELECT - users_tasks.id AS UserTaskId, - tasks.description AS Description, - tasks.photos_required AS PhotosRequired, - COALESCE(tasks.example_path, ARRAY[]::text[]) AS ExamplePhotos, - COALESCE(users_tasks.photos_path, ARRAY[]::text[]) AS UserPhotos - FROM - users_tasks - JOIN tasks ON tasks.id = users_tasks.task_id - WHERE - users_tasks.username = {username} - AND users_tasks.task_id = {taskId} - """); - var result = await builder.QueryFirstOrDefaultAsync(); - if (result is null) - return null; - return await result.ToTaskModelExtendedInfo(storageService); - } - public async Task GetTaskExtendedInfo(int id) { await using var conn = await dataSource.OpenConnectionAsync(); @@ -114,47 +86,6 @@ tasks.reward AS Reward return await builder.QueryFirstOrDefaultAsync(); } - public async Task GetTaskFullInfo(string username, string taskId) - { - logger.LogInformation("вызов GetTaskFullInfo для username: {username} task: {taskId} ", username, taskId); - await using var conn = await dataSource.OpenConnectionAsync(); - var builder = conn.QueryBuilder( - $"""" - SELECT - users_tasks.task_id AS TaskId, - users_tasks.start_time::timestamp AS StartTime, - tasks.name AS Name, - tasks.reward AS Reward - FROM - users_tasks - JOIN tasks ON tasks.id = users_tasks.task_id - WHERE - users_tasks.username = {username} - AND users_tasks.task_id = {taskId} - """" - ); - var task = await builder.QueryFirstOrDefaultAsync(); - if (task is null) - return null; - task.ExtendedInfo = await GetTaskExtendedInfo(username, taskId); - return task; - } - - public async Task ChangeModerationStatus(string username, string taskId, ModerationStatus moderationStatus) - { - await using var conn = await dataSource.OpenConnectionAsync(); - var builder = conn.QueryBuilder( - $""" - UPDATE users_tasks - SET moderation_status = {moderationStatus.ToString().ToLower()}::moderation_status - WHERE - users_tasks.username = {username} - AND users_tasks.task_id = {taskId} - """ - ); - return await builder.ExecuteAsync() == 1; - } - public async Task ChangeModerationStatus(int id, ModerationStatus moderationStatus) { await using var conn = await dataSource.OpenConnectionAsync(); diff --git a/Infrastructure/Interfaces/IUsersTasksTable.cs b/Infrastructure/Interfaces/IUsersTasksTable.cs index 2966054..9012f5b 100644 --- a/Infrastructure/Interfaces/IUsersTasksTable.cs +++ b/Infrastructure/Interfaces/IUsersTasksTable.cs @@ -6,12 +6,9 @@ namespace Infrastructure.Interfaces; public interface IUsersTasksTable { public Task> GetListActiveUserTasks(string username); - public Task GetTaskExtendedInfo(string username, string taskId); public Task GetTaskExtendedInfo(int id); - public Task GetTaskFullInfo(string taskId, string username); public Task GetTaskFullInfo(int taskId); public Task GetTaskNoExtendedInfo(int id); - public Task ChangeModerationStatus(string username, string taskId, ModerationStatus moderationStatus); public Task ChangeModerationStatus(int id, ModerationStatus moderationStatus); public Task> GetUserSubmissionHistory(string username); public Task SetPhotos(int id, string[] photosNames); diff --git a/Test/Infrastructure/FakePasswordHasher.cs b/Test/Fake/FakePasswordHasher.cs similarity index 91% rename from Test/Infrastructure/FakePasswordHasher.cs rename to Test/Fake/FakePasswordHasher.cs index 2a14dd8..27dfda8 100644 --- a/Test/Infrastructure/FakePasswordHasher.cs +++ b/Test/Fake/FakePasswordHasher.cs @@ -1,5 +1,7 @@ using Infrastructure.Interfaces; +namespace Test.Fake; + public sealed class FakePasswordHasher : IPasswordHasher { public string Hash(string password) => $"HASH::{password}"; diff --git a/Test/Fake/FakeStorageService.cs b/Test/Fake/FakeStorageService.cs new file mode 100644 index 0000000..c54accb --- /dev/null +++ b/Test/Fake/FakeStorageService.cs @@ -0,0 +1,21 @@ +using Infrastructure.Interfaces; + +public sealed class FakeStorageService : IStorageService +{ + public Task> GetTemporaryUrlsAsync(List fileNames) + { + if (fileNames is null || fileNames.Count == 0) + return Task.FromResult(new List()); + + var result = fileNames + .Where(x => !string.IsNullOrWhiteSpace(x)) + .Select(x => + { + var safe = x.TrimStart('/').Replace("\\", "/"); + return new Uri($"https://fake.local/{safe}", UriKind.Absolute); + }) + .ToList(); + + return Task.FromResult(result); + } +} \ No newline at end of file diff --git a/Test/Infrastructure/UserTableTests.cs b/Test/Infrastructure/UserTableTests.cs index 2693262..095adf4 100644 --- a/Test/Infrastructure/UserTableTests.cs +++ b/Test/Infrastructure/UserTableTests.cs @@ -1,5 +1,6 @@ using Dapper; using Infrastructure.Interfaces; +using InterpolatedSql.Dapper; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using Shared.Models.Entities; @@ -120,17 +121,17 @@ public async Task GetUser_by_userTaskId_returns_user() await using var conn = await DataSource.OpenConnectionAsync(); - var taskId = await conn.ExecuteScalarAsync( - """ - INSERT INTO users_tasks (task_id, username) - VALUES ('t1', 'test') - RETURNING id; - """ - ); + var taskId = await conn.QueryBuilder( + $""" + INSERT INTO users_tasks (task_id, username) + VALUES ('t1', 'test') + RETURNING id; + """ + ).ExecuteScalarAsync(); var user = await users.GetUser(taskId); Assert.That(user, Is.Not.Null); Assert.That(user!.Username, Is.EqualTo("test")); } -} +} \ No newline at end of file diff --git a/Test/Infrastructure/UsersTasksTableTests.cs b/Test/Infrastructure/UsersTasksTableTests.cs new file mode 100644 index 0000000..8928dd5 --- /dev/null +++ b/Test/Infrastructure/UsersTasksTableTests.cs @@ -0,0 +1,146 @@ +using Dapper; +using Infrastructure.Interfaces; +using Microsoft.Extensions.DependencyInjection; +using InterpolatedSql.Dapper; +using NUnit.Framework; +using Shared.Models.Enums; +using Test; + +[TestFixture] +public class UsersTasksTableTests : TestBase +{ + private IUsersTasksTable table = null!; + + [SetUp] + public void Setup() + { + table = Provider.GetRequiredService(); + } + + [Test] + public async Task GetTaskNoExtendedInfo_returns_task() + { + var id = await SeedUserTask(); + + var task = await table.GetTaskNoExtendedInfo(id); + + Assert.That(task, Is.Not.Null); + Assert.That(task!.UserTaskId, Is.EqualTo(id)); + } + + [Test] + public async Task GetTaskExtendedInfo_returns_extended_info() + { + var id = await SeedUserTask( + examplePath: ["img1.png", "img2.png"]); + + var info = await table.GetTaskExtendedInfo(id); + + Assert.That(info, Is.Not.Null); + Assert.That(info!.ExamplePhotos, Has.Count.EqualTo(2)); + } + + [Test] + public async Task GetTaskFullInfo_returns_task_with_extension() + { + var id = await SeedUserTask(); + + var task = await table.GetTaskFullInfo(id); + + Assert.That(task, Is.Not.Null); + Assert.That(task!.ExtendedInfo, Is.Not.Null); + } + + [Test] + public async Task ChangeModerationStatus_updates_status() + { + var id = await SeedUserTask(); + + var result = await table.ChangeModerationStatus(id, ModerationStatus.Approved); + var status = await table.GetModerationStatus(id); + + Assert.That(result, Is.True); + Assert.That(status, Is.EqualTo("approved")); + } + + [Test] + public async Task SetPhotos_replaces_array_and_count() + { + var id = await SeedUserTask(); + + var photos = new[] { "a.png", "b.png" }; + var result = await table.SetPhotos(id, photos); + + Assert.That(result, Is.True); + + await using var conn = await DataSource.OpenConnectionAsync(); + + var count = await conn.QueryBuilder( + $""" + SELECT photos_count + FROM users_tasks + WHERE id = {id} + """ + ).ExecuteScalarAsync(); + + Assert.That(count, Is.EqualTo(2)); + } + + [Test] + public async Task GetModerationTask_returns_waiting_task() + { + await SeedUserTask(moderationStatus: "waiting"); + + var task = await table.GetModerationTask(); + + Assert.That(task, Is.Not.Null); + Assert.That(task!.ExtendedInfo, Is.Not.Null); + } + + [Test] + public async Task GetReward_returns_reward() + { + var id = await SeedUserTask(); + + var reward = await table.GetReward(id); + + Assert.That(reward, Is.EqualTo(10)); + } + + private async Task SeedUserTask( + string username = "user", + string taskId = "task1", + string moderationStatus = "default", + string[]? examplePath = null) + { + await using var conn = await DataSource.OpenConnectionAsync(); + + await conn.QueryBuilder( + $""" + INSERT INTO users (username) + VALUES ({username}) + """ + ).ExecuteAsync(); + + await conn.QueryBuilder( + $""" + INSERT INTO tasks (id, name, reward, example_path) + VALUES ({taskId}, 'Task', 10, {examplePath}::text[]) + """ + ).ExecuteAsync(); + + var builder = conn.QueryBuilder( + $""" + INSERT INTO users_tasks (task_id, username, moderation_status) + VALUES ( + {taskId}, + {username}, + {moderationStatus}::moderation_status + ) + RETURNING id + """ + ); + + return await builder.ExecuteScalarAsync(); + } +} \ No newline at end of file diff --git a/Test/TestBase.cs b/Test/TestBase.cs index aa08c88..716b5e0 100644 --- a/Test/TestBase.cs +++ b/Test/TestBase.cs @@ -4,6 +4,7 @@ using Infrastructure.Interfaces; using Microsoft.Extensions.DependencyInjection; using Npgsql; +using Test.Fake; namespace Test; @@ -25,6 +26,7 @@ public async Task OneTimeSetUp() services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddSingleton(); services.AddSingleton(); Provider = services.BuildServiceProvider(); diff --git a/db_init/001_schema.sql b/db_init/001_schema.sql index b29534b..66da5b3 100644 --- a/db_init/001_schema.sql +++ b/db_init/001_schema.sql @@ -25,7 +25,7 @@ CREATE TABLE DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'moderation_status') THEN - CREATE TYPE moderation_status AS ENUM ('not', 'on', 'approved', 'reject'); + CREATE TYPE moderation_status AS ENUM ('default', 'waiting', 'approved', 'reject'); END IF; END$$;