Compare commits
	
		
			2 Commits
		
	
	
		
			50e888b075
			...
			5d7429a416
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 5d7429a416 | |||
| fb7e52d6f3 | 
| @@ -1,7 +1,7 @@ | ||||
| using System.ComponentModel.DataAnnotations; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| using DysonNetwork.Develop.Project; | ||||
| using DysonNetwork.Shared.Data; | ||||
| using NodaTime; | ||||
| using NodaTime.Serialization.Protobuf; | ||||
|  | ||||
| namespace DysonNetwork.Develop.Identity; | ||||
| @@ -15,6 +15,8 @@ public class BotAccount : ModelBase | ||||
|  | ||||
|     public Guid ProjectId { get; set; } | ||||
|     public DevProject Project { get; set; } = null!; | ||||
|      | ||||
|     [NotMapped] public AccountReference? Account { get; set; } | ||||
|  | ||||
|     public Shared.Proto.BotAccount ToProtoValue() | ||||
|     { | ||||
|   | ||||
| @@ -1,8 +1,11 @@ | ||||
| using System.ComponentModel.DataAnnotations; | ||||
| using DysonNetwork.Develop.Project; | ||||
| using DysonNetwork.Shared.Proto; | ||||
| using DysonNetwork.Shared.Registry; | ||||
| using Microsoft.AspNetCore.Authorization; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
| using NodaTime; | ||||
| using NodaTime.Serialization.Protobuf; | ||||
|  | ||||
| namespace DysonNetwork.Develop.Identity; | ||||
|  | ||||
| @@ -13,18 +16,61 @@ public class BotAccountController( | ||||
|     BotAccountService botService, | ||||
|     DeveloperService developerService, | ||||
|     DevProjectService projectService, | ||||
|     ILogger<BotAccountController> logger | ||||
|     ILogger<BotAccountController> logger, | ||||
|     AccountClientHelper accounts | ||||
| ) | ||||
|     : ControllerBase | ||||
| { | ||||
|     public record BotRequest( | ||||
|         [Required] [MaxLength(1024)] string? Slug | ||||
|     ); | ||||
|     public class CommonBotRequest | ||||
|     { | ||||
|         [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(1024)] string? Slug, | ||||
|         bool? IsActive | ||||
|     ) : BotRequest(Slug); | ||||
|         [MaxLength(32)] public string? PictureId { get; set; } | ||||
|         [MaxLength(32)] public string? BackgroundId { get; set; } | ||||
|     } | ||||
|  | ||||
|     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] | ||||
|     public async Task<IActionResult> ListBots( | ||||
| @@ -47,7 +93,7 @@ public class BotAccountController( | ||||
|             return NotFound("Project not found or you don't have access"); | ||||
|  | ||||
|         var bots = await botService.GetBotsByProjectAsync(projectId); | ||||
|         return Ok(bots); | ||||
|         return Ok(await botService.LoadBotsAccountAsync(bots)); | ||||
|     } | ||||
|  | ||||
|     [HttpGet("{botId:guid}")] | ||||
| @@ -75,18 +121,16 @@ public class BotAccountController( | ||||
|         if (bot is null || bot.ProjectId != projectId) | ||||
|             return NotFound("Bot not found"); | ||||
|  | ||||
|         return Ok(bot); | ||||
|         return Ok(await botService.LoadBotAccountAsync(bot)); | ||||
|     } | ||||
|  | ||||
|     [HttpPost] | ||||
|     public async Task<IActionResult> CreateBot( | ||||
|         [FromRoute] string pubName, | ||||
|         [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) | ||||
|             return Unauthorized(); | ||||
|  | ||||
| @@ -102,9 +146,30 @@ public class BotAccountController( | ||||
|         if (project is null) | ||||
|             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 | ||||
|         { | ||||
|             var bot = await botService.CreateBotAsync(project, request.Slug); | ||||
|             var bot = await botService.CreateBotAsync(project, createRequest.Slug, account); | ||||
|             return Ok(bot); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
| @@ -141,10 +206,29 @@ public class BotAccountController( | ||||
|         if (bot is null || bot.ProjectId != projectId) | ||||
|             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 | ||||
|         { | ||||
|             var updatedBot = await botService.UpdateBotAsync( | ||||
|                 bot, | ||||
|                 botAccount, | ||||
|                 request.Slug, | ||||
|                 request.IsActive | ||||
|             ); | ||||
|   | ||||
| @@ -1,13 +1,18 @@ | ||||
| using DysonNetwork.Develop.Project; | ||||
| using DysonNetwork.Shared.Data; | ||||
| using DysonNetwork.Shared.Proto; | ||||
| using DysonNetwork.Shared.Registry; | ||||
| using Grpc.Core; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using NodaTime; | ||||
| using NodaTime.Serialization.Protobuf; | ||||
|  | ||||
| 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) | ||||
|     { | ||||
| @@ -23,39 +28,23 @@ public class BotAccountService(AppDatabase db, BotAccountReceiverService.BotAcco | ||||
|             .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 | ||||
|         var existingBot = await db.BotAccounts | ||||
|             .FirstOrDefaultAsync(b => b.ProjectId == project.Id && b.Slug == slug); | ||||
|              | ||||
|  | ||||
|         if (existingBot != null) | ||||
|         { | ||||
|             throw new InvalidOperationException("A bot with this slug already exists in this project."); | ||||
|         } | ||||
|  | ||||
|         var now = SystemClock.Instance.GetCurrentInstant(); | ||||
|          | ||||
|         try | ||||
|         { | ||||
|             // First create the bot account in the Pass service | ||||
|             var createRequest = new CreateBotAccountRequest | ||||
|             { | ||||
|                 AutomatedId = Guid.NewGuid().ToString(), | ||||
|                 Account = new 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() | ||||
|                 } | ||||
|                 Account = account | ||||
|             }; | ||||
|  | ||||
|             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) | ||||
|         { | ||||
|             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) | ||||
|         { | ||||
| @@ -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; | ||||
|         if (slug != null && bot.Slug != slug) | ||||
| @@ -100,7 +91,7 @@ public class BotAccountService(AppDatabase db, BotAccountReceiverService.BotAcco | ||||
|             bot.Slug = slug; | ||||
|             updated = true; | ||||
|         } | ||||
|          | ||||
|  | ||||
|         if (isActive.HasValue && bot.IsActive != isActive.Value) | ||||
|         { | ||||
|             bot.IsActive = isActive.Value; | ||||
| @@ -108,19 +99,14 @@ public class BotAccountService(AppDatabase db, BotAccountReceiverService.BotAcco | ||||
|         } | ||||
|  | ||||
|         if (!updated) return bot; | ||||
|          | ||||
|  | ||||
|         try | ||||
|         { | ||||
|             // Update the bot account in the Pass service | ||||
|             var updateRequest = new UpdateBotAccountRequest | ||||
|             { | ||||
|                 AutomatedId = bot.Id.ToString(), | ||||
|                 Account = new Shared.Proto.Account | ||||
|                 { | ||||
|                     Name = $"bot-{bot.Slug}", | ||||
|                     Nick = $"Bot {bot.Slug}", | ||||
|                     UpdatedAt = SystemClock.Instance.GetCurrentInstant().ToTimestamp() | ||||
|                 } | ||||
|                 Account = account | ||||
|             }; | ||||
|  | ||||
|             var updateResponse = await accountReceiver.UpdateBotAccountAsync(updateRequest); | ||||
| @@ -160,9 +146,28 @@ public class BotAccountService(AppDatabase db, BotAccountReceiverService.BotAcco | ||||
|         { | ||||
|             // Account not found in Pass service, continue with local deletion | ||||
|         } | ||||
|          | ||||
|  | ||||
|         // Delete the local bot account | ||||
|         db.BotAccounts.Remove(bot); | ||||
|         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.Text.Json.Serialization; | ||||
| using DysonNetwork.Develop.Project; | ||||
| using DysonNetwork.Shared.Proto; | ||||
| using DysonNetwork.Shared.Data; | ||||
| @@ -11,7 +12,7 @@ public class Developer | ||||
|     public Guid Id { get; set; } = Guid.NewGuid(); | ||||
|     public Guid PublisherId { get; set; } | ||||
|      | ||||
|     public List<DevProject> Projects { get; set; } = []; | ||||
|     [JsonIgnore] public List<DevProject> Projects { get; set; } = []; | ||||
|      | ||||
|     [NotMapped] public PublisherInfo? Publisher { get; set; } | ||||
| } | ||||
|   | ||||
| @@ -24,7 +24,7 @@ | ||||
|   }, | ||||
|   "Service": { | ||||
|     "Name": "DysonNetwork.Develop", | ||||
|     "Url": "https://localhost:7099", | ||||
|     "Url": "https://localhost:7192", | ||||
|     "ClientCert": "../Certificates/client.crt", | ||||
|     "ClientKey": "../Certificates/client.key" | ||||
|   } | ||||
|   | ||||
| @@ -51,7 +51,8 @@ public class Account : ModelBase | ||||
|             Profile = Profile.ToProtoValue(), | ||||
|             PerkSubscription = PerkSubscription?.ToProtoValue(), | ||||
|             CreatedAt = CreatedAt.ToTimestamp(), | ||||
|             UpdatedAt = UpdatedAt.ToTimestamp() | ||||
|             UpdatedAt = UpdatedAt.ToTimestamp(), | ||||
|             AutomatedId = AutomatedId?.ToString() | ||||
|         }; | ||||
|  | ||||
|         // Add contacts | ||||
| @@ -81,6 +82,7 @@ public class Account : ModelBase | ||||
|                 : null, | ||||
|             CreatedAt = proto.CreatedAt.ToInstant(), | ||||
|             UpdatedAt = proto.UpdatedAt.ToInstant(), | ||||
|             AutomatedId = proto.AutomatedId is not null ? Guid.Parse(proto.AutomatedId) : null | ||||
|         }; | ||||
|  | ||||
|         account.Profile = AccountProfile.FromProtoValue(proto.Profile); | ||||
| @@ -119,7 +121,7 @@ public abstract class Leveling | ||||
|  | ||||
| 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? MiddleName { get; set; } | ||||
|     [MaxLength(256)] public string? LastName { get; set; } | ||||
|   | ||||
| @@ -30,6 +30,7 @@ public class AccountCurrentController( | ||||
| { | ||||
|     [HttpGet] | ||||
|     [ProducesResponseType<Account>(StatusCodes.Status200OK)] | ||||
|     [ProducesResponseType<ApiError>(StatusCodes.Status401Unauthorized)] | ||||
|     public async Task<ActionResult<Account>> GetCurrentIdentity() | ||||
|     { | ||||
|         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.Permission; | ||||
| using DysonNetwork.Shared.Cache; | ||||
| using DysonNetwork.Shared.Data; | ||||
| using DysonNetwork.Shared.Proto; | ||||
| using DysonNetwork.Shared.Stream; | ||||
| using EFCore.BulkExtensions; | ||||
| @@ -21,6 +22,8 @@ namespace DysonNetwork.Pass.Account; | ||||
| public class AccountService( | ||||
|     AppDatabase db, | ||||
|     MagicSpellService spells, | ||||
|     FileService.FileServiceClient files, | ||||
|     FileReferenceService.FileReferenceServiceClient fileRefs, | ||||
|     AccountUsernameService uname, | ||||
|     EmailService mailer, | ||||
|     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(); | ||||
|         if (dupeAutomateCount > 0) | ||||
| @@ -195,8 +198,38 @@ public class AccountService( | ||||
|         account.AutomatedId = automatedId; | ||||
|         account.ActivatedAt = SystemClock.Instance.GetCurrentInstant(); | ||||
|         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); | ||||
|         await db.SaveChangesAsync(); | ||||
|  | ||||
|         return account; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -42,6 +42,26 @@ public class AccountServiceGrpc( | ||||
|         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, | ||||
|         ServerCallContext context) | ||||
|     { | ||||
| @@ -56,7 +76,35 @@ public class AccountServiceGrpc( | ||||
|             .Where(a => accountIds.Contains(a.Id)) | ||||
|             .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<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() | ||||
|         ); | ||||
| @@ -76,7 +124,8 @@ public class AccountServiceGrpc( | ||||
|         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 | ||||
|             .Select(id => Guid.TryParse(id, out var accountId) ? accountId : (Guid?)null) | ||||
| @@ -98,14 +147,14 @@ public class AccountServiceGrpc( | ||||
|             .Where(a => accountNames.Contains(a.Name)) | ||||
|             .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; | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| using DysonNetwork.Shared.Data; | ||||
| using DysonNetwork.Shared.Proto; | ||||
| using Grpc.Core; | ||||
| using NodaTime; | ||||
| @@ -5,7 +6,12 @@ using NodaTime.Serialization.Protobuf; | ||||
|  | ||||
| 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 | ||||
| { | ||||
|     public override async Task<CreateBotAccountResponse> CreateBotAccount( | ||||
| @@ -14,7 +20,12 @@ public class BotAccountReceiverGrpc(AppDatabase db, AccountService accounts) | ||||
|     ) | ||||
|     { | ||||
|         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 | ||||
|         { | ||||
| @@ -34,16 +45,44 @@ public class BotAccountReceiverGrpc(AppDatabase db, AccountService accounts) | ||||
|         ServerCallContext context | ||||
|     ) | ||||
|     { | ||||
|         var automatedId = Guid.Parse(request.AutomatedId); | ||||
|         var account = await accounts.GetBotAccount(automatedId); | ||||
|         if (account is null) | ||||
|             throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, "Account not found")); | ||||
|         var account = Account.FromProtoValue(request.Account); | ||||
|  | ||||
|         if (request.PictureId is not null) | ||||
|         { | ||||
|             var file = await files.GetFileAsync(new GetFileRequest { Id = request.PictureId }); | ||||
|             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); | ||||
|         } | ||||
|  | ||||
|         account.Name = request.Account.Name; | ||||
|         account.Nick = request.Account.Nick; | ||||
|         account.Profile = AccountProfile.FromProtoValue(request.Account.Profile); | ||||
|         account.Language = request.Account.Language; | ||||
|          | ||||
|         db.Accounts.Update(account); | ||||
|         await db.SaveChangesAsync(); | ||||
|  | ||||
| @@ -56,7 +95,7 @@ public class BotAccountReceiverGrpc(AppDatabase db, AccountService accounts) | ||||
|                 CreatedAt = account.CreatedAt.ToTimestamp(), | ||||
|                 UpdatedAt = account.UpdatedAt.ToTimestamp(), | ||||
|                 IsActive = true | ||||
|             }  | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
|  | ||||
| @@ -69,9 +108,9 @@ public class BotAccountReceiverGrpc(AppDatabase db, AccountService accounts) | ||||
|         var account = await accounts.GetBotAccount(automatedId); | ||||
|         if (account is null) | ||||
|             throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, "Account not found")); | ||||
|          | ||||
|  | ||||
|         await accounts.DeleteAccount(account); | ||||
|          | ||||
|  | ||||
|         return new DeleteBotAccountResponse(); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										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 updated_at = 15; | ||||
|    | ||||
|   optional string automated_id = 17; | ||||
| } | ||||
|  | ||||
| // Enum for status attitude | ||||
| @@ -246,7 +248,9 @@ message GetAccountStatusBatchResponse { | ||||
| service AccountService { | ||||
|   // Account Operations | ||||
|   rpc GetAccount(GetAccountRequest) returns (Account) {} | ||||
|   rpc GetBotAccount(GetBotAccountRequest) returns (Account) {} | ||||
|   rpc GetAccountBatch(GetAccountBatchRequest) returns (GetAccountBatchResponse) {} | ||||
|   rpc GetBotAccountBatch(GetBotAccountBatchRequest) returns (GetAccountBatchResponse) {} | ||||
|   rpc LookupAccountBatch(LookupAccountBatchRequest) returns (GetAccountBatchResponse) {} | ||||
|   rpc ListAccounts(ListAccountsRequest) returns (ListAccountsResponse) {} | ||||
|  | ||||
| @@ -321,10 +325,18 @@ message GetAccountRequest { | ||||
|   string id = 1;  // Account ID to retrieve | ||||
| } | ||||
|  | ||||
| message GetBotAccountRequest { | ||||
|   string automated_id = 1; | ||||
| } | ||||
|  | ||||
| message GetAccountBatchRequest { | ||||
|   repeated string id = 1;  // Account ID to retrieve | ||||
| } | ||||
|  | ||||
| message GetBotAccountBatchRequest { | ||||
|   repeated string automated_id = 1; | ||||
| } | ||||
|  | ||||
| message LookupAccountBatchRequest { | ||||
|   repeated string names = 1; | ||||
| } | ||||
|   | ||||
| @@ -108,6 +108,8 @@ message BotAccount { | ||||
| message CreateBotAccountRequest { | ||||
|   Account account = 1; | ||||
|   string automated_id = 2; | ||||
|   optional string picture_id = 8; | ||||
|   optional string background_id = 9; | ||||
| } | ||||
|  | ||||
| message CreateBotAccountResponse { | ||||
| @@ -117,6 +119,8 @@ message CreateBotAccountResponse { | ||||
| message UpdateBotAccountRequest { | ||||
|   string automated_id = 1;       // ID of the bot account to update | ||||
|   Account account = 2;           // Updated account information | ||||
|   optional string picture_id = 8; | ||||
|   optional string background_id = 9; | ||||
| } | ||||
|  | ||||
| message UpdateBotAccountResponse { | ||||
|   | ||||
| @@ -11,6 +11,13 @@ public class AccountClientHelper(AccountService.AccountServiceClient accounts) | ||||
|         var response = await accounts.GetAccountAsync(request); | ||||
|         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) | ||||
|     { | ||||
| @@ -19,6 +26,14 @@ public class AccountClientHelper(AccountService.AccountServiceClient accounts) | ||||
|         var response = await accounts.GetAccountBatchAsync(request); | ||||
|         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) | ||||
|     { | ||||
|   | ||||
| @@ -75,6 +75,7 @@ public class StickerController( | ||||
|         var packs = await queryable | ||||
|             .Skip(offset) | ||||
|             .Take(take) | ||||
|             .Include(e => e.Stickers.OrderByDescending(s => s.CreatedAt).Take(8)) | ||||
|             .ToListAsync(); | ||||
|  | ||||
|         Response.Headers["X-Total"] = totalCount.ToString(); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user