Developer projects

This commit is contained in:
2025-08-18 20:49:09 +08:00
parent 29550401fd
commit 665595b8b4
14 changed files with 752 additions and 52 deletions

View File

@@ -0,0 +1,16 @@
using System.ComponentModel.DataAnnotations;
using DysonNetwork.Develop.Identity;
using DysonNetwork.Shared.Data;
namespace DysonNetwork.Develop.Project;
public class DevProject : ModelBase
{
public Guid Id { get; set; } = Guid.NewGuid();
[MaxLength(1024)] public string Slug { get; set; } = string.Empty;
[MaxLength(1024)] public string Name { get; set; } = string.Empty;
[MaxLength(4096)] public string Description { get; set; } = string.Empty;
public Developer Developer { get; set; } = null!;
public Guid DeveloperId { get; set; }
}

View File

@@ -0,0 +1,107 @@
using System.ComponentModel.DataAnnotations;
using DysonNetwork.Develop.Identity;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using DysonNetwork.Shared.Proto;
namespace DysonNetwork.Develop.Project;
[ApiController]
[Route("/api/developers/{pubName}/projects")]
public class DevProjectController(DevProjectService projectService, DeveloperService developerService) : ControllerBase
{
public record DevProjectRequest(
[MaxLength(1024)] string? Slug,
[MaxLength(1024)] string? Name,
[MaxLength(4096)] string? Description
);
[HttpGet]
public async Task<IActionResult> ListProjects([FromRoute] string pubName)
{
var developer = await developerService.GetDeveloperByName(pubName);
if (developer is null) return NotFound();
var projects = await projectService.GetProjectsByDeveloperAsync(developer.Id);
return Ok(projects);
}
[HttpGet("{id:guid}")]
public async Task<IActionResult> GetProject([FromRoute] string pubName, Guid id)
{
var developer = await developerService.GetDeveloperByName(pubName);
if (developer is null) return NotFound();
var project = await projectService.GetProjectAsync(id, developer.Id);
if (project is null) return NotFound();
return Ok(project);
}
[HttpPost]
[Authorize]
public async Task<IActionResult> CreateProject([FromRoute] string pubName, [FromBody] DevProjectRequest request)
{
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
return Unauthorized();
var developer = await developerService.GetDeveloperByName(pubName);
if (developer is null)
return NotFound("Developer not found");
if (!await developerService.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id), PublisherMemberRole.Editor))
return StatusCode(403, "You must be an editor of the developer to create a project");
if (string.IsNullOrWhiteSpace(request.Slug) || string.IsNullOrWhiteSpace(request.Name))
return BadRequest("Slug and Name are required");
var project = await projectService.CreateProjectAsync(developer, request);
return CreatedAtAction(
nameof(GetProject),
new { pubName, id = project.Id },
project
);
}
[HttpPut("{id:guid}")]
[Authorize]
public async Task<IActionResult> UpdateProject(
[FromRoute] string pubName,
[FromRoute] Guid id,
[FromBody] DevProjectRequest request
)
{
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
return Unauthorized();
var developer = await developerService.GetDeveloperByName(pubName);
var accountId = Guid.Parse(currentUser.Id);
if (developer is null || developer.Id != accountId)
return Forbid();
var project = await projectService.UpdateProjectAsync(id, developer.Id, request);
if (project is null)
return NotFound();
return Ok(project);
}
[HttpDelete("{id:guid}")]
[Authorize]
public async Task<IActionResult> DeleteProject([FromRoute] string pubName, [FromRoute] Guid id)
{
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
return Unauthorized();
var developer = await developerService.GetDeveloperByName(pubName);
var accountId = Guid.Parse(currentUser.Id);
if (developer is null || developer.Id != accountId)
return Forbid();
var success = await projectService.DeleteProjectAsync(id, developer.Id);
if (!success)
return NotFound();
return NoContent();
}
}

View File

@@ -0,0 +1,77 @@
using DysonNetwork.Develop.Identity;
using Microsoft.EntityFrameworkCore;
using DysonNetwork.Shared.Proto;
namespace DysonNetwork.Develop.Project;
public class DevProjectService(
AppDatabase db,
FileReferenceService.FileReferenceServiceClient fileRefs,
FileService.FileServiceClient files
)
{
public async Task<DevProject> CreateProjectAsync(
Developer developer,
DevProjectController.DevProjectRequest request
)
{
var project = new DevProject
{
Slug = request.Slug!,
Name = request.Name!,
Description = request.Description ?? string.Empty,
DeveloperId = developer.Id
};
db.DevProjects.Add(project);
await db.SaveChangesAsync();
return project;
}
public async Task<DevProject?> GetProjectAsync(Guid id, Guid? developerId = null)
{
var query = db.DevProjects.AsQueryable();
if (developerId.HasValue)
{
query = query.Where(p => p.DeveloperId == developerId.Value);
}
return await query.FirstOrDefaultAsync(p => p.Id == id);
}
public async Task<List<DevProject>> GetProjectsByDeveloperAsync(Guid developerId)
{
return await db.DevProjects
.Where(p => p.DeveloperId == developerId)
.ToListAsync();
}
public async Task<DevProject?> UpdateProjectAsync(
Guid id,
Guid developerId,
DevProjectController.DevProjectRequest request
)
{
var project = await GetProjectAsync(id, developerId);
if (project == null) return null;
if (request.Slug != null) project.Slug = request.Slug;
if (request.Name != null) project.Name = request.Name;
if (request.Description != null) project.Description = request.Description;
await db.SaveChangesAsync();
return project;
}
public async Task<bool> DeleteProjectAsync(Guid id, Guid developerId)
{
var project = await GetProjectAsync(id, developerId);
if (project == null) return false;
db.DevProjects.Remove(project);
await db.SaveChangesAsync();
return true;
}
}