✨ Developer projects
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
using DysonNetwork.Develop.Project;
|
||||
using DysonNetwork.Shared.Data;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using Google.Protobuf;
|
||||
@@ -37,8 +38,11 @@ public class CustomApp : ModelBase, IIdentifiedResource
|
||||
|
||||
[JsonIgnore] public ICollection<CustomAppSecret> Secrets { get; set; } = new List<CustomAppSecret>();
|
||||
|
||||
public Guid DeveloperId { get; set; }
|
||||
public Developer Developer { get; set; } = null!;
|
||||
public Guid ProjectId { get; set; }
|
||||
public DevProject Project { get; set; } = null!;
|
||||
|
||||
[NotMapped]
|
||||
public Developer Developer => Project.Developer;
|
||||
|
||||
[NotMapped] public string ResourceIdentifier => "custom-app:" + Id;
|
||||
|
||||
@@ -72,7 +76,7 @@ public class CustomApp : ModelBase, IIdentifiedResource
|
||||
RequirePkce = OauthConfig.RequirePkce,
|
||||
AllowOfflineAccess = OauthConfig.AllowOfflineAccess
|
||||
},
|
||||
DeveloperId = DeveloperId.ToString(),
|
||||
ProjectId = ProjectId.ToString(),
|
||||
CreatedAt = CreatedAt.ToTimestamp(),
|
||||
UpdatedAt = UpdatedAt.ToTimestamp()
|
||||
};
|
||||
@@ -92,7 +96,7 @@ public class CustomApp : ModelBase, IIdentifiedResource
|
||||
Shared.Proto.CustomAppStatus.Suspended => CustomAppStatus.Suspended,
|
||||
_ => CustomAppStatus.Developing
|
||||
};
|
||||
DeveloperId = string.IsNullOrEmpty(p.DeveloperId) ? Guid.Empty : Guid.Parse(p.DeveloperId);
|
||||
ProjectId = string.IsNullOrEmpty(p.ProjectId) ? Guid.Empty : Guid.Parse(p.ProjectId);
|
||||
CreatedAt = p.CreatedAt.ToInstant();
|
||||
UpdatedAt = p.UpdatedAt.ToInstant();
|
||||
if (p.Picture.Length > 0) Picture = System.Text.Json.JsonSerializer.Deserialize<CloudFileReferenceObject>(p.Picture.ToStringUtf8());
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using DysonNetwork.Develop.Project;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -6,8 +7,8 @@ using Microsoft.AspNetCore.Mvc;
|
||||
namespace DysonNetwork.Develop.Identity;
|
||||
|
||||
[ApiController]
|
||||
[Route("/api/developers/{pubName}/apps")]
|
||||
public class CustomAppController(CustomAppService customApps, DeveloperService ds) : ControllerBase
|
||||
[Route("/api/developers/{pubName}/projects/{projectId:guid}/apps")]
|
||||
public class CustomAppController(CustomAppService customApps, DeveloperService ds, DevProjectService projectService) : ControllerBase
|
||||
{
|
||||
public record CustomAppRequest(
|
||||
[MaxLength(1024)] string? Slug,
|
||||
@@ -21,21 +22,28 @@ public class CustomAppController(CustomAppService customApps, DeveloperService d
|
||||
);
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> ListApps([FromRoute] string pubName)
|
||||
public async Task<IActionResult> ListApps([FromRoute] string pubName, [FromRoute] Guid projectId)
|
||||
{
|
||||
var developer = await ds.GetDeveloperByName(pubName);
|
||||
if (developer is null) return NotFound();
|
||||
var apps = await customApps.GetAppsByPublisherAsync(developer.Id);
|
||||
|
||||
var project = await projectService.GetProjectAsync(projectId, developer.Id);
|
||||
if (project is null) return NotFound();
|
||||
|
||||
var apps = await customApps.GetAppsByProjectAsync(projectId);
|
||||
return Ok(apps);
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
public async Task<IActionResult> GetApp([FromRoute] string pubName, Guid id)
|
||||
[HttpGet("{appId:guid}")]
|
||||
public async Task<IActionResult> GetApp([FromRoute] string pubName, [FromRoute] Guid projectId, [FromRoute] Guid appId)
|
||||
{
|
||||
var developer = await ds.GetDeveloperByName(pubName);
|
||||
if (developer is null) return NotFound();
|
||||
|
||||
var project = await projectService.GetProjectAsync(projectId, developer.Id);
|
||||
if (project is null) return NotFound();
|
||||
|
||||
var app = await customApps.GetAppAsync(id, developerId: developer.Id);
|
||||
var app = await customApps.GetAppAsync(appId, projectId);
|
||||
if (app == null)
|
||||
return NotFound();
|
||||
|
||||
@@ -44,23 +52,40 @@ public class CustomAppController(CustomAppService customApps, DeveloperService d
|
||||
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> CreateApp([FromRoute] string pubName, [FromBody] CustomAppRequest request)
|
||||
public async Task<IActionResult> CreateApp(
|
||||
[FromRoute] string pubName,
|
||||
[FromRoute] Guid projectId,
|
||||
[FromBody] CustomAppRequest request)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
||||
return Unauthorized();
|
||||
|
||||
var developer = await ds.GetDeveloperByName(pubName);
|
||||
var accountId = Guid.Parse(currentUser.Id);
|
||||
if (developer is null || developer.Id != accountId)
|
||||
return Forbid();
|
||||
|
||||
var project = await projectService.GetProjectAsync(projectId, developer.Id);
|
||||
if (project is null)
|
||||
return NotFound("Project not found or you don't have access");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.Name) || string.IsNullOrWhiteSpace(request.Slug))
|
||||
return BadRequest("Name and slug are required");
|
||||
|
||||
var developer = await ds.GetDeveloperByName(pubName);
|
||||
if (developer is null) return NotFound();
|
||||
|
||||
if (!await ds.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id), PublisherMemberRole.Editor))
|
||||
return StatusCode(403, "You must be an editor of the developer to create a custom app");
|
||||
|
||||
try
|
||||
{
|
||||
var app = await customApps.CreateAppAsync(developer, request);
|
||||
return Ok(app);
|
||||
var app = await customApps.CreateAppAsync(projectId, request);
|
||||
if (app == null)
|
||||
return BadRequest("Failed to create app");
|
||||
|
||||
return CreatedAtAction(
|
||||
nameof(GetApp),
|
||||
new { pubName, projectId, appId = app.Id },
|
||||
app
|
||||
);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
@@ -68,23 +93,30 @@ public class CustomAppController(CustomAppService customApps, DeveloperService d
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPatch("{id:guid}")]
|
||||
[HttpPatch("{appId:guid}")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> UpdateApp(
|
||||
[FromRoute] string pubName,
|
||||
[FromRoute] Guid id,
|
||||
[FromRoute] Guid projectId,
|
||||
[FromRoute] Guid appId,
|
||||
[FromBody] CustomAppRequest request
|
||||
)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
||||
return Unauthorized();
|
||||
|
||||
var developer = await ds.GetDeveloperByName(pubName);
|
||||
if (developer is null) return NotFound();
|
||||
|
||||
if (developer is null)
|
||||
return NotFound("Developer not found");
|
||||
|
||||
if (!await ds.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id), PublisherMemberRole.Editor))
|
||||
return StatusCode(403, "You must be an editor of the developer to update a custom app");
|
||||
|
||||
var project = await projectService.GetProjectAsync(projectId, developer.Id);
|
||||
if (project is null)
|
||||
return NotFound("Project not found or you don't have access");
|
||||
|
||||
var app = await customApps.GetAppAsync(id, developerId: developer.Id);
|
||||
var app = await customApps.GetAppAsync(appId, projectId);
|
||||
if (app == null)
|
||||
return NotFound();
|
||||
|
||||
@@ -99,28 +131,36 @@ public class CustomAppController(CustomAppService customApps, DeveloperService d
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
[HttpDelete("{appId:guid}")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> DeleteApp(
|
||||
[FromRoute] string pubName,
|
||||
[FromRoute] Guid id
|
||||
[FromRoute] Guid projectId,
|
||||
[FromRoute] Guid appId
|
||||
)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
||||
return Unauthorized();
|
||||
|
||||
var developer = await ds.GetDeveloperByName(pubName);
|
||||
if (developer is null) return NotFound();
|
||||
|
||||
if (developer is null)
|
||||
return NotFound("Developer not found");
|
||||
|
||||
if (!await ds.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id), PublisherMemberRole.Editor))
|
||||
return StatusCode(403, "You must be an editor of the developer to delete a custom app");
|
||||
|
||||
var project = await projectService.GetProjectAsync(projectId, developer.Id);
|
||||
if (project is null)
|
||||
return NotFound("Project not found or you don't have access");
|
||||
|
||||
var app = await customApps.GetAppAsync(id, developerId: developer.Id);
|
||||
var app = await customApps.GetAppAsync(appId, projectId);
|
||||
if (app == null)
|
||||
return NotFound();
|
||||
|
||||
var result = await customApps.DeleteAppAsync(id);
|
||||
var result = await customApps.DeleteAppAsync(appId);
|
||||
if (!result)
|
||||
return NotFound();
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
}
|
@@ -1,3 +1,4 @@
|
||||
using DysonNetwork.Develop.Project;
|
||||
using DysonNetwork.Shared.Data;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -11,10 +12,17 @@ public class CustomAppService(
|
||||
)
|
||||
{
|
||||
public async Task<CustomApp?> CreateAppAsync(
|
||||
Developer pub,
|
||||
Guid projectId,
|
||||
CustomAppController.CustomAppRequest request
|
||||
)
|
||||
{
|
||||
var project = await db.DevProjects
|
||||
.Include(p => p.Developer)
|
||||
.FirstOrDefaultAsync(p => p.Id == projectId);
|
||||
|
||||
if (project == null)
|
||||
return null;
|
||||
|
||||
var app = new CustomApp
|
||||
{
|
||||
Slug = request.Slug!,
|
||||
@@ -23,7 +31,7 @@ public class CustomAppService(
|
||||
Status = request.Status ?? CustomAppStatus.Developing,
|
||||
Links = request.Links,
|
||||
OauthConfig = request.OauthConfig,
|
||||
DeveloperId = pub.Id
|
||||
ProjectId = projectId
|
||||
};
|
||||
|
||||
if (request.PictureId is not null)
|
||||
@@ -74,17 +82,23 @@ public class CustomAppService(
|
||||
return app;
|
||||
}
|
||||
|
||||
public async Task<CustomApp?> GetAppAsync(Guid id, Guid? developerId = null)
|
||||
public async Task<CustomApp?> GetAppAsync(Guid id, Guid? projectId = null)
|
||||
{
|
||||
var query = db.CustomApps.Where(a => a.Id == id).AsQueryable();
|
||||
if (developerId.HasValue)
|
||||
query = query.Where(a => a.DeveloperId == developerId.Value);
|
||||
return await query.FirstOrDefaultAsync();
|
||||
var query = db.CustomApps.AsQueryable();
|
||||
|
||||
if (projectId.HasValue)
|
||||
{
|
||||
query = query.Where(a => a.ProjectId == projectId.Value);
|
||||
}
|
||||
|
||||
return await query.FirstOrDefaultAsync(a => a.Id == id);
|
||||
}
|
||||
|
||||
public async Task<List<CustomApp>> GetAppsByPublisherAsync(Guid publisherId)
|
||||
public async Task<List<CustomApp>> GetAppsByProjectAsync(Guid projectId)
|
||||
{
|
||||
return await db.CustomApps.Where(a => a.DeveloperId == publisherId).ToListAsync();
|
||||
return await db.CustomApps
|
||||
.Where(a => a.ProjectId == projectId)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<CustomApp?> UpdateAppAsync(CustomApp app, CustomAppController.CustomAppRequest request)
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using DysonNetwork.Develop.Project;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using DysonNetwork.Shared.Data;
|
||||
using VerificationMark = DysonNetwork.Shared.Data.VerificationMark;
|
||||
@@ -10,6 +11,8 @@ public class Developer
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid PublisherId { get; set; }
|
||||
|
||||
public List<DevProject> Projects { get; set; } = [];
|
||||
|
||||
[NotMapped] public PublisherInfo? Publisher { get; set; }
|
||||
}
|
||||
|
||||
|
@@ -33,7 +33,8 @@ public class DeveloperController(
|
||||
|
||||
// Get custom apps count
|
||||
var customAppsCount = await db.CustomApps
|
||||
.Where(a => a.DeveloperId == developer.Id)
|
||||
.Include(a => a.Project)
|
||||
.Where(a => a.Project.DeveloperId == developer.Id)
|
||||
.CountAsync();
|
||||
|
||||
var stats = new DeveloperStats
|
||||
|
Reference in New Issue
Block a user