Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Admin API for sending announcements to specific identities #1018

Closed
Closed
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
5435a16
feat: added optional property recipients and validation rule
devx247 Jan 8, 2025
60208d0
fix: declared the property Recipients as nullable
devx247 Jan 8, 2025
ccc0caf
fix: validation rule of Recipients
devx247 Jan 8, 2025
25d9bc8
fix: validation of property Recipients
devx247 Jan 8, 2025
095dd29
feat: added AnnouncementRecipient + Migration + Handler update
devx247 Jan 10, 2025
244b257
feat: added AnnouncementRecipient Migration
devx247 Jan 13, 2025
c2de326
feat: added announcement-recipients, updated outgoing/incoming domain…
devx247 Jan 13, 2025
4cdda3b
feat: return recipients in admin-api on get request
devx247 Jan 14, 2025
73738a9
feat: added Announcement Recipient IdentityDeleter
devx247 Jan 14, 2025
695ca99
Merge branch 'main' into ABL-174-admin-api-send-announcements-to-spec…
devx247 Jan 14, 2025
2a641b5
Merge branch 'main' into ABL-174-admin-api-send-announcements-to-spec…
mergify[bot] Jan 14, 2025
c10da3f
Merge branch 'main' into ABL-174-admin-api-send-announcements-to-spec…
mergify[bot] Jan 15, 2025
42269c3
chore: removed pubspec.lock
devx247 Jan 15, 2025
003e408
Merge remote-tracking branch 'origin/ABL-174-admin-api-send-announcem…
devx247 Jan 15, 2025
9277fcb
fix: fixed AnnouncementRecipient + PushService usage per PnsHandle
devx247 Jan 15, 2025
f5261ba
test: added CreateAnnouncement Handler Tests
devx247 Jan 16, 2025
9096229
test: added AnnouncementRecipient Anonymize IdentityDeleter Tests
devx247 Jan 16, 2025
6e170dc
test: added AnonymizeRecipient tests
devx247 Jan 16, 2025
9328f7f
Merge branch 'main' into ABL-174-admin-api-send-announcements-to-spec…
tnotheis Jan 17, 2025
014dd0a
Merge branch 'main' into ABL-174-admin-api-send-announcements-to-spec…
devx247 Jan 20, 2025
81f9fc6
fix: updated docker file
devx247 Jan 20, 2025
9b8f0f8
fix: updated docker-file
devx247 Jan 20, 2025
9af11c3
chore: added debug-logging
devx247 Jan 20, 2025
31d33a7
chore: added some debug statement
devx247 Jan 20, 2025
dda86bc
chore: debug - check database-migrator logs
devx247 Jan 20, 2025
b46d207
chore: debug - fixed the check database-migrator-test log cmd
devx247 Jan 20, 2025
c9c4e18
chore: added new migration as error seen in logs regarding Announceme…
devx247 Jan 20, 2025
2df11a5
fix: added announcement-recipient migration for sqlserver + postgres db
devx247 Jan 21, 2025
5829632
fix: another attempt to get the database-migrator-test logs
devx247 Jan 21, 2025
b48c27c
chore: try to log exiting ci-seed-database-1 container
devx247 Jan 21, 2025
3324fcf
chore: try to log exiting ci-seed-database-1 container
devx247 Jan 21, 2025
779d2df
fix: replaced DistinctBy (can't be translated to SQL by EF Core)
devx247 Jan 21, 2025
e155c15
chore: removed debug log
devx247 Jan 22, 2025
0cf641c
fix: added AnnouncementsModule
devx247 Jan 22, 2025
17dbb0c
fix: added missing DOCKERFILE project reference
devx247 Jan 22, 2025
44be2f6
fix: added missing DOCKERFILE project reference
devx247 Jan 22, 2025
6c344d2
fix: updated regarding PR remarks
devx247 Jan 22, 2025
7423ed5
fix: use change-tracker announcements
devx247 Jan 22, 2025
91fb134
fix: updated regarding PR remarks
devx247 Jan 23, 2025
a4f3236
test: new AnonymizeRecipient Handler UnitTest
devx247 Jan 23, 2025
fafb1d6
Merge branch 'main' into ABL-174-admin-api-send-announcements-to-spec…
devx247 Jan 23, 2025
aac51b7
chore: added ValidId check
devx247 Jan 23, 2025
297a235
fix: failed test
devx247 Jan 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions Applications/AdminApi/http/Announcements/Announcements.bru
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
meta {
name: /Announcements
type: http
seq: 1
}

post {
url: {{baseUrl}}/Announcements
body: json
auth: none
}

body:json {
{
"severity": 1,
"texts": [
{
"language": "en",
"title": "System Maintenance",
"body": "The system will be undergoing maintenance on Saturday."
}
],
"expiresAt": "2023-12-31T23:59:59Z",
"recipients": [
"did:e:localhost:dids:8234cca0160ff05c785636",
"did:e:localhost:dids:5b8640b14cc9796fbf8d0d"
]
}
}
11 changes: 11 additions & 0 deletions Applications/AdminApi/http/Announcements/List Announcements.bru
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
meta {
name: /Announcements
type: http
seq: 2
}

get {
url: {{baseUrl}}/Announcements
body: none
auth: none
}
1 change: 1 addition & 0 deletions Applications/AdminApi/src/AdminApi/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"Modules": {
"Announcements": {
"Application": {
"DidDomainName": "localhost",
"Pagination": {
"DefaultPageSize": 50,
"MaxPageSize": 200
Expand Down
4 changes: 2 additions & 2 deletions Applications/AdminUi/apps/admin_ui/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -556,10 +556,10 @@ packages:
dependency: "direct dev"
description:
name: very_good_analysis
sha256: "1fb637c0022034b1f19ea2acb42a3603cbd8314a470646a59a2fb01f5f3a8629"
sha256: "62d2b86d183fb81b2edc22913d9f155d26eb5cf3855173adb1f59fac85035c63"
url: "https://pub.dev"
source: hosted
version: "6.0.0"
version: "7.0.0"
vm_service:
dependency: transitive
description:
Expand Down
1 change: 1 addition & 0 deletions Applications/ConsumerApi/src/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"Modules": {
"Announcements": {
"Application": {
"DidDomainName": "localhost",
"Pagination": {
"DefaultPageSize": 50,
"MaxPageSize": 200
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
meta {
name: /Announcements
type: http
seq: 1
}

get {
url: {{baseUrl}}/Announcements
body: none
auth: inherit
}
8 changes: 4 additions & 4 deletions Applications/ConsumerApi/src/http/Tokens/List Tokens.bru
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ meta {
}

get {
url: {{baseUrl}}/Tokens
url: {{baseUrl}}/Tokens?ids=TOKsjPynl0FHYJzHnkIo&PageNumber=1&PageSize=1
body: none
auth: inherit
}

params:query {
~ids: TOKsjPynl0FHYJzHnkIo
~PageNumber: 1
~PageSize: 1
ids: TOKsjPynl0FHYJzHnkIo
PageNumber: 1
PageSize: 1
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<ProjectReference Include="..\..\..\..\BuildingBlocks\src\BuildingBlocks.Infrastructure\BuildingBlocks.Infrastructure.csproj" />
<ProjectReference Include="..\..\..\..\BuildingBlocks\src\Tooling\Tooling.csproj" />
<ProjectReference Include="..\..\..\..\Infrastructure\Infrastructure.csproj" />
<ProjectReference Include="..\..\..\..\Modules\Announcements\src\Announcements.Application\Announcements.Application.csproj" />
<ProjectReference Include="..\..\..\..\Modules\Challenges\src\Challenges.Application\Challenges.Application.csproj" />
<ProjectReference Include="..\..\..\..\Modules\Challenges\src\Challenges.ConsumerApi\Challenges.ConsumerApi.csproj" />
<ProjectReference Include="..\..\..\..\Modules\Files\src\Files.Application\Files.Application.csproj" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
using Backbone.BuildingBlocks.Application.Identities;
using Backbone.Modules.Challenges.Application.Identities;

namespace Backbone.Job.IdentityDeletion;

public static class ServicesExtensions
{
public static IServiceCollection RegisterIdentityDeleters(this IServiceCollection services)
{
services.AddTransient<IIdentityDeleter, Modules.Challenges.Application.Identities.IdentityDeleter>();
services.AddTransient<IIdentityDeleter, IdentityDeleter>();
services.AddTransient<IIdentityDeleter, Modules.Devices.Application.Identities.IdentityDeleter>();
services.AddTransient<IIdentityDeleter, Modules.Files.Application.Identities.IdentityDeleter>();
services.AddTransient<IIdentityDeleter, Modules.Messages.Application.Identities.IdentityDeleter>();
services.AddTransient<IIdentityDeleter, Modules.Quotas.Application.Identities.IdentityDeleter>();
services.AddTransient<IIdentityDeleter, Modules.Relationships.Application.Identities.IdentityDeleter>();
services.AddTransient<IIdentityDeleter, Modules.Synchronization.Application.Identities.IdentityDeleter>();
services.AddTransient<IIdentityDeleter, Modules.Tokens.Application.Identities.IdentityDeleter>();
services.AddTransient<IIdentityDeleter, Modules.Announcements.Application.Identities.IdentityDeleter>();

return services;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"Modules": {
"Announcements": {
"Application": {
"DidDomainName": "localhost",
"Pagination": {
"DefaultPageSize": 50,
"MaxPageSize": 200
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

<ItemGroup>
<ProjectReference Include="..\..\..\..\BuildingBlocks\src\BuildingBlocks.Application\BuildingBlocks.Application.csproj" />
<ProjectReference Include="..\..\..\Devices\src\Devices.Application\Devices.Application.csproj" />
<ProjectReference Include="..\Announcements.Domain\Announcements.Domain.csproj" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using MediatR;

namespace Backbone.Modules.Announcements.Application.Announcements.Commands.AnonymizeRecipient;

public record AnonymizeRecipientForIdentityCommand : IRequest
{
public AnonymizeRecipientForIdentityCommand(string identityAddress)
{
IdentityAddress = identityAddress;
}

public string IdentityAddress { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Backbone.DevelopmentKit.Identity.ValueObjects;
using Backbone.Modules.Announcements.Application.Infrastructure.Persistence.Repository;
using MediatR;
using Microsoft.Extensions.Options;

namespace Backbone.Modules.Announcements.Application.Announcements.Commands.AnonymizeRecipient;

public class Handler : IRequestHandler<AnonymizeRecipientForIdentityCommand>
{
private readonly IAnnouncementRecipientRepository _announcementRecipientRepository;
private readonly ApplicationOptions _applicationOptions;

public Handler(IAnnouncementRecipientRepository announcementRecipientRepository, IOptions<ApplicationOptions> applicationOptions)
{
_announcementRecipientRepository = announcementRecipientRepository;
_applicationOptions = applicationOptions.Value;
}

public async Task Handle(AnonymizeRecipientForIdentityCommand request, CancellationToken cancellationToken)
{
var announcementRecipients = await _announcementRecipientRepository.FindAllForIdentityAddress(IdentityAddress.Parse(request.IdentityAddress), cancellationToken);

foreach (var announcementRecipient in announcementRecipients)
{
announcementRecipient.Anonymize(_applicationOptions.DidDomainName);
}

await _announcementRecipientRepository.Update(announcementRecipients, cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Backbone.BuildingBlocks.Application.Extensions;
using Backbone.DevelopmentKit.Identity.ValueObjects;
using FluentValidation;

namespace Backbone.Modules.Announcements.Application.Announcements.Commands.AnonymizeRecipient;

public class Validator : AbstractValidator<AnonymizeRecipientForIdentityCommand>
{
public Validator()
{
RuleFor(c => c.IdentityAddress)
.ValidId<AnonymizeRecipientForIdentityCommand, IdentityAddress>();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ public class CreateAnnouncementCommand : IRequest<AnnouncementDTO>
public required AnnouncementSeverity Severity { get; set; }
public required List<CreateAnnouncementCommandText> Texts { get; set; }
public DateTime? ExpiresAt { get; set; }

public List<string>? Recipients { get; set; }
}

public class CreateAnnouncementCommandText
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,59 @@
using Backbone.Modules.Announcements.Application.Announcements.DTOs;
using Backbone.DevelopmentKit.Identity.ValueObjects;
using Backbone.Modules.Announcements.Application.Announcements.DTOs;
using Backbone.Modules.Announcements.Application.Infrastructure.Persistence.Repository;
using Backbone.Modules.Announcements.Domain.Entities;
using Backbone.Modules.Devices.Application.Infrastructure.Persistence.Repository;
using Backbone.Modules.Devices.Domain.Entities.Identities;
using MediatR;
using Microsoft.Extensions.Logging;

namespace Backbone.Modules.Announcements.Application.Announcements.Commands.CreateAnnouncement;

public class Handler : IRequestHandler<CreateAnnouncementCommand, AnnouncementDTO>
{
private readonly IAnnouncementsRepository _announcementsRepository;
private readonly IIdentitiesRepository _identityRepository;
private readonly ILogger<Handler> _logger;

public Handler(IAnnouncementsRepository announcementsRepository)
public Handler(IAnnouncementsRepository announcementsRepository, ILogger<Handler> logger, IIdentitiesRepository identityRepository)
{
_announcementsRepository = announcementsRepository;
_logger = logger;
_identityRepository = identityRepository;
}

public async Task<AnnouncementDTO> Handle(CreateAnnouncementCommand request, CancellationToken cancellationToken)
{
List<AnnouncementRecipient> announcementRecipients = [];

if (request.Recipients != null && request.Recipients.Count != 0)
{
var requestRecipients = request.Recipients.OrderBy(address => address).ToList();

var requestRecipientsAsIdentityAddresses = requestRecipients.Select(IdentityAddress.Parse).ToList();
var foundRecipients = (await _identityRepository.Find(Identity.HasAddress(requestRecipientsAsIdentityAddresses), cancellationToken)).ToArray();
var foundRecipientAddresses = foundRecipients.Select(r => r.Address.Value).OrderBy(address => address).ToList();
var notFoundRecipientAddresses = requestRecipients.Except(foundRecipientAddresses).ToList();

if (notFoundRecipientAddresses.Count > 0)
{
_logger.LogError("Not all recipients were found in the database. \r\n" +
"Request Recipients: {requestRecipients}. \r\n" +
"Not Found Recipients: {notFoundRecipientAddresses}",
string.Join(',', requestRecipients),
string.Join(',', notFoundRecipientAddresses));
}

foreach (var recipient in foundRecipients)
{
announcementRecipients.AddRange(recipient.Devices.Select(recipientDevice =>
new AnnouncementRecipient(recipientDevice.Id.Value, recipient.Address.Value)));
}
}

var texts = request.Texts.Select(t => new AnnouncementText(AnnouncementLanguage.Parse(t.Language), t.Title, t.Body)).ToList();

var announcement = new Announcement(request.Severity, texts, request.ExpiresAt);
var announcement = new Announcement(request.Severity, texts, request.ExpiresAt, announcementRecipients);

await _announcementsRepository.Add(announcement, cancellationToken);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ public Validator()
.WithErrorCode(GenericApplicationErrors.Validation.InvalidPropertyValue().Code)
.WithMessage("There must be a text for English.");

RuleFor(x => x.Recipients)
.Must(x => x is null or { Count: <= 100 })
.WithErrorCode(GenericApplicationErrors.Validation.InvalidPropertyValue().Code)
.WithMessage("The maximum number of recipients is 100.");

RuleForEach(x => x.Texts).SetValidator(new CreateAnnouncementCommandTextValidator());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,26 @@ public AnnouncementDTO(Announcement announcement)
ExpiresAt = announcement.ExpiresAt;
Severity = announcement.Severity;
Texts = announcement.Texts.Select(t => new AnnouncementTextDTO(t));
Recipients = announcement.Recipients.Select(r => new AnnouncementRecipientDTO(r));
}

public string Id { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime? ExpiresAt { get; set; }
public AnnouncementSeverity Severity { get; set; }
public IEnumerable<AnnouncementTextDTO> Texts { get; set; }
public IEnumerable<AnnouncementRecipientDTO> Recipients { get; set; }
}

public record AnnouncementRecipientDTO
{
public AnnouncementRecipientDTO(AnnouncementRecipient announcementRecipient)
{
DeviceId = announcementRecipient.DeviceId;
Address = announcementRecipient.Address;
}

public string DeviceId { get; set; }
public string Address { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ public class ApplicationOptions
{
[Required]
public PaginationOptions Pagination { get; set; } = new();

[Required]
[MinLength(3)]
[MaxLength(45)]
public string DidDomainName { get; set; } = null!;
}

public class PaginationOptions
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Backbone.BuildingBlocks.Application.Identities;
using Backbone.DevelopmentKit.Identity.ValueObjects;
using Backbone.Modules.Announcements.Application.Announcements.Commands.AnonymizeRecipient;
using MediatR;

namespace Backbone.Modules.Announcements.Application.Identities;

public class IdentityDeleter : IIdentityDeleter
{
private readonly IDeletionProcessLogger _deletionProcessLogger;
private readonly IMediator _mediator;

public IdentityDeleter(IMediator mediator, IDeletionProcessLogger deletionProcessLogger)
{
_mediator = mediator;
_deletionProcessLogger = deletionProcessLogger;
}

public async Task Delete(IdentityAddress identityAddress)
{
await _mediator.Send(new AnonymizeRecipientForIdentityCommand(identityAddress));
await _deletionProcessLogger.LogDeletion(identityAddress, "AnnouncementRecipients");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Backbone.DevelopmentKit.Identity.ValueObjects;
using Backbone.Modules.Announcements.Domain.Entities;

namespace Backbone.Modules.Announcements.Application.Infrastructure.Persistence.Repository;

public interface IAnnouncementRecipientRepository
{
Task<List<AnnouncementRecipient>> FindAllForIdentityAddress(IdentityAddress identityAddress, CancellationToken cancellationToken);
Task Update(List<AnnouncementRecipient> announcementRecipients, CancellationToken cancellationToken);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

<ItemGroup>
<ProjectReference Include="..\..\..\..\BuildingBlocks\src\BuildingBlocks.Domain\BuildingBlocks.Domain.csproj" />
<ProjectReference Include="..\..\..\..\BuildingBlocks\src\DevelopmentKit.Identity\DevelopmentKit.Identity.csproj" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ public AnnouncementCreatedDomainEvent(Announcement announcement) : base($"{annou
Id = announcement.Id.Value;
Severity = announcement.Severity.ToString();
Texts = announcement.Texts.Select(t => new AnnouncementCreatedDomainEventText(t)).ToList();
Recipients = announcement.Recipients.Select(r => r.Address).ToList();
}

public string Id { get; }
public string Severity { get; }
public List<AnnouncementCreatedDomainEventText> Texts { get; }
public List<string> Recipients { get; }
}

public class AnnouncementCreatedDomainEventText
Expand Down
Loading
Loading