225 lines
7.3 KiB
C#
225 lines
7.3 KiB
C#
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.Identity.Data;
|
|
|
|
namespace Socialize.Api.Modules.CalendarIntegrations.Handlers;
|
|
|
|
public record UserCalendarExportFeedDto(
|
|
bool IsEnabled,
|
|
string? FeedUrl,
|
|
DateTimeOffset? CreatedAt,
|
|
DateTimeOffset? UpdatedAt,
|
|
DateTimeOffset? RevokedAt);
|
|
|
|
public class GetUserCalendarExportFeedHandler(AppDbContext dbContext)
|
|
: EndpointWithoutRequest<UserCalendarExportFeedDto>
|
|
{
|
|
public override void Configure()
|
|
{
|
|
Get("/api/calendar-integrations/export-feed");
|
|
Options(o => o.WithTags("Calendar Integrations"));
|
|
}
|
|
|
|
public override async Task HandleAsync(CancellationToken ct)
|
|
{
|
|
UserCalendarExportFeed? feed = await dbContext.UserCalendarExportFeeds
|
|
.SingleOrDefaultAsync(candidate => candidate.UserId == User.GetUserId(), ct);
|
|
|
|
await SendAsync(UserCalendarExportFeedMapper.ToDto(feed, UserCalendarExportFeedMapper.BuildFeedUrl(feed)), cancellation: ct);
|
|
}
|
|
}
|
|
|
|
public class EnableUserCalendarExportFeedHandler(AppDbContext dbContext)
|
|
: EndpointWithoutRequest<UserCalendarExportFeedDto>
|
|
{
|
|
public override void Configure()
|
|
{
|
|
Post("/api/calendar-integrations/export-feed/enable");
|
|
Options(o => o.WithTags("Calendar Integrations"));
|
|
}
|
|
|
|
public override async Task HandleAsync(CancellationToken ct)
|
|
{
|
|
Guid userId = User.GetUserId();
|
|
string token = CalendarExportFeedTokenService.GenerateToken();
|
|
string tokenHash = CalendarExportFeedTokenService.HashToken(token);
|
|
DateTimeOffset now = DateTimeOffset.UtcNow;
|
|
|
|
UserCalendarExportFeed? feed = await dbContext.UserCalendarExportFeeds
|
|
.SingleOrDefaultAsync(candidate => candidate.UserId == userId, ct);
|
|
|
|
if (feed is null)
|
|
{
|
|
feed = new UserCalendarExportFeed
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
UserId = userId,
|
|
Token = token,
|
|
TokenHash = tokenHash,
|
|
UpdatedAt = now,
|
|
};
|
|
dbContext.UserCalendarExportFeeds.Add(feed);
|
|
}
|
|
else if (feed.TokenHash is null || feed.RevokedAt.HasValue)
|
|
{
|
|
feed.Token = token;
|
|
feed.TokenHash = tokenHash;
|
|
feed.RevokedAt = null;
|
|
feed.UpdatedAt = now;
|
|
}
|
|
else
|
|
{
|
|
token = string.Empty;
|
|
}
|
|
|
|
await dbContext.SaveChangesAsync(ct);
|
|
await SendAsync(UserCalendarExportFeedMapper.ToDto(feed, UserCalendarExportFeedMapper.BuildFeedUrl(feed, token)), cancellation: ct);
|
|
}
|
|
}
|
|
|
|
public class RegenerateUserCalendarExportFeedHandler(AppDbContext dbContext)
|
|
: EndpointWithoutRequest<UserCalendarExportFeedDto>
|
|
{
|
|
public override void Configure()
|
|
{
|
|
Post("/api/calendar-integrations/export-feed/regenerate");
|
|
Options(o => o.WithTags("Calendar Integrations"));
|
|
}
|
|
|
|
public override async Task HandleAsync(CancellationToken ct)
|
|
{
|
|
Guid userId = User.GetUserId();
|
|
string token = CalendarExportFeedTokenService.GenerateToken();
|
|
DateTimeOffset now = DateTimeOffset.UtcNow;
|
|
|
|
UserCalendarExportFeed? feed = await dbContext.UserCalendarExportFeeds
|
|
.SingleOrDefaultAsync(candidate => candidate.UserId == userId, ct);
|
|
|
|
if (feed is null)
|
|
{
|
|
feed = new UserCalendarExportFeed
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
UserId = userId,
|
|
UpdatedAt = now,
|
|
};
|
|
dbContext.UserCalendarExportFeeds.Add(feed);
|
|
}
|
|
|
|
feed.TokenHash = CalendarExportFeedTokenService.HashToken(token);
|
|
feed.Token = token;
|
|
feed.RevokedAt = null;
|
|
feed.UpdatedAt = now;
|
|
|
|
await dbContext.SaveChangesAsync(ct);
|
|
await SendAsync(UserCalendarExportFeedMapper.ToDto(feed, UserCalendarExportFeedMapper.BuildFeedUrl(feed, token)), cancellation: ct);
|
|
}
|
|
}
|
|
|
|
public class RevokeUserCalendarExportFeedHandler(AppDbContext dbContext)
|
|
: EndpointWithoutRequest<UserCalendarExportFeedDto>
|
|
{
|
|
public override void Configure()
|
|
{
|
|
Delete("/api/calendar-integrations/export-feed");
|
|
Options(o => o.WithTags("Calendar Integrations"));
|
|
}
|
|
|
|
public override async Task HandleAsync(CancellationToken ct)
|
|
{
|
|
UserCalendarExportFeed? feed = await dbContext.UserCalendarExportFeeds
|
|
.SingleOrDefaultAsync(candidate => candidate.UserId == User.GetUserId(), ct);
|
|
|
|
if (feed is not null)
|
|
{
|
|
feed.TokenHash = null;
|
|
feed.Token = null;
|
|
feed.RevokedAt = DateTimeOffset.UtcNow;
|
|
feed.UpdatedAt = feed.RevokedAt.Value;
|
|
await dbContext.SaveChangesAsync(ct);
|
|
}
|
|
|
|
await SendAsync(UserCalendarExportFeedMapper.ToDto(feed, null), cancellation: ct);
|
|
}
|
|
}
|
|
|
|
public class GetUserCalendarExportFeedIcsHandler(
|
|
AppDbContext dbContext,
|
|
CalendarExportFeedService feedService)
|
|
: EndpointWithoutRequest
|
|
{
|
|
public override void Configure()
|
|
{
|
|
AllowAnonymous();
|
|
Get("/api/calendar-integrations/export-feed/{token}.ics");
|
|
Options(o => o.WithTags("Calendar Integrations"));
|
|
}
|
|
|
|
public override async Task HandleAsync(CancellationToken ct)
|
|
{
|
|
string? token = Route<string?>("token");
|
|
if (string.IsNullOrWhiteSpace(token))
|
|
{
|
|
await SendNotFoundAsync(ct);
|
|
return;
|
|
}
|
|
|
|
string tokenHash = CalendarExportFeedTokenService.HashToken(token);
|
|
|
|
UserCalendarExportFeed? feed = await dbContext.UserCalendarExportFeeds
|
|
.SingleOrDefaultAsync(candidate =>
|
|
candidate.TokenHash == tokenHash &&
|
|
!candidate.RevokedAt.HasValue,
|
|
ct);
|
|
|
|
if (feed is null)
|
|
{
|
|
await SendNotFoundAsync(ct);
|
|
return;
|
|
}
|
|
|
|
User? user = await dbContext.Users.SingleOrDefaultAsync(candidate => candidate.Id == feed.UserId, ct);
|
|
if (user is null)
|
|
{
|
|
await SendNotFoundAsync(ct);
|
|
return;
|
|
}
|
|
|
|
string appBaseUrl = $"{HttpContext.Request.Scheme}://{HttpContext.Request.Host}";
|
|
string ics = await feedService.BuildUserFeedAsync(feed.UserId, user.Email, appBaseUrl, ct);
|
|
|
|
HttpContext.Response.ContentType = "text/calendar; charset=utf-8";
|
|
await HttpContext.Response.WriteAsync(ics, ct);
|
|
}
|
|
}
|
|
|
|
file static class UserCalendarExportFeedMapper
|
|
{
|
|
public static UserCalendarExportFeedDto ToDto(UserCalendarExportFeed? feed, string? feedUrl)
|
|
{
|
|
return new UserCalendarExportFeedDto(
|
|
feed?.TokenHash is not null && !feed.RevokedAt.HasValue,
|
|
feedUrl,
|
|
feed?.CreatedAt,
|
|
feed?.UpdatedAt,
|
|
feed?.RevokedAt);
|
|
}
|
|
|
|
public static string? BuildFeedUrl(UserCalendarExportFeed? feed, string? token = null)
|
|
{
|
|
if (feed?.TokenHash is null || feed.RevokedAt.HasValue)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
string effectiveToken = string.IsNullOrWhiteSpace(token) ? feed.Token ?? string.Empty : token;
|
|
return string.IsNullOrWhiteSpace(effectiveToken)
|
|
? null
|
|
: $"/api/calendar-integrations/export-feed/{effectiveToken}.ics";
|
|
}
|
|
}
|