using FastEndpoints; using Microsoft.EntityFrameworkCore; using Socialize.Api.Data; using Socialize.Api.Infrastructure.Security; using Socialize.Api.Modules.CalendarIntegrations.Data; using Socialize.Api.Modules.CalendarIntegrations.Services; using Socialize.Api.Modules.Organizations.Services; namespace Socialize.Api.Modules.CalendarIntegrations.Handlers; public class CreateCalendarSourceHandler( AppDbContext dbContext, AccessScopeService accessScopeService, OrganizationAccessService organizationAccessService) : Endpoint { public override void Configure() { Post("/api/calendar-integrations/sources"); Options(o => o.WithTags("Calendar Integrations")); } public override async Task HandleAsync(UpsertCalendarSourceRequest request, CancellationToken ct) { ArgumentNullException.ThrowIfNull(request); Guid currentUserId = User.GetUserId(); string scope = request.Scope.Trim(); Guid? organizationId = request.OrganizationId; Guid? workspaceId = request.WorkspaceId; if (!await CanCreateAsync(scope, organizationId, workspaceId, currentUserId, ct)) { await SendForbiddenAsync(ct); return; } string? sourceUrl = NormalizeOptional(request.SourceUrl); string? catalogSourceReference = NormalizeOptional(request.CatalogSourceReference); if (await SourceAlreadyExistsAsync(scope, organizationId, workspaceId, currentUserId, sourceUrl, catalogSourceReference, ct)) { AddError(request => request.SourceUrl, "This calendar source has already been added."); await SendErrorsAsync(cancellation: ct); return; } CalendarSource source = new() { Id = Guid.NewGuid(), Scope = scope, OrganizationId = scope == CalendarSourceScopes.Organization ? organizationId : null, WorkspaceId = scope == CalendarSourceScopes.Workspace ? workspaceId : null, UserId = scope == CalendarSourceScopes.User ? currentUserId : null, SourceUrl = sourceUrl, CatalogSourceReference = catalogSourceReference, DisplayTitle = request.DisplayTitle.Trim(), Color = request.Color.Trim(), Category = request.Category.Trim(), IsEnabled = request.IsEnabled, InheritanceMode = scope == CalendarSourceScopes.Organization ? NormalizeOptional(request.InheritanceMode) ?? CalendarSourceInheritanceModes.Optional : null, UpdatedAt = DateTimeOffset.UtcNow, }; dbContext.CalendarSources.Add(source); await dbContext.SaveChangesAsync(ct); await SendAsync(CalendarSourceDto.FromSource(source, isReadOnly: false), StatusCodes.Status201Created, ct); } private async Task CanCreateAsync( string scope, Guid? organizationId, Guid? workspaceId, Guid currentUserId, CancellationToken ct) { return scope switch { CalendarSourceScopes.Organization when organizationId.HasValue => await dbContext.Organizations.AnyAsync(organization => organization.Id == organizationId.Value, ct) && await organizationAccessService.HasOrganizationPermissionAsync( User, organizationId.Value, OrganizationPermissions.ManageConnectors, ct), CalendarSourceScopes.Workspace when workspaceId.HasValue => await dbContext.Workspaces.AnyAsync(workspace => workspace.Id == workspaceId.Value, ct) && await accessScopeService.CanManageWorkspaceAsync(User, workspaceId.Value, ct), CalendarSourceScopes.User => currentUserId != Guid.Empty, _ => false, }; } private Task SourceAlreadyExistsAsync( string scope, Guid? organizationId, Guid? workspaceId, Guid currentUserId, string? sourceUrl, string? catalogSourceReference, CancellationToken ct) { IQueryable query = dbContext.CalendarSources .Where(source => source.Scope == scope); query = scope switch { CalendarSourceScopes.Organization => query.Where(source => source.OrganizationId == organizationId), CalendarSourceScopes.Workspace => query.Where(source => source.WorkspaceId == workspaceId), CalendarSourceScopes.User => query.Where(source => source.UserId == currentUserId), _ => query.Where(_ => false), }; string? normalizedUrl = sourceUrl?.Trim(); string? normalizedCatalogReference = catalogSourceReference?.Trim(); return query.AnyAsync(source => (!string.IsNullOrWhiteSpace(normalizedCatalogReference) && source.CatalogSourceReference == normalizedCatalogReference) || (!string.IsNullOrWhiteSpace(normalizedUrl) && source.SourceUrl != null && source.SourceUrl.ToUpper() == normalizedUrl.ToUpper()), ct); } private static string? NormalizeOptional(string? value) { return string.IsNullOrWhiteSpace(value) ? null : value.Trim(); } }