Compare commits
	
		
			2 Commits
		
	
	
		
			50e888b075
			...
			5d7429a416
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 5d7429a416 | |||
| fb7e52d6f3 | 
| @@ -1,7 +1,7 @@ | |||||||
| using System.ComponentModel.DataAnnotations; | using System.ComponentModel.DataAnnotations; | ||||||
|  | using System.ComponentModel.DataAnnotations.Schema; | ||||||
| using DysonNetwork.Develop.Project; | using DysonNetwork.Develop.Project; | ||||||
| using DysonNetwork.Shared.Data; | using DysonNetwork.Shared.Data; | ||||||
| using NodaTime; |  | ||||||
| using NodaTime.Serialization.Protobuf; | using NodaTime.Serialization.Protobuf; | ||||||
|  |  | ||||||
| namespace DysonNetwork.Develop.Identity; | namespace DysonNetwork.Develop.Identity; | ||||||
| @@ -16,6 +16,8 @@ public class BotAccount : ModelBase | |||||||
|     public Guid ProjectId { get; set; } |     public Guid ProjectId { get; set; } | ||||||
|     public DevProject Project { get; set; } = null!; |     public DevProject Project { get; set; } = null!; | ||||||
|      |      | ||||||
|  |     [NotMapped] public AccountReference? Account { get; set; } | ||||||
|  |  | ||||||
|     public Shared.Proto.BotAccount ToProtoValue() |     public Shared.Proto.BotAccount ToProtoValue() | ||||||
|     { |     { | ||||||
|         var proto = new Shared.Proto.BotAccount |         var proto = new Shared.Proto.BotAccount | ||||||
|   | |||||||
| @@ -1,8 +1,11 @@ | |||||||
| using System.ComponentModel.DataAnnotations; | using System.ComponentModel.DataAnnotations; | ||||||
| using DysonNetwork.Develop.Project; | using DysonNetwork.Develop.Project; | ||||||
| using DysonNetwork.Shared.Proto; | using DysonNetwork.Shared.Proto; | ||||||
|  | using DysonNetwork.Shared.Registry; | ||||||
| using Microsoft.AspNetCore.Authorization; | using Microsoft.AspNetCore.Authorization; | ||||||
| using Microsoft.AspNetCore.Mvc; | using Microsoft.AspNetCore.Mvc; | ||||||
|  | using NodaTime; | ||||||
|  | using NodaTime.Serialization.Protobuf; | ||||||
|  |  | ||||||
| namespace DysonNetwork.Develop.Identity; | namespace DysonNetwork.Develop.Identity; | ||||||
|  |  | ||||||
| @@ -13,18 +16,61 @@ public class BotAccountController( | |||||||
|     BotAccountService botService, |     BotAccountService botService, | ||||||
|     DeveloperService developerService, |     DeveloperService developerService, | ||||||
|     DevProjectService projectService, |     DevProjectService projectService, | ||||||
|     ILogger<BotAccountController> logger |     ILogger<BotAccountController> logger, | ||||||
|  |     AccountClientHelper accounts | ||||||
| ) | ) | ||||||
|     : ControllerBase |     : ControllerBase | ||||||
| { | { | ||||||
|     public record BotRequest( |     public class CommonBotRequest | ||||||
|         [Required] [MaxLength(1024)] string? Slug |     { | ||||||
|     ); |         [MaxLength(256)] public string? FirstName { get; set; } | ||||||
|  |         [MaxLength(256)] public string? MiddleName { get; set; } | ||||||
|  |         [MaxLength(256)] public string? LastName { get; set; } | ||||||
|  |         [MaxLength(1024)] public string? Gender { get; set; } | ||||||
|  |         [MaxLength(1024)] public string? Pronouns { get; set; } | ||||||
|  |         [MaxLength(1024)] public string? TimeZone { get; set; } | ||||||
|  |         [MaxLength(1024)] public string? Location { get; set; } | ||||||
|  |         [MaxLength(4096)] public string? Bio { get; set; } | ||||||
|  |         public Instant? Birthday { get; set; } | ||||||
|  |  | ||||||
|     public record UpdateBotRequest( |         [MaxLength(32)] public string? PictureId { get; set; } | ||||||
|        [MaxLength(1024)] string? Slug, |         [MaxLength(32)] public string? BackgroundId { get; set; } | ||||||
|         bool? IsActive |     } | ||||||
|     ) : BotRequest(Slug); |  | ||||||
|  |     public class BotCreateRequest : CommonBotRequest | ||||||
|  |     { | ||||||
|  |         [Required] | ||||||
|  |         [MinLength(2)] | ||||||
|  |         [MaxLength(256)] | ||||||
|  |         [RegularExpression(@"^[A-Za-z0-9_-]+$", | ||||||
|  |             ErrorMessage = "Name can only contain letters, numbers, underscores, and hyphens.") | ||||||
|  |         ] | ||||||
|  |         public string Name { get; set; } = string.Empty; | ||||||
|  |  | ||||||
|  |         [Required] [MaxLength(256)] public string Nick { get; set; } = string.Empty; | ||||||
|  |  | ||||||
|  |         [Required] [MaxLength(1024)] public string Slug { get; set; } = string.Empty; | ||||||
|  |  | ||||||
|  |         [MaxLength(128)] public string Language { get; set; } = "en-us"; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public class UpdateBotRequest : CommonBotRequest | ||||||
|  |     { | ||||||
|  |         [MinLength(2)] | ||||||
|  |         [MaxLength(256)] | ||||||
|  |         [RegularExpression(@"^[A-Za-z0-9_-]+$", | ||||||
|  |             ErrorMessage = "Name can only contain letters, numbers, underscores, and hyphens.") | ||||||
|  |         ] | ||||||
|  |         public string? Name { get; set; } = string.Empty; | ||||||
|  |  | ||||||
|  |         [MaxLength(256)] public string? Nick { get; set; } = string.Empty; | ||||||
|  |          | ||||||
|  |         [Required] [MaxLength(1024)] public string Slug { get; set; } = string.Empty; | ||||||
|  |  | ||||||
|  |         [MaxLength(128)] public string? Language { get; set; } | ||||||
|  |  | ||||||
|  |         public bool? IsActive { get; set; } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     [HttpGet] |     [HttpGet] | ||||||
|     public async Task<IActionResult> ListBots( |     public async Task<IActionResult> ListBots( | ||||||
| @@ -47,7 +93,7 @@ public class BotAccountController( | |||||||
|             return NotFound("Project not found or you don't have access"); |             return NotFound("Project not found or you don't have access"); | ||||||
|  |  | ||||||
|         var bots = await botService.GetBotsByProjectAsync(projectId); |         var bots = await botService.GetBotsByProjectAsync(projectId); | ||||||
|         return Ok(bots); |         return Ok(await botService.LoadBotsAccountAsync(bots)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     [HttpGet("{botId:guid}")] |     [HttpGet("{botId:guid}")] | ||||||
| @@ -75,18 +121,16 @@ public class BotAccountController( | |||||||
|         if (bot is null || bot.ProjectId != projectId) |         if (bot is null || bot.ProjectId != projectId) | ||||||
|             return NotFound("Bot not found"); |             return NotFound("Bot not found"); | ||||||
|  |  | ||||||
|         return Ok(bot); |         return Ok(await botService.LoadBotAccountAsync(bot)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     [HttpPost] |     [HttpPost] | ||||||
|     public async Task<IActionResult> CreateBot( |     public async Task<IActionResult> CreateBot( | ||||||
|         [FromRoute] string pubName, |         [FromRoute] string pubName, | ||||||
|         [FromRoute] Guid projectId, |         [FromRoute] Guid projectId, | ||||||
|         [FromBody] BotRequest request |         [FromBody] BotCreateRequest createRequest | ||||||
|     ) |     ) | ||||||
|     { |     { | ||||||
|         if (string.IsNullOrWhiteSpace(request.Slug)) |  | ||||||
|             return BadRequest("Name is required"); |  | ||||||
|         if (HttpContext.Items["CurrentUser"] is not Account currentUser) |         if (HttpContext.Items["CurrentUser"] is not Account currentUser) | ||||||
|             return Unauthorized(); |             return Unauthorized(); | ||||||
|  |  | ||||||
| @@ -102,9 +146,30 @@ public class BotAccountController( | |||||||
|         if (project is null) |         if (project is null) | ||||||
|             return NotFound("Project not found or you don't have access"); |             return NotFound("Project not found or you don't have access"); | ||||||
|  |  | ||||||
|  |         var account = new Account() | ||||||
|  |         { | ||||||
|  |             Name = createRequest.Name, | ||||||
|  |             Nick = createRequest.Nick, | ||||||
|  |             Language = createRequest.Language, | ||||||
|  |             Profile = new AccountProfile() | ||||||
|  |             { | ||||||
|  |                 Bio = createRequest.Bio, | ||||||
|  |                 Gender = createRequest.Gender, | ||||||
|  |                 FirstName = createRequest.FirstName, | ||||||
|  |                 MiddleName = createRequest.MiddleName, | ||||||
|  |                 LastName = createRequest.LastName, | ||||||
|  |                 TimeZone = createRequest.TimeZone, | ||||||
|  |                 Pronouns = createRequest.Pronouns, | ||||||
|  |                 Location = createRequest.Location, | ||||||
|  |                 Birthday = createRequest.Birthday?.ToTimestamp(), | ||||||
|  |                 Picture = new CloudFile() { Id = createRequest.PictureId }, | ||||||
|  |                 Background = new CloudFile() { Id = createRequest.BackgroundId } | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |  | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
|             var bot = await botService.CreateBotAsync(project, request.Slug); |             var bot = await botService.CreateBotAsync(project, createRequest.Slug, account); | ||||||
|             return Ok(bot); |             return Ok(bot); | ||||||
|         } |         } | ||||||
|         catch (Exception ex) |         catch (Exception ex) | ||||||
| @@ -141,10 +206,29 @@ public class BotAccountController( | |||||||
|         if (bot is null || bot.ProjectId != projectId) |         if (bot is null || bot.ProjectId != projectId) | ||||||
|             return NotFound("Bot not found"); |             return NotFound("Bot not found"); | ||||||
|  |  | ||||||
|  |         var botAccount = await accounts.GetBotAccount(bot.Id); | ||||||
|  |  | ||||||
|  |         if (request.Name is not null) botAccount.Name = request.Name; | ||||||
|  |         if (request.Nick is not null) botAccount.Nick = request.Nick; | ||||||
|  |         if (request.Language is not null) botAccount.Language = request.Language; | ||||||
|  |         if (request.Bio is not null) botAccount.Profile.Bio = request.Bio; | ||||||
|  |         if (request.Gender is not null) botAccount.Profile.Gender = request.Gender; | ||||||
|  |         if (request.FirstName is not null) botAccount.Profile.FirstName = request.FirstName; | ||||||
|  |         if (request.MiddleName is not null) botAccount.Profile.MiddleName = request.MiddleName; | ||||||
|  |         if (request.LastName is not null) botAccount.Profile.LastName = request.LastName; | ||||||
|  |         if (request.TimeZone is not null) botAccount.Profile.TimeZone = request.TimeZone; | ||||||
|  |         if (request.Pronouns is not null) botAccount.Profile.Pronouns = request.Pronouns; | ||||||
|  |         if (request.Location is not null) botAccount.Profile.Location = request.Location; | ||||||
|  |         if (request.Birthday is not null) botAccount.Profile.Birthday = request.Birthday?.ToTimestamp(); | ||||||
|  |         if (request.PictureId is not null) botAccount.Profile.Picture = new CloudFile() { Id = request.PictureId }; | ||||||
|  |         if (request.BackgroundId is not null) | ||||||
|  |             botAccount.Profile.Background = new CloudFile() { Id = request.BackgroundId }; | ||||||
|  |          | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
|             var updatedBot = await botService.UpdateBotAsync( |             var updatedBot = await botService.UpdateBotAsync( | ||||||
|                 bot, |                 bot, | ||||||
|  |                 botAccount, | ||||||
|                 request.Slug, |                 request.Slug, | ||||||
|                 request.IsActive |                 request.IsActive | ||||||
|             ); |             ); | ||||||
|   | |||||||
| @@ -1,13 +1,18 @@ | |||||||
| using DysonNetwork.Develop.Project; | using DysonNetwork.Develop.Project; | ||||||
|  | using DysonNetwork.Shared.Data; | ||||||
| using DysonNetwork.Shared.Proto; | using DysonNetwork.Shared.Proto; | ||||||
|  | using DysonNetwork.Shared.Registry; | ||||||
| using Grpc.Core; | using Grpc.Core; | ||||||
| using Microsoft.EntityFrameworkCore; | using Microsoft.EntityFrameworkCore; | ||||||
| using NodaTime; |  | ||||||
| using NodaTime.Serialization.Protobuf; | using NodaTime.Serialization.Protobuf; | ||||||
|  |  | ||||||
| namespace DysonNetwork.Develop.Identity; | namespace DysonNetwork.Develop.Identity; | ||||||
|  |  | ||||||
| public class BotAccountService(AppDatabase db, BotAccountReceiverService.BotAccountReceiverServiceClient accountReceiver) | public class BotAccountService( | ||||||
|  |     AppDatabase db, | ||||||
|  |     BotAccountReceiverService.BotAccountReceiverServiceClient accountReceiver, | ||||||
|  |     AccountClientHelper accounts | ||||||
|  | ) | ||||||
| { | { | ||||||
|     public async Task<BotAccount?> GetBotByIdAsync(Guid id) |     public async Task<BotAccount?> GetBotByIdAsync(Guid id) | ||||||
|     { |     { | ||||||
| @@ -23,7 +28,7 @@ public class BotAccountService(AppDatabase db, BotAccountReceiverService.BotAcco | |||||||
|             .ToListAsync(); |             .ToListAsync(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public async Task<BotAccount> CreateBotAsync(DevProject project, string slug) |     public async Task<BotAccount> CreateBotAsync(DevProject project, string slug, Account account) | ||||||
|     { |     { | ||||||
|         // First, check if a bot with this slug already exists in this project |         // First, check if a bot with this slug already exists in this project | ||||||
|         var existingBot = await db.BotAccounts |         var existingBot = await db.BotAccounts | ||||||
| @@ -34,28 +39,12 @@ public class BotAccountService(AppDatabase db, BotAccountReceiverService.BotAcco | |||||||
|             throw new InvalidOperationException("A bot with this slug already exists in this project."); |             throw new InvalidOperationException("A bot with this slug already exists in this project."); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         var now = SystemClock.Instance.GetCurrentInstant(); |  | ||||||
|          |  | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
|             // First create the bot account in the Pass service |  | ||||||
|             var createRequest = new CreateBotAccountRequest |             var createRequest = new CreateBotAccountRequest | ||||||
|             { |             { | ||||||
|                 AutomatedId = Guid.NewGuid().ToString(), |                 AutomatedId = Guid.NewGuid().ToString(), | ||||||
|                 Account = new Account |                 Account = account | ||||||
|                 { |  | ||||||
|                     Name = slug, |  | ||||||
|                     Nick = $"Bot {slug}", |  | ||||||
|                     Language = "en", |  | ||||||
|                     Profile = new AccountProfile |  | ||||||
|                     { |  | ||||||
|                         Id = Guid.NewGuid().ToString(), |  | ||||||
|                         CreatedAt = now.ToTimestamp(), |  | ||||||
|                         UpdatedAt = now.ToTimestamp() |  | ||||||
|                     }, |  | ||||||
|                     CreatedAt = now.ToTimestamp(), |  | ||||||
|                     UpdatedAt = now.ToTimestamp() |  | ||||||
|                 } |  | ||||||
|             }; |             }; | ||||||
|  |  | ||||||
|             var createResponse = await accountReceiver.CreateBotAccountAsync(createRequest); |             var createResponse = await accountReceiver.CreateBotAccountAsync(createRequest); | ||||||
| @@ -80,7 +69,8 @@ public class BotAccountService(AppDatabase db, BotAccountReceiverService.BotAcco | |||||||
|         } |         } | ||||||
|         catch (RpcException ex) when (ex.StatusCode == StatusCode.AlreadyExists) |         catch (RpcException ex) when (ex.StatusCode == StatusCode.AlreadyExists) | ||||||
|         { |         { | ||||||
|             throw new InvalidOperationException("A bot account with this ID already exists in the authentication service.", ex); |             throw new InvalidOperationException( | ||||||
|  |                 "A bot account with this ID already exists in the authentication service.", ex); | ||||||
|         } |         } | ||||||
|         catch (RpcException ex) when (ex.StatusCode == StatusCode.InvalidArgument) |         catch (RpcException ex) when (ex.StatusCode == StatusCode.InvalidArgument) | ||||||
|         { |         { | ||||||
| @@ -92,7 +82,8 @@ public class BotAccountService(AppDatabase db, BotAccountReceiverService.BotAcco | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public async Task<BotAccount> UpdateBotAsync(BotAccount bot, string? slug = null, bool? isActive = null) |     public async Task<BotAccount> UpdateBotAsync(BotAccount bot, Account account, string? slug = null, | ||||||
|  |         bool? isActive = null) | ||||||
|     { |     { | ||||||
|         var updated = false; |         var updated = false; | ||||||
|         if (slug != null && bot.Slug != slug) |         if (slug != null && bot.Slug != slug) | ||||||
| @@ -115,12 +106,7 @@ public class BotAccountService(AppDatabase db, BotAccountReceiverService.BotAcco | |||||||
|             var updateRequest = new UpdateBotAccountRequest |             var updateRequest = new UpdateBotAccountRequest | ||||||
|             { |             { | ||||||
|                 AutomatedId = bot.Id.ToString(), |                 AutomatedId = bot.Id.ToString(), | ||||||
|                 Account = new Shared.Proto.Account |                 Account = account | ||||||
|                 { |  | ||||||
|                     Name = $"bot-{bot.Slug}", |  | ||||||
|                     Nick = $"Bot {bot.Slug}", |  | ||||||
|                     UpdatedAt = SystemClock.Instance.GetCurrentInstant().ToTimestamp() |  | ||||||
|                 } |  | ||||||
|             }; |             }; | ||||||
|  |  | ||||||
|             var updateResponse = await accountReceiver.UpdateBotAccountAsync(updateRequest); |             var updateResponse = await accountReceiver.UpdateBotAccountAsync(updateRequest); | ||||||
| @@ -165,4 +151,23 @@ public class BotAccountService(AppDatabase db, BotAccountReceiverService.BotAcco | |||||||
|         db.BotAccounts.Remove(bot); |         db.BotAccounts.Remove(bot); | ||||||
|         await db.SaveChangesAsync(); |         await db.SaveChangesAsync(); | ||||||
|     } |     } | ||||||
|  |      | ||||||
|  |     public async Task<BotAccount?> LoadBotAccountAsync(BotAccount bot) => | ||||||
|  |         (await LoadBotsAccountAsync([bot])).FirstOrDefault(); | ||||||
|  |  | ||||||
|  |     public async Task<List<BotAccount>> LoadBotsAccountAsync(IEnumerable<BotAccount> bots) | ||||||
|  |     { | ||||||
|  |         bots = bots.ToList(); | ||||||
|  |         var automatedIds = bots.Select(b => b.Id).ToList(); | ||||||
|  |         var data = await accounts.GetBotAccountBatch(automatedIds); | ||||||
|  |  | ||||||
|  |         foreach (var bot in bots) | ||||||
|  |         { | ||||||
|  |             bot.Account = data | ||||||
|  |                 .Select(AccountReference.FromProtoValue) | ||||||
|  |                 .FirstOrDefault(e => e.AutomatedId == bot.Id); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return bots as List<BotAccount> ?? []; | ||||||
|  |     } | ||||||
| } | } | ||||||
| @@ -1,4 +1,5 @@ | |||||||
| using System.ComponentModel.DataAnnotations.Schema; | using System.ComponentModel.DataAnnotations.Schema; | ||||||
|  | using System.Text.Json.Serialization; | ||||||
| using DysonNetwork.Develop.Project; | using DysonNetwork.Develop.Project; | ||||||
| using DysonNetwork.Shared.Proto; | using DysonNetwork.Shared.Proto; | ||||||
| using DysonNetwork.Shared.Data; | using DysonNetwork.Shared.Data; | ||||||
| @@ -11,7 +12,7 @@ public class Developer | |||||||
|     public Guid Id { get; set; } = Guid.NewGuid(); |     public Guid Id { get; set; } = Guid.NewGuid(); | ||||||
|     public Guid PublisherId { get; set; } |     public Guid PublisherId { get; set; } | ||||||
|      |      | ||||||
|     public List<DevProject> Projects { get; set; } = []; |     [JsonIgnore] public List<DevProject> Projects { get; set; } = []; | ||||||
|      |      | ||||||
|     [NotMapped] public PublisherInfo? Publisher { get; set; } |     [NotMapped] public PublisherInfo? Publisher { get; set; } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -24,7 +24,7 @@ | |||||||
|   }, |   }, | ||||||
|   "Service": { |   "Service": { | ||||||
|     "Name": "DysonNetwork.Develop", |     "Name": "DysonNetwork.Develop", | ||||||
|     "Url": "https://localhost:7099", |     "Url": "https://localhost:7192", | ||||||
|     "ClientCert": "../Certificates/client.crt", |     "ClientCert": "../Certificates/client.crt", | ||||||
|     "ClientKey": "../Certificates/client.key" |     "ClientKey": "../Certificates/client.key" | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -51,7 +51,8 @@ public class Account : ModelBase | |||||||
|             Profile = Profile.ToProtoValue(), |             Profile = Profile.ToProtoValue(), | ||||||
|             PerkSubscription = PerkSubscription?.ToProtoValue(), |             PerkSubscription = PerkSubscription?.ToProtoValue(), | ||||||
|             CreatedAt = CreatedAt.ToTimestamp(), |             CreatedAt = CreatedAt.ToTimestamp(), | ||||||
|             UpdatedAt = UpdatedAt.ToTimestamp() |             UpdatedAt = UpdatedAt.ToTimestamp(), | ||||||
|  |             AutomatedId = AutomatedId?.ToString() | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         // Add contacts |         // Add contacts | ||||||
| @@ -81,6 +82,7 @@ public class Account : ModelBase | |||||||
|                 : null, |                 : null, | ||||||
|             CreatedAt = proto.CreatedAt.ToInstant(), |             CreatedAt = proto.CreatedAt.ToInstant(), | ||||||
|             UpdatedAt = proto.UpdatedAt.ToInstant(), |             UpdatedAt = proto.UpdatedAt.ToInstant(), | ||||||
|  |             AutomatedId = proto.AutomatedId is not null ? Guid.Parse(proto.AutomatedId) : null | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         account.Profile = AccountProfile.FromProtoValue(proto.Profile); |         account.Profile = AccountProfile.FromProtoValue(proto.Profile); | ||||||
| @@ -119,7 +121,7 @@ public abstract class Leveling | |||||||
|  |  | ||||||
| public class AccountProfile : ModelBase, IIdentifiedResource | public class AccountProfile : ModelBase, IIdentifiedResource | ||||||
| { | { | ||||||
|     public Guid Id { get; set; } |     public Guid Id { get; set; } = Guid.NewGuid(); | ||||||
|     [MaxLength(256)] public string? FirstName { get; set; } |     [MaxLength(256)] public string? FirstName { get; set; } | ||||||
|     [MaxLength(256)] public string? MiddleName { get; set; } |     [MaxLength(256)] public string? MiddleName { get; set; } | ||||||
|     [MaxLength(256)] public string? LastName { get; set; } |     [MaxLength(256)] public string? LastName { get; set; } | ||||||
|   | |||||||
| @@ -30,6 +30,7 @@ public class AccountCurrentController( | |||||||
| { | { | ||||||
|     [HttpGet] |     [HttpGet] | ||||||
|     [ProducesResponseType<Account>(StatusCodes.Status200OK)] |     [ProducesResponseType<Account>(StatusCodes.Status200OK)] | ||||||
|  |     [ProducesResponseType<ApiError>(StatusCodes.Status401Unauthorized)] | ||||||
|     public async Task<ActionResult<Account>> GetCurrentIdentity() |     public async Task<ActionResult<Account>> GetCurrentIdentity() | ||||||
|     { |     { | ||||||
|         if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); |         if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ using DysonNetwork.Pass.Email; | |||||||
| using DysonNetwork.Pass.Localization; | using DysonNetwork.Pass.Localization; | ||||||
| using DysonNetwork.Pass.Permission; | using DysonNetwork.Pass.Permission; | ||||||
| using DysonNetwork.Shared.Cache; | using DysonNetwork.Shared.Cache; | ||||||
|  | using DysonNetwork.Shared.Data; | ||||||
| using DysonNetwork.Shared.Proto; | using DysonNetwork.Shared.Proto; | ||||||
| using DysonNetwork.Shared.Stream; | using DysonNetwork.Shared.Stream; | ||||||
| using EFCore.BulkExtensions; | using EFCore.BulkExtensions; | ||||||
| @@ -21,6 +22,8 @@ namespace DysonNetwork.Pass.Account; | |||||||
| public class AccountService( | public class AccountService( | ||||||
|     AppDatabase db, |     AppDatabase db, | ||||||
|     MagicSpellService spells, |     MagicSpellService spells, | ||||||
|  |     FileService.FileServiceClient files, | ||||||
|  |     FileReferenceService.FileReferenceServiceClient fileRefs, | ||||||
|     AccountUsernameService uname, |     AccountUsernameService uname, | ||||||
|     EmailService mailer, |     EmailService mailer, | ||||||
|     PusherService.PusherServiceClient pusher, |     PusherService.PusherServiceClient pusher, | ||||||
| @@ -182,7 +185,7 @@ public class AccountService( | |||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public async Task<Account> CreateBotAccount(Account account, Guid automatedId) |     public async Task<Account> CreateBotAccount(Account account, Guid automatedId, string? pictureId, string? backgroundId) | ||||||
|     { |     { | ||||||
|         var dupeAutomateCount = await db.Accounts.Where(a => a.AutomatedId == automatedId).CountAsync(); |         var dupeAutomateCount = await db.Accounts.Where(a => a.AutomatedId == automatedId).CountAsync(); | ||||||
|         if (dupeAutomateCount > 0) |         if (dupeAutomateCount > 0) | ||||||
| @@ -195,8 +198,38 @@ public class AccountService( | |||||||
|         account.AutomatedId = automatedId; |         account.AutomatedId = automatedId; | ||||||
|         account.ActivatedAt = SystemClock.Instance.GetCurrentInstant(); |         account.ActivatedAt = SystemClock.Instance.GetCurrentInstant(); | ||||||
|         account.IsSuperuser = false; |         account.IsSuperuser = false; | ||||||
|  |  | ||||||
|  |         if (!string.IsNullOrEmpty(pictureId)) | ||||||
|  |         { | ||||||
|  |             var file = await files.GetFileAsync(new GetFileRequest { Id = pictureId }); | ||||||
|  |             await fileRefs.CreateReferenceAsync( | ||||||
|  |                 new CreateReferenceRequest | ||||||
|  |                 { | ||||||
|  |                     ResourceId = account.Profile.ResourceIdentifier, | ||||||
|  |                     FileId = pictureId, | ||||||
|  |                     Usage = "profile.picture" | ||||||
|  |                 } | ||||||
|  |             ); | ||||||
|  |             account.Profile.Picture = CloudFileReferenceObject.FromProtoValue(file); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (!string.IsNullOrEmpty(backgroundId)) | ||||||
|  |         { | ||||||
|  |             var file = await files.GetFileAsync(new GetFileRequest { Id = backgroundId }); | ||||||
|  |             await fileRefs.CreateReferenceAsync( | ||||||
|  |                 new CreateReferenceRequest | ||||||
|  |                 { | ||||||
|  |                     ResourceId = account.Profile.ResourceIdentifier, | ||||||
|  |                     FileId = backgroundId, | ||||||
|  |                     Usage = "profile.background" | ||||||
|  |                 } | ||||||
|  |             ); | ||||||
|  |             account.Profile.Background = CloudFileReferenceObject.FromProtoValue(file); | ||||||
|  |         } | ||||||
|  |          | ||||||
|         db.Accounts.Add(account); |         db.Accounts.Add(account); | ||||||
|         await db.SaveChangesAsync(); |         await db.SaveChangesAsync(); | ||||||
|  |  | ||||||
|         return account; |         return account; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -42,6 +42,26 @@ public class AccountServiceGrpc( | |||||||
|         return account.ToProtoValue(); |         return account.ToProtoValue(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public override async Task<Shared.Proto.Account> GetBotAccount(GetBotAccountRequest request, | ||||||
|  |         ServerCallContext context) | ||||||
|  |     { | ||||||
|  |         if (!Guid.TryParse(request.AutomatedId, out var automatedId)) | ||||||
|  |             throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid automated ID format")); | ||||||
|  |  | ||||||
|  |         var account = await _db.Accounts | ||||||
|  |             .AsNoTracking() | ||||||
|  |             .Include(a => a.Profile) | ||||||
|  |             .FirstOrDefaultAsync(a => a.AutomatedId == automatedId); | ||||||
|  |  | ||||||
|  |         if (account == null) | ||||||
|  |             throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, $"Account with automated ID {request.AutomatedId} not found")); | ||||||
|  |  | ||||||
|  |         var perk = await subscriptions.GetPerkSubscriptionAsync(account.Id); | ||||||
|  |         account.PerkSubscription = perk?.ToReference(); | ||||||
|  |  | ||||||
|  |         return account.ToProtoValue(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public override async Task<GetAccountBatchResponse> GetAccountBatch(GetAccountBatchRequest request, |     public override async Task<GetAccountBatchResponse> GetAccountBatch(GetAccountBatchRequest request, | ||||||
|         ServerCallContext context) |         ServerCallContext context) | ||||||
|     { |     { | ||||||
| @@ -69,6 +89,34 @@ public class AccountServiceGrpc( | |||||||
|         return response; |         return response; | ||||||
|     } |     } | ||||||
|      |      | ||||||
|  |      | ||||||
|  |     public override async Task<GetAccountBatchResponse> GetBotAccountBatch(GetBotAccountBatchRequest request, | ||||||
|  |         ServerCallContext context) | ||||||
|  |     { | ||||||
|  |         var automatedIds = request.AutomatedId | ||||||
|  |             .Select(id => Guid.TryParse(id, out var automatedId) ? automatedId : (Guid?)null) | ||||||
|  |             .Where(id => id.HasValue) | ||||||
|  |             .Select(id => id!.Value) | ||||||
|  |             .ToList(); | ||||||
|  |  | ||||||
|  |         var accounts = await _db.Accounts | ||||||
|  |             .AsNoTracking() | ||||||
|  |             .Where(a => a.AutomatedId != null && automatedIds.Contains(a.AutomatedId.Value)) | ||||||
|  |             .Include(a => a.Profile) | ||||||
|  |             .ToListAsync(); | ||||||
|  |  | ||||||
|  |         var perks = await subscriptions.GetPerkSubscriptionsAsync( | ||||||
|  |             accounts.Select(x => x.Id).ToList() | ||||||
|  |         ); | ||||||
|  |         foreach (var account in accounts) | ||||||
|  |             if (perks.TryGetValue(account.Id, out var perk)) | ||||||
|  |                 account.PerkSubscription = perk?.ToReference(); | ||||||
|  |  | ||||||
|  |         var response = new GetAccountBatchResponse(); | ||||||
|  |         response.Accounts.AddRange(accounts.Select(a => a.ToProtoValue())); | ||||||
|  |         return response; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public override async Task<AccountStatus> GetAccountStatus(GetAccountRequest request, ServerCallContext context) |     public override async Task<AccountStatus> GetAccountStatus(GetAccountRequest request, ServerCallContext context) | ||||||
|     { |     { | ||||||
|         var accountId = Guid.Parse(request.Id); |         var accountId = Guid.Parse(request.Id); | ||||||
| @@ -76,7 +124,8 @@ public class AccountServiceGrpc( | |||||||
|         return status.ToProtoValue(); |         return status.ToProtoValue(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public override async Task<GetAccountStatusBatchResponse> GetAccountStatusBatch(GetAccountBatchRequest request, ServerCallContext context) |     public override async Task<GetAccountStatusBatchResponse> GetAccountStatusBatch(GetAccountBatchRequest request, | ||||||
|  |         ServerCallContext context) | ||||||
|     { |     { | ||||||
|         var accountIds = request.Id |         var accountIds = request.Id | ||||||
|             .Select(id => Guid.TryParse(id, out var accountId) ? accountId : (Guid?)null) |             .Select(id => Guid.TryParse(id, out var accountId) ? accountId : (Guid?)null) | ||||||
|   | |||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | using DysonNetwork.Shared.Data; | ||||||
| using DysonNetwork.Shared.Proto; | using DysonNetwork.Shared.Proto; | ||||||
| using Grpc.Core; | using Grpc.Core; | ||||||
| using NodaTime; | using NodaTime; | ||||||
| @@ -5,7 +6,12 @@ using NodaTime.Serialization.Protobuf; | |||||||
|  |  | ||||||
| namespace DysonNetwork.Pass.Account; | namespace DysonNetwork.Pass.Account; | ||||||
|  |  | ||||||
| public class BotAccountReceiverGrpc(AppDatabase db, AccountService accounts) | public class BotAccountReceiverGrpc( | ||||||
|  |     AppDatabase db, | ||||||
|  |     AccountService accounts, | ||||||
|  |     FileService.FileServiceClient files, | ||||||
|  |     FileReferenceService.FileReferenceServiceClient fileRefs | ||||||
|  | ) | ||||||
|     : BotAccountReceiverService.BotAccountReceiverServiceBase |     : BotAccountReceiverService.BotAccountReceiverServiceBase | ||||||
| { | { | ||||||
|     public override async Task<CreateBotAccountResponse> CreateBotAccount( |     public override async Task<CreateBotAccountResponse> CreateBotAccount( | ||||||
| @@ -14,7 +20,12 @@ public class BotAccountReceiverGrpc(AppDatabase db, AccountService accounts) | |||||||
|     ) |     ) | ||||||
|     { |     { | ||||||
|         var account = Account.FromProtoValue(request.Account); |         var account = Account.FromProtoValue(request.Account); | ||||||
|         account = await accounts.CreateBotAccount(account, Guid.Parse(request.AutomatedId)); |         account = await accounts.CreateBotAccount( | ||||||
|  |             account, | ||||||
|  |             Guid.Parse(request.AutomatedId), | ||||||
|  |             request.PictureId, | ||||||
|  |             request.BackgroundId | ||||||
|  |         ); | ||||||
|  |  | ||||||
|         return new CreateBotAccountResponse |         return new CreateBotAccountResponse | ||||||
|         { |         { | ||||||
| @@ -34,15 +45,43 @@ public class BotAccountReceiverGrpc(AppDatabase db, AccountService accounts) | |||||||
|         ServerCallContext context |         ServerCallContext context | ||||||
|     ) |     ) | ||||||
|     { |     { | ||||||
|         var automatedId = Guid.Parse(request.AutomatedId); |         var account = Account.FromProtoValue(request.Account); | ||||||
|         var account = await accounts.GetBotAccount(automatedId); |  | ||||||
|         if (account is null) |  | ||||||
|             throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, "Account not found")); |  | ||||||
|  |  | ||||||
|         account.Name = request.Account.Name; |         if (request.PictureId is not null) | ||||||
|         account.Nick = request.Account.Nick; |         { | ||||||
|         account.Profile = AccountProfile.FromProtoValue(request.Account.Profile); |             var file = await files.GetFileAsync(new GetFileRequest { Id = request.PictureId }); | ||||||
|         account.Language = request.Account.Language; |             if (account.Profile.Picture is not null) | ||||||
|  |                 await fileRefs.DeleteResourceReferencesAsync( | ||||||
|  |                     new DeleteResourceReferencesRequest { ResourceId = account.Profile.ResourceIdentifier } | ||||||
|  |                 ); | ||||||
|  |             await fileRefs.CreateReferenceAsync( | ||||||
|  |                 new CreateReferenceRequest | ||||||
|  |                 { | ||||||
|  |                     ResourceId = account.Profile.ResourceIdentifier, | ||||||
|  |                     FileId = request.PictureId, | ||||||
|  |                     Usage = "profile.picture" | ||||||
|  |                 } | ||||||
|  |             ); | ||||||
|  |             account.Profile.Picture = CloudFileReferenceObject.FromProtoValue(file); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (request.BackgroundId is not null) | ||||||
|  |         { | ||||||
|  |             var file = await files.GetFileAsync(new GetFileRequest { Id = request.BackgroundId }); | ||||||
|  |             if (account.Profile.Background is not null) | ||||||
|  |                 await fileRefs.DeleteResourceReferencesAsync( | ||||||
|  |                     new DeleteResourceReferencesRequest { ResourceId = account.Profile.ResourceIdentifier } | ||||||
|  |                 ); | ||||||
|  |             await fileRefs.CreateReferenceAsync( | ||||||
|  |                 new CreateReferenceRequest | ||||||
|  |                 { | ||||||
|  |                     ResourceId = account.Profile.ResourceIdentifier, | ||||||
|  |                     FileId = request.BackgroundId, | ||||||
|  |                     Usage = "profile.background" | ||||||
|  |                 } | ||||||
|  |             ); | ||||||
|  |             account.Profile.Background = CloudFileReferenceObject.FromProtoValue(file); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         db.Accounts.Update(account); |         db.Accounts.Update(account); | ||||||
|         await db.SaveChangesAsync(); |         await db.SaveChangesAsync(); | ||||||
|   | |||||||
							
								
								
									
										309
									
								
								DysonNetwork.Shared/Data/Account.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										309
									
								
								DysonNetwork.Shared/Data/Account.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,309 @@ | |||||||
|  | using DysonNetwork.Shared.Proto; | ||||||
|  | using NodaTime; | ||||||
|  | using NodaTime.Serialization.Protobuf; | ||||||
|  |  | ||||||
|  | namespace DysonNetwork.Shared.Data; | ||||||
|  |  | ||||||
|  | public class AccountReference | ||||||
|  | { | ||||||
|  |     public Guid Id { get; set; } | ||||||
|  |     public string Name { get; set; } = string.Empty; | ||||||
|  |     public string Nick { get; set; } = string.Empty; | ||||||
|  |     public string Language { get; set; } = string.Empty; | ||||||
|  |     public Instant? ActivatedAt { get; set; } | ||||||
|  |     public bool IsSuperuser { get; set; } | ||||||
|  |     public Guid? AutomatedId { get; set; } | ||||||
|  |     public AccountProfileReference Profile { get; set; } = null!; | ||||||
|  |     public List<AccountContactReference> Contacts { get; set; } = new(); | ||||||
|  |     public List<AccountBadgeReference> Badges { get; set; } = new(); | ||||||
|  |     public SubscriptionReference? PerkSubscription { get; set; } | ||||||
|  |     public Instant CreatedAt { get; set; } | ||||||
|  |     public Instant UpdatedAt { get; set; } | ||||||
|  |  | ||||||
|  |     public Proto.Account ToProtoValue() | ||||||
|  |     { | ||||||
|  |         var proto = new Proto.Account | ||||||
|  |         { | ||||||
|  |             Id = Id.ToString(), | ||||||
|  |             Name = Name, | ||||||
|  |             Nick = Nick, | ||||||
|  |             Language = Language, | ||||||
|  |             ActivatedAt = ActivatedAt?.ToTimestamp(), | ||||||
|  |             IsSuperuser = IsSuperuser, | ||||||
|  |             Profile = Profile.ToProtoValue(), | ||||||
|  |             PerkSubscription = PerkSubscription?.ToProtoValue(), | ||||||
|  |             CreatedAt = CreatedAt.ToTimestamp(), | ||||||
|  |             UpdatedAt = UpdatedAt.ToTimestamp() | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         foreach (var contact in Contacts) | ||||||
|  |             proto.Contacts.Add(contact.ToProtoValue()); | ||||||
|  |  | ||||||
|  |         foreach (var badge in Badges) | ||||||
|  |             proto.Badges.Add(badge.ToProtoValue()); | ||||||
|  |  | ||||||
|  |         return proto; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static AccountReference FromProtoValue(Proto.Account proto) | ||||||
|  |     { | ||||||
|  |         var account = new AccountReference | ||||||
|  |         { | ||||||
|  |             Id = Guid.Parse(proto.Id), | ||||||
|  |             Name = proto.Name, | ||||||
|  |             Nick = proto.Nick, | ||||||
|  |             Language = proto.Language, | ||||||
|  |             ActivatedAt = proto.ActivatedAt?.ToInstant(), | ||||||
|  |             IsSuperuser = proto.IsSuperuser, | ||||||
|  |             AutomatedId = string.IsNullOrEmpty(proto.AutomatedId) ? null : Guid.Parse(proto.AutomatedId), | ||||||
|  |             PerkSubscription = proto.PerkSubscription != null | ||||||
|  |                 ? SubscriptionReference.FromProtoValue(proto.PerkSubscription) | ||||||
|  |                 : null, | ||||||
|  |             CreatedAt = proto.CreatedAt.ToInstant(), | ||||||
|  |             UpdatedAt = proto.UpdatedAt.ToInstant() | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         account.Profile = AccountProfileReference.FromProtoValue(proto.Profile); | ||||||
|  |  | ||||||
|  |         foreach (var contactProto in proto.Contacts) | ||||||
|  |             account.Contacts.Add(AccountContactReference.FromProtoValue(contactProto)); | ||||||
|  |  | ||||||
|  |         foreach (var badgeProto in proto.Badges) | ||||||
|  |             account.Badges.Add(AccountBadgeReference.FromProtoValue(badgeProto)); | ||||||
|  |  | ||||||
|  |         return account; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | public class AccountProfileReference | ||||||
|  | { | ||||||
|  |     public Guid Id { get; set; } | ||||||
|  |     public string? FirstName { get; set; } | ||||||
|  |     public string? MiddleName { get; set; } | ||||||
|  |     public string? LastName { get; set; } | ||||||
|  |     public string? Bio { get; set; } | ||||||
|  |     public string? Gender { get; set; } | ||||||
|  |     public string? Pronouns { get; set; } | ||||||
|  |     public string? TimeZone { get; set; } | ||||||
|  |     public string? Location { get; set; } | ||||||
|  |     public List<ProfileLinkReference>? Links { get; set; } | ||||||
|  |     public Instant? Birthday { get; set; } | ||||||
|  |     public Instant? LastSeenAt { get; set; } | ||||||
|  |     public VerificationMark? Verification { get; set; } | ||||||
|  |     public int Experience { get; set; } | ||||||
|  |     public int Level => Leveling.ExperiencePerLevel.Count(xp => Experience >= xp) - 1; | ||||||
|  |     public double SocialCredits { get; set; } = 100; | ||||||
|  |  | ||||||
|  |     public int SocialCreditsLevel => SocialCredits switch | ||||||
|  |     { | ||||||
|  |         < 100 => -1, | ||||||
|  |         > 100 and < 200 => 0, | ||||||
|  |         < 200 => 1, | ||||||
|  |         _ => 2 | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     public double LevelingProgress => Level >= Leveling.ExperiencePerLevel.Count - 1 | ||||||
|  |         ? 100 | ||||||
|  |         : (Experience - Leveling.ExperiencePerLevel[Level]) * 100.0 / | ||||||
|  |           (Leveling.ExperiencePerLevel[Level + 1] - Leveling.ExperiencePerLevel[Level]); | ||||||
|  |  | ||||||
|  |     public CloudFileReferenceObject? Picture { get; set; } | ||||||
|  |     public CloudFileReferenceObject? Background { get; set; } | ||||||
|  |     public Guid AccountId { get; set; } | ||||||
|  |  | ||||||
|  |     public Proto.AccountProfile ToProtoValue() | ||||||
|  |     { | ||||||
|  |         var proto = new Proto.AccountProfile | ||||||
|  |         { | ||||||
|  |             Id = Id.ToString(), | ||||||
|  |             FirstName = FirstName ?? string.Empty, | ||||||
|  |             MiddleName = MiddleName ?? string.Empty, | ||||||
|  |             LastName = LastName ?? string.Empty, | ||||||
|  |             Bio = Bio ?? string.Empty, | ||||||
|  |             Gender = Gender ?? string.Empty, | ||||||
|  |             Pronouns = Pronouns ?? string.Empty, | ||||||
|  |             TimeZone = TimeZone ?? string.Empty, | ||||||
|  |             Location = Location ?? string.Empty, | ||||||
|  |             Birthday = Birthday?.ToTimestamp(), | ||||||
|  |             LastSeenAt = LastSeenAt?.ToTimestamp(), | ||||||
|  |             Experience = Experience, | ||||||
|  |             Level = Level, | ||||||
|  |             LevelingProgress = LevelingProgress, | ||||||
|  |             SocialCredits = SocialCredits, | ||||||
|  |             SocialCreditsLevel = SocialCreditsLevel, | ||||||
|  |             Picture = Picture?.ToProtoValue(), | ||||||
|  |             Background = Background?.ToProtoValue(), | ||||||
|  |             AccountId = AccountId.ToString(), | ||||||
|  |             Verification = Verification?.ToProtoValue(), | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         return proto; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static AccountProfileReference FromProtoValue(Proto.AccountProfile proto) | ||||||
|  |     { | ||||||
|  |         return new AccountProfileReference | ||||||
|  |         { | ||||||
|  |             Id = Guid.Parse(proto.Id), | ||||||
|  |             FirstName = string.IsNullOrEmpty(proto.FirstName) ? null : proto.FirstName, | ||||||
|  |             MiddleName = string.IsNullOrEmpty(proto.MiddleName) ? null : proto.MiddleName, | ||||||
|  |             LastName = string.IsNullOrEmpty(proto.LastName) ? null : proto.LastName, | ||||||
|  |             Bio = string.IsNullOrEmpty(proto.Bio) ? null : proto.Bio, | ||||||
|  |             Gender = string.IsNullOrEmpty(proto.Gender) ? null : proto.Gender, | ||||||
|  |             Pronouns = string.IsNullOrEmpty(proto.Pronouns) ? null : proto.Pronouns, | ||||||
|  |             TimeZone = string.IsNullOrEmpty(proto.TimeZone) ? null : proto.TimeZone, | ||||||
|  |             Location = string.IsNullOrEmpty(proto.Location) ? null : proto.Location, | ||||||
|  |             Birthday = proto.Birthday?.ToInstant(), | ||||||
|  |             LastSeenAt = proto.LastSeenAt?.ToInstant(), | ||||||
|  |             Experience = proto.Experience, | ||||||
|  |             SocialCredits = proto.SocialCredits, | ||||||
|  |             Picture = proto.Picture != null ? CloudFileReferenceObject.FromProtoValue(proto.Picture) : null, | ||||||
|  |             Background = proto.Background != null ? CloudFileReferenceObject.FromProtoValue(proto.Background) : null, | ||||||
|  |             AccountId = Guid.Parse(proto.AccountId), | ||||||
|  |             Verification = proto.Verification != null ? VerificationMark.FromProtoValue(proto.Verification) : null, | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | public class AccountContactReference : ModelBase | ||||||
|  | { | ||||||
|  |     public Guid Id { get; set; } | ||||||
|  |     public AccountContactReferenceType Type { get; set; } | ||||||
|  |     public Instant? VerifiedAt { get; set; } | ||||||
|  |     public bool IsPrimary { get; set; } = false; | ||||||
|  |     public bool IsPublic { get; set; } = false; | ||||||
|  |     public string Content { get; set; } = string.Empty;  | ||||||
|  |      | ||||||
|  |     public Guid AccountId { get; set; } | ||||||
|  |      | ||||||
|  |     public Shared.Proto.AccountContact ToProtoValue() | ||||||
|  |     { | ||||||
|  |         var proto = new Shared.Proto.AccountContact | ||||||
|  |         { | ||||||
|  |             Id = Id.ToString(), | ||||||
|  |             Type = Type switch | ||||||
|  |             { | ||||||
|  |                 AccountContactReferenceType.Email => Shared.Proto.AccountContactType.Email, | ||||||
|  |                 AccountContactReferenceType.PhoneNumber => Shared.Proto.AccountContactType.PhoneNumber, | ||||||
|  |                 AccountContactReferenceType.Address => Shared.Proto.AccountContactType.Address, | ||||||
|  |                 _ => Shared.Proto.AccountContactType.Unspecified | ||||||
|  |             }, | ||||||
|  |             Content = Content, | ||||||
|  |             IsPrimary = IsPrimary, | ||||||
|  |             VerifiedAt = VerifiedAt?.ToTimestamp(), | ||||||
|  |             AccountId = AccountId.ToString(), | ||||||
|  |             CreatedAt = CreatedAt.ToTimestamp(), | ||||||
|  |             UpdatedAt = UpdatedAt.ToTimestamp() | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         return proto; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static AccountContactReference FromProtoValue(Shared.Proto.AccountContact proto) | ||||||
|  |     { | ||||||
|  |         var contact = new AccountContactReference | ||||||
|  |         { | ||||||
|  |             Id = Guid.Parse(proto.Id), | ||||||
|  |             AccountId = Guid.Parse(proto.AccountId), | ||||||
|  |             Type = proto.Type switch | ||||||
|  |             { | ||||||
|  |                 Shared.Proto.AccountContactType.Email => AccountContactReferenceType.Email, | ||||||
|  |                 Shared.Proto.AccountContactType.PhoneNumber => AccountContactReferenceType.PhoneNumber, | ||||||
|  |                 Shared.Proto.AccountContactType.Address => AccountContactReferenceType.Address, | ||||||
|  |                 _ => AccountContactReferenceType.Email | ||||||
|  |             }, | ||||||
|  |             Content = proto.Content, | ||||||
|  |             IsPrimary = proto.IsPrimary, | ||||||
|  |             VerifiedAt = proto.VerifiedAt?.ToInstant(), | ||||||
|  |             CreatedAt = proto.CreatedAt.ToInstant(), | ||||||
|  |             UpdatedAt = proto.UpdatedAt.ToInstant() | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         return contact; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | public enum AccountContactReferenceType | ||||||
|  | { | ||||||
|  |     Email, | ||||||
|  |     PhoneNumber, | ||||||
|  |     Address | ||||||
|  | } | ||||||
|  |  | ||||||
|  | public class AccountBadgeReference : ModelBase | ||||||
|  | { | ||||||
|  |     public Guid Id { get; set; } = Guid.NewGuid(); | ||||||
|  |     public string Type { get; set; } = null!; | ||||||
|  |     public string? Label { get; set; } | ||||||
|  |     public string? Caption { get; set; } | ||||||
|  |     public Dictionary<string, object?> Meta { get; set; } = new(); | ||||||
|  |     public Instant? ActivatedAt { get; set; } | ||||||
|  |     public Instant? ExpiredAt { get; set; } | ||||||
|  |  | ||||||
|  |     public Guid AccountId { get; set; } | ||||||
|  |  | ||||||
|  |     public AccountBadge ToProtoValue() | ||||||
|  |     { | ||||||
|  |         var proto = new AccountBadge | ||||||
|  |         { | ||||||
|  |             Id = Id.ToString(), | ||||||
|  |             Type = Type, | ||||||
|  |             Label = Label ?? string.Empty, | ||||||
|  |             Caption = Caption ?? string.Empty, | ||||||
|  |             ActivatedAt = ActivatedAt?.ToTimestamp(), | ||||||
|  |             ExpiredAt = ExpiredAt?.ToTimestamp(), | ||||||
|  |             AccountId = AccountId.ToString(), | ||||||
|  |             CreatedAt = CreatedAt.ToTimestamp(), | ||||||
|  |             UpdatedAt = UpdatedAt.ToTimestamp() | ||||||
|  |         }; | ||||||
|  |         proto.Meta.Add(GrpcTypeHelper.ConvertToValueMap(Meta)); | ||||||
|  |  | ||||||
|  |         return proto; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static AccountBadgeReference FromProtoValue(AccountBadge proto) | ||||||
|  |     { | ||||||
|  |         var badge = new AccountBadgeReference | ||||||
|  |         { | ||||||
|  |             Id = Guid.Parse(proto.Id), | ||||||
|  |             AccountId = Guid.Parse(proto.AccountId), | ||||||
|  |             Type = proto.Type, | ||||||
|  |             Label = proto.Label, | ||||||
|  |             Caption = proto.Caption, | ||||||
|  |             ActivatedAt = proto.ActivatedAt?.ToInstant(), | ||||||
|  |             ExpiredAt = proto.ExpiredAt?.ToInstant(), | ||||||
|  |             CreatedAt = proto.CreatedAt.ToInstant(), | ||||||
|  |             UpdatedAt = proto.UpdatedAt.ToInstant() | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         return badge; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | public class ProfileLinkReference | ||||||
|  | { | ||||||
|  |     public string Name { get; set; } = string.Empty; | ||||||
|  |     public string Url { get; set; } = string.Empty; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | public static class Leveling | ||||||
|  | { | ||||||
|  |     public static readonly List<int> ExperiencePerLevel = | ||||||
|  |     [ | ||||||
|  |         0, // Level 0 | ||||||
|  |         100, // Level 1 | ||||||
|  |         250, // Level 2 | ||||||
|  |         500, // Level 3 | ||||||
|  |         1000, // Level 4 | ||||||
|  |         2000, // Level 5 | ||||||
|  |         4000, // Level 6 | ||||||
|  |         8000, // Level 7 | ||||||
|  |         16000, // Level 8 | ||||||
|  |         32000, // Level 9 | ||||||
|  |         64000, // Level 10 | ||||||
|  |         128000, // Level 11 | ||||||
|  |         256000, // Level 12 | ||||||
|  |         512000, // Level 13 | ||||||
|  |         1024000 | ||||||
|  |     ]; | ||||||
|  | } | ||||||
							
								
								
									
										64
									
								
								DysonNetwork.Shared/Data/Subscription.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								DysonNetwork.Shared/Data/Subscription.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | |||||||
|  | using NodaTime; | ||||||
|  | using NodaTime.Serialization.Protobuf; | ||||||
|  |  | ||||||
|  | namespace DysonNetwork.Shared.Data; | ||||||
|  |  | ||||||
|  | public class SubscriptionReference | ||||||
|  | { | ||||||
|  |     public Guid Id { get; set; } | ||||||
|  |     public string Identifier { get; set; } = string.Empty; | ||||||
|  |     public string DisplayName { get; set; } = string.Empty; | ||||||
|  |     public bool IsActive { get; set; } | ||||||
|  |     public bool IsAvailable { get; set; } | ||||||
|  |     public Instant BegunAt { get; set; } | ||||||
|  |     public Instant? EndedAt { get; set; } | ||||||
|  |     public Instant? RenewalAt { get; set; } | ||||||
|  |     public SubscriptionReferenceStatus Status { get; set; } | ||||||
|  |  | ||||||
|  |     public static SubscriptionReference FromProtoValue(Proto.SubscriptionReferenceObject proto) | ||||||
|  |     { | ||||||
|  |         return new SubscriptionReference | ||||||
|  |         { | ||||||
|  |             Id = Guid.Parse(proto.Id), | ||||||
|  |             Identifier = proto.Identifier, | ||||||
|  |             DisplayName = proto.DisplayName, | ||||||
|  |             IsActive = proto.IsActive, | ||||||
|  |             IsAvailable = proto.IsAvailable, | ||||||
|  |             BegunAt = proto.BegunAt.ToInstant(), | ||||||
|  |             EndedAt = proto.EndedAt?.ToInstant(), | ||||||
|  |             RenewalAt = proto.RenewalAt?.ToInstant(), | ||||||
|  |             Status = (SubscriptionReferenceStatus)proto.Status | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public Proto.SubscriptionReferenceObject ToProtoValue() | ||||||
|  |     { | ||||||
|  |         return new Proto.SubscriptionReferenceObject | ||||||
|  |         { | ||||||
|  |             Id = Id.ToString(), | ||||||
|  |             Identifier = Identifier, | ||||||
|  |             DisplayName = DisplayName, | ||||||
|  |             IsActive = IsActive, | ||||||
|  |             IsAvailable = IsAvailable, | ||||||
|  |             BegunAt = BegunAt.ToTimestamp(), | ||||||
|  |             EndedAt = EndedAt?.ToTimestamp(), | ||||||
|  |             RenewalAt = RenewalAt?.ToTimestamp(), | ||||||
|  |             Status = Status switch | ||||||
|  |             { | ||||||
|  |                 SubscriptionReferenceStatus.Unpaid => Proto.SubscriptionStatus.Unpaid, | ||||||
|  |                 SubscriptionReferenceStatus.Active => Proto.SubscriptionStatus.Active, | ||||||
|  |                 SubscriptionReferenceStatus.Expired => Proto.SubscriptionStatus.Expired, | ||||||
|  |                 SubscriptionReferenceStatus.Cancelled => Proto.SubscriptionStatus.Cancelled, | ||||||
|  |                 _ => Proto.SubscriptionStatus.Unpaid | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | public enum SubscriptionReferenceStatus | ||||||
|  | { | ||||||
|  |     Unpaid = 0, | ||||||
|  |     Active = 1, | ||||||
|  |     Expired = 2, | ||||||
|  |     Cancelled = 3 | ||||||
|  | } | ||||||
| @@ -32,6 +32,8 @@ message Account { | |||||||
|  |  | ||||||
|   google.protobuf.Timestamp created_at = 14; |   google.protobuf.Timestamp created_at = 14; | ||||||
|   google.protobuf.Timestamp updated_at = 15; |   google.protobuf.Timestamp updated_at = 15; | ||||||
|  |    | ||||||
|  |   optional string automated_id = 17; | ||||||
| } | } | ||||||
|  |  | ||||||
| // Enum for status attitude | // Enum for status attitude | ||||||
| @@ -246,7 +248,9 @@ message GetAccountStatusBatchResponse { | |||||||
| service AccountService { | service AccountService { | ||||||
|   // Account Operations |   // Account Operations | ||||||
|   rpc GetAccount(GetAccountRequest) returns (Account) {} |   rpc GetAccount(GetAccountRequest) returns (Account) {} | ||||||
|  |   rpc GetBotAccount(GetBotAccountRequest) returns (Account) {} | ||||||
|   rpc GetAccountBatch(GetAccountBatchRequest) returns (GetAccountBatchResponse) {} |   rpc GetAccountBatch(GetAccountBatchRequest) returns (GetAccountBatchResponse) {} | ||||||
|  |   rpc GetBotAccountBatch(GetBotAccountBatchRequest) returns (GetAccountBatchResponse) {} | ||||||
|   rpc LookupAccountBatch(LookupAccountBatchRequest) returns (GetAccountBatchResponse) {} |   rpc LookupAccountBatch(LookupAccountBatchRequest) returns (GetAccountBatchResponse) {} | ||||||
|   rpc ListAccounts(ListAccountsRequest) returns (ListAccountsResponse) {} |   rpc ListAccounts(ListAccountsRequest) returns (ListAccountsResponse) {} | ||||||
|  |  | ||||||
| @@ -321,10 +325,18 @@ message GetAccountRequest { | |||||||
|   string id = 1;  // Account ID to retrieve |   string id = 1;  // Account ID to retrieve | ||||||
| } | } | ||||||
|  |  | ||||||
|  | message GetBotAccountRequest { | ||||||
|  |   string automated_id = 1; | ||||||
|  | } | ||||||
|  |  | ||||||
| message GetAccountBatchRequest { | message GetAccountBatchRequest { | ||||||
|   repeated string id = 1;  // Account ID to retrieve |   repeated string id = 1;  // Account ID to retrieve | ||||||
| } | } | ||||||
|  |  | ||||||
|  | message GetBotAccountBatchRequest { | ||||||
|  |   repeated string automated_id = 1; | ||||||
|  | } | ||||||
|  |  | ||||||
| message LookupAccountBatchRequest { | message LookupAccountBatchRequest { | ||||||
|   repeated string names = 1; |   repeated string names = 1; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -108,6 +108,8 @@ message BotAccount { | |||||||
| message CreateBotAccountRequest { | message CreateBotAccountRequest { | ||||||
|   Account account = 1; |   Account account = 1; | ||||||
|   string automated_id = 2; |   string automated_id = 2; | ||||||
|  |   optional string picture_id = 8; | ||||||
|  |   optional string background_id = 9; | ||||||
| } | } | ||||||
|  |  | ||||||
| message CreateBotAccountResponse { | message CreateBotAccountResponse { | ||||||
| @@ -117,6 +119,8 @@ message CreateBotAccountResponse { | |||||||
| message UpdateBotAccountRequest { | message UpdateBotAccountRequest { | ||||||
|   string automated_id = 1;       // ID of the bot account to update |   string automated_id = 1;       // ID of the bot account to update | ||||||
|   Account account = 2;           // Updated account information |   Account account = 2;           // Updated account information | ||||||
|  |   optional string picture_id = 8; | ||||||
|  |   optional string background_id = 9; | ||||||
| } | } | ||||||
|  |  | ||||||
| message UpdateBotAccountResponse { | message UpdateBotAccountResponse { | ||||||
|   | |||||||
| @@ -12,6 +12,13 @@ public class AccountClientHelper(AccountService.AccountServiceClient accounts) | |||||||
|         return response; |         return response; | ||||||
|     } |     } | ||||||
|      |      | ||||||
|  |     public async Task<Account> GetBotAccount(Guid automatedId) | ||||||
|  |     { | ||||||
|  |         var request = new GetBotAccountRequest { AutomatedId = automatedId.ToString() }; | ||||||
|  |         var response = await accounts.GetBotAccountAsync(request); | ||||||
|  |         return response; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public async Task<List<Account>> GetAccountBatch(List<Guid> ids) |     public async Task<List<Account>> GetAccountBatch(List<Guid> ids) | ||||||
|     { |     { | ||||||
|         var request = new GetAccountBatchRequest(); |         var request = new GetAccountBatchRequest(); | ||||||
| @@ -20,6 +27,14 @@ public class AccountClientHelper(AccountService.AccountServiceClient accounts) | |||||||
|         return response.Accounts.ToList(); |         return response.Accounts.ToList(); | ||||||
|     } |     } | ||||||
|      |      | ||||||
|  |     public async Task<List<Account>> GetBotAccountBatch(List<Guid> automatedIds) | ||||||
|  |     { | ||||||
|  |         var request = new GetBotAccountBatchRequest(); | ||||||
|  |         request.AutomatedId.AddRange(automatedIds.Select(id => id.ToString())); | ||||||
|  |         var response = await accounts.GetBotAccountBatchAsync(request); | ||||||
|  |         return response.Accounts.ToList(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public async Task<Dictionary<Guid, AccountStatusReference>> GetAccountStatusBatch(List<Guid> ids) |     public async Task<Dictionary<Guid, AccountStatusReference>> GetAccountStatusBatch(List<Guid> ids) | ||||||
|     { |     { | ||||||
|         var request = new GetAccountBatchRequest(); |         var request = new GetAccountBatchRequest(); | ||||||
|   | |||||||
| @@ -75,6 +75,7 @@ public class StickerController( | |||||||
|         var packs = await queryable |         var packs = await queryable | ||||||
|             .Skip(offset) |             .Skip(offset) | ||||||
|             .Take(take) |             .Take(take) | ||||||
|  |             .Include(e => e.Stickers.OrderByDescending(s => s.CreatedAt).Take(8)) | ||||||
|             .ToListAsync(); |             .ToListAsync(); | ||||||
|  |  | ||||||
|         Response.Headers["X-Total"] = totalCount.ToString(); |         Response.Headers["X-Total"] = totalCount.ToString(); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user