feat(ideas): add idea creator, grant calls and applications

Implement the idea creator and application generator: idea status workflow, social canvas card, grant call windows with 409 when closed, and an LLM-backed idea assistant with offline fallback.
This commit is contained in:
Rafał Tyński
2026-10-04 03:13:45 +02:00
parent fd408b102b
commit 127ca92f63
15 changed files with 1089 additions and 0 deletions
@@ -0,0 +1,59 @@
using TarninaHub.Application.Dtos;
namespace TarninaHub.Application.Abstractions;
public interface IIdeaService
{
Task<IdeaResponse> CreateAsync(string authorId, CreateIdeaRequest request, CancellationToken cancellationToken);
Task<IdeaResponse?> GetAsync(string ideaId, CancellationToken cancellationToken);
Task<IReadOnlyList<IdeaResponse>> ListForAuthorAsync(string authorId, CancellationToken cancellationToken);
Task<IdeaResponse?> UpdateAsync(string ideaId, UpdateIdeaRequest request, CancellationToken cancellationToken);
Task<IdeaCardResponse?> GetCardAsync(string ideaId, CancellationToken cancellationToken);
Task<IdeaCardResponse?> UpdateCardAsync(string ideaId, IdeaCardRequest request, CancellationToken cancellationToken);
}
public interface IIdeaAssistant
{
Task<IdeaAssistResponse?> AssistAsync(
string ideaId,
IdeaAssistRequest request,
CancellationToken cancellationToken);
}
public interface IGrantCallService
{
Task<IReadOnlyList<GrantCallResponse>> ListAsync(string? status, CancellationToken cancellationToken);
Task<GrantCallResponse?> GetAsync(string grantCallId, CancellationToken cancellationToken);
}
public enum CreateApplicationOutcome
{
Created,
CallNotFound,
CallNotOpen,
IdeaNotFound,
}
public sealed record CreateApplicationResult(
CreateApplicationOutcome Outcome,
ApplicationResponse? Application);
public interface IApplicationService
{
Task<CreateApplicationResult> CreateAsync(
string userId,
CreateApplicationRequest request,
CancellationToken cancellationToken);
Task<ApplicationResponse?> GetAsync(string applicationId, CancellationToken cancellationToken);
Task<IReadOnlyList<ApplicationResponse>> ListForAuthorAsync(
string authorId,
CancellationToken cancellationToken);
}
@@ -0,0 +1,28 @@
using System.ComponentModel.DataAnnotations;
namespace TarninaHub.Application.Dtos;
public sealed record GrantCallResponse(
string Id,
string Name,
DateTimeOffset OpensAt,
DateTimeOffset ClosesAt,
string Status,
string? ExternalId);
public sealed record CreateApplicationRequest(
[Required] string GrantCallId,
[Required] string IdeaId,
IReadOnlyDictionary<string, object?>? Content);
public sealed record UpdateApplicationRequest(
IReadOnlyDictionary<string, object?>? Content,
string? Status);
public sealed record ApplicationResponse(
string Id,
string GrantCallId,
string IdeaId,
IReadOnlyDictionary<string, object?> Content,
string Status,
DateTimeOffset CreatedAt);
+41
View File
@@ -0,0 +1,41 @@
using System.ComponentModel.DataAnnotations;
namespace TarninaHub.Application.Dtos;
public sealed record IdeaCardRequest(
string? Essence,
string? Audience,
string? Stage);
public sealed record CreateIdeaRequest(
[Required, StringLength(200, MinimumLength = 3)] string Title,
[Required, StringLength(4000, MinimumLength = 10)] string Description,
IdeaCardRequest? Card);
public sealed record UpdateIdeaRequest(
[Required, StringLength(200, MinimumLength = 3)] string Title,
[Required, StringLength(4000, MinimumLength = 10)] string Description,
string? Status);
public sealed record IdeaCardResponse(
string Id,
string IdeaId,
string Essence,
string Audience,
string Stage);
public sealed record IdeaResponse(
string Id,
string Title,
string Description,
string AuthorId,
string Status,
IdeaCardResponse? Card,
DateTimeOffset? UpdatedAt);
public sealed record IdeaAssistRequest(string? Focus);
public sealed record IdeaAssistResponse(
IReadOnlyList<string> Suggestions,
string ImprovedDescription,
bool FromLlm);
@@ -0,0 +1,26 @@
using TarninaHub.Domain.Enums;
namespace TarninaHub.Application.Mappings;
public static class IdeaStatusWire
{
public static string ToWire(this IdeaStatus status) => status switch
{
IdeaStatus.Draft => "draft",
IdeaStatus.Submitted => "submitted",
IdeaStatus.InReview => "in_review",
IdeaStatus.Accepted => "accepted",
IdeaStatus.Rejected => "rejected",
_ => throw new ArgumentOutOfRangeException(nameof(status), status, null),
};
public static IdeaStatus? Parse(string? value) => value switch
{
"draft" => IdeaStatus.Draft,
"submitted" => IdeaStatus.Submitted,
"in_review" => IdeaStatus.InReview,
"accepted" => IdeaStatus.Accepted,
"rejected" => IdeaStatus.Rejected,
_ => null,
};
}