♻️ Refind bot account
This commit is contained in:
@@ -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;
|
||||
@@ -16,6 +16,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()
|
||||
{
|
||||
var proto = new Shared.Proto.BotAccount
|
||||
|
@@ -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,7 +28,7 @@ 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
|
||||
@@ -34,28 +39,12 @@ public class BotAccountService(AppDatabase db, BotAccountReceiverService.BotAcco
|
||||
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)
|
||||
@@ -115,12 +106,7 @@ public class BotAccountService(AppDatabase db, BotAccountReceiverService.BotAcco
|
||||
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);
|
||||
@@ -165,4 +151,23 @@ public class BotAccountService(AppDatabase db, BotAccountReceiverService.BotAcco
|
||||
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)
|
||||
{
|
||||
@@ -69,6 +89,34 @@ public class AccountServiceGrpc(
|
||||
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)
|
||||
{
|
||||
var accountId = Guid.Parse(request.Id);
|
||||
@@ -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)
|
||||
|
@@ -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,15 +45,43 @@ 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);
|
||||
|
||||
account.Name = request.Account.Name;
|
||||
account.Nick = request.Account.Nick;
|
||||
account.Profile = AccountProfile.FromProtoValue(request.Account.Profile);
|
||||
account.Language = request.Account.Language;
|
||||
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);
|
||||
}
|
||||
|
||||
db.Accounts.Update(account);
|
||||
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 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 {
|
||||
|
@@ -12,6 +12,13 @@ public class AccountClientHelper(AccountService.AccountServiceClient accounts)
|
||||
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)
|
||||
{
|
||||
var request = new GetAccountBatchRequest();
|
||||
@@ -20,6 +27,14 @@ public class AccountClientHelper(AccountService.AccountServiceClient accounts)
|
||||
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)
|
||||
{
|
||||
var request = new GetAccountBatchRequest();
|
||||
|
Reference in New Issue
Block a user