using SurrealDb.Net; using TarninaHub.Application.Abstractions; using TarninaHub.Application.Dtos; using TarninaHub.Domain.Rules; namespace TarninaHub.Infrastructure.Persistence; public sealed class ApplicationService : IApplicationService { private const string SelectFields = "type::string(id) AS id, type::string(grant_call) AS grant_call, type::string(idea) AS idea, " + "content, status, created_at"; private readonly ISurrealDbClient _client; private readonly IGrantCallService _grantCallService; private readonly IIdeaService _ideaService; public ApplicationService( ISurrealDbClient client, IGrantCallService grantCallService, IIdeaService ideaService) { _client = client; _grantCallService = grantCallService; _ideaService = ideaService; } public async Task CreateAsync( string userId, CreateApplicationRequest request, CancellationToken cancellationToken) { var grantCall = await _grantCallService.GetAsync(request.GrantCallId, cancellationToken); if (grantCall is null) { return new CreateApplicationResult(CreateApplicationOutcome.CallNotFound, null); } if (!ApplicationWindow.IsOpen(grantCall.Status, grantCall.OpensAt, grantCall.ClosesAt, DateTimeOffset.UtcNow)) { return new CreateApplicationResult(CreateApplicationOutcome.CallNotOpen, null); } var idea = await _ideaService.GetAsync(request.IdeaId, cancellationToken); if (idea is null) { return new CreateApplicationResult(CreateApplicationOutcome.IdeaNotFound, null); } var id = Guid.NewGuid().ToString("N"); var (grantTable, grantId) = SurrealRecordId.Split(request.GrantCallId); var (ideaTable, ideaIdPart) = SurrealRecordId.Split(request.IdeaId); var response = await _client.RawQuery( "CREATE type::record('application', $id) SET " + "grant_call = type::record($grantTable, $grantId), " + "idea = type::record($ideaTable, $ideaId), " + "content = $content, status = 'draft'; " + $"SELECT {SelectFields} FROM type::record('application', $id) LIMIT 1;", new Dictionary { ["id"] = id, ["grantTable"] = grantTable, ["grantId"] = grantId, ["ideaTable"] = ideaTable, ["ideaId"] = ideaIdPart, ["content"] = request.Content ?? new Dictionary(), }, cancellationToken); response.EnsureAllOks(); var rows = response.GetValue>(1) ?? []; return new CreateApplicationResult(CreateApplicationOutcome.Created, Map(rows[0])); } public async Task GetAsync(string applicationId, CancellationToken cancellationToken) { var (table, id) = SurrealRecordId.Split(applicationId); var response = await _client.RawQuery( $"SELECT {SelectFields} FROM type::record($table, $id) LIMIT 1;", new Dictionary { ["table"] = table, ["id"] = id }, cancellationToken); response.EnsureAllOks(); var rows = response.GetValue>(0) ?? []; return rows.Count > 0 ? Map(rows[0]) : null; } public async Task> ListForAuthorAsync( string authorId, CancellationToken cancellationToken) { var (table, id) = SurrealRecordId.Split(authorId); var response = await _client.RawQuery( $"SELECT {SelectFields} FROM application WHERE idea.author = type::record($table, $id) " + "ORDER BY created_at DESC;", new Dictionary { ["table"] = table, ["id"] = id }, cancellationToken); response.EnsureAllOks(); var rows = response.GetValue>(0) ?? []; return rows.Select(Map).ToList(); } private static ApplicationResponse Map(ApplicationRow row) => new( row.Id, row.GrantCall, row.Idea, row.Content, row.Status, row.CreatedAt); }