shoc-backend/SeaHaven.DataServices/Implementation/TeamMemberInviteDataService.cs

144 lines
6.1 KiB
C#

using Data.SeaHavenIndustries;
using Microsoft.EntityFrameworkCore;
using SeaHaven.DataServices.Dto;
using SeaHaven.DataServices.Interfaces;
namespace SeaHaven.DataServices.Implementation
{
public class TeamMemberInviteDataService : ITeamMemberInviteDataService
{
private readonly ApplicationDbContext _context;
public TeamMemberInviteDataService(ApplicationDbContext context)
{
_context = context;
}
public async Task AddAsync(TeamMemberInvite invite, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(invite);
_context.TeamMemberInvites.Add(invite);
await _context.SaveChangesAsync(cancellationToken);
}
public async Task ReplaceOpenForUserAsync(
TeamMemberInvite invite,
DateTime now,
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(invite);
await using var transaction = await _context.Database.BeginTransactionAsync(cancellationToken);
await _context.TeamMemberInvites
.Where(existing => existing.UserId == invite.UserId
&& existing.UsedAt == null
&& existing.RevokedAt == null)
.ExecuteUpdateAsync(
setters => setters.SetProperty(existing => existing.RevokedAt, now),
cancellationToken);
_context.TeamMemberInvites.Add(invite);
await _context.SaveChangesAsync(cancellationToken);
await transaction.CommitAsync(cancellationToken);
}
public Task<TeamMemberInviteData?> GetByTokenHashAsync(string tokenHash, CancellationToken cancellationToken)
{
return _context.TeamMemberInvites
.AsNoTracking()
.Where(invite => invite.TokenHash == tokenHash)
.Select(invite => new TeamMemberInviteData
{
Id = invite.Id,
UserId = invite.UserId,
ExpiresAt = invite.ExpiresAt,
UsedAt = invite.UsedAt,
RevokedAt = invite.RevokedAt,
CodeHash = invite.CodeHash,
CodeSalt = invite.CodeSalt,
CodeExpiresAt = invite.CodeExpiresAt,
CodeFailedAttempts = invite.CodeFailedAttempts,
CodeSentAt = invite.CodeSentAt,
CodeSendCount = invite.CodeSendCount,
EmailConfirmedAt = invite.EmailConfirmedAt
})
.SingleOrDefaultAsync(cancellationToken);
}
public async Task<bool> TryStartCodeAsync(
StartTeamMemberInviteCodeCommand command,
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(command);
var updated = await OpenInvite(command.InviteId, command.Now)
.Where(invite => invite.CodeSendCount < command.MaxSends
&& (invite.CodeSentAt == null || invite.CodeSentAt <= command.LastSentNoLaterThan))
.ExecuteUpdateAsync(
setters => setters
.SetProperty(invite => invite.CodeHash, command.CodeHash)
.SetProperty(invite => invite.CodeSalt, command.CodeSalt)
.SetProperty(invite => invite.CodeExpiresAt, command.CodeExpiresAt)
.SetProperty(invite => invite.CodeFailedAttempts, 0)
.SetProperty(invite => invite.CodeSentAt, command.Now)
.SetProperty(invite => invite.CodeSendCount, invite => invite.CodeSendCount + 1),
cancellationToken);
return updated == 1;
}
public async Task<bool> TryReserveCodeAttemptAsync(
int inviteId,
int maxAttempts,
DateTime now,
CancellationToken cancellationToken)
{
var updated = await OpenInvite(inviteId, now)
.Where(invite => invite.CodeHash != null
&& invite.CodeExpiresAt > now
&& invite.CodeFailedAttempts < maxAttempts)
.ExecuteUpdateAsync(
setters => setters.SetProperty(
invite => invite.CodeFailedAttempts,
invite => invite.CodeFailedAttempts + 1),
cancellationToken);
return updated == 1;
}
public async Task<bool> TryConfirmEmailAsync(
int inviteId,
string codeHash,
DateTime now,
CancellationToken cancellationToken)
{
var updated = await OpenInvite(inviteId, now)
.Where(invite => invite.CodeHash == codeHash && invite.CodeExpiresAt > now)
.ExecuteUpdateAsync(
setters => setters
.SetProperty(invite => invite.EmailConfirmedAt, now)
.SetProperty(invite => invite.CodeHash, (string?)null)
.SetProperty(invite => invite.CodeSalt, (string?)null)
.SetProperty(invite => invite.CodeExpiresAt, (DateTime?)null)
.SetProperty(invite => invite.CodeFailedAttempts, 0),
cancellationToken);
return updated == 1;
}
public async Task<bool> TryClaimAsync(int inviteId, DateTime now, CancellationToken cancellationToken)
{
var updated = await OpenInvite(inviteId, now)
.Where(invite => invite.EmailConfirmedAt != null)
.ExecuteUpdateAsync(
setters => setters.SetProperty(invite => invite.UsedAt, now),
cancellationToken);
return updated == 1;
}
private IQueryable<TeamMemberInvite> OpenInvite(int inviteId, DateTime now)
{
return _context.TeamMemberInvites.Where(invite => invite.Id == inviteId
&& invite.UsedAt == null
&& invite.RevokedAt == null
&& invite.ExpiresAt > now);
}
}
}