Implement step 17 modules: threads/messages, notifications with automation (submission.created, idea.created, grant_call.opened), innovation test feedback, admin panel (submissions, verify, metrics, grant call status), Middleman adaptation and partnerships. Split multi-type files into one type per file and replace object-valued dictionaries with string-valued maps in data contracts (migrations 0006/0007).
226 lines
6.5 KiB
C#
226 lines
6.5 KiB
C#
using Asp.Versioning;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using TarninaHub.Application.Abstractions;
|
|
using TarninaHub.Application.Dtos;
|
|
using TarninaHub.Application.Mappings;
|
|
using TarninaHub.Application.Security;
|
|
using TarninaHub.Domain.Rules;
|
|
|
|
namespace TarninaHub.Controllers;
|
|
|
|
[ApiController]
|
|
[ApiVersion("1.0")]
|
|
[Route("api/submissions")]
|
|
[Authorize]
|
|
public class SubmissionsController : ControllerBase
|
|
{
|
|
private readonly ISubmissionService _submissionService;
|
|
private readonly IMatchmakingService _matchmakingService;
|
|
private readonly INotificationService _notifications;
|
|
|
|
public SubmissionsController(
|
|
ISubmissionService submissionService,
|
|
IMatchmakingService matchmakingService,
|
|
INotificationService notifications)
|
|
{
|
|
_submissionService = submissionService;
|
|
_matchmakingService = matchmakingService;
|
|
_notifications = notifications;
|
|
}
|
|
|
|
[HttpPost]
|
|
public async Task<ActionResult<SubmissionResponse>> Create(
|
|
CreateSubmissionRequest request,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var userId = User.GetUserId();
|
|
|
|
if (string.IsNullOrWhiteSpace(userId))
|
|
{
|
|
return Unauthorized();
|
|
}
|
|
|
|
var created = await _submissionService.CreateAsync(userId, request, cancellationToken);
|
|
|
|
await _matchmakingService.IndexSubmissionAsync(created.Id, cancellationToken);
|
|
|
|
await _notifications.NotifyStaffAsync(
|
|
"submission.created",
|
|
new Dictionary<string, string?>
|
|
{
|
|
["submissionId"] = created.Id,
|
|
["title"] = created.Title,
|
|
["authorId"] = created.AuthorId,
|
|
},
|
|
cancellationToken);
|
|
|
|
return CreatedAtAction(nameof(GetById), new { submissionId = created.Id }, created);
|
|
}
|
|
|
|
[HttpGet]
|
|
public async Task<ActionResult<PagedResponse<SubmissionResponse>>> List(
|
|
[FromQuery] string? status,
|
|
[FromQuery] int page = 1,
|
|
[FromQuery] int pageSize = 20,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var userId = User.GetUserId();
|
|
|
|
if (string.IsNullOrWhiteSpace(userId))
|
|
{
|
|
return Unauthorized();
|
|
}
|
|
|
|
page = page < 1 ? 1 : page;
|
|
pageSize = pageSize is < 1 or > 100 ? 20 : pageSize;
|
|
|
|
var result = await _submissionService.ListAsync(
|
|
userId,
|
|
IsStaff(),
|
|
status,
|
|
page,
|
|
pageSize,
|
|
cancellationToken);
|
|
|
|
return Ok(result);
|
|
}
|
|
|
|
[HttpGet("{submissionId}")]
|
|
public async Task<ActionResult<SubmissionResponse>> GetById(
|
|
string submissionId,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var userId = User.GetUserId();
|
|
|
|
if (string.IsNullOrWhiteSpace(userId))
|
|
{
|
|
return Unauthorized();
|
|
}
|
|
|
|
var submission = await _submissionService.GetAsync(submissionId, cancellationToken);
|
|
|
|
if (submission is null)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
if (!IsStaff() && !string.Equals(submission.AuthorId, userId, StringComparison.Ordinal))
|
|
{
|
|
return Forbid();
|
|
}
|
|
|
|
return Ok(submission);
|
|
}
|
|
|
|
[HttpPut("{submissionId}/status")]
|
|
[Authorize(Policy = Policies.OperatorOrAdmin)]
|
|
public async Task<ActionResult<SubmissionResponse>> UpdateStatus(
|
|
string submissionId,
|
|
UpdateSubmissionStatusRequest request,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var current = await _submissionService.GetAsync(submissionId, cancellationToken);
|
|
|
|
if (current is null)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
var from = SubmissionStatusWire.Parse(current.Status);
|
|
var to = SubmissionStatusWire.Parse(request.Status);
|
|
|
|
if (to is null)
|
|
{
|
|
ModelState.AddModelError(nameof(request.Status), "Unknown status value.");
|
|
|
|
return ValidationProblem(ModelState);
|
|
}
|
|
|
|
if (from is null || !SubmissionWorkflow.CanTransition(from.Value, to.Value))
|
|
{
|
|
ModelState.AddModelError(
|
|
nameof(request.Status),
|
|
$"Transition from '{current.Status}' to '{request.Status}' is not allowed.");
|
|
|
|
return ValidationProblem(ModelState);
|
|
}
|
|
|
|
var updated = await _submissionService.UpdateStatusAsync(submissionId, to.Value.ToWire(), cancellationToken);
|
|
|
|
return updated is null ? NotFound() : Ok(updated);
|
|
}
|
|
|
|
[HttpPost("{submissionId}/match")]
|
|
public async Task<ActionResult<MatchResponse>> Match(
|
|
string submissionId,
|
|
[FromBody] MatchRequest? request,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var access = await AuthorizeSubmissionAccessAsync(submissionId, cancellationToken);
|
|
|
|
if (access is not null)
|
|
{
|
|
return access;
|
|
}
|
|
|
|
var result = await _matchmakingService.MatchAsync(
|
|
submissionId,
|
|
request ?? new MatchRequest(null, null),
|
|
cancellationToken);
|
|
|
|
return result is null ? NotFound() : Ok(result);
|
|
}
|
|
|
|
[HttpGet("{submissionId}/matches")]
|
|
public async Task<ActionResult<MatchResponse>> Matches(
|
|
string submissionId,
|
|
[FromQuery] string? challengeId,
|
|
[FromQuery] int? maxResults,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var access = await AuthorizeSubmissionAccessAsync(submissionId, cancellationToken);
|
|
|
|
if (access is not null)
|
|
{
|
|
return access;
|
|
}
|
|
|
|
var result = await _matchmakingService.MatchAsync(
|
|
submissionId,
|
|
new MatchRequest(maxResults, challengeId),
|
|
cancellationToken);
|
|
|
|
return result is null ? NotFound() : Ok(result);
|
|
}
|
|
|
|
private async Task<ActionResult?> AuthorizeSubmissionAccessAsync(
|
|
string submissionId,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var userId = User.GetUserId();
|
|
|
|
if (string.IsNullOrWhiteSpace(userId))
|
|
{
|
|
return Unauthorized();
|
|
}
|
|
|
|
var submission = await _submissionService.GetAsync(submissionId, cancellationToken);
|
|
|
|
if (submission is null)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
if (!IsStaff() && !string.Equals(submission.AuthorId, userId, StringComparison.Ordinal))
|
|
{
|
|
return Forbid();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private bool IsStaff() =>
|
|
User.IsInRole(RoleNames.Operator) || User.IsInRole(RoleNames.Manager) || User.IsInRole(RoleNames.Admin);
|
|
}
|