From a53fcb10dd208956ba9972f5404136e42a310e83 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 29 Jun 2025 18:37:23 +0800 Subject: [PATCH] :sparkles: Developer programs APIs --- .../Developer/DeveloperController.cs | 148 ++++++++++++++++++ DysonNetwork.Sphere/Publisher/Publisher.cs | 5 +- 2 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 DysonNetwork.Sphere/Developer/DeveloperController.cs diff --git a/DysonNetwork.Sphere/Developer/DeveloperController.cs b/DysonNetwork.Sphere/Developer/DeveloperController.cs new file mode 100644 index 0000000..da13e70 --- /dev/null +++ b/DysonNetwork.Sphere/Developer/DeveloperController.cs @@ -0,0 +1,148 @@ +using DysonNetwork.Sphere.Account; +using DysonNetwork.Sphere.Permission; +using DysonNetwork.Sphere.Publisher; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace DysonNetwork.Sphere.Developer; + +[ApiController] +[Route("/developers")] +public class DeveloperController( + AppDatabase db, + PublisherService ps, + ActionLogService als +) + : ControllerBase +{ + [HttpGet("{name}")] + public async Task> GetDeveloper(string name) + { + var publisher = await db.Publishers + .Where(e => e.Name == name) + .FirstOrDefaultAsync(); + if (publisher is null) return NotFound(); + if (publisher.AccountId is null) return Ok(publisher); + + var account = await db.Accounts + .Where(a => a.Id == publisher.AccountId) + .Include(a => a.Profile) + .FirstOrDefaultAsync(); + publisher.Account = account; + + return Ok(publisher); + } + + [HttpGet("{name}/stats")] + public async Task> 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"); + + // Get custom apps count + var customAppsCount = await db.CustomApps + .Where(a => a.PublisherId == publisher.Id) + .CountAsync(); + + var stats = new DeveloperStats + { + TotalCustomApps = customAppsCount + }; + + return Ok(stats); + } + + [HttpGet] + [Authorize] + public async Task>> ListJoinedDevelopers() + { + if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + var userId = currentUser.Id; + + var members = await db.PublisherMembers + .Where(m => m.AccountId == userId) + .Where(m => m.JoinedAt != null) + .Include(e => e.Publisher) + .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(); + } + + [HttpPost("{name}/enroll")] + [Authorize] + [RequiredPermission("global", "developers.create")] + public async Task> EnrollDeveloperProgram(string name) + { + if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + var userId = currentUser.Id; + + var publisher = await db.Publishers + .Where(p => p.Name == name) + .FirstOrDefaultAsync(); + if (publisher is null) return NotFound(); + + // Check if the user is an owner of the publisher + var isOwner = await db.PublisherMembers + .AnyAsync(m => + m.PublisherId == publisher.Id && + m.AccountId == userId && + 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 hasDeveloperFeature = await db.PublisherFeatures + .AnyAsync(f => + f.PublisherId == publisher.Id && + f.Flag == PublisherFeatureFlag.Develop && + (f.ExpiredAt == null || f.ExpiredAt > SystemClock.Instance.GetCurrentInstant())); + + if (hasDeveloperFeature) return BadRequest("Publisher is already in the developer program"); + + // Add developer feature flag + var feature = new PublisherFeature + { + PublisherId = publisher.Id, + Flag = PublisherFeatureFlag.Develop, + ExpiredAt = null + }; + + db.PublisherFeatures.Add(feature); + await db.SaveChangesAsync(); + + return Ok(publisher); + } + + public class DeveloperStats + { + public int TotalCustomApps { get; set; } + } +} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Publisher/Publisher.cs b/DysonNetwork.Sphere/Publisher/Publisher.cs index 298e20b..bae02ad 100644 --- a/DysonNetwork.Sphere/Publisher/Publisher.cs +++ b/DysonNetwork.Sphere/Publisher/Publisher.cs @@ -35,6 +35,7 @@ public class Publisher : ModelBase, IIdentifiedResource [JsonIgnore] public ICollection Posts { get; set; } = new List(); [JsonIgnore] public ICollection Collections { get; set; } = new List(); [JsonIgnore] public ICollection Members { get; set; } = new List(); + [JsonIgnore] public ICollection Features { get; set; } = new List(); [JsonIgnore] public ICollection Subscriptions { get; set; } = new List(); @@ -98,6 +99,6 @@ public class PublisherFeature : ModelBase public abstract class PublisherFeatureFlag { - public static List AllFlags => [Developer]; - public static string Developer = "develop"; + public static List AllFlags => [Develop]; + public static string Develop = "develop"; } \ No newline at end of file