Eerste commit
This commit is contained in:
@@ -0,0 +1,530 @@
|
||||
using MijnBesluit.Application.Abstractions;
|
||||
using MijnBesluit.Domain.Entities;
|
||||
using MijnBesluit.Domain.Enums;
|
||||
|
||||
namespace MijnBesluit.SelfCheck;
|
||||
|
||||
/// <summary>Eenvoudige opslag in het geheugen, zodat de regels zonder database te controleren zijn.</summary>
|
||||
public class Store
|
||||
{
|
||||
public List<User> Users = new();
|
||||
public List<Person> People = new();
|
||||
public List<Family> Families = new();
|
||||
public List<FamilyMember> Members = new();
|
||||
public List<Invitation> Invitations = new();
|
||||
public List<VaultItem> VaultItems = new();
|
||||
public List<VaultFile> VaultFiles = new();
|
||||
public List<ContentGrant> Grants = new();
|
||||
public List<Message> Messages = new();
|
||||
public List<DeathDeclaration> Declarations = new();
|
||||
public List<LegacyManager> Managers = new();
|
||||
public List<AuditLogEntry> Audit = new();
|
||||
public List<LoginCode> Codes = new();
|
||||
public List<Toestel> Toestellen = new();
|
||||
public List<Samenvoeging> Samenvoegingen = new();
|
||||
public List<Adverteerder> Adverteerders = new();
|
||||
public List<Campagne> Campagnes = new();
|
||||
public List<CampagneUur> CampagneUren = new();
|
||||
|
||||
private long _seq;
|
||||
public long NextId() => ++_seq;
|
||||
}
|
||||
|
||||
public class TestClock : IClock
|
||||
{
|
||||
public DateTime UtcNow { get; set; } = new(2026, 1, 1, 12, 0, 0, DateTimeKind.Utc);
|
||||
public void Advance(TimeSpan by) => UtcNow = UtcNow.Add(by);
|
||||
}
|
||||
|
||||
/// <summary>Mastersleutel in het geheugen. In productie komt deze uit een sleutelbestand of sleutelkluis.</summary>
|
||||
/// <summary>
|
||||
/// Een verkleiner die je per test aanzet. Standaard uit, zodat de andere controles blijven
|
||||
/// werken zoals ze waren: zonder verkleiner is er geen kleine versie en wordt er niets omgezet,
|
||||
/// precies zoals op een server waar het verkleinen niet beschikbaar is.
|
||||
/// </summary>
|
||||
public class TestVerkleiner : IAfbeeldingVerkleiner
|
||||
{
|
||||
public bool Aan { get; set; }
|
||||
|
||||
/// <summary>Wat er teruggegeven wordt als hij aanstaat. Klein, zodat het verschil duidelijk is.</summary>
|
||||
public byte[] Uitkomst { get; set; } = { 1, 2, 3, 4, 5, 6, 7, 8 };
|
||||
|
||||
/// <summary>Waarmee hij is aangeroepen; zo kan een test nakijken of de maat klopt.</summary>
|
||||
public List<(string Soort, int MaxZijde)> Aanroepen { get; } = new();
|
||||
|
||||
public byte[]? Verklein(byte[] inhoud, string soort, int maxZijde)
|
||||
{
|
||||
if (!Aan) return null;
|
||||
if (!soort.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) return null;
|
||||
|
||||
Aanroepen.Add((soort, maxZijde));
|
||||
return Uitkomst;
|
||||
}
|
||||
}
|
||||
|
||||
public class TestKeyProtector : IMasterKeyProtector
|
||||
{
|
||||
private readonly byte[] _master = MijnBesluit.Application.Security.EnvelopeCrypto.NewKey();
|
||||
public int CurrentKeyVersion => 1;
|
||||
public byte[] Protect(byte[] dataKey) => MijnBesluit.Application.Security.EnvelopeCrypto.WrapKey(dataKey, _master);
|
||||
public byte[] Unprotect(byte[] p) => MijnBesluit.Application.Security.EnvelopeCrypto.UnwrapKey(p, _master);
|
||||
}
|
||||
|
||||
public class TestEmail : IEmailSender
|
||||
{
|
||||
public List<(string To, string Subject, string Body)> Sent = new();
|
||||
|
||||
/// <summary>Zet dit aan om te doen alsof de mailserver onbereikbaar is.</summary>
|
||||
public bool Mislukt { get; set; }
|
||||
|
||||
public Task SendAsync(string to, string subject, string body, CancellationToken ct = default)
|
||||
{
|
||||
if (Mislukt) throw new InvalidOperationException("Failure sending mail.");
|
||||
Sent.Add((to, subject, body));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public class TestFiles : IFileStore
|
||||
{
|
||||
private readonly Dictionary<string, byte[]> _files = new();
|
||||
public Task<string> WriteAsync(byte[] bytes, string name, CancellationToken ct = default)
|
||||
{ var key = Guid.NewGuid().ToString("N"); _files[key] = bytes; return Task.FromResult(key); }
|
||||
public Task<byte[]> ReadAsync(string key, CancellationToken ct = default) => Task.FromResult(_files[key]);
|
||||
public Task DeleteAsync(string key, CancellationToken ct = default) { _files.Remove(key); return Task.CompletedTask; }
|
||||
}
|
||||
|
||||
public class Uow : IUnitOfWork
|
||||
{
|
||||
private readonly Store _s;
|
||||
public Uow(Store s) => _s = s;
|
||||
|
||||
/// <summary>Deelt id's uit aan nieuwe records, net zoals de database dat bij opslaan doet.</summary>
|
||||
public Task<int> SaveChangesAsync(CancellationToken ct = default)
|
||||
{
|
||||
foreach (var p in _s.People.Where(x => x.Id == 0)) p.Id = _s.NextId();
|
||||
foreach (var u in _s.Users.Where(x => x.Id == 0))
|
||||
{
|
||||
u.Id = _s.NextId();
|
||||
if (u.PersonId == 0 && u.Person is not null) u.PersonId = u.Person.Id;
|
||||
}
|
||||
foreach (var f in _s.Families.Where(x => x.Id == 0)) f.Id = _s.NextId();
|
||||
foreach (var m in _s.Members.Where(x => x.Id == 0)) m.Id = _s.NextId();
|
||||
foreach (var i in _s.Invitations.Where(x => x.Id == 0)) i.Id = _s.NextId();
|
||||
foreach (var v in _s.VaultItems.Where(x => x.Id == 0)) v.Id = _s.NextId();
|
||||
foreach (var v in _s.VaultFiles.Where(x => x.Id == 0)) v.Id = _s.NextId();
|
||||
foreach (var g in _s.Grants.Where(x => x.Id == 0)) g.Id = _s.NextId();
|
||||
foreach (var m in _s.Messages.Where(x => x.Id == 0)) m.Id = _s.NextId();
|
||||
foreach (var d in _s.Declarations.Where(x => x.Id == 0)) d.Id = _s.NextId();
|
||||
foreach (var m in _s.Managers.Where(x => x.Id == 0)) m.Id = _s.NextId();
|
||||
foreach (var a in _s.Audit.Where(x => x.Id == 0)) a.Id = _s.NextId();
|
||||
foreach (var c in _s.Codes.Where(x => x.Id == 0)) c.Id = _s.NextId();
|
||||
foreach (var v in _s.Samenvoegingen.Where(x => x.Id == 0)) v.Id = _s.NextId();
|
||||
foreach (var a in _s.Adverteerders.Where(x => x.Id == 0)) a.Id = _s.NextId();
|
||||
foreach (var c in _s.Campagnes.Where(x => x.Id == 0)) c.Id = _s.NextId();
|
||||
foreach (var u in _s.CampagneUren.Where(x => x.Id == 0)) u.Id = _s.NextId();
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
|
||||
public class UserRepo : IUserRepository
|
||||
{
|
||||
private readonly Store _s; public UserRepo(Store s) => _s = s;
|
||||
|
||||
/// <summary>
|
||||
/// Vult de persoon in, net als de echte opslag met Include doet. Zonder dit gedraagt de
|
||||
/// zelftest zich anders dan de server en merk je een ontbrekende Include hier niet.
|
||||
/// </summary>
|
||||
private User? MetPersoon(User? u)
|
||||
{
|
||||
if (u is not null) u.Person ??= _s.People.FirstOrDefault(p => p.Id == u.PersonId);
|
||||
return u;
|
||||
}
|
||||
|
||||
public Task<User?> FindByIdAsync(long id, CancellationToken ct = default) => Task.FromResult(MetPersoon(_s.Users.FirstOrDefault(u => u.Id == id)));
|
||||
public Task<User?> FindByEmailAsync(string e, CancellationToken ct = default) => Task.FromResult(MetPersoon(_s.Users.FirstOrDefault(u => u.Email == e)));
|
||||
public Task<User?> FindByPersonIdAsync(long pid, CancellationToken ct = default) => Task.FromResult(MetPersoon(_s.Users.FirstOrDefault(u => u.PersonId == pid)));
|
||||
public Task<bool> EmailExistsAsync(string e, CancellationToken ct = default) => Task.FromResult(_s.Users.Any(u => u.Email == e));
|
||||
public Task<IReadOnlyList<User>> ListBeheerdersAsync(CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<User>>(_s.Users.Where(u => u.IsBeheerder).OrderBy(u => u.Id).ToList());
|
||||
public void Add(User u) => _s.Users.Add(u);
|
||||
public void Remove(User u) => _s.Users.Remove(u);
|
||||
}
|
||||
|
||||
public class LoginCodeRepo : ILoginCodeRepository
|
||||
{
|
||||
private readonly Store _s; public LoginCodeRepo(Store s) => _s = s;
|
||||
|
||||
public Task<LoginCode?> FindOpenAsync(string email, LoginCodePurpose doel, DateTime nu, CancellationToken ct = default)
|
||||
=> Task.FromResult(_s.Codes.Where(c => c.Email == email && c.Purpose == doel && c.IsOpen(nu))
|
||||
.OrderByDescending(c => c.CreatedAt).FirstOrDefault());
|
||||
|
||||
public Task<IReadOnlyList<LoginCode>> ListOpenAsync(string email, LoginCodePurpose doel, DateTime nu, CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<LoginCode>>(
|
||||
_s.Codes.Where(c => c.Email == email && c.Purpose == doel && c.IsOpen(nu)).ToList());
|
||||
|
||||
public Task<int> CountSinceAsync(string email, DateTime sinds, CancellationToken ct = default)
|
||||
=> Task.FromResult(_s.Codes.Count(c => c.Email == email && c.CreatedAt >= sinds && c.SignedInAt is null));
|
||||
|
||||
public Task<int> RemoveExpiredAsync(DateTime ouderDan, CancellationToken ct = default)
|
||||
{
|
||||
var aantal = _s.Codes.RemoveAll(c => c.ExpiresAt < ouderDan);
|
||||
return Task.FromResult(aantal);
|
||||
}
|
||||
|
||||
public Task<int> RemoveForEmailAsync(string email, CancellationToken ct = default)
|
||||
{
|
||||
var aantal = _s.Codes.RemoveAll(c => c.Email == email);
|
||||
return Task.FromResult(aantal);
|
||||
}
|
||||
|
||||
public void Add(LoginCode code) => _s.Codes.Add(code);
|
||||
}
|
||||
|
||||
public class PersonRepo : IPersonRepository
|
||||
{
|
||||
private readonly Store _s; public PersonRepo(Store s) => _s = s;
|
||||
public Task<Person?> FindByIdAsync(long id, CancellationToken ct = default) => Task.FromResult(_s.People.FirstOrDefault(p => p.Id == id));
|
||||
// Dezelfde volgorde als in de database: geclaimde profielen eerst. Er kunnen er meerdere
|
||||
// zijn met hetzelfde adres; zie docs/Identiteit.md.
|
||||
public Task<Person?> FindByEmailAsync(string e, CancellationToken ct = default) =>
|
||||
Task.FromResult(_s.People.Where(p => p.Email == e)
|
||||
.OrderByDescending(p => p.ClaimedByUserId != null).ThenBy(p => p.Id)
|
||||
.FirstOrDefault());
|
||||
|
||||
public Task<IReadOnlyList<Person>> ListByEmailAsync(string e, CancellationToken ct = default) =>
|
||||
Task.FromResult<IReadOnlyList<Person>>(
|
||||
_s.People.Where(p => p.Email == e)
|
||||
.OrderByDescending(p => p.ClaimedByUserId != null).ThenBy(p => p.Id).ToList());
|
||||
public Task<IReadOnlyList<Person>> ListAantekeningenOverAdresAsync(string e, long gebruikerId,
|
||||
CancellationToken ct = default) =>
|
||||
Task.FromResult<IReadOnlyList<Person>>(
|
||||
_s.People.Where(p => p.Email == e
|
||||
&& p.ClaimedByUserId == null
|
||||
&& p.MergedIntoPersonId == null
|
||||
&& p.ManagedByUserId != null
|
||||
&& p.ManagedByUserId != gebruikerId
|
||||
&& p.LinkDeclinedByUserId != gebruikerId)
|
||||
.OrderBy(p => p.CreatedAt).ToList());
|
||||
public Task<IReadOnlyList<Person>> ListByIdsAsync(IEnumerable<long> ids, CancellationToken ct = default)
|
||||
{ var set = ids.ToHashSet(); return Task.FromResult<IReadOnlyList<Person>>(_s.People.Where(p => set.Contains(p.Id)).ToList()); }
|
||||
public Task<Person?> FindByObjectionTokenAsync(byte[] hash, CancellationToken ct = default)
|
||||
=> Task.FromResult(_s.People.FirstOrDefault(
|
||||
p => p.ObjectionTokenHash is not null && p.ObjectionTokenHash.SequenceEqual(hash)));
|
||||
public void Add(Person p) => _s.People.Add(p);
|
||||
public void Remove(Person p) => _s.People.Remove(p);
|
||||
}
|
||||
|
||||
public class FamilyRepo : IFamilyRepository
|
||||
{
|
||||
private readonly Store _s; public FamilyRepo(Store s) => _s = s;
|
||||
public Task<Family?> FindByIdAsync(long id, CancellationToken ct = default) => Task.FromResult(_s.Families.FirstOrDefault(f => f.Id == id));
|
||||
public Task<IReadOnlyList<Family>> ListOwnedByPersonAsync(long pid, CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<Family>>(_s.Families.Where(f => f.OwnerPersonId == pid).ToList());
|
||||
public Task<IReadOnlyList<FamilyMember>> ListMembersAsync(long fid, CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<FamilyMember>>(_s.Members.Where(m => m.FamilyId == fid).ToList());
|
||||
public Task<FamilyMember?> FindMemberAsync(long fid, long pid, CancellationToken ct = default)
|
||||
=> Task.FromResult(_s.Members.FirstOrDefault(m => m.FamilyId == fid && m.PersonId == pid));
|
||||
public Task<IReadOnlyList<Family>> ListFamiliesContainingAsync(long pid, CancellationToken ct = default)
|
||||
{ var ids = _s.Members.Where(m => m.PersonId == pid).Select(m => m.FamilyId).ToHashSet();
|
||||
return Task.FromResult<IReadOnlyList<Family>>(_s.Families.Where(f => ids.Contains(f.Id)).ToList()); }
|
||||
public Task<IReadOnlyList<long>> ListCircleAsync(long pid, CancellationToken ct = default)
|
||||
{
|
||||
var families = _s.Members.Where(m => m.PersonId == pid).Select(m => m.FamilyId)
|
||||
.Concat(_s.Families.Where(f => f.OwnerPersonId == pid).Select(f => f.Id))
|
||||
.ToHashSet();
|
||||
|
||||
var kring = _s.Members.Where(m => families.Contains(m.FamilyId)).Select(m => m.PersonId).ToHashSet();
|
||||
kring.Add(pid);
|
||||
return Task.FromResult<IReadOnlyList<long>>(kring.ToList());
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<FamilyMember>> ListMembershipsOfPersonAsync(long pid, CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<FamilyMember>>(_s.Members.Where(m => m.PersonId == pid).ToList());
|
||||
public Task<IReadOnlyList<Family>> ListOwnedAsync(long pid, CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<Family>>(_s.Families.Where(f => f.OwnerPersonId == pid).ToList());
|
||||
public void Add(Family f) => _s.Families.Add(f);
|
||||
public void Add(FamilyMember m) => _s.Members.Add(m);
|
||||
public void Remove(FamilyMember m) => _s.Members.Remove(m);
|
||||
public void Remove(Family f) => _s.Families.Remove(f);
|
||||
}
|
||||
|
||||
public class InvitationRepo : IInvitationRepository
|
||||
{
|
||||
private readonly Store _s; public InvitationRepo(Store s) => _s = s;
|
||||
public Task<Invitation?> FindByIdAsync(long id, CancellationToken ct = default) => Task.FromResult(_s.Invitations.FirstOrDefault(i => i.Id == id));
|
||||
public Task<Invitation?> FindByTokenHashAsync(byte[] h, CancellationToken ct = default)
|
||||
=> Task.FromResult(_s.Invitations.FirstOrDefault(i => i.TokenHash.SequenceEqual(h)));
|
||||
public Task<IReadOnlyList<Invitation>> ListOpenForPersonAsync(long pid, CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<Invitation>>(_s.Invitations.Where(i => i.PersonId == pid).ToList());
|
||||
public Task<bool> HasOpenInvitationAsync(long pid, DateTime now, CancellationToken ct = default)
|
||||
=> Task.FromResult(_s.Invitations.Any(i => i.PersonId == pid && i.IsOpen(now)));
|
||||
public Task<IReadOnlyList<Invitation>> ListLatestForPeopleAsync(IEnumerable<long> personIds, CancellationToken ct = default)
|
||||
{
|
||||
var ids = personIds.Distinct().ToHashSet();
|
||||
return Task.FromResult<IReadOnlyList<Invitation>>(
|
||||
_s.Invitations.Where(i => ids.Contains(i.PersonId))
|
||||
.GroupBy(i => i.PersonId)
|
||||
.Select(g => g.OrderByDescending(i => i.SentAt ?? i.CreatedAt).First())
|
||||
.ToList());
|
||||
}
|
||||
public Task<IReadOnlyList<Invitation>> ListAllForPersonAsync(long pid, CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<Invitation>>(_s.Invitations.Where(i => i.PersonId == pid).ToList());
|
||||
public Task<IReadOnlyList<Invitation>> ListSentByUserAsync(long userId, CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<Invitation>>(_s.Invitations.Where(i => i.CreatedByUserId == userId).ToList());
|
||||
|
||||
public void Add(Invitation i) => _s.Invitations.Add(i);
|
||||
public void Remove(Invitation i) => _s.Invitations.Remove(i);
|
||||
}
|
||||
|
||||
public class VaultRepo : IVaultRepository
|
||||
{
|
||||
private readonly Store _s; public VaultRepo(Store s) => _s = s;
|
||||
public Task<VaultItem?> FindByIdAsync(long id, CancellationToken ct = default) => Task.FromResult(_s.VaultItems.FirstOrDefault(v => v.Id == id));
|
||||
public Task<IReadOnlyList<VaultItem>> ListForPersonAsync(long pid, VaultCategory? cat = null, CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<VaultItem>>(_s.VaultItems.Where(v => v.OwnerPersonId == pid && v.DeletedAt == null
|
||||
&& (cat == null || v.Category == cat)).ToList());
|
||||
public Task<VaultFile?> FindFileAsync(long id, CancellationToken ct = default) => Task.FromResult(_s.VaultFiles.FirstOrDefault(f => f.Id == id));
|
||||
public Task<IReadOnlyList<VaultFile>> ListFilesForItemAsync(long itemId, CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<VaultFile>>(_s.VaultFiles
|
||||
.Where(f => f.VaultItemId == itemId && f.DeletedAt == null).OrderBy(f => f.CreatedAt).ToList());
|
||||
public Task<IReadOnlyList<VaultFile>> ListFilesForPersonAsync(long pid, CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<VaultFile>>(_s.VaultFiles
|
||||
.Where(f => f.SubjectPersonId == pid && f.DeletedAt == null).OrderBy(f => f.CreatedAt).ToList());
|
||||
public Task<IReadOnlyList<VaultFile>> ListFilesForMessageAsync(long mid, CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<VaultFile>>(_s.VaultFiles
|
||||
.Where(f => f.MessageId == mid && f.DeletedAt == null).OrderBy(f => f.CreatedAt).ToList());
|
||||
public Task<IReadOnlyList<VaultFile>> ListProfilePhotosAsync(IEnumerable<long> pids, CancellationToken ct = default)
|
||||
{
|
||||
var ids = pids.Distinct().ToHashSet();
|
||||
return Task.FromResult<IReadOnlyList<VaultFile>>(_s.VaultFiles
|
||||
.Where(f => f.SubjectPersonId is { } p && ids.Contains(p) && f.IsProfilePhoto && f.DeletedAt == null)
|
||||
.ToList());
|
||||
}
|
||||
public Task<ContentGrant?> FindGrantAsync(string type, long rid, long uid, CancellationToken ct = default)
|
||||
=> Task.FromResult(_s.Grants.FirstOrDefault(g => g.ResourceType == type && g.ResourceId == rid && g.UserId == uid));
|
||||
public Task<IReadOnlyList<VaultItem>> ListSharedWithAsync(long userId, CancellationToken ct = default)
|
||||
{
|
||||
var ids = _s.Grants.Where(g => g.ResourceType == nameof(VaultItem) && g.UserId == userId && g.RevokedAt == null)
|
||||
.Select(g => g.ResourceId).ToHashSet();
|
||||
return Task.FromResult<IReadOnlyList<VaultItem>>(
|
||||
_s.VaultItems.Where(v => ids.Contains(v.Id) && v.DeletedAt == null).ToList());
|
||||
}
|
||||
public Task<IReadOnlyList<VaultFile>> ListFilesOwnedByAsync(long pid, CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<VaultFile>>(_s.VaultFiles.Where(f => f.OwnerPersonId == pid).ToList());
|
||||
public void Add(VaultItem i) => _s.VaultItems.Add(i);
|
||||
public void Add(VaultFile f) => _s.VaultFiles.Add(f);
|
||||
public void Add(ContentGrant g) => _s.Grants.Add(g);
|
||||
public void Remove(VaultItem i) => _s.VaultItems.Remove(i);
|
||||
public void Remove(VaultFile f) => _s.VaultFiles.Remove(f);
|
||||
|
||||
public Task RemoveGrantsForUserAsync(long userId, CancellationToken ct = default)
|
||||
{
|
||||
_s.Grants.RemoveAll(g => g.UserId == userId);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
public Task<int> RevokeGrantsForOwnerAsync(long ownerPersonId, string reason, DateTime nowUtc,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var items = _s.VaultItems.Where(i => i.OwnerPersonId == ownerPersonId).Select(i => i.Id).ToHashSet();
|
||||
var verleend = _s.Grants.Where(g => g.ResourceType == nameof(VaultItem) && items.Contains(g.ResourceId)
|
||||
&& g.Reason == reason && g.RevokedAt == null).ToList();
|
||||
foreach (var t in verleend) t.RevokedAt = nowUtc;
|
||||
return Task.FromResult(verleend.Count);
|
||||
}
|
||||
|
||||
public Task RemoveGrantsAsync(long itemId, CancellationToken ct = default)
|
||||
{
|
||||
_s.Grants.RemoveAll(g => g.ResourceType == nameof(VaultItem) && g.ResourceId == itemId);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public class MessageRepo : IMessageRepository
|
||||
{
|
||||
private readonly Store _s; public MessageRepo(Store s) => _s = s;
|
||||
public Task<Message?> FindByIdAsync(long id, CancellationToken ct = default) => Task.FromResult(_s.Messages.FirstOrDefault(m => m.Id == id));
|
||||
public Task<IReadOnlyList<Message>> ListOutboxAsync(long pid, CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<Message>>(_s.Messages.Where(m => m.FromPersonId == pid).ToList());
|
||||
public Task<IReadOnlyList<Message>> ListInboxAsync(long pid, CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<Message>>(_s.Messages.Where(m => m.ToPersonId == pid && m.Status >= MessageStatus.Bezorgd).ToList());
|
||||
public Task<IReadOnlyList<Message>> ListDueAsync(DateTime now, int max, CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<Message>>(_s.Messages.Where(m => m.Status == MessageStatus.Gepland).Take(max).ToList());
|
||||
public Task<IReadOnlyList<Message>> ListPlannedForAsync(long pid, CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<Message>>(_s.Messages
|
||||
.Where(m => m.ToPersonId == pid && m.Status == MessageStatus.Gepland).ToList());
|
||||
public Task<IReadOnlyList<Message>> ListAwaitingDeathOfAsync(long pid, CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<Message>>(_s.Messages.Where(m => m.FromPersonId == pid && m.DeliverAfterDeath).ToList());
|
||||
public Task<IReadOnlyList<Message>> ListAllTouchingAsync(long pid, CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<Message>>(
|
||||
_s.Messages.Where(m => m.FromPersonId == pid || m.ToPersonId == pid).ToList());
|
||||
public Task<Message?> FindByViewTokenHashAsync(byte[] tokenHash, CancellationToken ct = default)
|
||||
=> Task.FromResult(_s.Messages.FirstOrDefault(m => m.ViewTokenHash is not null && m.ViewTokenHash.SequenceEqual(tokenHash)));
|
||||
public void Add(Message m) => _s.Messages.Add(m);
|
||||
public void Remove(Message m) => _s.Messages.Remove(m);
|
||||
}
|
||||
|
||||
public class LegacyRepo : ILegacyRepository
|
||||
{
|
||||
private readonly Store _s; public LegacyRepo(Store s) => _s = s;
|
||||
public Task<IReadOnlyList<DeathDeclaration>> ListDeclarationsAsync(long pid, CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<DeathDeclaration>>(_s.Declarations.Where(d => d.PersonId == pid).ToList());
|
||||
public Task<DeathDeclaration?> FindDeclarationByUserAsync(long pid, long uid, CancellationToken ct = default)
|
||||
=> Task.FromResult(_s.Declarations.FirstOrDefault(d => d.PersonId == pid && d.DeclaredByUserId == uid));
|
||||
public Task<IReadOnlyList<LegacyManager>> ListManagersForAsync(long pid, CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<LegacyManager>>(_s.Managers.Where(m => m.PersonId == pid).ToList());
|
||||
public Task<IReadOnlyList<LegacyManager>> ListAssignmentsOfAsync(long mpid, CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<LegacyManager>>(_s.Managers.Where(m => m.ManagerPersonId == mpid).ToList());
|
||||
public Task<IReadOnlyList<DeathDeclaration>> ListAllDeclarationsAsync(long pid, CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<DeathDeclaration>>(_s.Declarations.Where(d => d.PersonId == pid).ToList());
|
||||
public Task<IReadOnlyList<DeathDeclaration>> ListDeclarationsByUserAsync(long userId, CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<DeathDeclaration>>(
|
||||
_s.Declarations.Where(d => d.DeclaredByUserId == userId).ToList());
|
||||
|
||||
public void Remove(DeathDeclaration d) => _s.Declarations.Remove(d);
|
||||
public void Add(DeathDeclaration d) => _s.Declarations.Add(d);
|
||||
public void Add(LegacyManager m) => _s.Managers.Add(m);
|
||||
public void Remove(LegacyManager m) => _s.Managers.Remove(m);
|
||||
}
|
||||
|
||||
public class SamenvoegRepo : ISamenvoegRepository
|
||||
{
|
||||
private readonly Store _s; public SamenvoegRepo(Store s) => _s = s;
|
||||
public Task<Samenvoeging?> FindByIdAsync(long id, CancellationToken ct = default)
|
||||
=> Task.FromResult(_s.Samenvoegingen.FirstOrDefault(x => x.Id == id));
|
||||
public Task<IReadOnlyList<Samenvoeging>> ListVoorDuplicaatAsync(long pid, CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<Samenvoeging>>(
|
||||
_s.Samenvoegingen.Where(x => x.DuplicaatPersonId == pid)
|
||||
.OrderByDescending(x => x.SamengevoegdOp).ToList());
|
||||
public Task<IReadOnlyList<Samenvoeging>> ListVoorDoelAsync(long pid, CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<Samenvoeging>>(
|
||||
_s.Samenvoegingen.Where(x => x.DoelPersonId == pid)
|
||||
.OrderByDescending(x => x.SamengevoegdOp).ToList());
|
||||
public void Add(Samenvoeging s) => _s.Samenvoegingen.Add(s);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adverteerders, campagnes en tellers. Vult de adverteerder in bij een campagne, net als de
|
||||
/// echte opslag met Include doet - zonder dat gedraagt de zelftest zich anders dan de server.
|
||||
/// </summary>
|
||||
public class AdvertentieRepo : IAdvertentieRepository
|
||||
{
|
||||
private readonly Store _s; public AdvertentieRepo(Store s) => _s = s;
|
||||
|
||||
private Campagne? Met(Campagne? c)
|
||||
{
|
||||
if (c is not null) c.Adverteerder ??= _s.Adverteerders.FirstOrDefault(a => a.Id == c.AdverteerderId);
|
||||
return c;
|
||||
}
|
||||
|
||||
public Task<Adverteerder?> FindAdverteerderAsync(long id, CancellationToken ct = default)
|
||||
=> Task.FromResult(_s.Adverteerders.FirstOrDefault(a => a.Id == id));
|
||||
|
||||
public Task<Adverteerder?> FindAdverteerderByEmailAsync(string email, CancellationToken ct = default)
|
||||
=> Task.FromResult(_s.Adverteerders.FirstOrDefault(a => a.Email == email));
|
||||
|
||||
public Task<IReadOnlyList<Adverteerder>> ListAdverteerdersAsync(CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<Adverteerder>>(_s.Adverteerders.OrderBy(a => a.Bedrijfsnaam).ToList());
|
||||
|
||||
public void Add(Adverteerder a) => _s.Adverteerders.Add(a);
|
||||
|
||||
public Task<Campagne?> FindCampagneAsync(long id, CancellationToken ct = default)
|
||||
=> Task.FromResult(Met(_s.Campagnes.FirstOrDefault(c => c.Id == id)));
|
||||
|
||||
public Task<IReadOnlyList<Campagne>> ListCampagnesAsync(long adverteerderId, CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<Campagne>>(
|
||||
_s.Campagnes.Where(c => c.AdverteerderId == adverteerderId)
|
||||
.OrderByDescending(c => c.AangemaaktOp).Select(c => Met(c)!).ToList());
|
||||
|
||||
public Task<IReadOnlyList<Campagne>> ListLopendAsync(DateOnly dag, CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<Campagne>>(
|
||||
_s.Campagnes.Where(c => c.DraaitOp(dag)).Select(c => Met(c)!).ToList());
|
||||
|
||||
public Task<IReadOnlyList<Campagne>> ListWachtendAsync(CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<Campagne>>(
|
||||
_s.Campagnes.Where(c => c.Stand == CampagneStand.WachtOpBetaling ||
|
||||
c.Stand == CampagneStand.WachtOpGoedkeuring)
|
||||
.OrderBy(c => c.AangemaaktOp).Select(c => Met(c)!).ToList());
|
||||
|
||||
public void Add(Campagne c) => _s.Campagnes.Add(c);
|
||||
|
||||
public Task<CampagneUur?> FindUurAsync(long campagneId, DateTime uurUtc, CancellationToken ct = default)
|
||||
=> Task.FromResult(_s.CampagneUren.FirstOrDefault(u => u.CampagneId == campagneId && u.UurUtc == uurUtc));
|
||||
|
||||
public Task<IReadOnlyList<CampagneUur>> ListUrenAsync(long campagneId, CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<CampagneUur>>(
|
||||
_s.CampagneUren.Where(u => u.CampagneId == campagneId).OrderBy(u => u.UurUtc).ToList());
|
||||
|
||||
public Task<IReadOnlyDictionary<long, int>> VertoningenInUurAsync(
|
||||
DateTime uurUtc, IEnumerable<long> campagneIds, CancellationToken ct = default)
|
||||
{
|
||||
var lijst = campagneIds.ToHashSet();
|
||||
return Task.FromResult<IReadOnlyDictionary<long, int>>(
|
||||
_s.CampagneUren.Where(u => u.UurUtc == uurUtc && lijst.Contains(u.CampagneId))
|
||||
.ToDictionary(u => u.CampagneId, u => u.Vertoningen));
|
||||
}
|
||||
|
||||
/// <summary>Doet hetzelfde als de INSERT ... ON DUPLICATE KEY UPDATE van de echte opslag.</summary>
|
||||
public Task TelAsync(long campagneId, DateTime uurUtc, int vertoningen, int kliks,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var regel = _s.CampagneUren.FirstOrDefault(u => u.CampagneId == campagneId && u.UurUtc == uurUtc);
|
||||
if (regel is null)
|
||||
{
|
||||
regel = new CampagneUur { Id = _s.NextId(), CampagneId = campagneId, UurUtc = uurUtc };
|
||||
_s.CampagneUren.Add(regel);
|
||||
}
|
||||
|
||||
regel.Vertoningen += vertoningen;
|
||||
regel.Kliks += kliks;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<(int Vertoningen, int Kliks)> TotalenAsync(long campagneId, CancellationToken ct = default)
|
||||
{
|
||||
var uren = _s.CampagneUren.Where(u => u.CampagneId == campagneId).ToList();
|
||||
return Task.FromResult((uren.Sum(u => u.Vertoningen), uren.Sum(u => u.Kliks)));
|
||||
}
|
||||
|
||||
public void Add(CampagneUur u) => _s.CampagneUren.Add(u);
|
||||
}
|
||||
|
||||
public class Audit : IAuditLog
|
||||
{
|
||||
private readonly Store _s; private readonly IClock _c;
|
||||
public Audit(Store s, IClock c) { _s = s; _c = c; }
|
||||
public Task WriteAsync(AuditAction a, long? uid, string? et = null, long? eid = null, string? d = null, CancellationToken ct = default)
|
||||
{ _s.Audit.Add(new AuditLogEntry { Action = a, UserId = uid, EntityType = et, EntityId = eid, Details = d, CreatedAt = _c.UtcNow }); return Task.CompletedTask; }
|
||||
}
|
||||
|
||||
public class ToestelRepo : IToestelRepository
|
||||
{
|
||||
private readonly Store _s; public ToestelRepo(Store s) => _s = s;
|
||||
public Task<Toestel?> FindByKenmerkAsync(string kenmerk, CancellationToken ct = default)
|
||||
=> Task.FromResult(_s.Toestellen.FirstOrDefault(t => t.Kenmerk == kenmerk));
|
||||
public Task<IReadOnlyList<Toestel>> ListForUserAsync(long userId, CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<Toestel>>(_s.Toestellen
|
||||
.Where(t => t.UserId == userId && t.AfgemeldOp == null).ToList());
|
||||
public void Add(Toestel t) => _s.Toestellen.Add(t);
|
||||
public void Remove(Toestel t) => _s.Toestellen.Remove(t);
|
||||
}
|
||||
|
||||
/// <summary>Onthoudt wat er verstuurd zou zijn, en kan doen alsof een toestel niet meer bestaat.</summary>
|
||||
public class TestPush : IPushSender
|
||||
{
|
||||
public List<(string Kenmerk, string Titel, string Tekst)> Verstuurd = new();
|
||||
|
||||
/// <summary>Kenmerken die Google niet meer kent.</summary>
|
||||
public HashSet<string> Onbekend = new();
|
||||
|
||||
public bool IsIngesteld { get; set; } = true;
|
||||
|
||||
public Task<bool> StuurAsync(string kenmerk, string titel, string tekst,
|
||||
IReadOnlyDictionary<string, string>? gegevens = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
if (Onbekend.Contains(kenmerk)) return Task.FromResult(false);
|
||||
Verstuurd.Add((kenmerk, titel, tekst));
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user