Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Duely/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
.idea
*obj
*bin
Duely.sln.DotSettings.user
Duely.sln.DotSettings.user
AGENTS.md
coverage.cobertura.xml
8 changes: 7 additions & 1 deletion Duely/Duely.sln
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

Microsoft Visual Studio Solution File, Format Version 12.00
#
#
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duely", "src\Duely\Duely.csproj", "{DF4F5CE8-1DC0-4905-9BF5-22F4B48A5BC1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duely.Application.UseCases", "src\Duely.Application.UseCases\Duely.Application.UseCases.csproj", "{8B2E991C-60D2-41D6-9E5C-713107FB79A9}"
Expand Down Expand Up @@ -33,6 +33,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duely.Domain.Tests", "tests
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duely.Infrastructure.Telemetry", "src\Duely.Infrastructure.Telemetry\Duely.Infrastructure.Telemetry.csproj", "{3A93E8DA-3343-4863-A64D-2E90F9675396}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duely.Application.Services", "src\Duely.Application.Services\Duely.Application.Services.csproj", "{4B1E5937-8D3B-4A67-9C42-B80694C054BD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -103,5 +105,9 @@ Global
{3A93E8DA-3343-4863-A64D-2E90F9675396}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3A93E8DA-3343-4863-A64D-2E90F9675396}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3A93E8DA-3343-4863-A64D-2E90F9675396}.Release|Any CPU.Build.0 = Release|Any CPU
{4B1E5937-8D3B-4A67-9C42-B80694C054BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4B1E5937-8D3B-4A67-9C42-B80694C054BD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4B1E5937-8D3B-4A67-9C42-B80694C054BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4B1E5937-8D3B-4A67-9C42-B80694C054BD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
4 changes: 2 additions & 2 deletions Duely/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ volumes:

services:
postgres:
image: postgres
image: postgres:17.7
container_name: duely_postgres
networks:
- coduels
Expand Down Expand Up @@ -69,4 +69,4 @@ services:
postgres:
condition: service_healthy
migration:
condition: service_completed_successfully
condition: service_completed_successfully
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,13 @@ protected override async Task ExecuteAsync(CancellationToken cancellationToken)
using (var scope = sp.CreateScope())
{
var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();

await mediator.Send(new CheckDuelsForFinishCommand(), cancellationToken);
var result = await mediator.Send(new CheckDuelsForFinishCommand(), cancellationToken);
if (result.IsFailed)
{
logger.LogWarning(
"CheckDuelsForFinishCommand failed: {Reason}",
string.Join("\n", result.Errors.Select(error => error.Message)));
}
}

await Task.Delay(options.Value.CheckIntervalMs, cancellationToken);
Expand Down
4 changes: 3 additions & 1 deletion Duely/src/Duely.Application.BackgroundJobs/DuelMakingJob.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ protected override async Task ExecuteAsync(CancellationToken cancellationToken)
var result = await mediator.Send(new TryCreateDuelCommand(), cancellationToken);
if (result.IsFailed)
{
logger.LogWarning("TryCreateDuel failed: {Reason}", string.Join("\n", result.Errors.Select(error => error.Message)));
logger.LogWarning(
"TryCreateDuel failed: {Reason}",
string.Join("\n", result.Errors.Select(error => error.Message)));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Duely.Application.Services\Duely.Application.Services.csproj" />
<ProjectReference Include="..\Duely.Domain.Services\Duely.Domain.Services.csproj" />
<ProjectReference Include="..\Duely.Application.UseCases\Duely.Application.UseCases.csproj" />
</ItemGroup>
Expand Down
14 changes: 6 additions & 8 deletions Duely/src/Duely.Application.BackgroundJobs/OutboxJob.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Duely.Application.UseCases.Features.Outbox;
using Duely.Domain.Models;
using Duely.Application.Services.Outbox;
using Duely.Application.Services.Outbox.Relay;
using Duely.Domain.Models;
using Duely.Infrastructure.DataAccess.EntityFramework;
using Duely.Application.UseCases.Features.Outbox.Relay;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
Expand All @@ -25,17 +25,15 @@ protected override async Task ExecuteAsync(CancellationToken cancellationToken)
var db = scope.ServiceProvider.GetRequiredService<Context>();
var dispatcher = scope.ServiceProvider.GetRequiredService<IOutboxDispatcher>();
var now = DateTime.UtcNow;

var deleted = await db.Outbox
.Where(m => m.RetryUntil <= now)
.ExecuteDeleteAsync(cancellationToken);

if (deleted > 0)
{
logger.LogInformation("Outbox expired messages deleted. Count = {Count}", deleted);
}

now = DateTime.UtcNow;
var message = await db.Outbox
.Where(m => m.Status == OutboxStatus.ToDo
&& (m.RetryUntil > now)
Expand Down Expand Up @@ -76,11 +74,11 @@ protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
message.Retries++;
message.Status = OutboxStatus.ToRetry;
var RetryDelayMs = CalculateRetryDelayMs(
var retryDelayMs = CalculateRetryDelayMs(
outboxOptions.InitialRetryDelayMs,
outboxOptions.MaxRetryDelayMs,
message.Retries);
message.RetryAt = processedAt.AddMilliseconds(RetryDelayMs);
message.RetryAt = processedAt.AddMilliseconds(retryDelayMs);

logger.LogDebug("Outbox retry scheduled. MessageId = {MessageId}, Type = {Type}, Retries = {Retries}, RetryAt = {RetryAt}",
message.Id, message.Type, message.Retries, message.RetryAt
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>12</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.10" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.9" />
<PackageReference Include="FluentResults" Version="4.0.0" />
<PackageReference Include="MediatR" Version="13.0.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Duely.Infrastructure.DataAccess.EntityFramework\Duely.Infrastructure.DataAccess.EntityFramework.csproj" />
<ProjectReference Include="..\Duely.Infrastructure.Gateway.Client.Abstracts\Duely.Infrastructure.Gateway.Client.Abstracts.csproj" />
<ProjectReference Include="..\Duely.Infrastructure.Gateway.Exesh.Abstracts\Duely.Infrastructure.Gateway.Exesh.Abstracts.csproj" />
<ProjectReference Include="..\Duely.Infrastructure.Gateway.Tasks.Abstracts\Duely.Infrastructure.Gateway.Tasks.Abstracts.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using Duely.Infrastructure.Gateway.Exesh.Abstracts;
using Duely.Infrastructure.Gateway.Exesh.Abstracts;

namespace Duely.Application.UseCases.Features.UserCodeRuns;
namespace Duely.Application.Services;

public class ExeshStepsBuilder
public static class ExeshStepsBuilder
{
private const int DefaultTimeLimitMs = 2000;
private const int DefaultMemoryLimitMb = 256;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
using Duely.Application.Services.Outbox.Relay;
using Duely.Domain.Models;
using FluentResults;
using MediatR;
using Duely.Application.UseCases.Features.Outbox.Relay;
using Duely.Domain.Models;

namespace Duely.Application.UseCases.Features.Outbox;
namespace Duely.Application.Services.Outbox;

public sealed record ExecuteOutboxMessageCommand(OutboxMessage Message) : IRequest<Result>;

public sealed class ExecuteOutboxMessageHandler(IOutboxDispatcher dispatcher): IRequestHandler<ExecuteOutboxMessageCommand, Result>
public sealed class ExecuteOutboxMessageHandler(IOutboxDispatcher dispatcher)
: IRequestHandler<ExecuteOutboxMessageCommand, Result>
{
public Task<Result> Handle(ExecuteOutboxMessageCommand request, CancellationToken cancellationToken)
=> dispatcher.DispatchAsync(request.Message, cancellationToken);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
using Duely.Application.UseCases.Payloads;
using Duely.Application.UseCases.Features.Outbox.Relay;
using Duely.Application.Services.Outbox.Payloads;
using Duely.Application.Services.Outbox.Relay;
using Duely.Domain.Models;
using Duely.Infrastructure.DataAccess.EntityFramework;
using Duely.Infrastructure.Gateway.Exesh.Abstracts;
using Duely.Application.UseCases.Features.UserCodeRuns;
using FluentResults;
using Microsoft.EntityFrameworkCore;
using Duely.Infrastructure.DataAccess.EntityFramework;



namespace Duely.Application.UseCases.Features.Outbox.Handlers;
namespace Duely.Application.Services.Outbox.Handlers;

public sealed class RunUserCodeOutboxHandler (
IExeshClient client,
Context context
) : IOutboxHandler<RunUserCodePayload>
public sealed class RunUserCodeOutboxHandler(IExeshClient client, Context context)
: IOutboxHandler<RunUserCodePayload>
{
public OutboxType Type => OutboxType.RunUserCode;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using Duely.Application.UseCases.Features.Outbox.Relay;
using Duely.Application.UseCases.Payloads;
using Duely.Application.Services.Outbox.Payloads;
using Duely.Application.Services.Outbox.Relay;
using Duely.Domain.Models;
using Duely.Domain.Models.Messages;
using Duely.Infrastructure.Gateway.Client.Abstracts;
using FluentResults;

namespace Duely.Application.UseCases.Features.Outbox.Handlers;
namespace Duely.Application.Services.Outbox.Handlers;

public sealed class SendMessageOutboxHandler(IMessageSender sender)
: IOutboxHandler<SendMessagePayload>
Expand All @@ -18,6 +18,12 @@ public async Task<Result> HandleAsync(SendMessagePayload payload, CancellationTo
{
MessageType.DuelStarted => new DuelStartedMessage { DuelId = payload.DuelId },
MessageType.DuelFinished => new DuelFinishedMessage { DuelId = payload.DuelId },
MessageType.DuelChanged => new DuelChangedMessage { DuelId = payload.DuelId },
MessageType.DuelCanceled => new DuelCanceledMessage
{
OpponentNickname = payload.OpponentNickname
?? throw new ArgumentException("Opponent nickname is required.", nameof(payload))
},
_ => throw new ArgumentOutOfRangeException(nameof(payload.Type), payload.Type, null)
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using Duely.Application.Services.Outbox.Payloads;
using Duely.Application.Services.Outbox.Relay;
using Duely.Domain.Models;
using Duely.Infrastructure.Gateway.Tasks.Abstracts;
using FluentResults;
using Duely.Application.UseCases.Payloads;
using Duely.Application.UseCases.Features.Outbox.Relay;
using Duely.Domain.Models;
using Duely.Infrastructure.Gateway.Tasks.Abstracts;

namespace Duely.Application.UseCases.Features.Outbox.Handlers;
namespace Duely.Application.Services.Outbox.Handlers;

public sealed class TestSolutionHandler(ITaskiClient client): IOutboxHandler<TestSolutionPayload>
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace Duely.Application.Services.Outbox.Payloads;

public interface IOutboxPayload;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace Duely.Application.Services.Outbox.Payloads;

public sealed record RunUserCodePayload(int RunId, string Code, string Language, string Input) : IOutboxPayload;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Duely.Domain.Models.Messages;

namespace Duely.Application.Services.Outbox.Payloads;

public sealed record SendMessagePayload(
int UserId,
MessageType Type,
int DuelId,
string? OpponentNickname = null) : IOutboxPayload;
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
namespace Duely.Application.UseCases.Payloads;
namespace Duely.Application.Services.Outbox.Payloads;

public sealed record TestSolutionPayload(string TaskId, int SubmissionId, string Code, string Language) : IOutboxPayload;
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using FluentResults;
using Duely.Domain.Models;
namespace Duely.Application.UseCases.Features.Outbox.Relay;
using FluentResults;

namespace Duely.Application.Services.Outbox.Relay;

public interface IOutboxDispatcher
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
using FluentResults;
using Duely.Application.Services.Outbox.Payloads;
using Duely.Domain.Models;
using Duely.Application.UseCases.Payloads;
namespace Duely.Application.UseCases.Features.Outbox.Relay;
using FluentResults;

namespace Duely.Application.Services.Outbox.Relay;

public interface IOutboxHandler<TPayload> where TPayload : IOutboxPayload
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
using System.Text.Json;
using FluentResults;
using Duely.Application.UseCases.Payloads;
using Duely.Application.Services.Outbox.Payloads;
using Duely.Domain.Models;
namespace Duely.Application.UseCases.Features.Outbox.Relay;
using FluentResults;

namespace Duely.Application.Services.Outbox.Relay;

public sealed class OutboxDispatcher(
IOutboxHandler<TestSolutionPayload> testSolutionHandler,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
namespace Duely.Application.UseCases.Features.RateLimiting;

namespace Duely.Application.Services.RateLimiting;

public sealed class RateLimitingOptions
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;

namespace Duely.Application.UseCases.Features.RateLimiting;

namespace Duely.Application.Services.RateLimiting;

public interface IRunUserCodeLimiter
{
Task<bool> IsLimitExceededAsync(int userId, CancellationToken cancellationToken);
}


public class RunUserCodeLimiter(Context context, IOptions<RateLimitingOptions> options) : IRunUserCodeLimiter
{
public async Task<bool> IsLimitExceededAsync(int userId, CancellationToken cancellationToken)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;

namespace Duely.Application.UseCases.Features.RateLimiting;

namespace Duely.Application.Services.RateLimiting;

public interface ISubmissionRateLimiter
{
Task<bool> IsLimitExceededAsync(int userId, CancellationToken cancellationToken);
}


public class SubmissionRateLimiter(Context context, IOptions<RateLimitingOptions> options) : ISubmissionRateLimiter
{
public async Task<bool> IsLimitExceededAsync(int userId, CancellationToken cancellationToken)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Duely.Application.Services.Outbox.Handlers;
using Duely.Application.Services.Outbox.Payloads;
using Duely.Application.Services.Outbox.Relay;
using Duely.Application.Services.RateLimiting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace Duely.Application.Services;

public static class ServiceCollectionExtensions
{
public static void SetupApplicationServices(this IServiceCollection services, IConfiguration configuration)
{
services.Configure<RateLimitingOptions>(configuration.GetSection(RateLimitingOptions.SectionName));

services.AddScoped<ISubmissionRateLimiter, SubmissionRateLimiter>();
services.AddScoped<IRunUserCodeLimiter, RunUserCodeLimiter>();

services.AddScoped<IOutboxHandler<TestSolutionPayload>, TestSolutionHandler>();
services.AddScoped<IOutboxHandler<RunUserCodePayload>, RunUserCodeOutboxHandler>();
services.AddScoped<IOutboxHandler<SendMessagePayload>, SendMessageOutboxHandler>();
services.AddScoped<IOutboxDispatcher, OutboxDispatcher>();
}
}
Loading