Complete the develop service

This commit is contained in:
2025-08-07 20:30:34 +08:00
parent 00cdd1bc5d
commit ee8e9df12e
12 changed files with 313 additions and 143 deletions

View File

@@ -1,8 +1,9 @@
using DysonNetwork.Shared.Auth;
using DysonNetwork.Shared.Proto;
using Grpc.Core;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using NodaTime;
using Microsoft.EntityFrameworkCore;
namespace DysonNetwork.Develop.Identity;
@@ -11,42 +12,28 @@ namespace DysonNetwork.Develop.Identity;
public class DeveloperController(
AppDatabase db,
PublisherService.PublisherServiceClient ps,
ActionLogService.ActionLogServiceClient als
ActionLogService.ActionLogServiceClient als,
DeveloperService ds
)
: ControllerBase
{
[HttpGet("{name}")]
public async Task<ActionResult<Publisher.Publisher>> GetDeveloper(string name)
public async Task<ActionResult<Developer>> GetDeveloper(string name)
{
var publisher = await db.Publishers
.Where(e => e.Name == name)
.FirstOrDefaultAsync();
if (publisher is null) return NotFound();
return Ok(publisher);
var developer = await ds.GetDeveloperByName(name);
if (developer is null) return NotFound();
return Ok(developer);
}
[HttpGet("{name}/stats")]
public async Task<ActionResult<DeveloperStats>> GetDeveloperStats(string name)
{
var publisher = await db.Publishers
.Where(p => p.Name == name)
.FirstOrDefaultAsync();
if (publisher is null) return NotFound();
// Check if publisher has developer feature
var now = SystemClock.Instance.GetCurrentInstant();
var hasDeveloperFeature = await db.PublisherFeatures
.Where(f => f.PublisherId == publisher.Id)
.Where(f => f.Flag == PublisherFeatureFlag.Develop)
.Where(f => f.ExpiredAt == null || f.ExpiredAt > now)
.AnyAsync();
if (!hasDeveloperFeature) return NotFound("Not a developer account");
var developer = await ds.GetDeveloperByName(name);
if (developer is null) return NotFound();
// Get custom apps count
var customAppsCount = await db.CustomApps
.Where(a => a.PublisherId == publisher.Id)
.Where(a => a.DeveloperId == developer.Id)
.CountAsync();
var stats = new DeveloperStats
@@ -59,75 +46,63 @@ public class DeveloperController(
[HttpGet]
[Authorize]
public async Task<ActionResult<List<Publisher.Publisher>>> ListJoinedDevelopers()
public async Task<ActionResult<List<Developer>>> ListJoinedDevelopers()
{
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
var accountId = Guid.Parse(currentUser.Id);
var pubResponse = await ps.ListPublishersAsync(new ListPublishersRequest { AccountId = currentUser.Id });
var pubIds = pubResponse.Publishers.Select(p => p.Id).Select(Guid.Parse).ToList();
var members = await db.PublisherMembers
.Where(m => m.AccountId == accountId)
.Where(m => m.JoinedAt != null)
.Include(e => e.Publisher)
.ToListAsync();
var developerQuery = db.Developers
.Where(d => pubIds.Contains(d.PublisherId))
.AsQueryable();
var totalCount = await developerQuery.CountAsync();
Response.Headers.Append("X-Total", totalCount.ToString());
var developers = await developerQuery.ToListAsync();
// Filter to only include publishers with the developer feature flag
var now = SystemClock.Instance.GetCurrentInstant();
var publisherIds = members.Select(m => m.Publisher.Id).ToList();
var developerPublisherIds = await db.PublisherFeatures
.Where(f => publisherIds.Contains(f.PublisherId))
.Where(f => f.Flag == PublisherFeatureFlag.Develop)
.Where(f => f.ExpiredAt == null || f.ExpiredAt > now)
.Select(f => f.PublisherId)
.ToListAsync();
return members
.Where(m => developerPublisherIds.Contains(m.Publisher.Id))
.Select(m => m.Publisher)
.ToList();
return Ok(developers);
}
[HttpPost("{name}/enroll")]
[Authorize]
[RequiredPermission("global", "developers.create")]
public async Task<ActionResult<Publisher.Publisher>> EnrollDeveloperProgram(string name)
public async Task<ActionResult<Developer>> EnrollDeveloperProgram(string name)
{
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
var accountId = Guid.Parse(currentUser.Id);
var publisher = await db.Publishers
.Where(p => p.Name == name)
.FirstOrDefaultAsync();
if (publisher is null) return NotFound();
PublisherInfo? pub;
try
{
var pubResponse = await ps.GetPublisherAsync(new GetPublisherRequest { Name = name });
pub = PublisherInfo.FromProto(pubResponse.Publisher);
} catch (RpcException ex)
{
return NotFound(ex.Status.Detail);
}
// Check if the user is an owner of the publisher
var isOwner = await db.PublisherMembers
.AnyAsync(m =>
m.PublisherId == publisher.Id &&
m.AccountId == accountId &&
m.Role == PublisherMemberRole.Owner &&
m.JoinedAt != null);
if (!isOwner) return StatusCode(403, "You must be the owner of the publisher to join the developer program");
// Check if already has a developer feature
var now = SystemClock.Instance.GetCurrentInstant();
var hasDeveloperFeature = await db.PublisherFeatures
.AnyAsync(f =>
f.PublisherId == publisher.Id &&
f.Flag == PublisherFeatureFlag.Develop &&
(f.ExpiredAt == null || f.ExpiredAt > now));
if (hasDeveloperFeature) return BadRequest("Publisher is already in the developer program");
// Add developer feature flag
var feature = new PublisherFeature
var permResponse = await ps.IsPublisherMemberAsync(new IsPublisherMemberRequest
{
PublisherId = publisher.Id,
Flag = PublisherFeatureFlag.Develop,
ExpiredAt = null
PublisherId = pub.Id.ToString(),
AccountId = currentUser.Id,
Role = PublisherMemberRole.Owner
});
if (!permResponse.Valid) return StatusCode(403, "You must be the owner of the publisher to join the developer program");
var hasDeveloper = await db.Developers.AnyAsync(d => d.PublisherId == pub.Id);
if (hasDeveloper) return BadRequest("Publisher is already in the developer program");
var developer = new Developer
{
Id = Guid.NewGuid(),
PublisherId = pub.Id
};
db.PublisherFeatures.Add(feature);
db.Developers.Add(developer);
await db.SaveChangesAsync();
_ = als.CreateActionLogAsync(new CreateActionLogRequest
@@ -135,15 +110,15 @@ public class DeveloperController(
Action = "developers.enroll",
Meta =
{
{ "publisher_id", Google.Protobuf.WellKnownTypes.Value.ForString(publisher.Id.ToString()) },
{ "publisher_name", Google.Protobuf.WellKnownTypes.Value.ForString(publisher.Name) }
{ "publisher_id", Google.Protobuf.WellKnownTypes.Value.ForString(pub.Id.ToString()) },
{ "publisher_name", Google.Protobuf.WellKnownTypes.Value.ForString(pub.Name) }
},
AccountId = currentUser.Id,
UserAgent = Request.Headers.UserAgent,
IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString()
});
return Ok(publisher);
return Ok(developer);
}
public class DeveloperStats