♻️ Finish centerlizing the data models

This commit is contained in:
2025-09-27 15:14:05 +08:00
parent e70d8371f8
commit 9ce31c4dd8
167 changed files with 780 additions and 42880 deletions

View File

@@ -1,4 +1,3 @@
using System.Text.Json;
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Design;

View File

@@ -1,6 +1,5 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using DysonNetwork.Develop.Project; using DysonNetwork.Develop.Project;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
using DysonNetwork.Shared.Proto; using DysonNetwork.Shared.Proto;
using DysonNetwork.Shared.Registry; using DysonNetwork.Shared.Registry;
@@ -89,7 +88,7 @@ public class BotAccountController(
return NotFound("Developer not found"); return NotFound("Developer not found");
if (!await developerService.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id), if (!await developerService.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id),
PublisherMemberRole.Viewer)) Shared.Proto.PublisherMemberRole.Viewer))
return StatusCode(403, "You must be an viewer of the developer to list bots"); return StatusCode(403, "You must be an viewer of the developer to list bots");
var project = await projectService.GetProjectAsync(projectId, developer.Id); var project = await projectService.GetProjectAsync(projectId, developer.Id);
@@ -114,7 +113,7 @@ public class BotAccountController(
return NotFound("Developer not found"); return NotFound("Developer not found");
if (!await developerService.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id), if (!await developerService.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id),
PublisherMemberRole.Viewer)) Shared.Proto.PublisherMemberRole.Viewer))
return StatusCode(403, "You must be an viewer of the developer to view bot details"); return StatusCode(403, "You must be an viewer of the developer to view bot details");
var project = await projectService.GetProjectAsync(projectId, developer.Id); var project = await projectService.GetProjectAsync(projectId, developer.Id);
@@ -143,7 +142,7 @@ public class BotAccountController(
return NotFound("Developer not found"); return NotFound("Developer not found");
if (!await developerService.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id), if (!await developerService.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id),
PublisherMemberRole.Editor)) Shared.Proto.PublisherMemberRole.Editor))
return StatusCode(403, "You must be an editor of the developer to create a bot"); return StatusCode(403, "You must be an editor of the developer to create a bot");
var project = await projectService.GetProjectAsync(projectId, developer.Id); var project = await projectService.GetProjectAsync(projectId, developer.Id);
@@ -212,7 +211,7 @@ public class BotAccountController(
return NotFound("Developer not found"); return NotFound("Developer not found");
if (!await developerService.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id), if (!await developerService.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id),
PublisherMemberRole.Editor)) Shared.Proto.PublisherMemberRole.Editor))
return StatusCode(403, "You must be an editor of the developer to update a bot"); return StatusCode(403, "You must be an editor of the developer to update a bot");
var project = await projectService.GetProjectAsync(projectId, developer.Id); var project = await projectService.GetProjectAsync(projectId, developer.Id);
@@ -273,7 +272,7 @@ public class BotAccountController(
return NotFound("Developer not found"); return NotFound("Developer not found");
if (!await developerService.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id), if (!await developerService.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id),
PublisherMemberRole.Editor)) Shared.Proto.PublisherMemberRole.Editor))
return StatusCode(403, "You must be an editor of the developer to delete a bot"); return StatusCode(403, "You must be an editor of the developer to delete a bot");
var project = await projectService.GetProjectAsync(projectId, developer.Id); var project = await projectService.GetProjectAsync(projectId, developer.Id);
@@ -297,7 +296,7 @@ public class BotAccountController(
} }
[HttpGet("{botId:guid}/keys")] [HttpGet("{botId:guid}/keys")]
public async Task<ActionResult<List<ApiKeyReference>>> ListBotKeys( public async Task<ActionResult<List<SnApiKey>>> ListBotKeys(
[FromRoute] string pubName, [FromRoute] string pubName,
[FromRoute] Guid projectId, [FromRoute] Guid projectId,
[FromRoute] Guid botId [FromRoute] Guid botId
@@ -306,7 +305,7 @@ public class BotAccountController(
if (HttpContext.Items["CurrentUser"] is not Account currentUser) if (HttpContext.Items["CurrentUser"] is not Account currentUser)
return Unauthorized(); return Unauthorized();
var (developer, project, bot) = await ValidateBotAccess(pubName, projectId, botId, currentUser, PublisherMemberRole.Viewer); var (developer, project, bot) = await ValidateBotAccess(pubName, projectId, botId, currentUser, Shared.Proto.PublisherMemberRole.Viewer);
if (developer == null) return NotFound("Developer not found"); if (developer == null) return NotFound("Developer not found");
if (project == null) return NotFound("Project not found or you don't have access"); if (project == null) return NotFound("Project not found or you don't have access");
if (bot == null) return NotFound("Bot not found"); if (bot == null) return NotFound("Bot not found");
@@ -315,13 +314,13 @@ public class BotAccountController(
{ {
AutomatedId = bot.Id.ToString() AutomatedId = bot.Id.ToString()
}); });
var data = keys.Data.Select(ApiKeyReference.FromProtoValue).ToList(); var data = keys.Data.Select(SnApiKey.FromProtoValue).ToList();
return Ok(data); return Ok(data);
} }
[HttpGet("{botId:guid}/keys/{keyId:guid}")] [HttpGet("{botId:guid}/keys/{keyId:guid}")]
public async Task<ActionResult<ApiKeyReference>> GetBotKey( public async Task<ActionResult<SnApiKey>> GetBotKey(
[FromRoute] string pubName, [FromRoute] string pubName,
[FromRoute] Guid projectId, [FromRoute] Guid projectId,
[FromRoute] Guid botId, [FromRoute] Guid botId,
@@ -330,7 +329,7 @@ public class BotAccountController(
if (HttpContext.Items["CurrentUser"] is not Account currentUser) if (HttpContext.Items["CurrentUser"] is not Account currentUser)
return Unauthorized(); return Unauthorized();
var (developer, project, bot) = await ValidateBotAccess(pubName, projectId, botId, currentUser, PublisherMemberRole.Viewer); var (developer, project, bot) = await ValidateBotAccess(pubName, projectId, botId, currentUser, Shared.Proto.PublisherMemberRole.Viewer);
if (developer == null) return NotFound("Developer not found"); if (developer == null) return NotFound("Developer not found");
if (project == null) return NotFound("Project not found or you don't have access"); if (project == null) return NotFound("Project not found or you don't have access");
if (bot == null) return NotFound("Bot not found"); if (bot == null) return NotFound("Bot not found");
@@ -339,7 +338,7 @@ public class BotAccountController(
{ {
var key = await accountsReceiver.GetApiKeyAsync(new GetApiKeyRequest { Id = keyId.ToString() }); var key = await accountsReceiver.GetApiKeyAsync(new GetApiKeyRequest { Id = keyId.ToString() });
if (key == null) return NotFound("API key not found"); if (key == null) return NotFound("API key not found");
return Ok(ApiKeyReference.FromProtoValue(key)); return Ok(SnApiKey.FromProtoValue(key));
} }
catch (RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.NotFound) catch (RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.NotFound)
{ {
@@ -354,7 +353,7 @@ public class BotAccountController(
} }
[HttpPost("{botId:guid}/keys")] [HttpPost("{botId:guid}/keys")]
public async Task<ActionResult<ApiKeyReference>> CreateBotKey( public async Task<ActionResult<SnApiKey>> CreateBotKey(
[FromRoute] string pubName, [FromRoute] string pubName,
[FromRoute] Guid projectId, [FromRoute] Guid projectId,
[FromRoute] Guid botId, [FromRoute] Guid botId,
@@ -363,7 +362,7 @@ public class BotAccountController(
if (HttpContext.Items["CurrentUser"] is not Account currentUser) if (HttpContext.Items["CurrentUser"] is not Account currentUser)
return Unauthorized(); return Unauthorized();
var (developer, project, bot) = await ValidateBotAccess(pubName, projectId, botId, currentUser, PublisherMemberRole.Editor); var (developer, project, bot) = await ValidateBotAccess(pubName, projectId, botId, currentUser, Shared.Proto.PublisherMemberRole.Editor);
if (developer == null) return NotFound("Developer not found"); if (developer == null) return NotFound("Developer not found");
if (project == null) return NotFound("Project not found or you don't have access"); if (project == null) return NotFound("Project not found or you don't have access");
if (bot == null) return NotFound("Bot not found"); if (bot == null) return NotFound("Bot not found");
@@ -377,7 +376,7 @@ public class BotAccountController(
}; };
var createdKey = await accountsReceiver.CreateApiKeyAsync(newKey); var createdKey = await accountsReceiver.CreateApiKeyAsync(newKey);
return Ok(ApiKeyReference.FromProtoValue(createdKey)); return Ok(SnApiKey.FromProtoValue(createdKey));
} }
catch (RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.InvalidArgument) catch (RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.InvalidArgument)
{ {
@@ -386,7 +385,7 @@ public class BotAccountController(
} }
[HttpPost("{botId:guid}/keys/{keyId:guid}/rotate")] [HttpPost("{botId:guid}/keys/{keyId:guid}/rotate")]
public async Task<ActionResult<ApiKeyReference>> RotateBotKey( public async Task<ActionResult<SnApiKey>> RotateBotKey(
[FromRoute] string pubName, [FromRoute] string pubName,
[FromRoute] Guid projectId, [FromRoute] Guid projectId,
[FromRoute] Guid botId, [FromRoute] Guid botId,
@@ -395,7 +394,7 @@ public class BotAccountController(
if (HttpContext.Items["CurrentUser"] is not Account currentUser) if (HttpContext.Items["CurrentUser"] is not Account currentUser)
return Unauthorized(); return Unauthorized();
var (developer, project, bot) = await ValidateBotAccess(pubName, projectId, botId, currentUser, PublisherMemberRole.Editor); var (developer, project, bot) = await ValidateBotAccess(pubName, projectId, botId, currentUser, Shared.Proto.PublisherMemberRole.Editor);
if (developer == null) return NotFound("Developer not found"); if (developer == null) return NotFound("Developer not found");
if (project == null) return NotFound("Project not found or you don't have access"); if (project == null) return NotFound("Project not found or you don't have access");
if (bot == null) return NotFound("Bot not found"); if (bot == null) return NotFound("Bot not found");
@@ -403,7 +402,7 @@ public class BotAccountController(
try try
{ {
var rotatedKey = await accountsReceiver.RotateApiKeyAsync(new GetApiKeyRequest { Id = keyId.ToString() }); var rotatedKey = await accountsReceiver.RotateApiKeyAsync(new GetApiKeyRequest { Id = keyId.ToString() });
return Ok(ApiKeyReference.FromProtoValue(rotatedKey)); return Ok(SnApiKey.FromProtoValue(rotatedKey));
} }
catch (RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.NotFound) catch (RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.NotFound)
{ {
@@ -421,7 +420,7 @@ public class BotAccountController(
if (HttpContext.Items["CurrentUser"] is not Account currentUser) if (HttpContext.Items["CurrentUser"] is not Account currentUser)
return Unauthorized(); return Unauthorized();
var (developer, project, bot) = await ValidateBotAccess(pubName, projectId, botId, currentUser, PublisherMemberRole.Editor); var (developer, project, bot) = await ValidateBotAccess(pubName, projectId, botId, currentUser, Shared.Proto.PublisherMemberRole.Editor);
if (developer == null) return NotFound("Developer not found"); if (developer == null) return NotFound("Developer not found");
if (project == null) return NotFound("Project not found or you don't have access"); if (project == null) return NotFound("Project not found or you don't have access");
if (bot == null) return NotFound("Bot not found"); if (bot == null) return NotFound("Bot not found");
@@ -437,12 +436,12 @@ public class BotAccountController(
} }
} }
private async Task<(SnDeveloper?, SnDevProject?, BotAccount?)> ValidateBotAccess( private async Task<(SnDeveloper?, SnDevProject?, SnBotAccount?)> ValidateBotAccess(
string pubName, string pubName,
Guid projectId, Guid projectId,
Guid botId, Guid botId,
Account currentUser, Account currentUser,
PublisherMemberRole requiredRole) Shared.Proto.PublisherMemberRole requiredRole)
{ {
var developer = await developerService.GetDeveloperByName(pubName); var developer = await developerService.GetDeveloperByName(pubName);
if (developer == null) return (null, null, null); if (developer == null) return (null, null, null);

View File

@@ -1,4 +1,3 @@
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
using DysonNetwork.Shared.Proto; using DysonNetwork.Shared.Proto;
using DysonNetwork.Shared.Registry; using DysonNetwork.Shared.Registry;
@@ -14,21 +13,21 @@ public class BotAccountService(
AccountClientHelper accounts AccountClientHelper accounts
) )
{ {
public async Task<BotAccount?> GetBotByIdAsync(Guid id) public async Task<SnBotAccount?> GetBotByIdAsync(Guid id)
{ {
return await db.BotAccounts return await db.BotAccounts
.Include(b => b.Project) .Include(b => b.Project)
.FirstOrDefaultAsync(b => b.Id == id); .FirstOrDefaultAsync(b => b.Id == id);
} }
public async Task<IEnumerable<BotAccount>> GetBotsByProjectAsync(Guid projectId) public async Task<IEnumerable<SnBotAccount>> GetBotsByProjectAsync(Guid projectId)
{ {
return await db.BotAccounts return await db.BotAccounts
.Where(b => b.ProjectId == projectId) .Where(b => b.ProjectId == projectId)
.ToListAsync(); .ToListAsync();
} }
public async Task<BotAccount> CreateBotAsync( public async Task<SnBotAccount> CreateBotAsync(
SnDevProject project, SnDevProject project,
string slug, string slug,
Account account, Account account,
@@ -58,7 +57,7 @@ public class BotAccountService(
var botAccount = createResponse.Bot; var botAccount = createResponse.Bot;
// Then create the local bot account // Then create the local bot account
var bot = new BotAccount var bot = new SnBotAccount
{ {
Id = automatedId, Id = automatedId,
Slug = slug, Slug = slug,
@@ -89,8 +88,8 @@ public class BotAccountService(
} }
} }
public async Task<BotAccount> UpdateBotAsync( public async Task<SnBotAccount> UpdateBotAsync(
BotAccount bot, SnBotAccount bot,
Account account, Account account,
string? pictureId, string? pictureId,
string? backgroundId string? backgroundId
@@ -130,7 +129,7 @@ public class BotAccountService(
return bot; return bot;
} }
public async Task DeleteBotAsync(BotAccount bot) public async Task DeleteBotAsync(SnBotAccount bot)
{ {
try try
{ {
@@ -153,12 +152,12 @@ public class BotAccountService(
await db.SaveChangesAsync(); await db.SaveChangesAsync();
} }
public async Task<BotAccount?> LoadBotAccountAsync(BotAccount bot) => public async Task<SnBotAccount?> LoadBotAccountAsync(SnBotAccount bot) =>
(await LoadBotsAccountAsync([bot])).FirstOrDefault(); (await LoadBotsAccountAsync([bot])).FirstOrDefault();
public async Task<List<BotAccount>> LoadBotsAccountAsync(IEnumerable<BotAccount> bots) public async Task<List<SnBotAccount>> LoadBotsAccountAsync(IEnumerable<SnBotAccount> bots)
{ {
bots = bots.ToList(); bots = [.. bots];
var automatedIds = bots.Select(b => b.Id).ToList(); var automatedIds = bots.Select(b => b.Id).ToList();
var data = await accounts.GetBotAccountBatch(automatedIds); var data = await accounts.GetBotAccountBatch(automatedIds);
@@ -169,6 +168,6 @@ public class BotAccountService(
.FirstOrDefault(e => e.AutomatedId == bot.Id); .FirstOrDefault(e => e.AutomatedId == bot.Id);
} }
return bots as List<BotAccount> ?? []; return bots as List<SnBotAccount> ?? [];
} }
} }

View File

@@ -19,9 +19,9 @@ public class CustomAppController(CustomAppService customApps, DeveloperService d
[MaxLength(4096)] string? Description, [MaxLength(4096)] string? Description,
string? PictureId, string? PictureId,
string? BackgroundId, string? BackgroundId,
CustomAppStatus? Status, Shared.Models.CustomAppStatus? Status,
CustomAppLinks? Links, SnCustomAppLinks? Links,
CustomAppOauthConfig? OauthConfig SnCustomAppOauthConfig? OauthConfig
); );
public record CreateSecretRequest( public record CreateSecretRequest(
@@ -51,7 +51,7 @@ public class CustomAppController(CustomAppService customApps, DeveloperService d
if (developer is null) return NotFound(); if (developer is null) return NotFound();
var accountId = Guid.Parse(currentUser.Id); var accountId = Guid.Parse(currentUser.Id);
if (!await ds.IsMemberWithRole(developer.PublisherId, accountId, PublisherMemberRole.Viewer)) if (!await ds.IsMemberWithRole(developer.PublisherId, accountId, Shared.Proto.PublisherMemberRole.Viewer))
return StatusCode(403, "You must be a viewer of the developer to list custom apps"); return StatusCode(403, "You must be a viewer of the developer to list custom apps");
var project = await projectService.GetProjectAsync(projectId, developer.Id); var project = await projectService.GetProjectAsync(projectId, developer.Id);
@@ -73,7 +73,7 @@ public class CustomAppController(CustomAppService customApps, DeveloperService d
if (developer is null) return NotFound(); if (developer is null) return NotFound();
var accountId = Guid.Parse(currentUser.Id); var accountId = Guid.Parse(currentUser.Id);
if (!await ds.IsMemberWithRole(developer.PublisherId, accountId, PublisherMemberRole.Viewer)) if (!await ds.IsMemberWithRole(developer.PublisherId, accountId, Shared.Proto.PublisherMemberRole.Viewer))
return StatusCode(403, "You must be a viewer of the developer to list custom apps"); return StatusCode(403, "You must be a viewer of the developer to list custom apps");
var project = await projectService.GetProjectAsync(projectId, developer.Id); var project = await projectService.GetProjectAsync(projectId, developer.Id);
@@ -100,7 +100,7 @@ public class CustomAppController(CustomAppService customApps, DeveloperService d
if (developer is null) if (developer is null)
return NotFound("Developer not found"); return NotFound("Developer not found");
if (!await ds.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id), PublisherMemberRole.Editor)) if (!await ds.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id), Shared.Proto.PublisherMemberRole.Editor))
return StatusCode(403, "You must be an editor of the developer to create a custom app"); return StatusCode(403, "You must be an editor of the developer to create a custom app");
var project = await projectService.GetProjectAsync(projectId, developer.Id); var project = await projectService.GetProjectAsync(projectId, developer.Id);
@@ -144,7 +144,7 @@ public class CustomAppController(CustomAppService customApps, DeveloperService d
if (developer is null) if (developer is null)
return NotFound("Developer not found"); return NotFound("Developer not found");
if (!await ds.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id), PublisherMemberRole.Editor)) if (!await ds.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id), Shared.Proto.PublisherMemberRole.Editor))
return StatusCode(403, "You must be an editor of the developer to update a custom app"); return StatusCode(403, "You must be an editor of the developer to update a custom app");
var project = await projectService.GetProjectAsync(projectId, developer.Id); var project = await projectService.GetProjectAsync(projectId, developer.Id);
@@ -181,7 +181,7 @@ public class CustomAppController(CustomAppService customApps, DeveloperService d
if (developer is null) if (developer is null)
return NotFound("Developer not found"); return NotFound("Developer not found");
if (!await ds.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id), PublisherMemberRole.Editor)) if (!await ds.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id), Shared.Proto.PublisherMemberRole.Editor))
return StatusCode(403, "You must be an editor of the developer to delete a custom app"); return StatusCode(403, "You must be an editor of the developer to delete a custom app");
var project = await projectService.GetProjectAsync(projectId, developer.Id); var project = await projectService.GetProjectAsync(projectId, developer.Id);
@@ -213,7 +213,7 @@ public class CustomAppController(CustomAppService customApps, DeveloperService d
if (developer is null) if (developer is null)
return NotFound("Developer not found"); return NotFound("Developer not found");
if (!await ds.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id), PublisherMemberRole.Editor)) if (!await ds.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id), Shared.Proto.PublisherMemberRole.Editor))
return StatusCode(403, "You must be an editor of the developer to view app secrets"); return StatusCode(403, "You must be an editor of the developer to view app secrets");
var project = await projectService.GetProjectAsync(projectId, developer.Id); var project = await projectService.GetProjectAsync(projectId, developer.Id);
@@ -251,7 +251,7 @@ public class CustomAppController(CustomAppService customApps, DeveloperService d
if (developer is null) if (developer is null)
return NotFound("Developer not found"); return NotFound("Developer not found");
if (!await ds.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id), PublisherMemberRole.Editor)) if (!await ds.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id), Shared.Proto.PublisherMemberRole.Editor))
return StatusCode(403, "You must be an editor of the developer to create app secrets"); return StatusCode(403, "You must be an editor of the developer to create app secrets");
var project = await projectService.GetProjectAsync(projectId, developer.Id); var project = await projectService.GetProjectAsync(projectId, developer.Id);
@@ -264,7 +264,7 @@ public class CustomAppController(CustomAppService customApps, DeveloperService d
try try
{ {
var secret = await customApps.CreateAppSecretAsync(new CustomAppSecret var secret = await customApps.CreateAppSecretAsync(new SnCustomAppSecret
{ {
AppId = appId, AppId = appId,
Description = request.Description, Description = request.Description,
@@ -310,7 +310,7 @@ public class CustomAppController(CustomAppService customApps, DeveloperService d
if (developer is null) if (developer is null)
return NotFound("Developer not found"); return NotFound("Developer not found");
if (!await ds.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id), PublisherMemberRole.Editor)) if (!await ds.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id), Shared.Proto.PublisherMemberRole.Editor))
return StatusCode(403, "You must be an editor of the developer to view app secrets"); return StatusCode(403, "You must be an editor of the developer to view app secrets");
var project = await projectService.GetProjectAsync(projectId, developer.Id); var project = await projectService.GetProjectAsync(projectId, developer.Id);
@@ -351,7 +351,7 @@ public class CustomAppController(CustomAppService customApps, DeveloperService d
if (developer is null) if (developer is null)
return NotFound("Developer not found"); return NotFound("Developer not found");
if (!await ds.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id), PublisherMemberRole.Editor)) if (!await ds.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id), Shared.Proto.PublisherMemberRole.Editor))
return StatusCode(403, "You must be an editor of the developer to delete app secrets"); return StatusCode(403, "You must be an editor of the developer to delete app secrets");
var project = await projectService.GetProjectAsync(projectId, developer.Id); var project = await projectService.GetProjectAsync(projectId, developer.Id);
@@ -389,7 +389,7 @@ public class CustomAppController(CustomAppService customApps, DeveloperService d
if (developer is null) if (developer is null)
return NotFound("Developer not found"); return NotFound("Developer not found");
if (!await ds.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id), PublisherMemberRole.Editor)) if (!await ds.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id), Shared.Proto.PublisherMemberRole.Editor))
return StatusCode(403, "You must be an editor of the developer to rotate app secrets"); return StatusCode(403, "You must be an editor of the developer to rotate app secrets");
var project = await projectService.GetProjectAsync(projectId, developer.Id); var project = await projectService.GetProjectAsync(projectId, developer.Id);
@@ -402,7 +402,7 @@ public class CustomAppController(CustomAppService customApps, DeveloperService d
try try
{ {
var secret = await customApps.RotateAppSecretAsync(new CustomAppSecret var secret = await customApps.RotateAppSecretAsync(new SnCustomAppSecret
{ {
Id = secretId, Id = secretId,
AppId = appId, AppId = appId,

View File

@@ -1,4 +1,3 @@
using DysonNetwork.Develop.Project;
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
using DysonNetwork.Shared.Proto; using DysonNetwork.Shared.Proto;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@@ -13,7 +12,7 @@ public class CustomAppService(
FileService.FileServiceClient files FileService.FileServiceClient files
) )
{ {
public async Task<CustomApp?> CreateAppAsync( public async Task<SnCustomApp?> CreateAppAsync(
Guid projectId, Guid projectId,
CustomAppController.CustomAppRequest request CustomAppController.CustomAppRequest request
) )
@@ -25,12 +24,12 @@ public class CustomAppService(
if (project == null) if (project == null)
return null; return null;
var app = new CustomApp var app = new SnCustomApp
{ {
Slug = request.Slug!, Slug = request.Slug!,
Name = request.Name!, Name = request.Name!,
Description = request.Description, Description = request.Description,
Status = request.Status ?? CustomAppStatus.Developing, Status = request.Status ?? Shared.Models.CustomAppStatus.Developing,
Links = request.Links, Links = request.Links,
OauthConfig = request.OauthConfig, OauthConfig = request.OauthConfig,
ProjectId = projectId ProjectId = projectId
@@ -84,7 +83,7 @@ public class CustomAppService(
return app; return app;
} }
public async Task<CustomApp?> GetAppAsync(Guid id, Guid? projectId = null) public async Task<SnCustomApp?> GetAppAsync(Guid id, Guid? projectId = null)
{ {
var query = db.CustomApps.AsQueryable(); var query = db.CustomApps.AsQueryable();
@@ -96,7 +95,7 @@ public class CustomAppService(
return await query.FirstOrDefaultAsync(a => a.Id == id); return await query.FirstOrDefaultAsync(a => a.Id == id);
} }
public async Task<List<CustomAppSecret>> GetAppSecretsAsync(Guid appId) public async Task<List<SnCustomAppSecret>> GetAppSecretsAsync(Guid appId)
{ {
return await db.CustomAppSecrets return await db.CustomAppSecrets
.Where(s => s.AppId == appId) .Where(s => s.AppId == appId)
@@ -104,13 +103,13 @@ public class CustomAppService(
.ToListAsync(); .ToListAsync();
} }
public async Task<CustomAppSecret?> GetAppSecretAsync(Guid secretId, Guid appId) public async Task<SnCustomAppSecret?> GetAppSecretAsync(Guid secretId, Guid appId)
{ {
return await db.CustomAppSecrets return await db.CustomAppSecrets
.FirstOrDefaultAsync(s => s.Id == secretId && s.AppId == appId); .FirstOrDefaultAsync(s => s.Id == secretId && s.AppId == appId);
} }
public async Task<CustomAppSecret> CreateAppSecretAsync(CustomAppSecret secret) public async Task<SnCustomAppSecret> CreateAppSecretAsync(SnCustomAppSecret secret)
{ {
if (string.IsNullOrWhiteSpace(secret.Secret)) if (string.IsNullOrWhiteSpace(secret.Secret))
{ {
@@ -141,7 +140,7 @@ public class CustomAppService(
return true; return true;
} }
public async Task<CustomAppSecret> RotateAppSecretAsync(CustomAppSecret secretUpdate) public async Task<SnCustomAppSecret> RotateAppSecretAsync(SnCustomAppSecret secretUpdate)
{ {
var existingSecret = await db.CustomAppSecrets var existingSecret = await db.CustomAppSecrets
.FirstOrDefaultAsync(s => s.Id == secretUpdate.Id && s.AppId == secretUpdate.AppId); .FirstOrDefaultAsync(s => s.Id == secretUpdate.Id && s.AppId == secretUpdate.AppId);
@@ -177,14 +176,14 @@ public class CustomAppService(
return res.ToString(); return res.ToString();
} }
public async Task<List<CustomApp>> GetAppsByProjectAsync(Guid projectId) public async Task<List<SnCustomApp>> GetAppsByProjectAsync(Guid projectId)
{ {
return await db.CustomApps return await db.CustomApps
.Where(a => a.ProjectId == projectId) .Where(a => a.ProjectId == projectId)
.ToListAsync(); .ToListAsync();
} }
public async Task<CustomApp?> UpdateAppAsync(CustomApp app, CustomAppController.CustomAppRequest request) public async Task<SnCustomApp?> UpdateAppAsync(SnCustomApp app, CustomAppController.CustomAppRequest request)
{ {
if (request.Slug is not null) if (request.Slug is not null)
app.Slug = request.Slug; app.Slug = request.Slug;

View File

@@ -38,7 +38,7 @@ public class CustomAppServiceGrpc(AppDatabase db) : Shared.Proto.CustomAppServic
if (string.IsNullOrEmpty(request.Secret)) if (string.IsNullOrEmpty(request.Secret))
throw new RpcException(new Status(StatusCode.InvalidArgument, "secret required")); throw new RpcException(new Status(StatusCode.InvalidArgument, "secret required"));
IQueryable<CustomAppSecret> q = db.CustomAppSecrets; IQueryable<SnCustomAppSecret> q = db.CustomAppSecrets;
switch (request.SecretIdentifierCase) switch (request.SecretIdentifierCase)
{ {
case CheckCustomAppSecretRequest.SecretIdentifierOneofCase.SecretId: case CheckCustomAppSecretRequest.SecretIdentifierOneofCase.SecretId:

View File

@@ -75,11 +75,11 @@ public class DeveloperController(
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
var accountId = Guid.Parse(currentUser.Id); var accountId = Guid.Parse(currentUser.Id);
PublisherInfo? pub; SnPublisher? pub;
try try
{ {
var pubResponse = await ps.GetPublisherAsync(new GetPublisherRequest { Name = name }); var pubResponse = await ps.GetPublisherAsync(new GetPublisherRequest { Name = name });
pub = PublisherInfo.FromProto(pubResponse.Publisher); pub = SnPublisher.FromProto(pubResponse.Publisher);
} catch (RpcException ex) } catch (RpcException ex)
{ {
return NotFound(ex.Status.Detail); return NotFound(ex.Status.Detail);
@@ -90,7 +90,7 @@ public class DeveloperController(
{ {
PublisherId = pub.Id.ToString(), PublisherId = pub.Id.ToString(),
AccountId = currentUser.Id, AccountId = currentUser.Id,
Role = PublisherMemberRole.Owner Role = Shared.Proto.PublisherMemberRole.Owner
}); });
if (!permResponse.Valid) return StatusCode(403, "You must be the owner of the publisher to join the developer program"); if (!permResponse.Valid) return StatusCode(403, "You must be the owner of the publisher to join the developer program");

View File

@@ -13,7 +13,7 @@ public class DeveloperService(
public async Task<SnDeveloper> LoadDeveloperPublisher(SnDeveloper developer) public async Task<SnDeveloper> LoadDeveloperPublisher(SnDeveloper developer)
{ {
var pubResponse = await ps.GetPublisherAsync(new GetPublisherRequest { Id = developer.PublisherId.ToString() }); var pubResponse = await ps.GetPublisherAsync(new GetPublisherRequest { Id = developer.PublisherId.ToString() });
developer.Publisher = PublisherInfo.FromProto(pubResponse.Publisher); developer.Publisher = SnPublisher.FromProto(pubResponse.Publisher);
return developer; return developer;
} }
@@ -25,7 +25,7 @@ public class DeveloperService(
var pubRequest = new GetPublisherBatchRequest(); var pubRequest = new GetPublisherBatchRequest();
pubIds.ForEach(x => pubRequest.Ids.Add(x.ToString())); pubIds.ForEach(x => pubRequest.Ids.Add(x.ToString()));
var pubResponse = await ps.GetPublisherBatchAsync(pubRequest); var pubResponse = await ps.GetPublisherBatchAsync(pubRequest);
var pubs = pubResponse.Publishers.ToDictionary(p => Guid.Parse(p.Id), PublisherInfo.FromProto); var pubs = pubResponse.Publishers.ToDictionary(p => Guid.Parse(p.Id), SnPublisher.FromProto);
return enumerable.Select(d => return enumerable.Select(d =>
{ {
@@ -56,7 +56,7 @@ public class DeveloperService(
return await db.Developers.FirstOrDefaultAsync(d => d.Id == id); return await db.Developers.FirstOrDefaultAsync(d => d.Id == id);
} }
public async Task<bool> IsMemberWithRole(Guid pubId, Guid accountId, PublisherMemberRole role) public async Task<bool> IsMemberWithRole(Guid pubId, Guid accountId, Shared.Proto.PublisherMemberRole role)
{ {
try try
{ {

View File

@@ -55,7 +55,7 @@ namespace DysonNetwork.Develop.Migrations
.HasColumnType("uuid") .HasColumnType("uuid")
.HasColumnName("developer_id"); .HasColumnName("developer_id");
b.Property<CustomAppLinks>("Links") b.Property<SnCustomAppLinks>("Links")
.HasColumnType("jsonb") .HasColumnType("jsonb")
.HasColumnName("links"); .HasColumnName("links");
@@ -65,7 +65,7 @@ namespace DysonNetwork.Develop.Migrations
.HasColumnType("character varying(1024)") .HasColumnType("character varying(1024)")
.HasColumnName("name"); .HasColumnName("name");
b.Property<CustomAppOauthConfig>("OauthConfig") b.Property<SnCustomAppOauthConfig>("OauthConfig")
.HasColumnType("jsonb") .HasColumnType("jsonb")
.HasColumnName("oauth_config"); .HasColumnName("oauth_config");

View File

@@ -1,5 +1,4 @@
using System; using DysonNetwork.Shared.Models;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using NodaTime; using NodaTime;
@@ -37,8 +36,8 @@ namespace DysonNetwork.Develop.Migrations
picture = table.Column<SnCloudFileReferenceObject>(type: "jsonb", nullable: true), picture = table.Column<SnCloudFileReferenceObject>(type: "jsonb", nullable: true),
background = table.Column<SnCloudFileReferenceObject>(type: "jsonb", nullable: true), background = table.Column<SnCloudFileReferenceObject>(type: "jsonb", nullable: true),
verification = table.Column<SnVerificationMark>(type: "jsonb", nullable: true), verification = table.Column<SnVerificationMark>(type: "jsonb", nullable: true),
oauth_config = table.Column<CustomAppOauthConfig>(type: "jsonb", nullable: true), oauth_config = table.Column<SnCustomAppOauthConfig>(type: "jsonb", nullable: true),
links = table.Column<CustomAppLinks>(type: "jsonb", nullable: true), links = table.Column<SnCustomAppLinks>(type: "jsonb", nullable: true),
developer_id = table.Column<Guid>(type: "uuid", nullable: false), developer_id = table.Column<Guid>(type: "uuid", nullable: false),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false), created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false), updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),

View File

@@ -51,7 +51,7 @@ namespace DysonNetwork.Develop.Migrations
.HasColumnType("character varying(4096)") .HasColumnType("character varying(4096)")
.HasColumnName("description"); .HasColumnName("description");
b.Property<CustomAppLinks>("Links") b.Property<SnCustomAppLinks>("Links")
.HasColumnType("jsonb") .HasColumnType("jsonb")
.HasColumnName("links"); .HasColumnName("links");
@@ -61,7 +61,7 @@ namespace DysonNetwork.Develop.Migrations
.HasColumnType("character varying(1024)") .HasColumnType("character varying(1024)")
.HasColumnName("name"); .HasColumnName("name");
b.Property<CustomAppOauthConfig>("OauthConfig") b.Property<SnCustomAppOauthConfig>("OauthConfig")
.HasColumnType("jsonb") .HasColumnType("jsonb")
.HasColumnName("oauth_config"); .HasColumnName("oauth_config");

View File

@@ -1,5 +1,4 @@
using System; using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations;
using NodaTime; using NodaTime;
#nullable disable #nullable disable

View File

@@ -93,7 +93,7 @@ namespace DysonNetwork.Develop.Migrations
.HasColumnType("character varying(4096)") .HasColumnType("character varying(4096)")
.HasColumnName("description"); .HasColumnName("description");
b.Property<CustomAppLinks>("Links") b.Property<SnCustomAppLinks>("Links")
.HasColumnType("jsonb") .HasColumnType("jsonb")
.HasColumnName("links"); .HasColumnName("links");
@@ -103,7 +103,7 @@ namespace DysonNetwork.Develop.Migrations
.HasColumnType("character varying(1024)") .HasColumnType("character varying(1024)")
.HasColumnName("name"); .HasColumnName("name");
b.Property<CustomAppOauthConfig>("OauthConfig") b.Property<SnCustomAppOauthConfig>("OauthConfig")
.HasColumnType("jsonb") .HasColumnType("jsonb")
.HasColumnName("oauth_config"); .HasColumnName("oauth_config");

View File

@@ -1,5 +1,4 @@
using System; using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations;
using NodaTime; using NodaTime;
#nullable disable #nullable disable

View File

@@ -90,7 +90,7 @@ namespace DysonNetwork.Develop.Migrations
.HasColumnType("character varying(4096)") .HasColumnType("character varying(4096)")
.HasColumnName("description"); .HasColumnName("description");
b.Property<CustomAppLinks>("Links") b.Property<SnCustomAppLinks>("Links")
.HasColumnType("jsonb") .HasColumnType("jsonb")
.HasColumnName("links"); .HasColumnName("links");
@@ -100,7 +100,7 @@ namespace DysonNetwork.Develop.Migrations
.HasColumnType("character varying(1024)") .HasColumnType("character varying(1024)")
.HasColumnName("name"); .HasColumnName("name");
b.Property<CustomAppOauthConfig>("OauthConfig") b.Property<SnCustomAppOauthConfig>("OauthConfig")
.HasColumnType("jsonb") .HasColumnType("jsonb")
.HasColumnName("oauth_config"); .HasColumnName("oauth_config");

View File

@@ -1,9 +1,6 @@
using System.Net;
using DysonNetwork.Develop.Identity; using DysonNetwork.Develop.Identity;
using DysonNetwork.Shared.Auth; using DysonNetwork.Shared.Auth;
using DysonNetwork.Shared.Http; using DysonNetwork.Shared.Http;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.OpenApi.Models;
using Prometheus; using Prometheus;
namespace DysonNetwork.Develop.Startup; namespace DysonNetwork.Develop.Startup;

View File

@@ -1,5 +1,4 @@
using System.Globalization; using System.Globalization;
using Microsoft.OpenApi.Models;
using NodaTime; using NodaTime;
using NodaTime.Serialization.SystemTextJson; using NodaTime.Serialization.SystemTextJson;
using System.Text.Json; using System.Text.Json;
@@ -7,7 +6,6 @@ using System.Text.Json.Serialization;
using DysonNetwork.Develop.Identity; using DysonNetwork.Develop.Identity;
using DysonNetwork.Develop.Project; using DysonNetwork.Develop.Project;
using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Cache;
using StackExchange.Redis;
namespace DysonNetwork.Develop.Startup; namespace DysonNetwork.Develop.Startup;

View File

@@ -1,7 +1,6 @@
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
using DysonNetwork.Drive.Billing; using DysonNetwork.Drive.Billing;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Design;

View File

@@ -1,4 +1,3 @@
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
using NodaTime; using NodaTime;

View File

@@ -1,7 +1,4 @@
using System; using DysonNetwork.Shared.Models;
using System.Collections.Generic;
using DysonNetwork.Drive.Storage;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using NodaTime; using NodaTime;

View File

@@ -1,5 +1,4 @@
using System.Collections.Generic; using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable #nullable disable

View File

@@ -1,6 +1,4 @@
using System; using DysonNetwork.Shared.Models;
using System.Collections.Generic;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using NodaTime; using NodaTime;

View File

@@ -1,5 +1,4 @@
using System; using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations;
using NodaTime; using NodaTime;
#nullable disable #nullable disable

View File

@@ -1,5 +1,4 @@
using System; using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations;
using NodaTime; using NodaTime;
#nullable disable #nullable disable

View File

@@ -3,11 +3,8 @@ using System.Text.Json.Serialization;
using System.Threading.RateLimiting; using System.Threading.RateLimiting;
using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Cache;
using Microsoft.AspNetCore.RateLimiting; using Microsoft.AspNetCore.RateLimiting;
using Microsoft.OpenApi.Models;
using NodaTime; using NodaTime;
using NodaTime.Serialization.SystemTextJson; using NodaTime.Serialization.SystemTextJson;
using StackExchange.Redis;
using DysonNetwork.Shared.Proto;
using tusdotnet.Stores; using tusdotnet.Stores;
namespace DysonNetwork.Drive.Startup; namespace DysonNetwork.Drive.Startup;

View File

@@ -165,7 +165,7 @@ public class FileController(
} }
[HttpGet("{id}/info")] [HttpGet("{id}/info")]
public async Task<ActionResult<CloudFile>> GetFileInfo(string id) public async Task<ActionResult<SnCloudFile>> GetFileInfo(string id)
{ {
var file = await fs.GetFileAsync(id); var file = await fs.GetFileAsync(id);
if (file is null) return NotFound("File not found."); if (file is null) return NotFound("File not found.");
@@ -175,7 +175,7 @@ public class FileController(
[Authorize] [Authorize]
[HttpPatch("{id}/name")] [HttpPatch("{id}/name")]
public async Task<ActionResult<CloudFile>> UpdateFileName(string id, [FromBody] string name) public async Task<ActionResult<SnCloudFile>> UpdateFileName(string id, [FromBody] string name)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
var accountId = Guid.Parse(currentUser.Id); var accountId = Guid.Parse(currentUser.Id);
@@ -194,7 +194,7 @@ public class FileController(
[Authorize] [Authorize]
[HttpPut("{id}/marks")] [HttpPut("{id}/marks")]
public async Task<ActionResult<CloudFile>> MarkFile(string id, [FromBody] MarkFileRequest request) public async Task<ActionResult<SnCloudFile>> MarkFile(string id, [FromBody] MarkFileRequest request)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
var accountId = Guid.Parse(currentUser.Id); var accountId = Guid.Parse(currentUser.Id);
@@ -208,7 +208,7 @@ public class FileController(
[Authorize] [Authorize]
[HttpPut("{id}/meta")] [HttpPut("{id}/meta")]
public async Task<ActionResult<CloudFile>> UpdateFileMeta(string id, [FromBody] Dictionary<string, object?> meta) public async Task<ActionResult<SnCloudFile>> UpdateFileMeta(string id, [FromBody] Dictionary<string, object?> meta)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
var accountId = Guid.Parse(currentUser.Id); var accountId = Guid.Parse(currentUser.Id);
@@ -222,7 +222,7 @@ public class FileController(
[Authorize] [Authorize]
[HttpGet("me")] [HttpGet("me")]
public async Task<ActionResult<List<CloudFile>>> GetMyFiles( public async Task<ActionResult<List<SnCloudFile>>> GetMyFiles(
[FromQuery] Guid? pool, [FromQuery] Guid? pool,
[FromQuery] bool recycled = false, [FromQuery] bool recycled = false,
[FromQuery] int offset = 0, [FromQuery] int offset = 0,
@@ -307,7 +307,7 @@ public class FileController(
[Authorize] [Authorize]
[HttpPost("fast")] [HttpPost("fast")]
[RequiredPermission("global", "files.create")] [RequiredPermission("global", "files.create")]
public async Task<ActionResult<CloudFile>> CreateFastFile([FromBody] CreateFastFileRequest request) public async Task<ActionResult<SnCloudFile>> CreateFastFile([FromBody] CreateFastFileRequest request)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
var accountId = Guid.Parse(currentUser.Id); var accountId = Guid.Parse(currentUser.Id);
@@ -368,7 +368,7 @@ public class FileController(
await using var transaction = await db.Database.BeginTransactionAsync(); await using var transaction = await db.Database.BeginTransactionAsync();
try try
{ {
var file = new CloudFile var file = new SnCloudFile
{ {
Name = request.Name, Name = request.Name,
Size = request.Size, Size = request.Size,

View File

@@ -28,11 +28,11 @@ public class FileService(
private const string CacheKeyPrefix = "file:"; private const string CacheKeyPrefix = "file:";
private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(15); private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(15);
public async Task<CloudFile?> GetFileAsync(string fileId) public async Task<SnCloudFile?> GetFileAsync(string fileId)
{ {
var cacheKey = $"{CacheKeyPrefix}{fileId}"; var cacheKey = $"{CacheKeyPrefix}{fileId}";
var cachedFile = await cache.GetAsync<CloudFile>(cacheKey); var cachedFile = await cache.GetAsync<SnCloudFile>(cacheKey);
if (cachedFile is not null) if (cachedFile is not null)
return cachedFile; return cachedFile;
@@ -48,15 +48,15 @@ public class FileService(
return file; return file;
} }
public async Task<List<CloudFile>> GetFilesAsync(List<string> fileIds) public async Task<List<SnCloudFile>> GetFilesAsync(List<string> fileIds)
{ {
var cachedFiles = new Dictionary<string, CloudFile>(); var cachedFiles = new Dictionary<string, SnCloudFile>();
var uncachedIds = new List<string>(); var uncachedIds = new List<string>();
foreach (var fileId in fileIds) foreach (var fileId in fileIds)
{ {
var cacheKey = $"{CacheKeyPrefix}{fileId}"; var cacheKey = $"{CacheKeyPrefix}{fileId}";
var cachedFile = await cache.GetAsync<CloudFile>(cacheKey); var cachedFile = await cache.GetAsync<SnCloudFile>(cacheKey);
if (cachedFile != null) if (cachedFile != null)
cachedFiles[fileId] = cachedFile; cachedFiles[fileId] = cachedFile;
@@ -82,11 +82,11 @@ public class FileService(
return fileIds return fileIds
.Select(f => cachedFiles.GetValueOrDefault(f)) .Select(f => cachedFiles.GetValueOrDefault(f))
.Where(f => f != null) .Where(f => f != null)
.Cast<CloudFile>() .Cast<SnCloudFile>()
.ToList(); .ToList();
} }
public async Task<CloudFile> ProcessNewFileAsync( public async Task<SnCloudFile> ProcessNewFileAsync(
Account account, Account account,
string fileId, string fileId,
string filePool, string filePool,
@@ -131,7 +131,7 @@ public class FileService(
var finalContentType = contentType ?? var finalContentType = contentType ??
(!fileName.Contains('.') ? "application/octet-stream" : MimeTypes.GetMimeType(fileName)); (!fileName.Contains('.') ? "application/octet-stream" : MimeTypes.GetMimeType(fileName));
var file = new CloudFile var file = new SnCloudFile
{ {
Id = fileId, Id = fileId,
Name = fileName, Name = fileName,
@@ -190,7 +190,7 @@ public class FileService(
return file; return file;
} }
private async Task ExtractMetadataAsync(CloudFile file, string filePath) private async Task ExtractMetadataAsync(SnCloudFile file, string filePath)
{ {
switch (file.MimeType?.Split('/')[0]) switch (file.MimeType?.Split('/')[0])
{ {
@@ -373,7 +373,7 @@ public class FileService(
); );
} }
public async Task<CloudFile> UpdateFileAsync(CloudFile file, FieldMask updateMask) public async Task<SnCloudFile> UpdateFileAsync(SnCloudFile file, FieldMask updateMask)
{ {
var existingFile = await db.Files.FirstOrDefaultAsync(f => f.Id == file.Id); var existingFile = await db.Files.FirstOrDefaultAsync(f => f.Id == file.Id);
if (existingFile == null) if (existingFile == null)
@@ -414,7 +414,7 @@ public class FileService(
return await db.Files.AsNoTracking().FirstAsync(f => f.Id == file.Id); return await db.Files.AsNoTracking().FirstAsync(f => f.Id == file.Id);
} }
public async Task DeleteFileAsync(CloudFile file) public async Task DeleteFileAsync(SnCloudFile file)
{ {
db.Remove(file); db.Remove(file);
await db.SaveChangesAsync(); await db.SaveChangesAsync();
@@ -423,7 +423,7 @@ public class FileService(
await DeleteFileDataAsync(file); await DeleteFileDataAsync(file);
} }
public async Task DeleteFileDataAsync(CloudFile file, bool force = false) public async Task DeleteFileDataAsync(SnCloudFile file, bool force = false)
{ {
if (!file.PoolId.HasValue) return; if (!file.PoolId.HasValue) return;
@@ -482,7 +482,7 @@ public class FileService(
} }
} }
public async Task DeleteFileDataBatchAsync(List<CloudFile> files) public async Task DeleteFileDataBatchAsync(List<SnCloudFile> files)
{ {
files = files.Where(f => f.PoolId.HasValue).ToList(); files = files.Where(f => f.PoolId.HasValue).ToList();
@@ -569,15 +569,15 @@ public class FileService(
await Task.WhenAll(tasks); await Task.WhenAll(tasks);
} }
public async Task<List<CloudFile?>> LoadFromReference(List<SnCloudFileReferenceObject> references) public async Task<List<SnCloudFile?>> LoadFromReference(List<SnCloudFileReferenceObject> references)
{ {
var cachedFiles = new Dictionary<string, CloudFile>(); var cachedFiles = new Dictionary<string, SnCloudFile>();
var uncachedIds = new List<string>(); var uncachedIds = new List<string>();
foreach (var reference in references) foreach (var reference in references)
{ {
var cacheKey = $"{CacheKeyPrefix}{reference.Id}"; var cacheKey = $"{CacheKeyPrefix}{reference.Id}";
var cachedFile = await cache.GetAsync<CloudFile>(cacheKey); var cachedFile = await cache.GetAsync<SnCloudFile>(cacheKey);
if (cachedFile != null) if (cachedFile != null)
{ {
@@ -603,10 +603,9 @@ public class FileService(
} }
} }
return references return [.. references
.Select(r => cachedFiles.GetValueOrDefault(r.Id)) .Select(r => cachedFiles.GetValueOrDefault(r.Id))
.Where(f => f != null) .Where(f => f != null)];
.ToList();
} }
public async Task<int> GetReferenceCountAsync(string fileId) public async Task<int> GetReferenceCountAsync(string fileId)
@@ -685,7 +684,7 @@ public class FileService(
return count; return count;
} }
public async Task<string> CreateFastUploadLinkAsync(CloudFile file) public async Task<string> CreateFastUploadLinkAsync(SnCloudFile file)
{ {
if (file.PoolId is null) throw new InvalidOperationException("Pool ID is null"); if (file.PoolId is null) throw new InvalidOperationException("Pool ID is null");
@@ -707,7 +706,7 @@ public class FileService(
} }
} }
file class UpdatableCloudFile(CloudFile file) file class UpdatableCloudFile(SnCloudFile file)
{ {
public string Name { get; set; } = file.Name; public string Name { get; set; } = file.Name;
public string? Description { get; set; } = file.Description; public string? Description { get; set; } = file.Description;
@@ -715,9 +714,9 @@ file class UpdatableCloudFile(CloudFile file)
public Dictionary<string, object?>? UserMeta { get; set; } = file.UserMeta; public Dictionary<string, object?>? UserMeta { get; set; } = file.UserMeta;
public bool IsMarkedRecycle { get; set; } = file.IsMarkedRecycle; public bool IsMarkedRecycle { get; set; } = file.IsMarkedRecycle;
public Expression<Func<SetPropertyCalls<CloudFile>, SetPropertyCalls<CloudFile>>> ToSetPropertyCalls() public Expression<Func<SetPropertyCalls<SnCloudFile>, SetPropertyCalls<SnCloudFile>>> ToSetPropertyCalls()
{ {
var userMeta = UserMeta ?? new Dictionary<string, object?>(); var userMeta = UserMeta ?? [];
return setter => setter return setter => setter
.SetProperty(f => f.Name, Name) .SetProperty(f => f.Name, Name)
.SetProperty(f => f.Description, Description) .SetProperty(f => f.Description, Description)

View File

@@ -1,4 +1,4 @@
using DysonNetwork.Shared.Data; using DysonNetwork.Shared.Models;
using DysonNetwork.Shared.Proto; using DysonNetwork.Shared.Proto;
using Google.Protobuf.WellKnownTypes; using Google.Protobuf.WellKnownTypes;
using Grpc.Core; using Grpc.Core;
@@ -48,7 +48,7 @@ namespace DysonNetwork.Drive.Storage
{ {
// Assuming CloudFileReferenceObject is a simple class/struct that holds an ID // Assuming CloudFileReferenceObject is a simple class/struct that holds an ID
// You might need to define this or adjust the LoadFromReference method in FileService // You might need to define this or adjust the LoadFromReference method in FileService
var references = request.ReferenceIds.Select(id => new CloudFileReferenceObject { Id = id }).ToList(); var references = request.ReferenceIds.Select(id => new SnCloudFileReferenceObject { Id = id }).ToList();
var files = await fileService.LoadFromReference(references); var files = await fileService.LoadFromReference(references);
var response = new LoadFromReferenceResponse(); var response = new LoadFromReferenceResponse();
response.Files.AddRange(files.Where(f => f != null).Select(f => f!.ToProtoValue())); response.Files.AddRange(files.Where(f => f != null).Select(f => f!.ToProtoValue()));

View File

@@ -4,6 +4,7 @@ using DysonNetwork.Pass.Credit;
using DysonNetwork.Pass.Wallet; using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.Error; using DysonNetwork.Shared.Error;
using DysonNetwork.Shared.GeoIp; using DysonNetwork.Shared.GeoIp;
using DysonNetwork.Shared.Models;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using NodaTime; using NodaTime;
@@ -23,9 +24,9 @@ public class AccountController(
) : ControllerBase ) : ControllerBase
{ {
[HttpGet("{name}")] [HttpGet("{name}")]
[ProducesResponseType<Account>(StatusCodes.Status200OK)] [ProducesResponseType<SnAccount>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<Account?>> GetByName(string name) public async Task<ActionResult<SnAccount?>> GetByName(string name)
{ {
var account = await db.Accounts var account = await db.Accounts
.Include(e => e.Badges) .Include(e => e.Badges)
@@ -42,9 +43,9 @@ public class AccountController(
} }
[HttpGet("{name}/badges")] [HttpGet("{name}/badges")]
[ProducesResponseType<List<AccountBadge>>(StatusCodes.Status200OK)] [ProducesResponseType<List<SnAccountBadge>>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<List<AccountBadge>>> GetBadgesByName(string name) public async Task<ActionResult<List<SnAccountBadge>>> GetBadgesByName(string name)
{ {
var account = await db.Accounts var account = await db.Accounts
.Include(e => e.Badges) .Include(e => e.Badges)
@@ -103,9 +104,9 @@ public class AccountController(
} }
[HttpPost] [HttpPost]
[ProducesResponseType<Account>(StatusCodes.Status200OK)] [ProducesResponseType<SnAccount>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<Account>> CreateAccount([FromBody] AccountCreateRequest request) public async Task<ActionResult<SnAccount>> CreateAccount([FromBody] AccountCreateRequest request)
{ {
if (!await auth.ValidateCaptcha(request.CaptchaToken)) if (!await auth.ValidateCaptcha(request.CaptchaToken))
return BadRequest(ApiError.Validation(new Dictionary<string, string[]> return BadRequest(ApiError.Validation(new Dictionary<string, string[]>
@@ -199,7 +200,7 @@ public class AccountController(
} }
[HttpGet("{name}/statuses")] [HttpGet("{name}/statuses")]
public async Task<ActionResult<Status>> GetOtherStatus(string name) public async Task<ActionResult<SnAccountStatus>> GetOtherStatus(string name)
{ {
var account = await db.Accounts.FirstOrDefaultAsync(a => a.Name == name); var account = await db.Accounts.FirstOrDefaultAsync(a => a.Name == name);
if (account is null) if (account is null)
@@ -254,7 +255,7 @@ public class AccountController(
} }
[HttpGet("search")] [HttpGet("search")]
public async Task<List<Account>> Search([FromQuery] string query, [FromQuery] int take = 20) public async Task<List<SnAccount>> Search([FromQuery] string query, [FromQuery] int take = 20)
{ {
if (string.IsNullOrWhiteSpace(query)) if (string.IsNullOrWhiteSpace(query))
return []; return [];

View File

@@ -28,11 +28,11 @@ public class AccountCurrentController(
) : ControllerBase ) : ControllerBase
{ {
[HttpGet] [HttpGet]
[ProducesResponseType<Account>(StatusCodes.Status200OK)] [ProducesResponseType<SnAccount>(StatusCodes.Status200OK)]
[ProducesResponseType<ApiError>(StatusCodes.Status401Unauthorized)] [ProducesResponseType<ApiError>(StatusCodes.Status401Unauthorized)]
public async Task<ActionResult<Account>> GetCurrentIdentity() public async Task<ActionResult<SnAccount>> GetCurrentIdentity()
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var userId = currentUser.Id; var userId = currentUser.Id;
var account = await db.Accounts var account = await db.Accounts
@@ -55,9 +55,9 @@ public class AccountCurrentController(
} }
[HttpPatch] [HttpPatch]
public async Task<ActionResult<Account>> UpdateBasicInfo([FromBody] BasicInfoRequest request) public async Task<ActionResult<SnAccount>> UpdateBasicInfo([FromBody] BasicInfoRequest request)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var account = await db.Accounts.FirstAsync(a => a.Id == currentUser.Id); var account = await db.Accounts.FirstAsync(a => a.Id == currentUser.Id);
@@ -88,9 +88,9 @@ public class AccountCurrentController(
} }
[HttpPatch("profile")] [HttpPatch("profile")]
public async Task<ActionResult<AccountProfile>> UpdateProfile([FromBody] ProfileRequest request) public async Task<ActionResult<SnAccountProfile>> UpdateProfile([FromBody] ProfileRequest request)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var userId = currentUser.Id; var userId = currentUser.Id;
var profile = await db.AccountProfiles var profile = await db.AccountProfiles
@@ -163,7 +163,7 @@ public class AccountCurrentController(
[HttpDelete] [HttpDelete]
public async Task<ActionResult> RequestDeleteAccount() public async Task<ActionResult> RequestDeleteAccount()
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
try try
{ {
@@ -184,18 +184,18 @@ public class AccountCurrentController(
} }
[HttpGet("statuses")] [HttpGet("statuses")]
public async Task<ActionResult<Status>> GetCurrentStatus() public async Task<ActionResult<SnAccountStatus>> GetCurrentStatus()
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var status = await events.GetStatus(currentUser.Id); var status = await events.GetStatus(currentUser.Id);
return Ok(status); return Ok(status);
} }
[HttpPatch("statuses")] [HttpPatch("statuses")]
[RequiredPermission("global", "accounts.statuses.update")] [RequiredPermission("global", "accounts.statuses.update")]
public async Task<ActionResult<Status>> UpdateStatus([FromBody] AccountController.StatusRequest request) public async Task<ActionResult<SnAccountStatus>> UpdateStatus([FromBody] AccountController.StatusRequest request)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
if (request is { IsAutomated: true, AppIdentifier: not null }) if (request is { IsAutomated: true, AppIdentifier: not null })
return BadRequest("Automated status cannot be updated."); return BadRequest("Automated status cannot be updated.");
@@ -227,9 +227,9 @@ public class AccountCurrentController(
[HttpPost("statuses")] [HttpPost("statuses")]
[RequiredPermission("global", "accounts.statuses.create")] [RequiredPermission("global", "accounts.statuses.create")]
public async Task<ActionResult<Status>> CreateStatus([FromBody] AccountController.StatusRequest request) public async Task<ActionResult<SnAccountStatus>> CreateStatus([FromBody] AccountController.StatusRequest request)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
if (request is { IsAutomated: true, AppIdentifier: not null }) if (request is { IsAutomated: true, AppIdentifier: not null })
{ {
@@ -261,7 +261,7 @@ public class AccountCurrentController(
return Ok(existingStatus); // Do not override manually set status with automated ones return Ok(existingStatus); // Do not override manually set status with automated ones
} }
var status = new Status var status = new SnAccountStatus
{ {
AccountId = currentUser.Id, AccountId = currentUser.Id,
Attitude = request.Attitude, Attitude = request.Attitude,
@@ -280,7 +280,7 @@ public class AccountCurrentController(
[HttpDelete("statuses")] [HttpDelete("statuses")]
public async Task<ActionResult> DeleteStatus([FromQuery] string? app) public async Task<ActionResult> DeleteStatus([FromQuery] string? app)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var now = SystemClock.Instance.GetCurrentInstant(); var now = SystemClock.Instance.GetCurrentInstant();
var queryable = db.AccountStatuses var queryable = db.AccountStatuses
@@ -301,9 +301,9 @@ public class AccountCurrentController(
} }
[HttpGet("check-in")] [HttpGet("check-in")]
public async Task<ActionResult<CheckInResult>> GetCheckInResult() public async Task<ActionResult<SnCheckInResult>> GetCheckInResult()
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var userId = currentUser.Id; var userId = currentUser.Id;
var now = SystemClock.Instance.GetCurrentInstant(); var now = SystemClock.Instance.GetCurrentInstant();
@@ -323,12 +323,12 @@ public class AccountCurrentController(
} }
[HttpPost("check-in")] [HttpPost("check-in")]
public async Task<ActionResult<CheckInResult>> DoCheckIn( public async Task<ActionResult<SnCheckInResult>> DoCheckIn(
[FromBody] string? captchaToken, [FromBody] string? captchaToken,
[FromQuery] Instant? backdated = null [FromQuery] Instant? backdated = null
) )
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
if (backdated is null) if (backdated is null)
{ {
@@ -399,7 +399,7 @@ public class AccountCurrentController(
public async Task<ActionResult<List<DailyEventResponse>>> GetEventCalendar([FromQuery] int? month, public async Task<ActionResult<List<DailyEventResponse>>> GetEventCalendar([FromQuery] int? month,
[FromQuery] int? year) [FromQuery] int? year)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var currentDate = SystemClock.Instance.GetCurrentInstant().InUtc().Date; var currentDate = SystemClock.Instance.GetCurrentInstant().InUtc().Date;
month ??= currentDate.Month; month ??= currentDate.Month;
@@ -428,7 +428,7 @@ public class AccountCurrentController(
[FromQuery] int offset = 0 [FromQuery] int offset = 0
) )
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var query = db.ActionLogs var query = db.ActionLogs
.Where(log => log.AccountId == currentUser.Id) .Where(log => log.AccountId == currentUser.Id)
@@ -446,9 +446,9 @@ public class AccountCurrentController(
} }
[HttpGet("factors")] [HttpGet("factors")]
public async Task<ActionResult<List<AccountAuthFactor>>> GetAuthFactors() public async Task<ActionResult<List<SnAccountAuthFactor>>> GetAuthFactors()
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var factors = await db.AccountAuthFactors var factors = await db.AccountAuthFactors
.Include(f => f.Account) .Include(f => f.Account)
@@ -460,15 +460,15 @@ public class AccountCurrentController(
public class AuthFactorRequest public class AuthFactorRequest
{ {
public AccountAuthFactorType Type { get; set; } public Shared.Models.AccountAuthFactorType Type { get; set; }
public string? Secret { get; set; } public string? Secret { get; set; }
} }
[HttpPost("factors")] [HttpPost("factors")]
[Authorize] [Authorize]
public async Task<ActionResult<AccountAuthFactor>> CreateAuthFactor([FromBody] AuthFactorRequest request) public async Task<ActionResult<SnAccountAuthFactor>> CreateAuthFactor([FromBody] AuthFactorRequest request)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
if (await accounts.CheckAuthFactorExists(currentUser, request.Type)) if (await accounts.CheckAuthFactorExists(currentUser, request.Type))
return BadRequest(new ApiError return BadRequest(new ApiError
{ {
@@ -484,9 +484,9 @@ public class AccountCurrentController(
[HttpPost("factors/{id:guid}/enable")] [HttpPost("factors/{id:guid}/enable")]
[Authorize] [Authorize]
public async Task<ActionResult<AccountAuthFactor>> EnableAuthFactor(Guid id, [FromBody] string? code) public async Task<ActionResult<SnAccountAuthFactor>> EnableAuthFactor(Guid id, [FromBody] string? code)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var factor = await db.AccountAuthFactors var factor = await db.AccountAuthFactors
.Where(f => f.AccountId == currentUser.Id && f.Id == id) .Where(f => f.AccountId == currentUser.Id && f.Id == id)
@@ -513,9 +513,9 @@ public class AccountCurrentController(
[HttpPost("factors/{id:guid}/disable")] [HttpPost("factors/{id:guid}/disable")]
[Authorize] [Authorize]
public async Task<ActionResult<AccountAuthFactor>> DisableAuthFactor(Guid id) public async Task<ActionResult<SnAccountAuthFactor>> DisableAuthFactor(Guid id)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var factor = await db.AccountAuthFactors var factor = await db.AccountAuthFactors
.Where(f => f.AccountId == currentUser.Id && f.Id == id) .Where(f => f.AccountId == currentUser.Id && f.Id == id)
@@ -535,9 +535,9 @@ public class AccountCurrentController(
[HttpDelete("factors/{id:guid}")] [HttpDelete("factors/{id:guid}")]
[Authorize] [Authorize]
public async Task<ActionResult<AccountAuthFactor>> DeleteAuthFactor(Guid id) public async Task<ActionResult<SnAccountAuthFactor>> DeleteAuthFactor(Guid id)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var factor = await db.AccountAuthFactors var factor = await db.AccountAuthFactors
.Where(f => f.AccountId == currentUser.Id && f.Id == id) .Where(f => f.AccountId == currentUser.Id && f.Id == id)
@@ -559,7 +559,7 @@ public class AccountCurrentController(
[Authorize] [Authorize]
public async Task<ActionResult<List<SnAuthClientWithChallenge>>> GetDevices() public async Task<ActionResult<List<SnAuthClientWithChallenge>>> GetDevices()
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser || if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser ||
HttpContext.Items["CurrentSession"] is not SnAuthSession currentSession) return Unauthorized(); HttpContext.Items["CurrentSession"] is not SnAuthSession currentSession) return Unauthorized();
Response.Headers.Append("X-Auth-Session", currentSession.Id.ToString()); Response.Headers.Append("X-Auth-Session", currentSession.Id.ToString());
@@ -589,7 +589,7 @@ public class AccountCurrentController(
[FromQuery] int offset = 0 [FromQuery] int offset = 0
) )
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser || if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser ||
HttpContext.Items["CurrentSession"] is not SnAuthSession currentSession) return Unauthorized(); HttpContext.Items["CurrentSession"] is not SnAuthSession currentSession) return Unauthorized();
var query = db.AuthSessions var query = db.AuthSessions
@@ -614,7 +614,7 @@ public class AccountCurrentController(
[Authorize] [Authorize]
public async Task<ActionResult<SnAuthSession>> DeleteSession(Guid id) public async Task<ActionResult<SnAuthSession>> DeleteSession(Guid id)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
try try
{ {
@@ -631,7 +631,7 @@ public class AccountCurrentController(
[Authorize] [Authorize]
public async Task<ActionResult<SnAuthSession>> DeleteDevice(string deviceId) public async Task<ActionResult<SnAuthSession>> DeleteDevice(string deviceId)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
try try
{ {
@@ -648,7 +648,7 @@ public class AccountCurrentController(
[Authorize] [Authorize]
public async Task<ActionResult<SnAuthSession>> DeleteCurrentSession() public async Task<ActionResult<SnAuthSession>> DeleteCurrentSession()
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser || if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser ||
HttpContext.Items["CurrentSession"] is not SnAuthSession currentSession) return Unauthorized(); HttpContext.Items["CurrentSession"] is not SnAuthSession currentSession) return Unauthorized();
try try
@@ -666,7 +666,7 @@ public class AccountCurrentController(
[Authorize] [Authorize]
public async Task<ActionResult<SnAuthSession>> UpdateDeviceLabel(string deviceId, [FromBody] string label) public async Task<ActionResult<SnAuthSession>> UpdateDeviceLabel(string deviceId, [FromBody] string label)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
try try
{ {
@@ -683,7 +683,7 @@ public class AccountCurrentController(
[Authorize] [Authorize]
public async Task<ActionResult<SnAuthSession>> UpdateCurrentDeviceLabel([FromBody] string label) public async Task<ActionResult<SnAuthSession>> UpdateCurrentDeviceLabel([FromBody] string label)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser || if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser ||
HttpContext.Items["CurrentSession"] is not SnAuthSession currentSession) return Unauthorized(); HttpContext.Items["CurrentSession"] is not SnAuthSession currentSession) return Unauthorized();
var device = await db.AuthClients.FirstOrDefaultAsync(d => d.Id == currentSession.Challenge.ClientId); var device = await db.AuthClients.FirstOrDefaultAsync(d => d.Id == currentSession.Challenge.ClientId);
@@ -702,9 +702,9 @@ public class AccountCurrentController(
[HttpGet("contacts")] [HttpGet("contacts")]
[Authorize] [Authorize]
public async Task<ActionResult<List<AccountContact>>> GetContacts() public async Task<ActionResult<List<SnAccountContact>>> GetContacts()
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var contacts = await db.AccountContacts var contacts = await db.AccountContacts
.Where(c => c.AccountId == currentUser.Id) .Where(c => c.AccountId == currentUser.Id)
@@ -715,15 +715,15 @@ public class AccountCurrentController(
public class AccountContactRequest public class AccountContactRequest
{ {
[Required] public AccountContactType Type { get; set; } [Required] public Shared.Models.AccountContactType Type { get; set; }
[Required] public string Content { get; set; } = null!; [Required] public string Content { get; set; } = null!;
} }
[HttpPost("contacts")] [HttpPost("contacts")]
[Authorize] [Authorize]
public async Task<ActionResult<AccountContact>> CreateContact([FromBody] AccountContactRequest request) public async Task<ActionResult<SnAccountContact>> CreateContact([FromBody] AccountContactRequest request)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
try try
{ {
@@ -738,9 +738,9 @@ public class AccountCurrentController(
[HttpPost("contacts/{id:guid}/verify")] [HttpPost("contacts/{id:guid}/verify")]
[Authorize] [Authorize]
public async Task<ActionResult<AccountContact>> VerifyContact(Guid id) public async Task<ActionResult<SnAccountContact>> VerifyContact(Guid id)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var contact = await db.AccountContacts var contact = await db.AccountContacts
.Where(c => c.AccountId == currentUser.Id && c.Id == id) .Where(c => c.AccountId == currentUser.Id && c.Id == id)
@@ -760,9 +760,9 @@ public class AccountCurrentController(
[HttpPost("contacts/{id:guid}/primary")] [HttpPost("contacts/{id:guid}/primary")]
[Authorize] [Authorize]
public async Task<ActionResult<AccountContact>> SetPrimaryContact(Guid id) public async Task<ActionResult<SnAccountContact>> SetPrimaryContact(Guid id)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var contact = await db.AccountContacts var contact = await db.AccountContacts
.Where(c => c.AccountId == currentUser.Id && c.Id == id) .Where(c => c.AccountId == currentUser.Id && c.Id == id)
@@ -782,9 +782,9 @@ public class AccountCurrentController(
[HttpPost("contacts/{id:guid}/public")] [HttpPost("contacts/{id:guid}/public")]
[Authorize] [Authorize]
public async Task<ActionResult<AccountContact>> SetPublicContact(Guid id) public async Task<ActionResult<SnAccountContact>> SetPublicContact(Guid id)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var contact = await db.AccountContacts var contact = await db.AccountContacts
.Where(c => c.AccountId == currentUser.Id && c.Id == id) .Where(c => c.AccountId == currentUser.Id && c.Id == id)
@@ -804,9 +804,9 @@ public class AccountCurrentController(
[HttpDelete("contacts/{id:guid}/public")] [HttpDelete("contacts/{id:guid}/public")]
[Authorize] [Authorize]
public async Task<ActionResult<AccountContact>> UnsetPublicContact(Guid id) public async Task<ActionResult<SnAccountContact>> UnsetPublicContact(Guid id)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var contact = await db.AccountContacts var contact = await db.AccountContacts
.Where(c => c.AccountId == currentUser.Id && c.Id == id) .Where(c => c.AccountId == currentUser.Id && c.Id == id)
@@ -826,9 +826,9 @@ public class AccountCurrentController(
[HttpDelete("contacts/{id:guid}")] [HttpDelete("contacts/{id:guid}")]
[Authorize] [Authorize]
public async Task<ActionResult<AccountContact>> DeleteContact(Guid id) public async Task<ActionResult<SnAccountContact>> DeleteContact(Guid id)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var contact = await db.AccountContacts var contact = await db.AccountContacts
.Where(c => c.AccountId == currentUser.Id && c.Id == id) .Where(c => c.AccountId == currentUser.Id && c.Id == id)
@@ -847,11 +847,11 @@ public class AccountCurrentController(
} }
[HttpGet("badges")] [HttpGet("badges")]
[ProducesResponseType<List<AccountBadge>>(StatusCodes.Status200OK)] [ProducesResponseType<List<SnAccountBadge>>(StatusCodes.Status200OK)]
[Authorize] [Authorize]
public async Task<ActionResult<List<AccountBadge>>> GetBadges() public async Task<ActionResult<List<SnAccountBadge>>> GetBadges()
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var badges = await db.Badges var badges = await db.Badges
.Where(b => b.AccountId == currentUser.Id) .Where(b => b.AccountId == currentUser.Id)
@@ -861,9 +861,9 @@ public class AccountCurrentController(
[HttpPost("badges/{id:guid}/active")] [HttpPost("badges/{id:guid}/active")]
[Authorize] [Authorize]
public async Task<ActionResult<AccountBadge>> ActivateBadge(Guid id) public async Task<ActionResult<SnAccountBadge>> ActivateBadge(Guid id)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
try try
{ {
@@ -878,12 +878,12 @@ public class AccountCurrentController(
[HttpGet("leveling")] [HttpGet("leveling")]
[Authorize] [Authorize]
public async Task<ActionResult<ExperienceRecord>> GetLevelingHistory( public async Task<ActionResult<SnExperienceRecord>> GetLevelingHistory(
[FromQuery] int take = 20, [FromQuery] int take = 20,
[FromQuery] int offset = 0 [FromQuery] int offset = 0
) )
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var queryable = db.ExperienceRecords var queryable = db.ExperienceRecords
.Where(r => r.AccountId == currentUser.Id) .Where(r => r.AccountId == currentUser.Id)
@@ -903,7 +903,7 @@ public class AccountCurrentController(
[HttpGet("credits")] [HttpGet("credits")]
public async Task<ActionResult<bool>> GetSocialCredit() public async Task<ActionResult<bool>> GetSocialCredit()
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var credit = await creditService.GetSocialCredit(currentUser.Id); var credit = await creditService.GetSocialCredit(currentUser.Id);
return Ok(credit); return Ok(credit);
@@ -915,7 +915,7 @@ public class AccountCurrentController(
[FromQuery] int offset = 0 [FromQuery] int offset = 0
) )
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var queryable = db.SocialCreditRecords var queryable = db.SocialCreditRecords
.Where(r => r.AccountId == currentUser.Id) .Where(r => r.AccountId == currentUser.Id)

View File

@@ -37,10 +37,10 @@ public class AccountEventService(
cache.RemoveAsync(cacheKey); cache.RemoveAsync(cacheKey);
} }
public async Task<Status> GetStatus(Guid userId) public async Task<SnAccountStatus> GetStatus(Guid userId)
{ {
var cacheKey = $"{StatusCacheKey}{userId}"; var cacheKey = $"{StatusCacheKey}{userId}";
var cachedStatus = await cache.GetAsync<Status>(cacheKey); var cachedStatus = await cache.GetAsync<SnAccountStatus>(cacheKey);
if (cachedStatus is not null) if (cachedStatus is not null)
{ {
cachedStatus!.IsOnline = !cachedStatus.IsInvisible && await GetAccountIsConnected(userId); cachedStatus!.IsOnline = !cachedStatus.IsInvisible && await GetAccountIsConnected(userId);
@@ -64,9 +64,9 @@ public class AccountEventService(
if (isOnline) if (isOnline)
{ {
return new Status return new SnAccountStatus
{ {
Attitude = StatusAttitude.Neutral, Attitude = Shared.Models.StatusAttitude.Neutral,
IsOnline = true, IsOnline = true,
IsCustomized = false, IsCustomized = false,
Label = "Online", Label = "Online",
@@ -74,9 +74,9 @@ public class AccountEventService(
}; };
} }
return new Status return new SnAccountStatus
{ {
Attitude = StatusAttitude.Neutral, Attitude = Shared.Models.StatusAttitude.Neutral,
IsOnline = false, IsOnline = false,
IsCustomized = false, IsCustomized = false,
Label = "Offline", Label = "Offline",
@@ -84,15 +84,15 @@ public class AccountEventService(
}; };
} }
public async Task<Dictionary<Guid, Status>> GetStatuses(List<Guid> userIds) public async Task<Dictionary<Guid, SnAccountStatus>> GetStatuses(List<Guid> userIds)
{ {
var results = new Dictionary<Guid, Status>(); var results = new Dictionary<Guid, SnAccountStatus>();
var cacheMissUserIds = new List<Guid>(); var cacheMissUserIds = new List<Guid>();
foreach (var userId in userIds) foreach (var userId in userIds)
{ {
var cacheKey = $"{StatusCacheKey}{userId}"; var cacheKey = $"{StatusCacheKey}{userId}";
var cachedStatus = await cache.GetAsync<Status>(cacheKey); var cachedStatus = await cache.GetAsync<SnAccountStatus>(cacheKey);
if (cachedStatus != null) if (cachedStatus != null)
{ {
cachedStatus.IsOnline = !cachedStatus.IsInvisible && await GetAccountIsConnected(userId); cachedStatus.IsOnline = !cachedStatus.IsInvisible && await GetAccountIsConnected(userId);
@@ -132,9 +132,9 @@ public class AccountEventService(
foreach (var userId in usersWithoutStatus) foreach (var userId in usersWithoutStatus)
{ {
var isOnline = await GetAccountIsConnected(userId); var isOnline = await GetAccountIsConnected(userId);
var defaultStatus = new Status var defaultStatus = new SnAccountStatus
{ {
Attitude = StatusAttitude.Neutral, Attitude = Shared.Models.StatusAttitude.Neutral,
IsOnline = isOnline, IsOnline = isOnline,
IsCustomized = false, IsCustomized = false,
Label = isOnline ? "Online" : "Offline", Label = isOnline ? "Online" : "Offline",
@@ -148,7 +148,7 @@ public class AccountEventService(
return results; return results;
} }
public async Task<Status> CreateStatus(Account user, Status status) public async Task<SnAccountStatus> CreateStatus(SnAccount user, SnAccountStatus status)
{ {
var now = SystemClock.Instance.GetCurrentInstant(); var now = SystemClock.Instance.GetCurrentInstant();
await db.AccountStatuses await db.AccountStatuses
@@ -161,7 +161,7 @@ public class AccountEventService(
return status; return status;
} }
public async Task ClearStatus(Account user, Status status) public async Task ClearStatus(SnAccount user, SnAccountStatus status)
{ {
status.ClearedAt = SystemClock.Instance.GetCurrentInstant(); status.ClearedAt = SystemClock.Instance.GetCurrentInstant();
db.Update(status); db.Update(status);
@@ -173,7 +173,7 @@ public class AccountEventService(
private const string CaptchaCacheKey = "checkin:captcha:"; private const string CaptchaCacheKey = "checkin:captcha:";
private const int CaptchaProbabilityPercent = 20; private const int CaptchaProbabilityPercent = 20;
public async Task<bool> CheckInDailyDoAskCaptcha(Account user) public async Task<bool> CheckInDailyDoAskCaptcha(SnAccount user)
{ {
var perkSubscription = await subscriptions.GetPerkSubscriptionAsync(user.Id); var perkSubscription = await subscriptions.GetPerkSubscriptionAsync(user.Id);
if (perkSubscription is not null) return false; if (perkSubscription is not null) return false;
@@ -188,7 +188,7 @@ public class AccountEventService(
return result; return result;
} }
public async Task<bool> CheckInDailyIsAvailable(Account user) public async Task<bool> CheckInDailyIsAvailable(SnAccount user)
{ {
var now = SystemClock.Instance.GetCurrentInstant(); var now = SystemClock.Instance.GetCurrentInstant();
var lastCheckIn = await db.AccountCheckInResults var lastCheckIn = await db.AccountCheckInResults
@@ -205,7 +205,7 @@ public class AccountEventService(
return lastDate < currentDate; return lastDate < currentDate;
} }
public async Task<bool> CheckInBackdatedIsAvailable(Account user, Instant backdated) public async Task<bool> CheckInBackdatedIsAvailable(SnAccount user, Instant backdated)
{ {
var aDay = Duration.FromDays(1); var aDay = Duration.FromDays(1);
var backdatedStart = backdated.ToDateTimeUtc().Date.ToInstant(); var backdatedStart = backdated.ToDateTimeUtc().Date.ToInstant();
@@ -253,7 +253,7 @@ public class AccountEventService(
public const string CheckInLockKey = "checkin:lock:"; public const string CheckInLockKey = "checkin:lock:";
public async Task<CheckInResult> CheckInDaily(Account user, Instant? backdated = null) public async Task<SnCheckInResult> CheckInDaily(SnAccount user, Instant? backdated = null)
{ {
var lockKey = $"{CheckInLockKey}{user.Id}"; var lockKey = $"{CheckInLockKey}{user.Id}";
@@ -271,9 +271,7 @@ public class AccountEventService(
// Now try to acquire the lock properly // Now try to acquire the lock properly
await using var lockObj = await using var lockObj =
await cache.AcquireLockAsync(lockKey, TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(5)); await cache.AcquireLockAsync(lockKey, TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(5)) ?? throw new InvalidOperationException("Check-in was in progress.");
if (lockObj is null) throw new InvalidOperationException("Check-in was in progress.");
var cultureInfo = new CultureInfo(user.Language, false); var cultureInfo = new CultureInfo(user.Language, false);
CultureInfo.CurrentCulture = cultureInfo; CultureInfo.CurrentCulture = cultureInfo;
CultureInfo.CurrentUICulture = cultureInfo; CultureInfo.CurrentUICulture = cultureInfo;
@@ -283,9 +281,10 @@ public class AccountEventService(
.OrderBy(_ => Random.Next()) .OrderBy(_ => Random.Next())
.Take(2) .Take(2)
.ToList(); .ToList();
var tips = positiveIndices.Select(index => new FortuneTip var tips = positiveIndices.Select(index => new CheckInFortuneTip
{ {
IsPositive = true, Title = localizer[$"FortuneTipPositiveTitle_{index}"].Value, IsPositive = true,
Title = localizer[$"FortuneTipPositiveTitle_{index}"].Value,
Content = localizer[$"FortuneTipPositiveContent_{index}"].Value Content = localizer[$"FortuneTipPositiveContent_{index}"].Value
}).ToList(); }).ToList();
@@ -295,9 +294,10 @@ public class AccountEventService(
.OrderBy(_ => Random.Next()) .OrderBy(_ => Random.Next())
.Take(2) .Take(2)
.ToList(); .ToList();
tips.AddRange(negativeIndices.Select(index => new FortuneTip tips.AddRange(negativeIndices.Select(index => new CheckInFortuneTip
{ {
IsPositive = false, Title = localizer[$"FortuneTipNegativeTitle_{index}"].Value, IsPositive = false,
Title = localizer[$"FortuneTipNegativeTitle_{index}"].Value,
Content = localizer[$"FortuneTipNegativeContent_{index}"].Value Content = localizer[$"FortuneTipNegativeContent_{index}"].Value
})); }));
@@ -313,7 +313,7 @@ public class AccountEventService(
if (accountBirthday.HasValue && accountBirthday.Value.InUtc().Date == now) if (accountBirthday.HasValue && accountBirthday.Value.InUtc().Date == now)
checkInLevel = CheckInResultLevel.Special; checkInLevel = CheckInResultLevel.Special;
var result = new CheckInResult var result = new SnCheckInResult
{ {
Tips = tips, Tips = tips,
Level = checkInLevel, Level = checkInLevel,
@@ -354,7 +354,7 @@ public class AccountEventService(
return result; return result;
} }
public async Task<List<DailyEventResponse>> GetEventCalendar(Account user, int month, int year = 0, public async Task<List<DailyEventResponse>> GetEventCalendar(SnAccount user, int month, int year = 0,
bool replaceInvisible = false) bool replaceInvisible = false)
{ {
if (year == 0) if (year == 0)
@@ -368,7 +368,7 @@ public class AccountEventService(
.AsNoTracking() .AsNoTracking()
.TagWith("eventcal:statuses") .TagWith("eventcal:statuses")
.Where(x => x.AccountId == user.Id && x.CreatedAt >= startOfMonth && x.CreatedAt < endOfMonth) .Where(x => x.AccountId == user.Id && x.CreatedAt >= startOfMonth && x.CreatedAt < endOfMonth)
.Select(x => new Status .Select(x => new SnAccountStatus
{ {
Id = x.Id, Id = x.Id,
Attitude = x.Attitude, Attitude = x.Attitude,
@@ -406,7 +406,7 @@ public class AccountEventService(
{ {
Date = date, Date = date,
CheckInResult = checkInByDate.GetValueOrDefault(utcDate), CheckInResult = checkInByDate.GetValueOrDefault(utcDate),
Statuses = statusesByDate.GetValueOrDefault(utcDate, new List<Status>()) Statuses = statusesByDate.GetValueOrDefault(utcDate, new List<SnAccountStatus>())
}; };
}).ToList(); }).ToList();
} }

View File

@@ -1,18 +1,14 @@
using System.Globalization; using System.Globalization;
using System.Text.Json;
using DysonNetwork.Pass.Auth.OpenId; using DysonNetwork.Pass.Auth.OpenId;
using DysonNetwork.Pass.Localization; using DysonNetwork.Pass.Localization;
using DysonNetwork.Pass.Mailer; using DysonNetwork.Pass.Mailer;
using DysonNetwork.Pass.Permission;
using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
using DysonNetwork.Shared.Proto; using DysonNetwork.Shared.Proto;
using DysonNetwork.Shared.Stream; using DysonNetwork.Shared.Stream;
using EFCore.BulkExtensions;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Localization; using Microsoft.Extensions.Localization;
using NATS.Client.Core; using NATS.Client.Core;
using NATS.Client.JetStream;
using NATS.Net; using NATS.Net;
using NodaTime; using NodaTime;
using OtpNet; using OtpNet;
@@ -35,7 +31,7 @@ public class AccountService(
INatsConnection nats INatsConnection nats
) )
{ {
public static void SetCultureInfo(Account account) public static void SetCultureInfo(SnAccount account)
{ {
SetCultureInfo(account.Language); SetCultureInfo(account.Language);
} }
@@ -49,12 +45,12 @@ public class AccountService(
public const string AccountCachePrefix = "account:"; public const string AccountCachePrefix = "account:";
public async Task PurgeAccountCache(Account account) public async Task PurgeAccountCache(SnAccount account)
{ {
await cache.RemoveGroupAsync($"{AccountCachePrefix}{account.Id}"); await cache.RemoveGroupAsync($"{AccountCachePrefix}{account.Id}");
} }
public async Task<Account?> LookupAccount(string probe) public async Task<SnAccount?> LookupAccount(string probe)
{ {
var account = await db.Accounts.Where(a => a.Name == probe).FirstOrDefaultAsync(); var account = await db.Accounts.Where(a => a.Name == probe).FirstOrDefaultAsync();
if (account is not null) return account; if (account is not null) return account;
@@ -66,7 +62,7 @@ public class AccountService(
return contact?.Account; return contact?.Account;
} }
public async Task<Account?> LookupAccountByConnection(string identifier, string provider) public async Task<SnAccount?> LookupAccountByConnection(string identifier, string provider)
{ {
var connection = await db.AccountConnections var connection = await db.AccountConnections
.Where(c => c.ProvidedIdentifier == identifier && c.Provider == provider) .Where(c => c.ProvidedIdentifier == identifier && c.Provider == provider)
@@ -83,7 +79,7 @@ public class AccountService(
return profile?.Level; return profile?.Level;
} }
public async Task<Account> CreateAccount( public async Task<SnAccount> CreateAccount(
string name, string name,
string nick, string nick,
string email, string email,
@@ -99,39 +95,39 @@ public class AccountService(
throw new InvalidOperationException("Account name has already been taken."); throw new InvalidOperationException("Account name has already been taken.");
var dupeEmailCount = await db.AccountContacts var dupeEmailCount = await db.AccountContacts
.Where(c => c.Content == email && c.Type == AccountContactType.Email .Where(c => c.Content == email && c.Type == Shared.Models.AccountContactType.Email
).CountAsync(); ).CountAsync();
if (dupeEmailCount > 0) if (dupeEmailCount > 0)
throw new InvalidOperationException("Account email has already been used."); throw new InvalidOperationException("Account email has already been used.");
var account = new Account var account = new SnAccount
{ {
Name = name, Name = name,
Nick = nick, Nick = nick,
Language = language, Language = language,
Region = region, Region = region,
Contacts = new List<AccountContact> Contacts =
{ [
new() new()
{ {
Type = AccountContactType.Email, Type = Shared.Models.AccountContactType.Email,
Content = email, Content = email,
VerifiedAt = isEmailVerified ? SystemClock.Instance.GetCurrentInstant() : null, VerifiedAt = isEmailVerified ? SystemClock.Instance.GetCurrentInstant() : null,
IsPrimary = true IsPrimary = true
} }
}, ],
AuthFactors = password is not null AuthFactors = password is not null
? new List<AccountAuthFactor> ? new List<SnAccountAuthFactor>
{ {
new AccountAuthFactor new SnAccountAuthFactor
{ {
Type = AccountAuthFactorType.Password, Type = Shared.Models.AccountAuthFactorType.Password,
Secret = password, Secret = password,
EnabledAt = SystemClock.Instance.GetCurrentInstant() EnabledAt = SystemClock.Instance.GetCurrentInstant()
}.HashSecret() }.HashSecret()
} }
: [], : [],
Profile = new AccountProfile() Profile = new SnAccountProfile()
}; };
if (isActivated) if (isActivated)
@@ -166,7 +162,7 @@ public class AccountService(
return account; return account;
} }
public async Task<Account> CreateAccount(OidcUserInfo userInfo) public async Task<SnAccount> CreateAccount(OidcUserInfo userInfo)
{ {
if (string.IsNullOrEmpty(userInfo.Email)) if (string.IsNullOrEmpty(userInfo.Email))
throw new ArgumentException("Email is required for account creation"); throw new ArgumentException("Email is required for account creation");
@@ -190,7 +186,7 @@ public class AccountService(
); );
} }
public async Task<Account> CreateBotAccount(Account account, Guid automatedId, string? pictureId, public async Task<SnAccount> CreateBotAccount(SnAccount account, Guid automatedId, string? pictureId,
string? backgroundId) string? backgroundId)
{ {
var dupeAutomateCount = await db.Accounts.Where(a => a.AutomatedId == automatedId).CountAsync(); var dupeAutomateCount = await db.Accounts.Where(a => a.AutomatedId == automatedId).CountAsync();
@@ -239,12 +235,12 @@ public class AccountService(
return account; return account;
} }
public async Task<Account?> GetBotAccount(Guid automatedId) public async Task<SnAccount?> GetBotAccount(Guid automatedId)
{ {
return await db.Accounts.FirstOrDefaultAsync(a => a.AutomatedId == automatedId); return await db.Accounts.FirstOrDefaultAsync(a => a.AutomatedId == automatedId);
} }
public async Task RequestAccountDeletion(Account account) public async Task RequestAccountDeletion(SnAccount account)
{ {
var spell = await spells.CreateMagicSpell( var spell = await spells.CreateMagicSpell(
account, account,
@@ -256,7 +252,7 @@ public class AccountService(
await spells.NotifyMagicSpell(spell); await spells.NotifyMagicSpell(spell);
} }
public async Task RequestPasswordReset(Account account) public async Task RequestPasswordReset(SnAccount account)
{ {
var spell = await spells.CreateMagicSpell( var spell = await spells.CreateMagicSpell(
account, account,
@@ -268,7 +264,7 @@ public class AccountService(
await spells.NotifyMagicSpell(spell); await spells.NotifyMagicSpell(spell);
} }
public async Task<bool> CheckAuthFactorExists(Account account, AccountAuthFactorType type) public async Task<bool> CheckAuthFactorExists(SnAccount account, Shared.Models.AccountAuthFactorType type)
{ {
var isExists = await db.AccountAuthFactors var isExists = await db.AccountAuthFactors
.Where(x => x.AccountId == account.Id && x.Type == type) .Where(x => x.AccountId == account.Id && x.Type == type)
@@ -276,45 +272,45 @@ public class AccountService(
return isExists; return isExists;
} }
public async Task<AccountAuthFactor?> CreateAuthFactor(Account account, AccountAuthFactorType type, string? secret) public async Task<SnAccountAuthFactor?> CreateAuthFactor(SnAccount account, Shared.Models.AccountAuthFactorType type, string? secret)
{ {
AccountAuthFactor? factor = null; SnAccountAuthFactor? factor = null;
switch (type) switch (type)
{ {
case AccountAuthFactorType.Password: case Shared.Models.AccountAuthFactorType.Password:
if (string.IsNullOrWhiteSpace(secret)) throw new ArgumentNullException(nameof(secret)); if (string.IsNullOrWhiteSpace(secret)) throw new ArgumentNullException(nameof(secret));
factor = new AccountAuthFactor factor = new SnAccountAuthFactor
{ {
Type = AccountAuthFactorType.Password, Type = Shared.Models.AccountAuthFactorType.Password,
Trustworthy = 1, Trustworthy = 1,
AccountId = account.Id, AccountId = account.Id,
Secret = secret, Secret = secret,
EnabledAt = SystemClock.Instance.GetCurrentInstant(), EnabledAt = SystemClock.Instance.GetCurrentInstant(),
}.HashSecret(); }.HashSecret();
break; break;
case AccountAuthFactorType.EmailCode: case Shared.Models.AccountAuthFactorType.EmailCode:
factor = new AccountAuthFactor factor = new SnAccountAuthFactor
{ {
Type = AccountAuthFactorType.EmailCode, Type = Shared.Models.AccountAuthFactorType.EmailCode,
Trustworthy = 2, Trustworthy = 2,
EnabledAt = SystemClock.Instance.GetCurrentInstant(), EnabledAt = SystemClock.Instance.GetCurrentInstant(),
}; };
break; break;
case AccountAuthFactorType.InAppCode: case Shared.Models.AccountAuthFactorType.InAppCode:
factor = new AccountAuthFactor factor = new SnAccountAuthFactor
{ {
Type = AccountAuthFactorType.InAppCode, Type = Shared.Models.AccountAuthFactorType.InAppCode,
Trustworthy = 1, Trustworthy = 1,
EnabledAt = SystemClock.Instance.GetCurrentInstant() EnabledAt = SystemClock.Instance.GetCurrentInstant()
}; };
break; break;
case AccountAuthFactorType.TimedCode: case Shared.Models.AccountAuthFactorType.TimedCode:
var skOtp = KeyGeneration.GenerateRandomKey(20); var skOtp = KeyGeneration.GenerateRandomKey(20);
var skOtp32 = Base32Encoding.ToString(skOtp); var skOtp32 = Base32Encoding.ToString(skOtp);
factor = new AccountAuthFactor factor = new SnAccountAuthFactor
{ {
Secret = skOtp32, Secret = skOtp32,
Type = AccountAuthFactorType.TimedCode, Type = Shared.Models.AccountAuthFactorType.TimedCode,
Trustworthy = 2, Trustworthy = 2,
EnabledAt = null, // It needs to be tired once to enable EnabledAt = null, // It needs to be tired once to enable
CreatedResponse = new Dictionary<string, object> CreatedResponse = new Dictionary<string, object>
@@ -328,13 +324,13 @@ public class AccountService(
} }
}; };
break; break;
case AccountAuthFactorType.PinCode: case Shared.Models.AccountAuthFactorType.PinCode:
if (string.IsNullOrWhiteSpace(secret)) throw new ArgumentNullException(nameof(secret)); if (string.IsNullOrWhiteSpace(secret)) throw new ArgumentNullException(nameof(secret));
if (!secret.All(char.IsDigit) || secret.Length != 6) if (!secret.All(char.IsDigit) || secret.Length != 6)
throw new ArgumentException("PIN code must be exactly 6 digits"); throw new ArgumentException("PIN code must be exactly 6 digits");
factor = new AccountAuthFactor factor = new SnAccountAuthFactor
{ {
Type = AccountAuthFactorType.PinCode, Type = Shared.Models.AccountAuthFactorType.PinCode,
Trustworthy = 0, // Only for confirming, can't be used for login Trustworthy = 0, // Only for confirming, can't be used for login
Secret = secret, Secret = secret,
EnabledAt = SystemClock.Instance.GetCurrentInstant(), EnabledAt = SystemClock.Instance.GetCurrentInstant(),
@@ -351,10 +347,10 @@ public class AccountService(
return factor; return factor;
} }
public async Task<AccountAuthFactor> EnableAuthFactor(AccountAuthFactor factor, string? code) public async Task<SnAccountAuthFactor> EnableAuthFactor(SnAccountAuthFactor factor, string? code)
{ {
if (factor.EnabledAt is not null) throw new ArgumentException("The factor has been enabled."); if (factor.EnabledAt is not null) throw new ArgumentException("The factor has been enabled.");
if (factor.Type is AccountAuthFactorType.Password or AccountAuthFactorType.TimedCode) if (factor.Type is Shared.Models.AccountAuthFactorType.Password or Shared.Models.AccountAuthFactorType.TimedCode)
{ {
if (code is null || !factor.VerifyPassword(code)) if (code is null || !factor.VerifyPassword(code))
throw new InvalidOperationException( throw new InvalidOperationException(
@@ -369,7 +365,7 @@ public class AccountService(
return factor; return factor;
} }
public async Task<AccountAuthFactor> DisableAuthFactor(AccountAuthFactor factor) public async Task<SnAccountAuthFactor> DisableAuthFactor(SnAccountAuthFactor factor)
{ {
if (factor.EnabledAt is null) throw new ArgumentException("The factor has been disabled."); if (factor.EnabledAt is null) throw new ArgumentException("The factor has been disabled.");
@@ -387,7 +383,7 @@ public class AccountService(
return factor; return factor;
} }
public async Task DeleteAuthFactor(AccountAuthFactor factor) public async Task DeleteAuthFactor(SnAccountAuthFactor factor)
{ {
var count = await db.AccountAuthFactors var count = await db.AccountAuthFactors
.Where(f => f.AccountId == factor.AccountId) .Where(f => f.AccountId == factor.AccountId)
@@ -405,13 +401,13 @@ public class AccountService(
/// </summary> /// </summary>
/// <param name="account">The owner of the auth factor</param> /// <param name="account">The owner of the auth factor</param>
/// <param name="factor">The auth factor needed to send code</param> /// <param name="factor">The auth factor needed to send code</param>
public async Task SendFactorCode(Account account, AccountAuthFactor factor) public async Task SendFactorCode(SnAccount account, SnAccountAuthFactor factor)
{ {
var code = new Random().Next(100000, 999999).ToString("000000"); var code = new Random().Next(100000, 999999).ToString("000000");
switch (factor.Type) switch (factor.Type)
{ {
case AccountAuthFactorType.InAppCode: case Shared.Models.AccountAuthFactorType.InAppCode:
if (await _GetFactorCode(factor) is not null) if (await _GetFactorCode(factor) is not null)
throw new InvalidOperationException("A factor code has been sent and in active duration."); throw new InvalidOperationException("A factor code has been sent and in active duration.");
@@ -430,12 +426,12 @@ public class AccountService(
); );
await _SetFactorCode(factor, code, TimeSpan.FromMinutes(5)); await _SetFactorCode(factor, code, TimeSpan.FromMinutes(5));
break; break;
case AccountAuthFactorType.EmailCode: case Shared.Models.AccountAuthFactorType.EmailCode:
if (await _GetFactorCode(factor) is not null) if (await _GetFactorCode(factor) is not null)
throw new InvalidOperationException("A factor code has been sent and in active duration."); throw new InvalidOperationException("A factor code has been sent and in active duration.");
var contact = await db.AccountContacts var contact = await db.AccountContacts
.Where(c => c.Type == AccountContactType.Email) .Where(c => c.Type == Shared.Models.AccountContactType.Email)
.Where(c => c.VerifiedAt != null) .Where(c => c.VerifiedAt != null)
.Where(c => c.IsPrimary) .Where(c => c.IsPrimary)
.Where(c => c.AccountId == account.Id) .Where(c => c.AccountId == account.Id)
@@ -464,27 +460,27 @@ public class AccountService(
await _SetFactorCode(factor, code, TimeSpan.FromMinutes(30)); await _SetFactorCode(factor, code, TimeSpan.FromMinutes(30));
break; break;
case AccountAuthFactorType.Password: case Shared.Models.AccountAuthFactorType.Password:
case AccountAuthFactorType.TimedCode: case Shared.Models.AccountAuthFactorType.TimedCode:
default: default:
// No need to send, such as password etc... // No need to send, such as password etc...
return; return;
} }
} }
public async Task<bool> VerifyFactorCode(AccountAuthFactor factor, string code) public async Task<bool> VerifyFactorCode(SnAccountAuthFactor factor, string code)
{ {
switch (factor.Type) switch (factor.Type)
{ {
case AccountAuthFactorType.EmailCode: case Shared.Models.AccountAuthFactorType.EmailCode:
case AccountAuthFactorType.InAppCode: case Shared.Models.AccountAuthFactorType.InAppCode:
var correctCode = await _GetFactorCode(factor); var correctCode = await _GetFactorCode(factor);
var isCorrect = correctCode is not null && var isCorrect = correctCode is not null &&
string.Equals(correctCode, code, StringComparison.OrdinalIgnoreCase); string.Equals(correctCode, code, StringComparison.OrdinalIgnoreCase);
await cache.RemoveAsync($"{AuthFactorCachePrefix}{factor.Id}:code"); await cache.RemoveAsync($"{AuthFactorCachePrefix}{factor.Id}:code");
return isCorrect; return isCorrect;
case AccountAuthFactorType.Password: case Shared.Models.AccountAuthFactorType.Password:
case AccountAuthFactorType.TimedCode: case Shared.Models.AccountAuthFactorType.TimedCode:
default: default:
return factor.VerifyPassword(code); return factor.VerifyPassword(code);
} }
@@ -492,7 +488,7 @@ public class AccountService(
private const string AuthFactorCachePrefix = "authfactor:"; private const string AuthFactorCachePrefix = "authfactor:";
private async Task _SetFactorCode(AccountAuthFactor factor, string code, TimeSpan expires) private async Task _SetFactorCode(SnAccountAuthFactor factor, string code, TimeSpan expires)
{ {
await cache.SetAsync( await cache.SetAsync(
$"{AuthFactorCachePrefix}{factor.Id}:code", $"{AuthFactorCachePrefix}{factor.Id}:code",
@@ -501,7 +497,7 @@ public class AccountService(
); );
} }
private async Task<string?> _GetFactorCode(AccountAuthFactor factor) private async Task<string?> _GetFactorCode(SnAccountAuthFactor factor)
{ {
return await cache.GetAsync<string?>( return await cache.GetAsync<string?>(
$"{AuthFactorCachePrefix}{factor.Id}:code" $"{AuthFactorCachePrefix}{factor.Id}:code"
@@ -515,7 +511,7 @@ public class AccountService(
.AnyAsync(s => s.Challenge.ClientId == id); .AnyAsync(s => s.Challenge.ClientId == id);
} }
public async Task<SnAuthClient> UpdateDeviceName(Account account, string deviceId, string label) public async Task<SnAuthClient> UpdateDeviceName(SnAccount account, string deviceId, string label)
{ {
var device = await db.AuthClients.FirstOrDefaultAsync(c => c.DeviceId == deviceId && c.AccountId == account.Id var device = await db.AuthClients.FirstOrDefaultAsync(c => c.DeviceId == deviceId && c.AccountId == account.Id
); );
@@ -528,7 +524,7 @@ public class AccountService(
return device; return device;
} }
public async Task DeleteSession(Account account, Guid sessionId) public async Task DeleteSession(SnAccount account, Guid sessionId)
{ {
var session = await db.AuthSessions var session = await db.AuthSessions
.Include(s => s.Challenge) .Include(s => s.Challenge)
@@ -554,7 +550,7 @@ public class AccountService(
await cache.RemoveAsync($"{AuthService.AuthCachePrefix}{session.Id}"); await cache.RemoveAsync($"{AuthService.AuthCachePrefix}{session.Id}");
} }
public async Task DeleteDevice(Account account, string deviceId) public async Task DeleteDevice(SnAccount account, string deviceId)
{ {
var device = await db.AuthClients.FirstOrDefaultAsync(c => c.DeviceId == deviceId && c.AccountId == account.Id var device = await db.AuthClients.FirstOrDefaultAsync(c => c.DeviceId == deviceId && c.AccountId == account.Id
); );
@@ -584,7 +580,7 @@ public class AccountService(
await cache.RemoveAsync($"{AuthService.AuthCachePrefix}{item.Id}"); await cache.RemoveAsync($"{AuthService.AuthCachePrefix}{item.Id}");
} }
public async Task<AccountContact> CreateContactMethod(Account account, AccountContactType type, string content) public async Task<SnAccountContact> CreateContactMethod(SnAccount account, Shared.Models.AccountContactType type, string content)
{ {
var isExists = await db.AccountContacts var isExists = await db.AccountContacts
.Where(x => x.AccountId == account.Id && x.Type == type && x.Content == content) .Where(x => x.AccountId == account.Id && x.Type == type && x.Content == content)
@@ -592,7 +588,7 @@ public class AccountService(
if (isExists) if (isExists)
throw new InvalidOperationException("Contact method already exists."); throw new InvalidOperationException("Contact method already exists.");
var contact = new AccountContact var contact = new SnAccountContact
{ {
Type = type, Type = type,
Content = content, Content = content,
@@ -605,7 +601,7 @@ public class AccountService(
return contact; return contact;
} }
public async Task VerifyContactMethod(Account account, AccountContact contact) public async Task VerifyContactMethod(SnAccount account, SnAccountContact contact)
{ {
var spell = await spells.CreateMagicSpell( var spell = await spells.CreateMagicSpell(
account, account,
@@ -617,7 +613,7 @@ public class AccountService(
await spells.NotifyMagicSpell(spell); await spells.NotifyMagicSpell(spell);
} }
public async Task<AccountContact> SetContactMethodPrimary(Account account, AccountContact contact) public async Task<SnAccountContact> SetContactMethodPrimary(SnAccount account, SnAccountContact contact)
{ {
if (contact.AccountId != account.Id) if (contact.AccountId != account.Id)
throw new InvalidOperationException("Contact method does not belong to this account."); throw new InvalidOperationException("Contact method does not belong to this account.");
@@ -646,7 +642,7 @@ public class AccountService(
} }
} }
public async Task<AccountContact> SetContactMethodPublic(Account account, AccountContact contact, bool isPublic) public async Task<SnAccountContact> SetContactMethodPublic(SnAccount account, SnAccountContact contact, bool isPublic)
{ {
contact.IsPublic = isPublic; contact.IsPublic = isPublic;
db.AccountContacts.Update(contact); db.AccountContacts.Update(contact);
@@ -654,7 +650,7 @@ public class AccountService(
return contact; return contact;
} }
public async Task DeleteContactMethod(Account account, AccountContact contact) public async Task DeleteContactMethod(SnAccount account, SnAccountContact contact)
{ {
if (contact.AccountId != account.Id) if (contact.AccountId != account.Id)
throw new InvalidOperationException("Contact method does not belong to this account."); throw new InvalidOperationException("Contact method does not belong to this account.");
@@ -669,7 +665,7 @@ public class AccountService(
/// This method will grant a badge to the account. /// This method will grant a badge to the account.
/// Shouldn't be exposed to normal user and the user itself. /// Shouldn't be exposed to normal user and the user itself.
/// </summary> /// </summary>
public async Task<AccountBadge> GrantBadge(Account account, AccountBadge badge) public async Task<SnAccountBadge> GrantBadge(SnAccount account, SnAccountBadge badge)
{ {
badge.AccountId = account.Id; badge.AccountId = account.Id;
db.Badges.Add(badge); db.Badges.Add(badge);
@@ -681,14 +677,12 @@ public class AccountService(
/// This method will revoke a badge from the account. /// This method will revoke a badge from the account.
/// Shouldn't be exposed to normal user and the user itself. /// Shouldn't be exposed to normal user and the user itself.
/// </summary> /// </summary>
public async Task RevokeBadge(Account account, Guid badgeId) public async Task RevokeBadge(SnAccount account, Guid badgeId)
{ {
var badge = await db.Badges var badge = await db.Badges
.Where(b => b.AccountId == account.Id && b.Id == badgeId) .Where(b => b.AccountId == account.Id && b.Id == badgeId)
.OrderByDescending(b => b.CreatedAt) .OrderByDescending(b => b.CreatedAt)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync() ?? throw new InvalidOperationException("Badge was not found.");
if (badge is null) throw new InvalidOperationException("Badge was not found.");
var profile = await db.AccountProfiles var profile = await db.AccountProfiles
.Where(p => p.AccountId == account.Id) .Where(p => p.AccountId == account.Id)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
@@ -699,7 +693,7 @@ public class AccountService(
await db.SaveChangesAsync(); await db.SaveChangesAsync();
} }
public async Task ActiveBadge(Account account, Guid badgeId) public async Task ActiveBadge(SnAccount account, Guid badgeId)
{ {
await using var transaction = await db.Database.BeginTransactionAsync(); await using var transaction = await db.Database.BeginTransactionAsync();
@@ -733,7 +727,7 @@ public class AccountService(
} }
} }
public async Task DeleteAccount(Account account) public async Task DeleteAccount(SnAccount account)
{ {
await db.AuthSessions await db.AuthSessions
.Where(s => s.AccountId == account.Id) .Where(s => s.AccountId == account.Id)

View File

@@ -236,7 +236,7 @@ public class AccountServiceGrpc(
var relationship = await relationships.GetRelationship( var relationship = await relationships.GetRelationship(
Guid.Parse(request.AccountId), Guid.Parse(request.AccountId),
Guid.Parse(request.RelatedId), Guid.Parse(request.RelatedId),
status: (RelationshipStatus?)request.Status status: (Shared.Models.RelationshipStatus?)request.Status
); );
return new GetRelationshipResponse return new GetRelationshipResponse
{ {
@@ -256,7 +256,7 @@ public class AccountServiceGrpc(
hasRelationship = await relationships.HasRelationshipWithStatus( hasRelationship = await relationships.HasRelationshipWithStatus(
Guid.Parse(request.AccountId), Guid.Parse(request.AccountId),
Guid.Parse(request.RelatedId), Guid.Parse(request.RelatedId),
(RelationshipStatus)request.Status (Shared.Models.RelationshipStatus)request.Status
); );
return new BoolValue { Value = hasRelationship }; return new BoolValue { Value = hasRelationship };
} }

View File

@@ -8,7 +8,7 @@ public class ActionLogService(GeoIpService geo, FlushBufferService fbs)
{ {
public void CreateActionLog(Guid accountId, string action, Dictionary<string, object?> meta) public void CreateActionLog(Guid accountId, string action, Dictionary<string, object?> meta)
{ {
var log = new ActionLog var log = new SnActionLog
{ {
Action = action, Action = action,
AccountId = accountId, AccountId = accountId,
@@ -19,9 +19,9 @@ public class ActionLogService(GeoIpService geo, FlushBufferService fbs)
} }
public void CreateActionLogFromRequest(string action, Dictionary<string, object> meta, HttpRequest request, public void CreateActionLogFromRequest(string action, Dictionary<string, object> meta, HttpRequest request,
Account? account = null) SnAccount? account = null)
{ {
var log = new ActionLog var log = new SnActionLog
{ {
Action = action, Action = action,
Meta = meta, Meta = meta,
@@ -30,7 +30,7 @@ public class ActionLogService(GeoIpService geo, FlushBufferService fbs)
Location = geo.GetPointFromIp(request.HttpContext.Connection.RemoteIpAddress?.ToString()) Location = geo.GetPointFromIp(request.HttpContext.Connection.RemoteIpAddress?.ToString())
}; };
if (request.HttpContext.Items["CurrentUser"] is Account currentUser) if (request.HttpContext.Items["CurrentUser"] is SnAccount currentUser)
log.AccountId = currentUser.Id; log.AccountId = currentUser.Id;
else if (account != null) else if (account != null)
log.AccountId = account.Id; log.AccountId = account.Id;

View File

@@ -22,7 +22,7 @@ public class BotAccountReceiverGrpc(
ServerCallContext context ServerCallContext context
) )
{ {
var account = Account.FromProtoValue(request.Account); var account = SnAccount.FromProtoValue(request.Account);
account = await accounts.CreateBotAccount( account = await accounts.CreateBotAccount(
account, account,
Guid.Parse(request.AutomatedId), Guid.Parse(request.AutomatedId),
@@ -48,7 +48,7 @@ public class BotAccountReceiverGrpc(
ServerCallContext context ServerCallContext context
) )
{ {
var account = Account.FromProtoValue(request.Account); var account = SnAccount.FromProtoValue(request.Account);
if (request.PictureId is not null) if (request.PictureId is not null)
{ {

View File

@@ -50,7 +50,7 @@ public class MagicSpellController(AppDatabase db, MagicSpellService sp) : Contro
return NotFound(); return NotFound();
try try
{ {
if (spell.Type == MagicSpellType.AuthPasswordReset && request?.NewPassword is not null) if (spell.Type == Shared.Models.MagicSpellType.AuthPasswordReset && request?.NewPassword is not null)
await sp.ApplyPasswordReset(spell, request.NewPassword); await sp.ApplyPasswordReset(spell, request.NewPassword);
else else
await sp.ApplyMagicSpell(spell); await sp.ApplyMagicSpell(spell);

View File

@@ -2,8 +2,8 @@ using System.Security.Cryptography;
using System.Text.Json; using System.Text.Json;
using DysonNetwork.Pass.Emails; using DysonNetwork.Pass.Emails;
using DysonNetwork.Pass.Mailer; using DysonNetwork.Pass.Mailer;
using DysonNetwork.Pass.Permission;
using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Localization; using Microsoft.Extensions.Localization;
using NodaTime; using NodaTime;
@@ -20,8 +20,8 @@ public class MagicSpellService(
ICacheService cache ICacheService cache
) )
{ {
public async Task<MagicSpell> CreateMagicSpell( public async Task<SnMagicSpell> CreateMagicSpell(
Account account, SnAccount account,
MagicSpellType type, MagicSpellType type,
Dictionary<string, object> meta, Dictionary<string, object> meta,
Instant? expiredAt = null, Instant? expiredAt = null,
@@ -42,7 +42,7 @@ public class MagicSpellService(
} }
var spellWord = _GenerateRandomString(128); var spellWord = _GenerateRandomString(128);
var spell = new MagicSpell var spell = new SnMagicSpell
{ {
Spell = spellWord, Spell = spellWord,
Type = type, Type = type,
@@ -60,7 +60,7 @@ public class MagicSpellService(
private const string SpellNotifyCacheKeyPrefix = "spells:notify:"; private const string SpellNotifyCacheKeyPrefix = "spells:notify:";
public async Task NotifyMagicSpell(MagicSpell spell, bool bypassVerify = false) public async Task NotifyMagicSpell(SnMagicSpell spell, bool bypassVerify = false)
{ {
var cacheKey = SpellNotifyCacheKeyPrefix + spell.Id; var cacheKey = SpellNotifyCacheKeyPrefix + spell.Id;
var (found, _) = await cache.GetAsyncWithStatus<bool?>(cacheKey); var (found, _) = await cache.GetAsyncWithStatus<bool?>(cacheKey);
@@ -156,7 +156,7 @@ public class MagicSpellService(
} }
} }
public async Task ApplyMagicSpell(MagicSpell spell) public async Task ApplyMagicSpell(SnMagicSpell spell)
{ {
switch (spell.Type) switch (spell.Type)
{ {
@@ -191,7 +191,7 @@ public class MagicSpellService(
var defaultGroup = await db.PermissionGroups.FirstOrDefaultAsync(g => g.Key == "default"); var defaultGroup = await db.PermissionGroups.FirstOrDefaultAsync(g => g.Key == "default");
if (defaultGroup is not null && account is not null) if (defaultGroup is not null && account is not null)
{ {
db.PermissionGroupMembers.Add(new PermissionGroupMember db.PermissionGroupMembers.Add(new SnPermissionGroupMember
{ {
Actor = $"user:{account.Id}", Actor = $"user:{account.Id}",
Group = defaultGroup Group = defaultGroup
@@ -218,7 +218,7 @@ public class MagicSpellService(
await db.SaveChangesAsync(); await db.SaveChangesAsync();
} }
public async Task ApplyPasswordReset(MagicSpell spell, string newPassword) public async Task ApplyPasswordReset(SnMagicSpell spell, string newPassword)
{ {
if (spell.Type != MagicSpellType.AuthPasswordReset) if (spell.Type != MagicSpellType.AuthPasswordReset)
throw new ArgumentException("This spell is not a password reset spell."); throw new ArgumentException("This spell is not a password reset spell.");
@@ -231,7 +231,7 @@ public class MagicSpellService(
{ {
var account = await db.Accounts.FirstOrDefaultAsync(c => c.Id == spell.AccountId); var account = await db.Accounts.FirstOrDefaultAsync(c => c.Id == spell.AccountId);
if (account is null) throw new InvalidOperationException("Both account and auth factor was not found."); if (account is null) throw new InvalidOperationException("Both account and auth factor was not found.");
passwordFactor = new AccountAuthFactor passwordFactor = new SnAccountAuthFactor
{ {
Type = AccountAuthFactorType.Password, Type = AccountAuthFactorType.Password,
Account = account, Account = account,
@@ -257,6 +257,6 @@ public class MagicSpellService(
var base64String = Convert.ToBase64String(randomBytes); var base64String = Convert.ToBase64String(randomBytes);
return base64String.Substring(0, length); return base64String[..length];
} }
} }

View File

@@ -1,3 +1,4 @@
using DysonNetwork.Shared.Models;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@@ -26,7 +27,7 @@ public class NotableDaysController(NotableDaysService days) : ControllerBase
[Authorize] [Authorize]
public async Task<ActionResult<List<NotableDay>>> GetAccountNotableDays(int year) public async Task<ActionResult<List<NotableDay>>> GetAccountNotableDays(int year)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var region = currentUser.Region; var region = currentUser.Region;
if (string.IsNullOrWhiteSpace(region)) region = "us"; if (string.IsNullOrWhiteSpace(region)) region = "us";
@@ -39,7 +40,7 @@ public class NotableDaysController(NotableDaysService days) : ControllerBase
[Authorize] [Authorize]
public async Task<ActionResult<List<NotableDay>>> GetAccountNotableDaysCurrentYear() public async Task<ActionResult<List<NotableDay>>> GetAccountNotableDaysCurrentYear()
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var currentYear = DateTime.Now.Year; var currentYear = DateTime.Now.Year;
var region = currentUser.Region; var region = currentUser.Region;
@@ -64,7 +65,7 @@ public class NotableDaysController(NotableDaysService days) : ControllerBase
[Authorize] [Authorize]
public async Task<ActionResult<NotableDay?>> GetAccountNextHoliday() public async Task<ActionResult<NotableDay?>> GetAccountNextHoliday()
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var region = currentUser.Region; var region = currentUser.Region;
if (string.IsNullOrWhiteSpace(region)) region = "us"; if (string.IsNullOrWhiteSpace(region)) region = "us";

View File

@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using DysonNetwork.Shared.Models;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@@ -12,10 +13,10 @@ public class RelationshipController(AppDatabase db, RelationshipService rels) :
{ {
[HttpGet] [HttpGet]
[Authorize] [Authorize]
public async Task<ActionResult<List<Relationship>>> ListRelationships([FromQuery] int offset = 0, public async Task<ActionResult<List<SnAccountRelationship>>> ListRelationships([FromQuery] int offset = 0,
[FromQuery] int take = 20) [FromQuery] int take = 20)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var userId = currentUser.Id; var userId = currentUser.Id;
var query = db.AccountRelationships.AsQueryable() var query = db.AccountRelationships.AsQueryable()
@@ -44,9 +45,9 @@ public class RelationshipController(AppDatabase db, RelationshipService rels) :
[HttpGet("requests")] [HttpGet("requests")]
[Authorize] [Authorize]
public async Task<ActionResult<List<Relationship>>> ListSentRequests() public async Task<ActionResult<List<SnAccountRelationship>>> ListSentRequests()
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var relationships = await db.AccountRelationships var relationships = await db.AccountRelationships
.Where(r => r.AccountId == currentUser.Id && r.Status == RelationshipStatus.Pending) .Where(r => r.AccountId == currentUser.Id && r.Status == RelationshipStatus.Pending)
@@ -66,10 +67,10 @@ public class RelationshipController(AppDatabase db, RelationshipService rels) :
[HttpPost("{userId:guid}")] [HttpPost("{userId:guid}")]
[Authorize] [Authorize]
public async Task<ActionResult<Relationship>> CreateRelationship(Guid userId, public async Task<ActionResult<SnAccountRelationship>> CreateRelationship(Guid userId,
[FromBody] RelationshipRequest request) [FromBody] RelationshipRequest request)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var relatedUser = await db.Accounts.FindAsync(userId); var relatedUser = await db.Accounts.FindAsync(userId);
if (relatedUser is null) return NotFound("Account was not found."); if (relatedUser is null) return NotFound("Account was not found.");
@@ -89,10 +90,10 @@ public class RelationshipController(AppDatabase db, RelationshipService rels) :
[HttpPatch("{userId:guid}")] [HttpPatch("{userId:guid}")]
[Authorize] [Authorize]
public async Task<ActionResult<Relationship>> UpdateRelationship(Guid userId, public async Task<ActionResult<SnAccountRelationship>> UpdateRelationship(Guid userId,
[FromBody] RelationshipRequest request) [FromBody] RelationshipRequest request)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
try try
{ {
@@ -111,9 +112,9 @@ public class RelationshipController(AppDatabase db, RelationshipService rels) :
[HttpGet("{userId:guid}")] [HttpGet("{userId:guid}")]
[Authorize] [Authorize]
public async Task<ActionResult<Relationship>> GetRelationship(Guid userId) public async Task<ActionResult<SnAccountRelationship>> GetRelationship(Guid userId)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var now = Instant.FromDateTimeUtc(DateTime.UtcNow); var now = Instant.FromDateTimeUtc(DateTime.UtcNow);
var queries = db.AccountRelationships.AsQueryable() var queries = db.AccountRelationships.AsQueryable()
@@ -131,9 +132,9 @@ public class RelationshipController(AppDatabase db, RelationshipService rels) :
[HttpPost("{userId:guid}/friends")] [HttpPost("{userId:guid}/friends")]
[Authorize] [Authorize]
public async Task<ActionResult<Relationship>> SendFriendRequest(Guid userId) public async Task<ActionResult<SnAccountRelationship>> SendFriendRequest(Guid userId)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var relatedUser = await db.Accounts.FindAsync(userId); var relatedUser = await db.Accounts.FindAsync(userId);
if (relatedUser is null) return NotFound("Account was not found."); if (relatedUser is null) return NotFound("Account was not found.");
@@ -158,7 +159,7 @@ public class RelationshipController(AppDatabase db, RelationshipService rels) :
[Authorize] [Authorize]
public async Task<ActionResult> DeleteFriendRequest(Guid userId) public async Task<ActionResult> DeleteFriendRequest(Guid userId)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
try try
{ {
@@ -173,9 +174,9 @@ public class RelationshipController(AppDatabase db, RelationshipService rels) :
[HttpPost("{userId:guid}/friends/accept")] [HttpPost("{userId:guid}/friends/accept")]
[Authorize] [Authorize]
public async Task<ActionResult<Relationship>> AcceptFriendRequest(Guid userId) public async Task<ActionResult<SnAccountRelationship>> AcceptFriendRequest(Guid userId)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var relationship = await rels.GetRelationship(userId, currentUser.Id, RelationshipStatus.Pending); var relationship = await rels.GetRelationship(userId, currentUser.Id, RelationshipStatus.Pending);
if (relationship is null) return NotFound("Friend request was not found."); if (relationship is null) return NotFound("Friend request was not found.");
@@ -193,9 +194,9 @@ public class RelationshipController(AppDatabase db, RelationshipService rels) :
[HttpPost("{userId:guid}/friends/decline")] [HttpPost("{userId:guid}/friends/decline")]
[Authorize] [Authorize]
public async Task<ActionResult<Relationship>> DeclineFriendRequest(Guid userId) public async Task<ActionResult<SnAccountRelationship>> DeclineFriendRequest(Guid userId)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var relationship = await rels.GetRelationship(userId, currentUser.Id, RelationshipStatus.Pending); var relationship = await rels.GetRelationship(userId, currentUser.Id, RelationshipStatus.Pending);
if (relationship is null) return NotFound("Friend request was not found."); if (relationship is null) return NotFound("Friend request was not found.");
@@ -213,9 +214,9 @@ public class RelationshipController(AppDatabase db, RelationshipService rels) :
[HttpPost("{userId:guid}/block")] [HttpPost("{userId:guid}/block")]
[Authorize] [Authorize]
public async Task<ActionResult<Relationship>> BlockUser(Guid userId) public async Task<ActionResult<SnAccountRelationship>> BlockUser(Guid userId)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var relatedUser = await db.Accounts.FindAsync(userId); var relatedUser = await db.Accounts.FindAsync(userId);
if (relatedUser is null) return NotFound("Account was not found."); if (relatedUser is null) return NotFound("Account was not found.");
@@ -233,9 +234,9 @@ public class RelationshipController(AppDatabase db, RelationshipService rels) :
[HttpDelete("{userId:guid}/block")] [HttpDelete("{userId:guid}/block")]
[Authorize] [Authorize]
public async Task<ActionResult<Relationship>> UnblockUser(Guid userId) public async Task<ActionResult<SnAccountRelationship>> UnblockUser(Guid userId)
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var relatedUser = await db.Accounts.FindAsync(userId); var relatedUser = await db.Accounts.FindAsync(userId);
if (relatedUser is null) return NotFound("Account was not found."); if (relatedUser is null) return NotFound("Account was not found.");

View File

@@ -1,5 +1,6 @@
using DysonNetwork.Pass.Localization; using DysonNetwork.Pass.Localization;
using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.Models;
using DysonNetwork.Shared.Proto; using DysonNetwork.Shared.Proto;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Localization; using Microsoft.Extensions.Localization;
@@ -26,7 +27,7 @@ public class RelationshipService(
return count > 0; return count > 0;
} }
public async Task<Relationship?> GetRelationship( public async Task<SnAccountRelationship?> GetRelationship(
Guid accountId, Guid accountId,
Guid relatedId, Guid relatedId,
RelationshipStatus? status = null, RelationshipStatus? status = null,
@@ -42,7 +43,7 @@ public class RelationshipService(
return relationship; return relationship;
} }
public async Task<Relationship> CreateRelationship(Account sender, Account target, RelationshipStatus status) public async Task<SnAccountRelationship> CreateRelationship(SnAccount sender, SnAccount target, RelationshipStatus status)
{ {
if (status == RelationshipStatus.Pending) if (status == RelationshipStatus.Pending)
throw new InvalidOperationException( throw new InvalidOperationException(
@@ -50,7 +51,7 @@ public class RelationshipService(
if (await HasExistingRelationship(sender.Id, target.Id)) if (await HasExistingRelationship(sender.Id, target.Id))
throw new InvalidOperationException("Found existing relationship between you and target user."); throw new InvalidOperationException("Found existing relationship between you and target user.");
var relationship = new Relationship var relationship = new SnAccountRelationship
{ {
AccountId = sender.Id, AccountId = sender.Id,
RelatedId = target.Id, RelatedId = target.Id,
@@ -65,14 +66,14 @@ public class RelationshipService(
return relationship; return relationship;
} }
public async Task<Relationship> BlockAccount(Account sender, Account target) public async Task<SnAccountRelationship> BlockAccount(SnAccount sender, SnAccount target)
{ {
if (await HasExistingRelationship(sender.Id, target.Id)) if (await HasExistingRelationship(sender.Id, target.Id))
return await UpdateRelationship(sender.Id, target.Id, RelationshipStatus.Blocked); return await UpdateRelationship(sender.Id, target.Id, RelationshipStatus.Blocked);
return await CreateRelationship(sender, target, RelationshipStatus.Blocked); return await CreateRelationship(sender, target, RelationshipStatus.Blocked);
} }
public async Task<Relationship> UnblockAccount(Account sender, Account target) public async Task<SnAccountRelationship> UnblockAccount(SnAccount sender, SnAccount target)
{ {
var relationship = await GetRelationship(sender.Id, target.Id, RelationshipStatus.Blocked); var relationship = await GetRelationship(sender.Id, target.Id, RelationshipStatus.Blocked);
if (relationship is null) throw new ArgumentException("There is no relationship between you and the user."); if (relationship is null) throw new ArgumentException("There is no relationship between you and the user.");
@@ -84,12 +85,12 @@ public class RelationshipService(
return relationship; return relationship;
} }
public async Task<Relationship> SendFriendRequest(Account sender, Account target) public async Task<SnAccountRelationship> SendFriendRequest(SnAccount sender, SnAccount target)
{ {
if (await HasExistingRelationship(sender.Id, target.Id)) if (await HasExistingRelationship(sender.Id, target.Id))
throw new InvalidOperationException("Found existing relationship between you and target user."); throw new InvalidOperationException("Found existing relationship between you and target user.");
var relationship = new Relationship var relationship = new SnAccountRelationship
{ {
AccountId = sender.Id, AccountId = sender.Id,
RelatedId = target.Id, RelatedId = target.Id,
@@ -128,8 +129,8 @@ public class RelationshipService(
await PurgeRelationshipCache(relationship.AccountId, relationship.RelatedId); await PurgeRelationshipCache(relationship.AccountId, relationship.RelatedId);
} }
public async Task<Relationship> AcceptFriendRelationship( public async Task<SnAccountRelationship> AcceptFriendRelationship(
Relationship relationship, SnAccountRelationship relationship,
RelationshipStatus status = RelationshipStatus.Friends RelationshipStatus status = RelationshipStatus.Friends
) )
{ {
@@ -144,7 +145,7 @@ public class RelationshipService(
relationship.ExpiredAt = null; relationship.ExpiredAt = null;
db.Update(relationship); db.Update(relationship);
var relationshipBackward = new Relationship var relationshipBackward = new SnAccountRelationship
{ {
AccountId = relationship.RelatedId, AccountId = relationship.RelatedId,
RelatedId = relationship.AccountId, RelatedId = relationship.AccountId,
@@ -159,7 +160,7 @@ public class RelationshipService(
return relationshipBackward; return relationshipBackward;
} }
public async Task<Relationship> UpdateRelationship(Guid accountId, Guid relatedId, RelationshipStatus status) public async Task<SnAccountRelationship> UpdateRelationship(Guid accountId, Guid relatedId, RelationshipStatus status)
{ {
var relationship = await GetRelationship(accountId, relatedId); var relationship = await GetRelationship(accountId, relatedId);
if (relationship is null) throw new ArgumentException("There is no relationship between you and the user."); if (relationship is null) throw new ArgumentException("There is no relationship between you and the user.");
@@ -173,7 +174,7 @@ public class RelationshipService(
return relationship; return relationship;
} }
public async Task<List<Guid>> ListAccountFriends(Account account) public async Task<List<Guid>> ListAccountFriends(SnAccount account)
{ {
return await ListAccountFriends(account.Id); return await ListAccountFriends(account.Id);
} }
@@ -197,7 +198,7 @@ public class RelationshipService(
return friends ?? []; return friends ?? [];
} }
public async Task<List<Guid>> ListAccountBlocked(Account account) public async Task<List<Guid>> ListAccountBlocked(SnAccount account)
{ {
return await ListAccountBlocked(account.Id); return await ListAccountBlocked(account.Id);
} }

View File

@@ -2,9 +2,7 @@ using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using DysonNetwork.Pass.Account;
using DysonNetwork.Pass.Permission; using DysonNetwork.Pass.Permission;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Design;
@@ -24,16 +22,16 @@ public class AppDatabase(
public DbSet<SnPermissionGroupMember> PermissionGroupMembers { get; set; } = null!; public DbSet<SnPermissionGroupMember> PermissionGroupMembers { get; set; } = null!;
public DbSet<SnMagicSpell> MagicSpells { get; set; } = null!; public DbSet<SnMagicSpell> MagicSpells { get; set; } = null!;
public DbSet<Account.Account> Accounts { get; set; } = null!; public DbSet<SnAccount> Accounts { get; set; } = null!;
public DbSet<AccountConnection> AccountConnections { get; set; } = null!; public DbSet<SnAccountConnection> AccountConnections { get; set; } = null!;
public DbSet<SnAccountProfile> AccountProfiles { get; set; } = null!; public DbSet<SnAccountProfile> AccountProfiles { get; set; } = null!;
public DbSet<AccountContact> AccountContacts { get; set; } = null!; public DbSet<SnAccountContact> AccountContacts { get; set; } = null!;
public DbSet<AccountAuthFactor> AccountAuthFactors { get; set; } = null!; public DbSet<SnAccountAuthFactor> AccountAuthFactors { get; set; } = null!;
public DbSet<SnAccountRelationship> AccountRelationships { get; set; } = null!; public DbSet<SnAccountRelationship> AccountRelationships { get; set; } = null!;
public DbSet<SnAccountStatus> AccountStatuses { get; set; } = null!; public DbSet<SnAccountStatus> AccountStatuses { get; set; } = null!;
public DbSet<SnCheckInResult> AccountCheckInResults { get; set; } = null!; public DbSet<SnCheckInResult> AccountCheckInResults { get; set; } = null!;
public DbSet<SnAccountBadge> Badges { get; set; } = null!; public DbSet<SnAccountBadge> Badges { get; set; } = null!;
public DbSet<ActionLog> ActionLogs { get; set; } = null!; public DbSet<SnActionLog> ActionLogs { get; set; } = null!;
public DbSet<SnAbuseReport> AbuseReports { get; set; } = null!; public DbSet<SnAbuseReport> AbuseReports { get; set; } = null!;
public DbSet<SnAuthSession> AuthSessions { get; set; } = null!; public DbSet<SnAuthSession> AuthSessions { get; set; } = null!;
@@ -41,17 +39,17 @@ public class AppDatabase(
public DbSet<SnAuthClient> AuthClients { get; set; } = null!; public DbSet<SnAuthClient> AuthClients { get; set; } = null!;
public DbSet<SnApiKey> ApiKeys { get; set; } = null!; public DbSet<SnApiKey> ApiKeys { get; set; } = null!;
public DbSet<Shared.Models.SnWallet> Wallets { get; set; } = null!; public DbSet<SnWallet> Wallets { get; set; } = null!;
public DbSet<SnWalletPocket> WalletPockets { get; set; } = null!; public DbSet<SnWalletPocket> WalletPockets { get; set; } = null!;
public DbSet<SnWalletOrder> PaymentOrders { get; set; } = null!; public DbSet<SnWalletOrder> PaymentOrders { get; set; } = null!;
public DbSet<SnWalletTransaction> PaymentTransactions { get; set; } = null!; public DbSet<SnWalletTransaction> PaymentTransactions { get; set; } = null!;
public DbSet<SnSubscription> WalletSubscriptions { get; set; } = null!; public DbSet<SnWalletSubscription> WalletSubscriptions { get; set; } = null!;
public DbSet<Coupon> WalletCoupons { get; set; } = null!; public DbSet<SnWalletCoupon> WalletCoupons { get; set; } = null!;
public DbSet<SnAccountPunishment> Punishments { get; set; } = null!; public DbSet<SnAccountPunishment> Punishments { get; set; } = null!;
public DbSet<SocialCreditRecord> SocialCreditRecords { get; set; } = null!; public DbSet<SnSocialCreditRecord> SocialCreditRecords { get; set; } = null!;
public DbSet<ExperienceRecord> ExperienceRecords { get; set; } = null!; public DbSet<SnExperienceRecord> ExperienceRecords { get; set; } = null!;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{ {

View File

@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using DysonNetwork.Shared.Models;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@@ -14,7 +15,7 @@ public class ApiKeyController(AppDatabase db, AuthService auth) : ControllerBase
[Authorize] [Authorize]
public async Task<IActionResult> GetKeys([FromQuery] int offset = 0, [FromQuery] int take = 20) public async Task<IActionResult> GetKeys([FromQuery] int offset = 0, [FromQuery] int take = 20)
{ {
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var query = db.ApiKeys var query = db.ApiKeys
.Where(e => e.AccountId == currentUser.Id) .Where(e => e.AccountId == currentUser.Id)
@@ -34,7 +35,7 @@ public class ApiKeyController(AppDatabase db, AuthService auth) : ControllerBase
[Authorize] [Authorize]
public async Task<IActionResult> GetKey(Guid id) public async Task<IActionResult> GetKey(Guid id)
{ {
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var key = await db.ApiKeys var key = await db.ApiKeys
.Where(e => e.AccountId == currentUser.Id) .Where(e => e.AccountId == currentUser.Id)
@@ -56,7 +57,7 @@ public class ApiKeyController(AppDatabase db, AuthService auth) : ControllerBase
{ {
if (string.IsNullOrWhiteSpace(request.Label)) if (string.IsNullOrWhiteSpace(request.Label))
return BadRequest("Label is required"); return BadRequest("Label is required");
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var key = await auth.CreateApiKey(currentUser.Id, request.Label, request.ExpiredAt); var key = await auth.CreateApiKey(currentUser.Id, request.Label, request.ExpiredAt);
key.Key = await auth.IssueApiKeyToken(key); key.Key = await auth.IssueApiKeyToken(key);
@@ -67,7 +68,7 @@ public class ApiKeyController(AppDatabase db, AuthService auth) : ControllerBase
[Authorize] [Authorize]
public async Task<IActionResult> RotateKey(Guid id) public async Task<IActionResult> RotateKey(Guid id)
{ {
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var key = await auth.GetApiKey(id, currentUser.Id); var key = await auth.GetApiKey(id, currentUser.Id);
if(key is null) return NotFound(); if(key is null) return NotFound();
@@ -80,7 +81,7 @@ public class ApiKeyController(AppDatabase db, AuthService auth) : ControllerBase
[Authorize] [Authorize]
public async Task<IActionResult> DeleteKey(Guid id) public async Task<IActionResult> DeleteKey(Guid id)
{ {
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var key = await auth.GetApiKey(id, currentUser.Id); var key = await auth.GetApiKey(id, currentUser.Id);
if(key is null) return NotFound(); if(key is null) return NotFound();

View File

@@ -1,5 +1,3 @@
using NodaTime;
namespace DysonNetwork.Pass.Auth; namespace DysonNetwork.Pass.Auth;
public static class AuthCacheConstants public static class AuthCacheConstants

View File

@@ -2,13 +2,10 @@ using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using NodaTime; using NodaTime;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using DysonNetwork.Pass.Account;
using DysonNetwork.Pass.Localization; using DysonNetwork.Pass.Localization;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.GeoIp; using DysonNetwork.Shared.GeoIp;
using DysonNetwork.Shared.Proto; using DysonNetwork.Shared.Proto;
using Microsoft.Extensions.Localization; using Microsoft.Extensions.Localization;
using AccountAuthFactor = DysonNetwork.Pass.Account.AccountAuthFactor;
using AccountService = DysonNetwork.Pass.Account.AccountService; using AccountService = DysonNetwork.Pass.Account.AccountService;
using ActionLogService = DysonNetwork.Pass.Account.ActionLogService; using ActionLogService = DysonNetwork.Pass.Account.ActionLogService;
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
@@ -41,7 +38,7 @@ public class AuthController(
} }
[HttpPost("challenge")] [HttpPost("challenge")]
public async Task<ActionResult<AuthChallenge>> CreateChallenge([FromBody] ChallengeRequest request) public async Task<ActionResult<SnAuthChallenge>> CreateChallenge([FromBody] ChallengeRequest request)
{ {
var account = await accounts.LookupAccount(request.Account); var account = await accounts.LookupAccount(request.Account);
if (account is null) return NotFound("Account was not found."); if (account is null) return NotFound("Account was not found.");
@@ -73,7 +70,7 @@ public class AuthController(
.Where(e => e.UserAgent == userAgent) .Where(e => e.UserAgent == userAgent)
.Where(e => e.StepRemain > 0) .Where(e => e.StepRemain > 0)
.Where(e => e.ExpiredAt != null && now < e.ExpiredAt) .Where(e => e.ExpiredAt != null && now < e.ExpiredAt)
.Where(e => e.Type == ChallengeType.Login) .Where(e => e.Type == Shared.Models.ChallengeType.Login)
.Where(e => e.ClientId == device.Id) .Where(e => e.ClientId == device.Id)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
if (existingChallenge is not null) if (existingChallenge is not null)
@@ -83,7 +80,7 @@ public class AuthController(
if (existingSession is null) return existingChallenge; if (existingSession is null) return existingChallenge;
} }
var challenge = new AuthChallenge var challenge = new SnAuthChallenge
{ {
ExpiredAt = Instant.FromDateTimeUtc(DateTime.UtcNow.AddHours(1)), ExpiredAt = Instant.FromDateTimeUtc(DateTime.UtcNow.AddHours(1)),
StepTotal = await auth.DetectChallengeRisk(Request, account), StepTotal = await auth.DetectChallengeRisk(Request, account),
@@ -107,7 +104,7 @@ public class AuthController(
} }
[HttpGet("challenge/{id:guid}")] [HttpGet("challenge/{id:guid}")]
public async Task<ActionResult<AuthChallenge>> GetChallenge([FromRoute] Guid id) public async Task<ActionResult<SnAuthChallenge>> GetChallenge([FromRoute] Guid id)
{ {
var challenge = await db.AuthChallenges var challenge = await db.AuthChallenges
.Include(e => e.Account) .Include(e => e.Account)
@@ -120,7 +117,7 @@ public class AuthController(
} }
[HttpGet("challenge/{id:guid}/factors")] [HttpGet("challenge/{id:guid}/factors")]
public async Task<ActionResult<List<AccountAuthFactor>>> GetChallengeFactors([FromRoute] Guid id) public async Task<ActionResult<List<SnAccountAuthFactor>>> GetChallengeFactors([FromRoute] Guid id)
{ {
var challenge = await db.AuthChallenges var challenge = await db.AuthChallenges
.Include(e => e.Account) .Include(e => e.Account)
@@ -166,7 +163,7 @@ public class AuthController(
} }
[HttpPatch("challenge/{id:guid}")] [HttpPatch("challenge/{id:guid}")]
public async Task<ActionResult<AuthChallenge>> DoChallenge( public async Task<ActionResult<SnAuthChallenge>> DoChallenge(
[FromRoute] Guid id, [FromRoute] Guid id,
[FromBody] PerformChallengeRequest request [FromBody] PerformChallengeRequest request
) )

View File

@@ -1,7 +1,6 @@
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using DysonNetwork.Pass.Account;
using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@@ -14,8 +13,7 @@ public class AuthService(
IConfiguration config, IConfiguration config,
IHttpClientFactory httpClientFactory, IHttpClientFactory httpClientFactory,
IHttpContextAccessor httpContextAccessor, IHttpContextAccessor httpContextAccessor,
ICacheService cache, ICacheService cache
ILogger<AuthService> logger
) )
{ {
private HttpContext HttpContext => httpContextAccessor.HttpContext!; private HttpContext HttpContext => httpContextAccessor.HttpContext!;
@@ -28,7 +26,7 @@ public class AuthService(
/// <param name="request">The request context</param> /// <param name="request">The request context</param>
/// <param name="account">The account to login</param> /// <param name="account">The account to login</param>
/// <returns>The required steps to login</returns> /// <returns>The required steps to login</returns>
public async Task<int> DetectChallengeRisk(HttpRequest request, Account.Account account) public async Task<int> DetectChallengeRisk(HttpRequest request, SnAccount account)
{ {
// 1) Find out how many authentication factors the account has enabled. // 1) Find out how many authentication factors the account has enabled.
var maxSteps = await db.AccountAuthFactors var maxSteps = await db.AccountAuthFactors
@@ -77,7 +75,7 @@ public class AuthService(
return totalRequiredSteps; return totalRequiredSteps;
} }
public async Task<SnAuthSession> CreateSessionForOidcAsync(Account.Account account, Instant time, public async Task<SnAuthSession> CreateSessionForOidcAsync(SnAccount account, Instant time,
Guid? customAppId = null) Guid? customAppId = null)
{ {
var challenge = new SnAuthChallenge var challenge = new SnAuthChallenge

View File

@@ -6,7 +6,6 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Web; using System.Web;
using DysonNetwork.Pass.Account;
using DysonNetwork.Pass.Auth.OidcProvider.Options; using DysonNetwork.Pass.Auth.OidcProvider.Options;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
@@ -131,7 +130,7 @@ public class OidcProviderController(
[FromForm(Name = "code_challenge_method")] [FromForm(Name = "code_challenge_method")]
string? codeChallengeMethod = null) string? codeChallengeMethod = null)
{ {
if (HttpContext.Items["CurrentUser"] is not Account.Account account) if (HttpContext.Items["CurrentUser"] is not SnAccount account)
return Unauthorized(); return Unauthorized();
// Find the client // Find the client
@@ -303,7 +302,7 @@ public class OidcProviderController(
[Authorize] [Authorize]
public async Task<IActionResult> GetUserInfo() public async Task<IActionResult> GetUserInfo()
{ {
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser || if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser ||
HttpContext.Items["CurrentSession"] is not SnAuthSession currentSession) return Unauthorized(); HttpContext.Items["CurrentSession"] is not SnAuthSession currentSession) return Unauthorized();
// Get requested scopes from the token // Get requested scopes from the token

View File

@@ -1,5 +1,3 @@
using System;
using System.Collections.Generic;
using NodaTime; using NodaTime;
namespace DysonNetwork.Pass.Auth.OidcProvider.Models; namespace DysonNetwork.Pass.Auth.OidcProvider.Models;

View File

@@ -1,4 +1,3 @@
using System.Text.Json.Serialization;
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
namespace DysonNetwork.Pass.Auth.OidcProvider.Responses; namespace DysonNetwork.Pass.Auth.OidcProvider.Responses;

View File

@@ -12,7 +12,7 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using NodaTime; using NodaTime;
using AccountContactType = DysonNetwork.Pass.Account.AccountContactType; using AccountContactType = DysonNetwork.Shared.Models.AccountContactType;
namespace DysonNetwork.Pass.Auth.OidcProvider.Services; namespace DysonNetwork.Pass.Auth.OidcProvider.Services;
@@ -39,7 +39,7 @@ public class OidcProviderService(
return resp.App ?? null; return resp.App ?? null;
} }
public async Task<AuthSession?> FindValidSessionAsync(Guid accountId, Guid clientId, bool withAccount = false) public async Task<SnAuthSession?> FindValidSessionAsync(Guid accountId, Guid clientId, bool withAccount = false)
{ {
var now = SystemClock.Instance.GetCurrentInstant(); var now = SystemClock.Instance.GetCurrentInstant();
@@ -58,7 +58,7 @@ public class OidcProviderService(
s.AppId == clientId && s.AppId == clientId &&
(s.ExpiredAt == null || s.ExpiredAt > now) && (s.ExpiredAt == null || s.ExpiredAt > now) &&
s.Challenge != null && s.Challenge != null &&
s.Challenge.Type == ChallengeType.OAuth) s.Challenge.Type == Shared.Models.ChallengeType.OAuth)
.OrderByDescending(s => s.CreatedAt) .OrderByDescending(s => s.CreatedAt)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
} }
@@ -81,7 +81,7 @@ public class OidcProviderService(
var client = await FindClientByIdAsync(clientId); var client = await FindClientByIdAsync(clientId);
if (client?.Status != CustomAppStatus.Production) if (client?.Status != Shared.Proto.CustomAppStatus.Production)
return true; return true;
if (client?.OauthConfig?.RedirectUris == null) if (client?.OauthConfig?.RedirectUris == null)
@@ -146,7 +146,7 @@ public class OidcProviderService(
private string GenerateIdToken( private string GenerateIdToken(
CustomApp client, CustomApp client,
AuthSession session, SnAuthSession session,
string? nonce = null, string? nonce = null,
IEnumerable<string>? scopes = null IEnumerable<string>? scopes = null
) )
@@ -225,11 +225,9 @@ public class OidcProviderService(
Guid? sessionId = null Guid? sessionId = null
) )
{ {
var client = await FindClientByIdAsync(clientId); var client = await FindClientByIdAsync(clientId) ?? throw new InvalidOperationException("Client not found");
if (client == null)
throw new InvalidOperationException("Client not found");
AuthSession session; SnAuthSession session;
var clock = SystemClock.Instance; var clock = SystemClock.Instance;
var now = clock.GetCurrentInstant(); var now = clock.GetCurrentInstant();
string? nonce = null; string? nonce = null;
@@ -300,7 +298,7 @@ public class OidcProviderService(
private string GenerateJwtToken( private string GenerateJwtToken(
CustomApp client, CustomApp client,
AuthSession session, SnAuthSession session,
Instant expiresAt, Instant expiresAt,
IEnumerable<string>? scopes = null IEnumerable<string>? scopes = null
) )
@@ -372,7 +370,7 @@ public class OidcProviderService(
} }
} }
public async Task<AuthSession?> FindSessionByIdAsync(Guid sessionId) public async Task<SnAuthSession?> FindSessionByIdAsync(Guid sessionId)
{ {
return await db.AuthSessions return await db.AuthSessions
.Include(s => s.Account) .Include(s => s.Account)
@@ -380,7 +378,7 @@ public class OidcProviderService(
.FirstOrDefaultAsync(s => s.Id == sessionId); .FirstOrDefaultAsync(s => s.Id == sessionId);
} }
private static string GenerateRefreshToken(AuthSession session) private static string GenerateRefreshToken(SnAuthSession session)
{ {
return Convert.ToBase64String(session.Id.ToByteArray()); return Convert.ToBase64String(session.Id.ToByteArray());
} }

View File

@@ -1,6 +1,4 @@
using System.Net.Http.Json;
using System.Text.Json; using System.Text.Json;
using DysonNetwork.Pass;
using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Cache;
namespace DysonNetwork.Pass.Auth.OpenId; namespace DysonNetwork.Pass.Auth.OpenId;

View File

@@ -1,6 +1,5 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
namespace DysonNetwork.Pass.Auth.OpenId; namespace DysonNetwork.Pass.Auth.OpenId;

View File

@@ -3,7 +3,6 @@ using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using DysonNetwork.Pass;
using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Cache;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;

View File

@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Cache;
using NodaTime; using NodaTime;
using DysonNetwork.Shared.Models;
namespace DysonNetwork.Pass.Auth.OpenId; namespace DysonNetwork.Pass.Auth.OpenId;
@@ -23,9 +24,9 @@ public class ConnectionController(
private static readonly TimeSpan StateExpiration = TimeSpan.FromMinutes(15); private static readonly TimeSpan StateExpiration = TimeSpan.FromMinutes(15);
[HttpGet] [HttpGet]
public async Task<ActionResult<List<AccountConnection>>> GetConnections() public async Task<ActionResult<List<SnAccountConnection>>> GetConnections()
{ {
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser)
return Unauthorized(); return Unauthorized();
var connections = await db.AccountConnections var connections = await db.AccountConnections
@@ -48,7 +49,7 @@ public class ConnectionController(
[HttpDelete("{id:guid}")] [HttpDelete("{id:guid}")]
public async Task<ActionResult> RemoveConnection(Guid id) public async Task<ActionResult> RemoveConnection(Guid id)
{ {
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser)
return Unauthorized(); return Unauthorized();
var connection = await db.AccountConnections var connection = await db.AccountConnections
@@ -66,7 +67,7 @@ public class ConnectionController(
[HttpPost("/api/auth/connect/apple/mobile")] [HttpPost("/api/auth/connect/apple/mobile")]
public async Task<ActionResult> ConnectAppleMobile([FromBody] AppleMobileConnectRequest request) public async Task<ActionResult> ConnectAppleMobile([FromBody] AppleMobileConnectRequest request)
{ {
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser)
return Unauthorized(); return Unauthorized();
if (GetOidcService("apple") is not AppleOidcService appleService) if (GetOidcService("apple") is not AppleOidcService appleService)
@@ -99,7 +100,7 @@ public class ConnectionController(
$"This Apple account is already linked to {(existingConnection.AccountId == currentUser.Id ? "your account" : "another user")}."); $"This Apple account is already linked to {(existingConnection.AccountId == currentUser.Id ? "your account" : "another user")}.");
} }
db.AccountConnections.Add(new AccountConnection db.AccountConnections.Add(new SnAccountConnection
{ {
AccountId = currentUser.Id, AccountId = currentUser.Id,
Provider = "apple", Provider = "apple",
@@ -250,7 +251,7 @@ public class ConnectionController(
else else
{ {
// Create new connection // Create new connection
db.AccountConnections.Add(new AccountConnection db.AccountConnections.Add(new SnAccountConnection
{ {
AccountId = accountId, AccountId = accountId,
Provider = provider, Provider = provider,
@@ -324,7 +325,7 @@ public class ConnectionController(
var account = await accounts.LookupAccount(userInfo.Email) ?? await accounts.CreateAccount(userInfo); var account = await accounts.LookupAccount(userInfo.Email) ?? await accounts.CreateAccount(userInfo);
// Create connection for new or existing user // Create connection for new or existing user
var newConnection = new AccountConnection var newConnection = new SnAccountConnection
{ {
Account = account, Account = account,
Provider = provider, Provider = provider,

View File

@@ -1,6 +1,4 @@
using System.Net.Http.Json;
using System.Text.Json; using System.Text.Json;
using DysonNetwork.Pass;
using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Cache;
namespace DysonNetwork.Pass.Auth.OpenId; namespace DysonNetwork.Pass.Auth.OpenId;

View File

@@ -1,6 +1,4 @@
using System.Net.Http.Json;
using System.Text.Json; using System.Text.Json;
using DysonNetwork.Pass;
using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Cache;
namespace DysonNetwork.Pass.Auth.OpenId; namespace DysonNetwork.Pass.Auth.OpenId;

View File

@@ -1,8 +1,4 @@
using System.IdentityModel.Tokens.Jwt; using System.IdentityModel.Tokens.Jwt;
using System.Net.Http.Json;
using System.Security.Cryptography;
using System.Text;
using DysonNetwork.Pass;
using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Cache;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;

View File

@@ -33,7 +33,7 @@ public class OidcController(
var oidcService = GetOidcService(provider); var oidcService = GetOidcService(provider);
// If the user is already authenticated, treat as an account connection request // If the user is already authenticated, treat as an account connection request
if (HttpContext.Items["CurrentUser"] is Account.Account currentUser) if (HttpContext.Items["CurrentUser"] is SnAccount currentUser)
{ {
var state = Guid.NewGuid().ToString(); var state = Guid.NewGuid().ToString();
var nonce = Guid.NewGuid().ToString(); var nonce = Guid.NewGuid().ToString();
@@ -128,7 +128,7 @@ public class OidcController(
}; };
} }
private async Task<Account.Account> FindOrCreateAccount(OidcUserInfo userInfo, string provider) private async Task<SnAccount> FindOrCreateAccount(OidcUserInfo userInfo, string provider)
{ {
if (string.IsNullOrEmpty(userInfo.Email)) if (string.IsNullOrEmpty(userInfo.Email))
throw new ArgumentException("Email is required for account creation"); throw new ArgumentException("Email is required for account creation");
@@ -157,7 +157,7 @@ public class OidcController(
return existingAccount; return existingAccount;
} }
var connection = new AccountConnection var connection = new SnAccountConnection
{ {
AccountId = existingAccount.Id, AccountId = existingAccount.Id,
Provider = provider, Provider = provider,
@@ -178,7 +178,7 @@ public class OidcController(
var newAccount = await accounts.CreateAccount(userInfo); var newAccount = await accounts.CreateAccount(userInfo);
// Create the provider connection // Create the provider connection
var newConnection = new AccountConnection var newConnection = new SnAccountConnection
{ {
AccountId = newAccount.Id, AccountId = newAccount.Id,
Provider = provider, Provider = provider,

View File

@@ -1,6 +1,5 @@
using System.IdentityModel.Tokens.Jwt; using System.IdentityModel.Tokens.Jwt;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using DysonNetwork.Pass.Account;
using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@@ -190,7 +189,7 @@ public abstract class OidcService(
/// </summary> /// </summary>
public async Task<SnAuthChallenge> CreateChallengeForUserAsync( public async Task<SnAuthChallenge> CreateChallengeForUserAsync(
OidcUserInfo userInfo, OidcUserInfo userInfo,
Account.Account account, SnAccount account,
HttpContext request, HttpContext request,
string deviceId, string deviceId,
string? deviceName = null string? deviceName = null
@@ -205,7 +204,7 @@ public abstract class OidcService(
if (connection is null) if (connection is null)
{ {
connection = new AccountConnection connection = new SnAccountConnection
{ {
Provider = ProviderName, Provider = ProviderName,
ProvidedIdentifier = userInfo.UserId ?? "", ProvidedIdentifier = userInfo.UserId ?? "",

View File

@@ -1,4 +1,3 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
using DysonNetwork.Pass.Wallet; using DysonNetwork.Pass.Wallet;

View File

@@ -8,9 +8,9 @@ public class SocialCreditService(AppDatabase db, ICacheService cache)
{ {
private const string CacheKeyPrefix = "account:credits:"; private const string CacheKeyPrefix = "account:credits:";
public async Task<SocialCreditRecord> AddRecord(string reasonType, string reason, double delta, Guid accountId) public async Task<SnSocialCreditRecord> AddRecord(string reasonType, string reason, double delta, Guid accountId)
{ {
var record = new SocialCreditRecord var record = new SnSocialCreditRecord
{ {
ReasonType = reasonType, ReasonType = reasonType,
Reason = reason, Reason = reason,

View File

@@ -1,14 +1,14 @@
using DysonNetwork.Pass.Account;
using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.Models;
using EFCore.BulkExtensions; using EFCore.BulkExtensions;
using NodaTime; using NodaTime;
using Quartz; using Quartz;
namespace DysonNetwork.Pass.Handlers; namespace DysonNetwork.Pass.Handlers;
public class ActionLogFlushHandler(IServiceProvider serviceProvider) : IFlushHandler<ActionLog> public class ActionLogFlushHandler(IServiceProvider serviceProvider) : IFlushHandler<SnActionLog>
{ {
public async Task FlushAsync(IReadOnlyList<ActionLog> items) public async Task FlushAsync(IReadOnlyList<SnActionLog> items)
{ {
using var scope = serviceProvider.CreateScope(); using var scope = serviceProvider.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDatabase>(); var db = scope.ServiceProvider.GetRequiredService<AppDatabase>();

View File

@@ -9,7 +9,7 @@ namespace DysonNetwork.Pass.Handlers;
public class LastActiveInfo public class LastActiveInfo
{ {
public SnAuthSession Session { get; set; } = null!; public SnAuthSession Session { get; set; } = null!;
public Account.Account Account { get; set; } = null!; public SnAccount Account { get; set; } = null!;
public Instant SeenAt { get; set; } public Instant SeenAt { get; set; }
} }

View File

@@ -7,9 +7,9 @@ namespace DysonNetwork.Pass.Leveling;
public class ExperienceService(AppDatabase db, SubscriptionService subscriptions, ICacheService cache) public class ExperienceService(AppDatabase db, SubscriptionService subscriptions, ICacheService cache)
{ {
public async Task<ExperienceRecord> AddRecord(string reasonType, string reason, long delta, Guid accountId) public async Task<SnExperienceRecord> AddRecord(string reasonType, string reason, long delta, Guid accountId)
{ {
var record = new ExperienceRecord var record = new SnExperienceRecord
{ {
ReasonType = reasonType, ReasonType = reasonType,
Reason = reason, Reason = reason,

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,4 @@
using System; using System.Text.Json;
using System.Collections.Generic;
using System.Text.Json;
using DysonNetwork.Pass.Account;
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using NetTopologySuite.Geometries; using NetTopologySuite.Geometries;
@@ -157,7 +154,7 @@ namespace DysonNetwork.Pass.Migrations
level = table.Column<int>(type: "integer", nullable: false), level = table.Column<int>(type: "integer", nullable: false),
reward_points = table.Column<decimal>(type: "numeric", nullable: true), reward_points = table.Column<decimal>(type: "numeric", nullable: true),
reward_experience = table.Column<int>(type: "integer", nullable: true), reward_experience = table.Column<int>(type: "integer", nullable: true),
tips = table.Column<ICollection<FortuneTip>>(type: "jsonb", nullable: false), tips = table.Column<ICollection<CheckInFortuneTip>>(type: "jsonb", nullable: false),
account_id = table.Column<Guid>(type: "uuid", nullable: false), account_id = table.Column<Guid>(type: "uuid", nullable: false),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false), created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false), updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
@@ -242,7 +239,7 @@ namespace DysonNetwork.Pass.Migrations
birthday = table.Column<Instant>(type: "timestamp with time zone", nullable: true), birthday = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
last_seen_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true), last_seen_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
verification = table.Column<SnVerificationMark>(type: "jsonb", nullable: true), verification = table.Column<SnVerificationMark>(type: "jsonb", nullable: true),
active_badge = table.Column<BadgeReferenceObject>(type: "jsonb", nullable: true), active_badge = table.Column<SnAccountBadge>(type: "jsonb", nullable: true),
experience = table.Column<int>(type: "integer", nullable: false), experience = table.Column<int>(type: "integer", nullable: false),
picture_id = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true), picture_id = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
background_id = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true), background_id = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
@@ -593,7 +590,7 @@ namespace DysonNetwork.Pass.Migrations
is_free_trial = table.Column<bool>(type: "boolean", nullable: false), is_free_trial = table.Column<bool>(type: "boolean", nullable: false),
status = table.Column<int>(type: "integer", nullable: false), status = table.Column<int>(type: "integer", nullable: false),
payment_method = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false), payment_method = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
payment_details = table.Column<PaymentDetails>(type: "jsonb", nullable: false), payment_details = table.Column<SnPaymentDetails>(type: "jsonb", nullable: false),
base_price = table.Column<decimal>(type: "numeric", nullable: false), base_price = table.Column<decimal>(type: "numeric", nullable: false),
coupon_id = table.Column<Guid>(type: "uuid", nullable: true), coupon_id = table.Column<Guid>(type: "uuid", nullable: true),
renewal_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true), renewal_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,4 @@
using System; using Microsoft.EntityFrameworkCore.Migrations;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
using NodaTime; using NodaTime;
#nullable disable #nullable disable

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,4 @@
using System; using DysonNetwork.Shared.Models;
using System.Collections.Generic;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using NodaTime; using NodaTime;

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,4 @@
using System.Collections.Generic; using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable #nullable disable

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,4 @@
using System; using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations;
using NodaTime; using NodaTime;
#nullable disable #nullable disable

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,4 @@
using System; using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable #nullable disable

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,4 @@
using System; using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations;
using NodaTime; using NodaTime;
#nullable disable #nullable disable

View File

@@ -1,5 +1,4 @@
using System; using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations;
using NodaTime; using NodaTime;
#nullable disable #nullable disable

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,4 @@
using System.Collections.Generic; using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable #nullable disable

View File

@@ -6,6 +6,7 @@ using DysonNetwork.Pass;
using DysonNetwork.Pass.Account; using DysonNetwork.Pass.Account;
using DysonNetwork.Shared.GeoIp; using DysonNetwork.Shared.GeoIp;
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
using DysonNetwork.Shared.Proto;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
@@ -395,7 +396,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("uuid") .HasColumnType("uuid")
.HasColumnName("account_id"); .HasColumnName("account_id");
b.Property<BadgeReferenceObject>("ActiveBadge") b.Property<SnAccountBadge>("ActiveBadge")
.HasColumnType("jsonb") .HasColumnType("jsonb")
.HasColumnName("active_badge"); .HasColumnName("active_badge");
@@ -589,7 +590,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("numeric") .HasColumnType("numeric")
.HasColumnName("reward_points"); .HasColumnName("reward_points");
b.Property<ICollection<FortuneTip>>("Tips") b.Property<ICollection<CheckInFortuneTip>>("Tips")
.IsRequired() .IsRequired()
.HasColumnType("jsonb") .HasColumnType("jsonb")
.HasColumnName("tips"); .HasColumnName("tips");
@@ -1481,7 +1482,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("boolean") .HasColumnType("boolean")
.HasColumnName("is_free_trial"); .HasColumnName("is_free_trial");
b.Property<PaymentDetails>("PaymentDetails") b.Property<SnPaymentDetails>("PaymentDetails")
.IsRequired() .IsRequired()
.HasColumnType("jsonb") .HasColumnType("jsonb")
.HasColumnName("payment_details"); .HasColumnName("payment_details");

View File

@@ -1,6 +1,7 @@
namespace DysonNetwork.Pass.Permission; namespace DysonNetwork.Pass.Permission;
using System; using System;
using DysonNetwork.Shared.Models;
[AttributeUsage(AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Method)]
public class RequiredPermissionAttribute(string area, string key) : Attribute public class RequiredPermissionAttribute(string area, string key) : Attribute
@@ -21,7 +22,7 @@ public class PermissionMiddleware(RequestDelegate next)
if (attr != null) if (attr != null)
{ {
if (httpContext.Items["CurrentUser"] is not Account.Account currentUser) if (httpContext.Items["CurrentUser"] is not SnAccount currentUser)
{ {
httpContext.Response.StatusCode = StatusCodes.Status403Forbidden; httpContext.Response.StatusCode = StatusCodes.Status403Forbidden;
await httpContext.Response.WriteAsync("Unauthorized"); await httpContext.Response.WriteAsync("Unauthorized");

View File

@@ -1,7 +1,6 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using NodaTime; using NodaTime;
using System.Text.Json; using System.Text.Json;
using DysonNetwork.Pass;
using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;

View File

@@ -4,7 +4,6 @@ using DysonNetwork.Shared.Proto;
using Google.Protobuf.WellKnownTypes; using Google.Protobuf.WellKnownTypes;
using System.Text.Json; using System.Text.Json;
using NodaTime.Serialization.Protobuf; using NodaTime.Serialization.Protobuf;
using DysonNetwork.Shared.Models;
namespace DysonNetwork.Pass.Permission; namespace DysonNetwork.Pass.Permission;
@@ -76,22 +75,3 @@ public class PermissionServiceGrpc(
return new RemovePermissionNodeFromGroupResponse { Success = true }; return new RemovePermissionNodeFromGroupResponse { Success = true };
} }
} }
public static class PermissionExtensions
{
public static DysonNetwork.Shared.Proto.PermissionNode ToProtoValue(this PermissionNode node)
{
return new DysonNetwork.Shared.Proto.PermissionNode
{
Id = node.Id.ToString(),
Actor = node.Actor,
Area = node.Area,
Key = node.Key,
Value = Value.Parser.ParseJson(node.Value.RootElement.GetRawText()),
ExpiredAt = node.ExpiredAt?.ToTimestamp(),
AffectedAt = node.AffectedAt?.ToTimestamp(),
GroupId = node.GroupId?.ToString() ?? string.Empty
};
}
}

View File

@@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using DysonNetwork.Pass.Account;
using DysonNetwork.Pass.Permission; using DysonNetwork.Pass.Permission;
using DysonNetwork.Shared.Models;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@@ -8,7 +8,7 @@ namespace DysonNetwork.Pass.Safety;
[ApiController] [ApiController]
[Route("/api/safety/reports")] [Route("/api/safety/reports")]
public class AbuseReportController( public class SnAbuseReportController(
SafetyService safety SafetyService safety
) : ControllerBase ) : ControllerBase
{ {
@@ -26,11 +26,11 @@ public class AbuseReportController(
[HttpPost("")] [HttpPost("")]
[Authorize] [Authorize]
[ProducesResponseType<AbuseReport>(StatusCodes.Status200OK)] [ProducesResponseType<SnAbuseReport>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<AbuseReport>> CreateReport([FromBody] CreateReportRequest request) public async Task<ActionResult<SnAbuseReport>> CreateReport([FromBody] CreateReportRequest request)
{ {
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
try try
{ {
@@ -52,8 +52,8 @@ public class AbuseReportController(
[HttpGet("")] [HttpGet("")]
[Authorize] [Authorize]
[RequiredPermission("safety", "reports.view")] [RequiredPermission("safety", "reports.view")]
[ProducesResponseType<List<AbuseReport>>(StatusCodes.Status200OK)] [ProducesResponseType<List<SnAbuseReport>>(StatusCodes.Status200OK)]
public async Task<ActionResult<List<AbuseReport>>> GetReports( public async Task<ActionResult<List<SnAbuseReport>>> GetReports(
[FromQuery] int offset = 0, [FromQuery] int offset = 0,
[FromQuery] int take = 20, [FromQuery] int take = 20,
[FromQuery] bool includeResolved = false [FromQuery] bool includeResolved = false
@@ -67,15 +67,15 @@ public class AbuseReportController(
[HttpGet("me")] [HttpGet("me")]
[Authorize] [Authorize]
[ProducesResponseType<List<AbuseReport>>(StatusCodes.Status200OK)] [ProducesResponseType<List<SnAbuseReport>>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task<ActionResult<List<AbuseReport>>> GetMyReports( public async Task<ActionResult<List<SnAbuseReport>>> GetMyReports(
[FromQuery] int offset = 0, [FromQuery] int offset = 0,
[FromQuery] int take = 20, [FromQuery] int take = 20,
[FromQuery] bool includeResolved = false [FromQuery] bool includeResolved = false
) )
{ {
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var totalCount = await safety.CountUserReports(currentUser.Id, includeResolved); var totalCount = await safety.CountUserReports(currentUser.Id, includeResolved);
var reports = await safety.GetUserReports(currentUser.Id, offset, take, includeResolved); var reports = await safety.GetUserReports(currentUser.Id, offset, take, includeResolved);
@@ -86,9 +86,9 @@ public class AbuseReportController(
[HttpGet("{id}")] [HttpGet("{id}")]
[Authorize] [Authorize]
[RequiredPermission("safety", "reports.view")] [RequiredPermission("safety", "reports.view")]
[ProducesResponseType<AbuseReport>(StatusCodes.Status200OK)] [ProducesResponseType<SnAbuseReport>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<AbuseReport>> GetReportById(Guid id) public async Task<ActionResult<SnAbuseReport>> GetReportById(Guid id)
{ {
var report = await safety.GetReportById(id); var report = await safety.GetReportById(id);
return report == null ? NotFound() : Ok(report); return report == null ? NotFound() : Ok(report);
@@ -96,12 +96,12 @@ public class AbuseReportController(
[HttpGet("me/{id}")] [HttpGet("me/{id}")]
[Authorize] [Authorize]
[ProducesResponseType<AbuseReport>(StatusCodes.Status200OK)] [ProducesResponseType<SnAbuseReport>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<ActionResult<AbuseReport>> GetMyReportById(Guid id) public async Task<ActionResult<SnAbuseReport>> GetMyReportById(Guid id)
{ {
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var report = await safety.GetReportById(id); var report = await safety.GetReportById(id);
if (report == null) return NotFound(); if (report == null) return NotFound();
@@ -123,9 +123,9 @@ public class AbuseReportController(
[HttpPost("{id}/resolve")] [HttpPost("{id}/resolve")]
[Authorize] [Authorize]
[RequiredPermission("safety", "reports.resolve")] [RequiredPermission("safety", "reports.resolve")]
[ProducesResponseType<AbuseReport>(StatusCodes.Status200OK)] [ProducesResponseType<SnAbuseReport>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<AbuseReport>> ResolveReport(Guid id, [FromBody] ResolveReportRequest request) public async Task<ActionResult<SnAbuseReport>> ResolveReport(Guid id, [FromBody] ResolveReportRequest request)
{ {
try try
{ {

View File

@@ -1,4 +1,4 @@
using DysonNetwork.Pass.Account; using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using NodaTime; using NodaTime;
@@ -6,7 +6,7 @@ namespace DysonNetwork.Pass.Safety;
public class SafetyService(AppDatabase db, ILogger<SafetyService> logger) public class SafetyService(AppDatabase db, ILogger<SafetyService> logger)
{ {
public async Task<AbuseReport> CreateReport(string resourceIdentifier, AbuseReportType type, string reason, Guid accountId) public async Task<SnAbuseReport> CreateReport(string resourceIdentifier, AbuseReportType type, string reason, Guid accountId)
{ {
// Check if a similar report already exists from this user // Check if a similar report already exists from this user
var existingReport = await db.AbuseReports var existingReport = await db.AbuseReports
@@ -20,7 +20,7 @@ public class SafetyService(AppDatabase db, ILogger<SafetyService> logger)
throw new InvalidOperationException("You have already reported this content."); throw new InvalidOperationException("You have already reported this content.");
} }
var report = new AbuseReport var report = new SnAbuseReport
{ {
ResourceIdentifier = resourceIdentifier, ResourceIdentifier = resourceIdentifier,
Type = type, Type = type,
@@ -52,7 +52,7 @@ public class SafetyService(AppDatabase db, ILogger<SafetyService> logger)
.CountAsync(); .CountAsync();
} }
public async Task<List<AbuseReport>> GetReports(int offset = 0, int take = 20, bool includeResolved = false) public async Task<List<SnAbuseReport>> GetReports(int offset = 0, int take = 20, bool includeResolved = false)
{ {
return await db.AbuseReports return await db.AbuseReports
.Where(r => includeResolved || r.ResolvedAt == null) .Where(r => includeResolved || r.ResolvedAt == null)
@@ -63,7 +63,7 @@ public class SafetyService(AppDatabase db, ILogger<SafetyService> logger)
.ToListAsync(); .ToListAsync();
} }
public async Task<List<AbuseReport>> GetUserReports(Guid accountId, int offset = 0, int take = 20, bool includeResolved = false) public async Task<List<SnAbuseReport>> GetUserReports(Guid accountId, int offset = 0, int take = 20, bool includeResolved = false)
{ {
return await db.AbuseReports return await db.AbuseReports
.Where(r => r.AccountId == accountId) .Where(r => r.AccountId == accountId)
@@ -74,21 +74,16 @@ public class SafetyService(AppDatabase db, ILogger<SafetyService> logger)
.ToListAsync(); .ToListAsync();
} }
public async Task<AbuseReport?> GetReportById(Guid id) public async Task<SnAbuseReport?> GetReportById(Guid id)
{ {
return await db.AbuseReports return await db.AbuseReports
.Include(r => r.Account) .Include(r => r.Account)
.FirstOrDefaultAsync(r => r.Id == id); .FirstOrDefaultAsync(r => r.Id == id);
} }
public async Task<AbuseReport> ResolveReport(Guid id, string resolution) public async Task<SnAbuseReport> ResolveReport(Guid id, string resolution)
{ {
var report = await db.AbuseReports.FindAsync(id); var report = await db.AbuseReports.FindAsync(id) ?? throw new KeyNotFoundException("Report not found");
if (report == null)
{
throw new KeyNotFoundException("Report not found");
}
report.ResolvedAt = SystemClock.Instance.GetCurrentInstant(); report.ResolvedAt = SystemClock.Instance.GetCurrentInstant();
report.Resolution = resolution; report.Resolution = resolution;

Some files were not shown because too many files have changed in this diff Show More