chore: moving towards agentic development
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
using Socialize.Infrastructure.BlobStorage.Contracts;
|
||||
using Socialize.Infrastructure.Security;
|
||||
using Socialize.Modules.Clients.Data;
|
||||
|
||||
namespace Socialize.Modules.Clients.Handlers;
|
||||
|
||||
public record ChangeClientPortraitRequest(
|
||||
IFormFile File);
|
||||
|
||||
public record ChangeClientPortraitResponse(
|
||||
string BlobUrl);
|
||||
|
||||
public sealed class ChangeClientPortraitRequestValidator : Validator<ChangeClientPortraitRequest>
|
||||
{
|
||||
public ChangeClientPortraitRequestValidator()
|
||||
{
|
||||
RuleFor(x => x.File)
|
||||
.NotNull()
|
||||
.NotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
public class ChangeClientPortraitHandler(
|
||||
AppDbContext clientsDbContext,
|
||||
IBlobStorage blobStorage,
|
||||
AccessScopeService accessScopeService)
|
||||
: Endpoint<ChangeClientPortraitRequest, ChangeClientPortraitResponse>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/api/clients/{id}/portrait");
|
||||
Options(o => o.WithTags("Clients"));
|
||||
AllowFileUploads();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(ChangeClientPortraitRequest request, CancellationToken ct)
|
||||
{
|
||||
Guid id = Route<Guid>("id");
|
||||
|
||||
Client? client = await clientsDbContext.Clients.SingleOrDefaultAsync(candidate => candidate.Id == id, ct);
|
||||
if (client is null)
|
||||
{
|
||||
await SendNotFoundAsync(ct);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!accessScopeService.CanManageWorkspace(User, client.WorkspaceId))
|
||||
{
|
||||
await SendForbiddenAsync(ct);
|
||||
return;
|
||||
}
|
||||
|
||||
string blobUrl = await blobStorage.UploadFileAsync(
|
||||
ContainerNames.Clients,
|
||||
$"{client.Id}/{SubDirectoryNames.Profile}/{CommonFileNames.LogoPicture}",
|
||||
request.File.OpenReadStream(),
|
||||
request.File.ContentType,
|
||||
ct);
|
||||
|
||||
client.PortraitUrl = blobUrl;
|
||||
await clientsDbContext.SaveChangesAsync(ct);
|
||||
|
||||
await SendOkAsync(new ChangeClientPortraitResponse(blobUrl), ct);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
using Socialize.Infrastructure.Security;
|
||||
namespace Socialize.Modules.Clients.Handlers;
|
||||
|
||||
public record CreateClientRequest(
|
||||
Guid WorkspaceId,
|
||||
string Name,
|
||||
string? PortraitUrl,
|
||||
string? PrimaryContactName,
|
||||
string? PrimaryContactEmail,
|
||||
string? PrimaryContactPortraitUrl);
|
||||
|
||||
public class CreateClientRequestValidator
|
||||
: Validator<CreateClientRequest>
|
||||
{
|
||||
public CreateClientRequestValidator()
|
||||
{
|
||||
RuleFor(x => x.WorkspaceId).NotEmpty();
|
||||
RuleFor(x => x.Name).NotEmpty().MaximumLength(256);
|
||||
RuleFor(x => x.PortraitUrl).MaximumLength(2048);
|
||||
RuleFor(x => x.PrimaryContactName).MaximumLength(256);
|
||||
RuleFor(x => x.PrimaryContactEmail).MaximumLength(256).EmailAddress().When(x => !string.IsNullOrWhiteSpace(x.PrimaryContactEmail));
|
||||
RuleFor(x => x.PrimaryContactPortraitUrl).MaximumLength(2048);
|
||||
}
|
||||
}
|
||||
|
||||
public class CreateClientHandler(
|
||||
AppDbContext dbContext,
|
||||
AccessScopeService accessScopeService)
|
||||
: Endpoint<CreateClientRequest, ClientDto>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/api/clients");
|
||||
Options(o => o.WithTags("Clients"));
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(CreateClientRequest request, CancellationToken ct)
|
||||
{
|
||||
if (!accessScopeService.CanManageWorkspace(User, request.WorkspaceId))
|
||||
{
|
||||
await SendForbiddenAsync(ct);
|
||||
return;
|
||||
}
|
||||
|
||||
bool workspaceExists = await dbContext.Workspaces
|
||||
.AnyAsync(workspace => workspace.Id == request.WorkspaceId, ct);
|
||||
|
||||
if (!workspaceExists)
|
||||
{
|
||||
AddError(request => request.WorkspaceId, "The selected workspace does not exist.");
|
||||
await SendErrorsAsync(StatusCodes.Status400BadRequest, ct);
|
||||
return;
|
||||
}
|
||||
|
||||
string normalizedName = request.Name.Trim();
|
||||
string? normalizedPortraitUrl = request.PortraitUrl?.Trim();
|
||||
string? normalizedPrimaryContactName = request.PrimaryContactName?.Trim();
|
||||
string? normalizedPrimaryContactEmail = request.PrimaryContactEmail?.Trim();
|
||||
string? normalizedPrimaryContactPortraitUrl = request.PrimaryContactPortraitUrl?.Trim();
|
||||
|
||||
bool duplicateClient = await dbContext.Clients
|
||||
.AnyAsync(
|
||||
client => client.WorkspaceId == request.WorkspaceId && client.Name == normalizedName,
|
||||
ct);
|
||||
|
||||
if (duplicateClient)
|
||||
{
|
||||
AddError(request => request.Name, "A client with this name already exists in the active workspace.");
|
||||
await SendErrorsAsync(StatusCodes.Status409Conflict, ct);
|
||||
return;
|
||||
}
|
||||
|
||||
Client client = new()
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
WorkspaceId = request.WorkspaceId,
|
||||
Name = normalizedName,
|
||||
Status = "Active",
|
||||
PortraitUrl = string.IsNullOrWhiteSpace(normalizedPortraitUrl) ? null : normalizedPortraitUrl,
|
||||
PrimaryContactName = string.IsNullOrWhiteSpace(normalizedPrimaryContactName) ? null : normalizedPrimaryContactName,
|
||||
PrimaryContactEmail = string.IsNullOrWhiteSpace(normalizedPrimaryContactEmail) ? null : normalizedPrimaryContactEmail,
|
||||
PrimaryContactPortraitUrl = string.IsNullOrWhiteSpace(normalizedPrimaryContactPortraitUrl) ? null : normalizedPrimaryContactPortraitUrl,
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
};
|
||||
|
||||
dbContext.Clients.Add(client);
|
||||
await dbContext.SaveChangesAsync(ct);
|
||||
|
||||
ClientDto dto = new(
|
||||
client.Id,
|
||||
client.WorkspaceId,
|
||||
client.Name,
|
||||
client.Status,
|
||||
client.PortraitUrl,
|
||||
client.PrimaryContactName,
|
||||
client.PrimaryContactEmail,
|
||||
client.PrimaryContactPortraitUrl);
|
||||
|
||||
await SendAsync(dto, StatusCodes.Status201Created, ct);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using Socialize.Infrastructure.Security;
|
||||
using Socialize.Modules.Clients.Data;
|
||||
|
||||
namespace Socialize.Modules.Clients.Handlers;
|
||||
|
||||
public record GetClientsRequest(Guid? WorkspaceId);
|
||||
|
||||
public record ClientDto(
|
||||
Guid Id,
|
||||
Guid WorkspaceId,
|
||||
string Name,
|
||||
string Status,
|
||||
string? PortraitUrl,
|
||||
string? PrimaryContactName,
|
||||
string? PrimaryContactEmail,
|
||||
string? PrimaryContactPortraitUrl);
|
||||
|
||||
public class GetClientsHandler(
|
||||
AppDbContext dbContext,
|
||||
AccessScopeService accessScopeService)
|
||||
: Endpoint<GetClientsRequest, IReadOnlyCollection<ClientDto>>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/api/clients");
|
||||
Options(o => o.WithTags("Clients"));
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(GetClientsRequest request, CancellationToken ct)
|
||||
{
|
||||
IQueryable<Client> query = dbContext.Clients.AsQueryable();
|
||||
|
||||
if (accessScopeService.IsManager(User))
|
||||
{
|
||||
if (request.WorkspaceId.HasValue)
|
||||
{
|
||||
query = query.Where(client => client.WorkspaceId == request.WorkspaceId.Value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
IReadOnlyCollection<Guid> workspaceScopeIds = User.GetWorkspaceScopeIds();
|
||||
IReadOnlyCollection<Guid> clientScopeIds = User.GetClientScopeIds();
|
||||
|
||||
query = query.Where(client => workspaceScopeIds.Contains(client.WorkspaceId));
|
||||
|
||||
if (clientScopeIds.Count > 0)
|
||||
{
|
||||
query = query.Where(client => clientScopeIds.Contains(client.Id));
|
||||
}
|
||||
|
||||
if (request.WorkspaceId.HasValue)
|
||||
{
|
||||
query = query.Where(client => client.WorkspaceId == request.WorkspaceId.Value);
|
||||
}
|
||||
}
|
||||
|
||||
List<ClientDto> clients = await query
|
||||
.OrderBy(client => client.Name)
|
||||
.Select(client => new ClientDto(
|
||||
client.Id,
|
||||
client.WorkspaceId,
|
||||
client.Name,
|
||||
client.Status,
|
||||
client.PortraitUrl,
|
||||
client.PrimaryContactName,
|
||||
client.PrimaryContactEmail,
|
||||
client.PrimaryContactPortraitUrl))
|
||||
.ToListAsync(ct);
|
||||
|
||||
await SendOkAsync(clients, ct);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
using Socialize.Infrastructure.Security;
|
||||
using Socialize.Modules.Clients.Data;
|
||||
|
||||
namespace Socialize.Modules.Clients.Handlers;
|
||||
|
||||
public record UpdateClientRequest(
|
||||
string Name,
|
||||
string? PortraitUrl,
|
||||
string Status,
|
||||
string? PrimaryContactName,
|
||||
string? PrimaryContactEmail,
|
||||
string? PrimaryContactPortraitUrl);
|
||||
|
||||
public class UpdateClientRequestValidator
|
||||
: Validator<UpdateClientRequest>
|
||||
{
|
||||
public UpdateClientRequestValidator()
|
||||
{
|
||||
RuleFor(x => x.Name).NotEmpty().MaximumLength(256);
|
||||
RuleFor(x => x.PortraitUrl).MaximumLength(2048);
|
||||
RuleFor(x => x.Status).NotEmpty().MaximumLength(64);
|
||||
RuleFor(x => x.PrimaryContactName).MaximumLength(256);
|
||||
RuleFor(x => x.PrimaryContactEmail).MaximumLength(256).EmailAddress().When(x => !string.IsNullOrWhiteSpace(x.PrimaryContactEmail));
|
||||
RuleFor(x => x.PrimaryContactPortraitUrl).MaximumLength(2048);
|
||||
}
|
||||
}
|
||||
|
||||
public class UpdateClientHandler(
|
||||
AppDbContext clientsDbContext,
|
||||
AccessScopeService accessScopeService)
|
||||
: Endpoint<UpdateClientRequest, ClientDto>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Put("/api/clients/{id}");
|
||||
Options(o => o.WithTags("Clients"));
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(UpdateClientRequest request, CancellationToken ct)
|
||||
{
|
||||
Guid id = Route<Guid>("id");
|
||||
|
||||
Client? client = await clientsDbContext.Clients.SingleOrDefaultAsync(candidate => candidate.Id == id, ct);
|
||||
if (client is null)
|
||||
{
|
||||
await SendNotFoundAsync(ct);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!accessScopeService.CanManageWorkspace(User, client.WorkspaceId))
|
||||
{
|
||||
await SendForbiddenAsync(ct);
|
||||
return;
|
||||
}
|
||||
|
||||
string normalizedName = request.Name.Trim();
|
||||
string normalizedStatus = request.Status.Trim();
|
||||
string? normalizedPortraitUrl = request.PortraitUrl?.Trim();
|
||||
string? normalizedPrimaryContactName = request.PrimaryContactName?.Trim();
|
||||
string? normalizedPrimaryContactEmail = request.PrimaryContactEmail?.Trim();
|
||||
string? normalizedPrimaryContactPortraitUrl = request.PrimaryContactPortraitUrl?.Trim();
|
||||
|
||||
bool duplicateClient = await clientsDbContext.Clients
|
||||
.AnyAsync(
|
||||
candidate => candidate.Id != id
|
||||
&& candidate.WorkspaceId == client.WorkspaceId
|
||||
&& candidate.Name == normalizedName,
|
||||
ct);
|
||||
|
||||
if (duplicateClient)
|
||||
{
|
||||
AddError(request => request.Name, "A client with this name already exists in the active workspace.");
|
||||
await SendErrorsAsync(StatusCodes.Status409Conflict, ct);
|
||||
return;
|
||||
}
|
||||
|
||||
client.Name = normalizedName;
|
||||
client.Status = normalizedStatus;
|
||||
client.PortraitUrl = string.IsNullOrWhiteSpace(normalizedPortraitUrl) ? null : normalizedPortraitUrl;
|
||||
client.PrimaryContactName = string.IsNullOrWhiteSpace(normalizedPrimaryContactName) ? null : normalizedPrimaryContactName;
|
||||
client.PrimaryContactEmail = string.IsNullOrWhiteSpace(normalizedPrimaryContactEmail) ? null : normalizedPrimaryContactEmail;
|
||||
client.PrimaryContactPortraitUrl = string.IsNullOrWhiteSpace(normalizedPrimaryContactPortraitUrl) ? null : normalizedPrimaryContactPortraitUrl;
|
||||
|
||||
await clientsDbContext.SaveChangesAsync(ct);
|
||||
|
||||
ClientDto dto = new(
|
||||
client.Id,
|
||||
client.WorkspaceId,
|
||||
client.Name,
|
||||
client.Status,
|
||||
client.PortraitUrl,
|
||||
client.PrimaryContactName,
|
||||
client.PrimaryContactEmail,
|
||||
client.PrimaryContactPortraitUrl);
|
||||
|
||||
await SendOkAsync(dto, ct);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user