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 userManager, AppDbContext dbContext) : ControllerBase { [HttpPost("create")] public async Task> 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 AddCommentAsync( string userName, string repositoryName, ulong 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 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>> 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(issues)); } [HttpGet("{issueId}")] public async Task> GetIssueDetailsAsync( string userName, string repositoryName, ulong 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 UpdateIssueAsync( string userName, string repositoryName, ulong 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 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>> GetIssueCommentsAsync( string userName, string repositoryName, ulong 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(comments)); } private async ValueTask 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; } }