1
0
2025-05-21 11:20:11 +08:00

240 lines
9.1 KiB
C#

using Flawless.Communication.Request;
using Flawless.Communication.Response;
using Flawless.Communication.Shared;
using Flawless.Server.Models;
using Flawless.Server.Services;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace Flawless.Server.Controllers;
[ApiController, Microsoft.AspNetCore.Authorization.Authorize, Route("api/issue/{userName}/{repositoryName}")]
public class IssueController(
UserManager<AppUser> userManager,
AppDbContext dbContext) : ControllerBase
{
[HttpPost("create")]
public async Task<ActionResult<IssueInfo>> CreateIssueAsync(
string userName,
string repositoryName,
[FromBody] CreateIssueRequest request)
{
var user = (await userManager.GetUserAsync(HttpContext.User))!;
var grantIssue = await ValidateRepositoryAsync(userName, repositoryName, user, RepositoryRole.Reporter);
if (grantIssue is not Repository repository) return (ActionResult)grantIssue;
var issue = new RepositoryIssue
{
Repository = repository,
Author = user,
Title = request.Title,
CreatedAt = DateTime.UtcNow,
Descripion = request.Description,
Closed = false,
Tag = request.Tag
};
dbContext.RepositoryIssues.Add(issue);
await dbContext.SaveChangesAsync();
return Ok(new IssueInfo(
issue.Id,
issue.Author.UserName!,
issue.Title,
issue.CreatedAt,
issue.CreatedAt,
issue.Closed,
issue.Tag));
}
[HttpPost("{issueId}/comment")]
public async Task<IActionResult> AddCommentAsync(
string userName,
string repositoryName,
int issueId,
[FromBody] AddCommentRequest request)
{
var user = (await userManager.GetUserAsync(HttpContext.User))!;
var grantIssue = await ValidateRepositoryAsync(userName, repositoryName, user, RepositoryRole.Reporter);
if (grantIssue is not Repository _) return (ActionResult) grantIssue;
var issue = await dbContext.RepositoryIssues
.Include(i => i.Repository)
.Include(repositoryIssue => repositoryIssue.Contents)
.FirstOrDefaultAsync(i => i.Id == issueId);
if (issue == null) return NotFound(new FailedResponse("Issue not found"));
if (issue.Closed) return BadRequest(new FailedResponse("Issue is closed"));
var comment = new RepositoryIssueContent
{
Issue = issue,
Author = user,
Content = request.Content,
CreatedAt = DateTime.UtcNow,
ReplyTo = request.ReplyTo.HasValue
? issue.Contents.Find(v => v.Id == request.ReplyTo.Value) : null
};
issue.Contents.Add(comment);
await dbContext.SaveChangesAsync();
return Ok();
}
[HttpPost("{issueId}/close")]
public async Task<IActionResult> CloseIssueAsync(
string userName,
string repositoryName,
ulong issueId)
{
var user = (await userManager.GetUserAsync(HttpContext.User))!;
var grantIssue = await ValidateRepositoryAsync(userName, repositoryName, user, RepositoryRole.Developer);
if (grantIssue is not Repository _) return (ActionResult) grantIssue;
var issue = await dbContext.RepositoryIssues.FindAsync(issueId);
if (issue == null) return NotFound(new FailedResponse("Issue not found"));
issue.Closed = true;
await dbContext.SaveChangesAsync();
return Ok();
}
[HttpGet("list")]
public async Task<ActionResult<ListingResponse<IssueInfo>>> GetIssuesAsync(
string userName,
string repositoryName)
{
var user = (await userManager.GetUserAsync(HttpContext.User))!;
var grantIssue = await ValidateRepositoryAsync(userName, repositoryName, user, RepositoryRole.Reporter);
if (grantIssue is not Repository repository) return (ActionResult)grantIssue;
var issues = await dbContext.RepositoryIssues
.Where(i => i.Repository == repository)
.Include(i => i.Author)
.ToArrayAsync();
return Ok(new ListingResponse<RepositoryIssue>(issues));
}
[HttpGet("{issueId}")]
public async Task<ActionResult<IssueDetailInfo>> GetIssueDetailsAsync(
string userName,
string repositoryName,
int issueId)
{
var user = (await userManager.GetUserAsync(HttpContext.User))!;
var grantIssue = await ValidateRepositoryAsync(userName, repositoryName, user, RepositoryRole.Reporter);
if (grantIssue is not Repository repo) return (ActionResult) grantIssue;
var issue = await dbContext.RepositoryIssues
.Include(i => i.Author)
.Include(i => i.Repository)
.Include(i => i.Contents)
.ThenInclude(c => c.Author)
.FirstOrDefaultAsync(x => x.Id == issueId && x.Repository == repo);
return issue == null
? NotFound(new FailedResponse("Issue not found"))
: Ok(new IssueDetailInfo(
issue.Id,
issue.Author.UserName!,
issue.Title,
issue.Descripion ?? String.Empty,
issue.CreatedAt,
issue.Contents.MaxBy(x => x.CreatedAt)?.CreatedAt ?? issue.CreatedAt,
issue.Closed,
issue.Tag));
}
[HttpPatch("{issueId}/edit")]
public async Task<IActionResult> UpdateIssueAsync(
string userName,
string repositoryName,
int issueId,
[FromBody] UpdateIssueRequest request)
{
var user = (await userManager.GetUserAsync(HttpContext.User))!;
var grantIssue = await ValidateRepositoryAsync(userName, repositoryName, user, RepositoryRole.Reporter);
if (grantIssue is not Repository repo) return (ActionResult) grantIssue;
var issue = await dbContext.RepositoryIssues
.Include(x => x.Author)
.FirstOrDefaultAsync(x => x.Id == issueId && x.Repository == repo);
if (issue == null) return NotFound(new FailedResponse("Issue not found"));
if (issue.Author != user && repo.Owner != user)
return BadRequest(new FailedResponse("You are not allowed to do this operation"));
if (!string.IsNullOrEmpty(request.Title)) issue.Title = request.Title;
if (!string.IsNullOrEmpty(request.Description)) issue.Descripion = request.Description;
if (request.Tag != null) issue.Tag = request.Tag;
await dbContext.SaveChangesAsync();
return Ok();
}
[HttpPost("{issueId}/reopen")]
public async Task<IActionResult> ReopenIssueAsync(
string userName,
string repositoryName,
ulong issueId)
{
var user = (await userManager.GetUserAsync(HttpContext.User))!;
var grantIssue = await ValidateRepositoryAsync(userName, repositoryName, user, RepositoryRole.Developer);
if (grantIssue is not Repository _) return (ActionResult) grantIssue;
var issue = await dbContext.RepositoryIssues.FindAsync(issueId);
if (issue == null) return NotFound(new FailedResponse("Issue not found"));
issue.Closed = false;
await dbContext.SaveChangesAsync();
return Ok();
}
[HttpGet("{issueId}/comments")]
public async Task<ActionResult<ListingResponse<CommentResponse>>> GetIssueCommentsAsync(
string userName,
string repositoryName,
int issueId)
{
var user = (await userManager.GetUserAsync(HttpContext.User))!;
var grantIssue = await ValidateRepositoryAsync(userName, repositoryName, user, RepositoryRole.Reporter);
if (grantIssue is not Repository repo) return (ActionResult) grantIssue;
var comments = await dbContext.RepositoryIssues
.Where(x => x.Id == issueId && x.Repository == repo)
.SelectMany(x => x.Contents)
.Include(c => c.Author)
.OrderBy(c => c.CreatedAt)
.Select(c => new CommentResponse(
c.Id,
c.Author.UserName!,
c.Content,
c.CreatedAt,
c.ReplyTo != null ? c.ReplyTo.Id : null))
.ToArrayAsync();
return Ok(new ListingResponse<CommentResponse>(comments));
}
private async ValueTask<object> ValidateRepositoryAsync(
string userName, string repositoryName, AppUser user, RepositoryRole role)
{
// 复用RepositoryInnieController中的验证逻辑
var rp = await dbContext.Repositories
.Include(r => r.Members)
.ThenInclude(m => m.User)
.Include(r => r.Owner)
.FirstOrDefaultAsync(r => r.Owner.UserName == userName && r.Name == repositoryName);
if (rp == null) return NotFound(new FailedResponse($"Could not find repository {userName}:{repositoryName}"));
if (rp.Owner != user && !rp.Members.Any(m => m.User == user && m.Role >= role))
return Unauthorized(new FailedResponse("You are not allowed to do this operation"));
return rp;
}
}