♻️ Refined custom apps

This commit is contained in:
2025-06-29 20:32:08 +08:00
parent cdeed3c318
commit c4ea15097e
10 changed files with 4448 additions and 141 deletions

View File

@ -1,63 +1,129 @@
using System.ComponentModel.DataAnnotations;
using DysonNetwork.Sphere.Account;
using DysonNetwork.Sphere.Publisher;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace DysonNetwork.Sphere.Developer;
[ApiController]
[Route("/developers/apps")]
public class CustomAppController(CustomAppService customAppService, PublisherService ps) : ControllerBase
[Route("/developers/{pubName}/apps")]
public class CustomAppController(CustomAppService customApps, PublisherService ps) : ControllerBase
{
public record CreateAppRequest(Guid PublisherId, string Name, string Slug);
public record UpdateAppRequest(string Name, string Slug);
public record CustomAppRequest(
[MaxLength(1024)] string? Slug,
[MaxLength(1024)] string? Name,
[MaxLength(4096)] string? Description,
string? PictureId,
string? BackgroundId,
CustomAppStatus? Status,
CustomAppLinks? Links,
CustomAppOauthConfig? OauthConfig
);
[HttpGet]
public async Task<IActionResult> ListApps([FromQuery] Guid publisherId)
public async Task<IActionResult> ListApps([FromRoute] string pubName)
{
var apps = await customAppService.GetAppsByPublisherAsync(publisherId);
var publisher = await ps.GetPublisherByName(pubName);
if (publisher is null) return NotFound();
var apps = await customApps.GetAppsByPublisherAsync(publisher.Id);
return Ok(apps);
}
[HttpGet("{id:guid}")]
public async Task<IActionResult> GetApp(Guid id)
public async Task<IActionResult> GetApp([FromRoute] string pubName, Guid id)
{
var app = await customAppService.GetAppAsync(id);
var publisher = await ps.GetPublisherByName(pubName);
if (publisher is null) return NotFound();
var app = await customApps.GetAppAsync(id, publisherId: publisher.Id);
if (app == null)
{
return NotFound();
}
return Ok(app);
}
[HttpPost]
public async Task<IActionResult> CreateApp([FromBody] CreateAppRequest request)
[Authorize]
public async Task<IActionResult> CreateApp([FromRoute] string pubName, [FromBody] CustomAppRequest request)
{
var app = await customAppService.CreateAppAsync(request.PublisherId, request.Name, request.Slug);
if (app == null)
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
if (string.IsNullOrWhiteSpace(request.Name) || string.IsNullOrWhiteSpace(request.Slug))
return BadRequest("Name and slug are required");
var publisher = await ps.GetPublisherByName(pubName);
if (publisher is null) return NotFound();
if (!await ps.IsMemberWithRole(publisher.Id, currentUser.Id, PublisherMemberRole.Editor))
return StatusCode(403, "You must be an editor of the publisher to create a custom app");
if (!await ps.HasFeature(publisher.Id, PublisherFeatureFlag.Develop))
return StatusCode(403, "Publisher must be a developer to create a custom app");
try
{
return BadRequest("Invalid publisher ID or missing developer feature flag");
var app = await customApps.CreateAppAsync(publisher, request);
return Ok(app);
}
catch (InvalidOperationException ex)
{
return BadRequest(ex.Message);
}
return CreatedAtAction(nameof(GetApp), new { id = app.Id }, app);
}
[HttpPatch("{id:guid}")]
public async Task<IActionResult> UpdateApp(Guid id, [FromBody] UpdateAppRequest request)
[Authorize]
public async Task<IActionResult> UpdateApp(
[FromRoute] string pubName,
[FromRoute] Guid id,
[FromBody] CustomAppRequest request
)
{
var app = await customAppService.UpdateAppAsync(id, request.Name, request.Slug);
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
var publisher = await ps.GetPublisherByName(pubName);
if (publisher is null) return NotFound();
if (!await ps.IsMemberWithRole(publisher.Id, currentUser.Id, PublisherMemberRole.Editor))
return StatusCode(403, "You must be an editor of the publisher to update a custom app");
var app = await customApps.GetAppAsync(id, publisherId: publisher.Id);
if (app == null)
{
return NotFound();
try
{
app = await customApps.UpdateAppAsync(app, request);
return Ok(app);
}
catch (InvalidOperationException ex)
{
return BadRequest(ex.Message);
}
return Ok(app);
}
[HttpDelete("{id:guid}")]
public async Task<IActionResult> DeleteApp(Guid id)
[Authorize]
public async Task<IActionResult> DeleteApp(
[FromRoute] string pubName,
[FromRoute] Guid id
)
{
var result = await customAppService.DeleteAppAsync(id);
if (!result)
{
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
var publisher = await ps.GetPublisherByName(pubName);
if (publisher is null) return NotFound();
if (!await ps.IsMemberWithRole(publisher.Id, currentUser.Id, PublisherMemberRole.Editor))
return StatusCode(403, "You must be an editor of the publisher to delete a custom app");
var app = await customApps.GetAppAsync(id, publisherId: publisher.Id);
if (app == null)
return NotFound();
var result = await customApps.DeleteAppAsync(id);
if (!result)
return NotFound();
}
return NoContent();
}
}
}