Skip to content

Commit

Permalink
feat: Add notification condition check endpoint (#965)
Browse files Browse the repository at this point in the history
## Description

This add an endpoint that can be used with Altinn Notification and
conditional notification orders

## Related Issue(s)

- #859 

## Verification

- [x] **Your** code builds clean without any errors or warnings
- [x] Manual testing done (required)
- [ ] Relevant automated test added (if you find this hard, leave it and
we'll help out)

## Documentation

- [ ] Documentation is updated (either in `docs`-directory, Altinnpedia
or a separate linked PR in
[altinn-studio-docs.](https://github.com/Altinn/altinn-studio-docs), if
applicable)

---------
  • Loading branch information
elsand authored Aug 11, 2024
1 parent 0757b33 commit f480ce0
Show file tree
Hide file tree
Showing 8 changed files with 263 additions and 0 deletions.
90 changes: 90 additions & 0 deletions docs/schema/V1/swagger.verified.json
Original file line number Diff line number Diff line change
Expand Up @@ -1350,6 +1350,84 @@
]
}
},
"/api/v1/serviceowner/dialogs/{dialogId}/notification-condition": {
"get": {
"tags": [
"Serviceowner"
],
"summary": "Returns a boolean value based on conditions used to determine if a notification is to be sent.",
"description": "Used by Altinn Notification only. Takes a dialogId and returns a boolean value based on conditions used to determine if a notification is to be sent.",
"operationId": "GetDialogActivityNotificationConditionSO",
"parameters": [
{
"name": "dialogId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "guid"
}
},
{
"name": "conditionType",
"in": "query",
"required": true,
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/NotificationConditionType"
}
]
}
},
{
"name": "activityType",
"in": "query",
"required": true,
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/DialogActivityType_Values"
}
]
}
},
{
"name": "transmissionId",
"in": "query",
"schema": {
"type": "string",
"format": "guid",
"nullable": true
}
}
],
"responses": {
"200": {
"description": "Successfully returned the notification determination.",
"content": {
"text/plain": {
"schema": {}
},
"application/json": {
"schema": {}
}
}
},
"401": {
"description": "Missing or invalid authentication token. Requires a Maskinporten-token with the scope \"altinn:system/notifications.condition.check\"."
},
"403": {
"description": "Forbidden"
}
},
"security": [
{
"JWTBearerAuth": []
}
]
}
},
"/api/v1/serviceowner/dialogs/{dialogId}/activities/{activityId}": {
"get": {
"tags": [
Expand Down Expand Up @@ -4167,6 +4245,18 @@
}
}
},
"NotificationConditionType": {
"type": "string",
"description": "",
"x-enumNames": [
"NotExists",
"Exists"
],
"enum": [
"NotExists",
"Exists"
]
},
"GetPartiesDto": {
"type": "object",
"additionalProperties": false,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.DialogActivities.Queries.NotificationCondition;

public sealed class NotificationConditionDto
{
public bool SendNotification { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using Digdir.Domain.Dialogporten.Application.Common.ReturnTypes;
using Digdir.Domain.Dialogporten.Application.Externals;
using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities;
using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities;
using MediatR;
using Microsoft.EntityFrameworkCore;
using OneOf;

namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.DialogActivities.Queries.NotificationCondition;

public sealed class NotificationConditionQuery : IRequest<NotificationConditionResult>
{
public Guid DialogId { get; set; }
public NotificationConditionType ConditionType { get; set; }
public DialogActivityType.Values ActivityType { get; set; }
public Guid? TransmissionId { get; set; }
}

public enum NotificationConditionType
{
NotExists = 1,
Exists = 2
}

[GenerateOneOf]
public partial class NotificationConditionResult : OneOfBase<NotificationConditionDto, ValidationError, EntityNotFound>;

internal sealed class NotificationConditionQueryHandler : IRequestHandler<NotificationConditionQuery, NotificationConditionResult>
{
private readonly IDialogDbContext _db;

public NotificationConditionQueryHandler(IDialogDbContext db)
{
_db = db ?? throw new ArgumentNullException(nameof(db));
}

public async Task<NotificationConditionResult> Handle(NotificationConditionQuery request, CancellationToken cancellationToken)
{
var dialog = await _db.Dialogs
.AsNoTracking()
.Include(x => x.Activities)
.IgnoreQueryFilters()
.FirstOrDefaultAsync(x => x.Id == request.DialogId,
cancellationToken: cancellationToken);

if (dialog is null)
{
return new EntityNotFound<DialogEntity>(request.DialogId);
}

var conditionMet = CheckDialogActivitiesCondition(dialog.Activities, request.ConditionType, request.ActivityType, request.TransmissionId);

return new NotificationConditionDto { SendNotification = conditionMet };
}

private static bool CheckDialogActivitiesCondition(
List<DialogActivity> activities,
NotificationConditionType conditionType,
DialogActivityType.Values activityType,
Guid? transmissionId) =>
activities.Where(
x => x.TypeId == activityType
&& (transmissionId is null || x.TransmissionId == transmissionId)).ToList()
.Count == 0
? conditionType == NotificationConditionType.NotExists
: conditionType == NotificationConditionType.Exists;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities;
using FluentValidation;

namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.DialogActivities.Queries.NotificationCondition;

internal sealed class NotificationConditionQueryValidator : AbstractValidator<NotificationConditionQuery>
{
public NotificationConditionQueryValidator()
{
RuleFor(x => x.DialogId)
.NotEqual(default(Guid));

RuleFor(x => x.ConditionType)
.NotNull()
.IsInEnum();

RuleFor(x => x.ActivityType)
.NotNull()
.IsInEnum();

RuleFor(x => x.TransmissionId)
.NotNull()
.NotEqual(default(Guid))
.When(x => x.ActivityType == DialogActivityType.Values.TransmissionOpened);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,9 @@ public void Configure(AuthorizationOptions options)
options.AddPolicy(AuthorizationPolicy.Testing, builder => builder
.Combine(options.DefaultPolicy)
.RequireScope(AuthorizationScope.Testing));

options.AddPolicy(AuthorizationPolicy.NotificationConditionCheck, builder => builder
.Combine(options.DefaultPolicy)
.RequireScope(AuthorizationScope.NotificationConditionCheck));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ internal static class AuthorizationPolicy
{
public const string EndUser = "enduser";
public const string ServiceProvider = "serviceprovider";
public const string NotificationConditionCheck = "notificationConditionCheck";
public const string ServiceProviderSearch = "serviceproviderSearch";
public const string Testing = "testing";
}
Expand All @@ -16,6 +17,7 @@ internal static class AuthorizationScope
public const string ServiceProvider = "digdir:dialogporten.serviceprovider";
public const string ServiceProviderSearch = "digdir:dialogporten.serviceprovider.search";
public const string Testing = "digdir:dialogporten.developer.test";
public const string NotificationConditionCheck = "altinn:system/notifications.condition.check";

internal static readonly Lazy<IReadOnlyCollection<string>> AllScopes = new(GetAll);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.DialogActivities.Queries.NotificationCondition;
using Digdir.Domain.Dialogporten.WebApi.Common.Authorization;
using Digdir.Domain.Dialogporten.WebApi.Common.Extensions;
using FastEndpoints;
using MediatR;

namespace Digdir.Domain.Dialogporten.WebApi.Endpoints.V1.ServiceOwner.DialogActivities.NotificationCondition;

public class NotificationConditionEndpoint : Endpoint<NotificationConditionQuery>
{
private readonly ISender _sender;

public NotificationConditionEndpoint(ISender sender)
{
_sender = sender ?? throw new ArgumentNullException(nameof(sender));
}

public override void Configure()
{
Get("dialogs/{dialogId}/notification-condition");
Policies(AuthorizationPolicy.NotificationConditionCheck);
Group<ServiceOwnerGroup>();

Description(b => NotificationConditionSwaggerConfig.SetDescription(b));
}

public override async Task HandleAsync(NotificationConditionQuery req, CancellationToken ct)
{
var result = await _sender.Send(req, ct);
await result.Match(
dto => SendOkAsync(dto, ct),
validationError => this.BadRequestAsync(validationError, ct),
notFound => this.NotFoundAsync(notFound, ct));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.DialogActivities.Queries.NotificationCondition;
using Digdir.Domain.Dialogporten.WebApi.Common;
using Digdir.Domain.Dialogporten.WebApi.Common.Authorization;
using Digdir.Domain.Dialogporten.WebApi.Common.Extensions;
using Digdir.Domain.Dialogporten.WebApi.Endpoints.V1.Common.Extensions;
using Digdir.Domain.Dialogporten.WebApi.Common.Swagger;
using FastEndpoints;


namespace Digdir.Domain.Dialogporten.WebApi.Endpoints.V1.ServiceOwner.DialogActivities.NotificationCondition;

public class NotificationConditionSwaggerConfig : ISwaggerConfig
{
public static string OperationId => "GetDialogActivityNotificationConditionSO";
public static RouteHandlerBuilder SetDescription(RouteHandlerBuilder builder)
=> builder.OperationId(OperationId);

public static object GetExample() => throw new NotImplementedException();
}

public sealed class SearchDialogActivityEndpointSummary : Summary<NotificationConditionEndpoint, NotificationConditionQuery>
{
public SearchDialogActivityEndpointSummary()
{
Summary = "Returns a boolean value based on conditions used to determine if a notification is to be sent.";
Description = """
Used by Altinn Notification only. Takes a dialogId and returns a boolean value based on conditions used to determine if a notification is to be sent.
""";
Responses[StatusCodes.Status200OK] = "Successfully returned the notification determination.";
Responses[StatusCodes.Status401Unauthorized] = Constants.SwaggerSummary.ServiceOwnerAuthenticationFailure.FormatInvariant(AuthorizationScope.NotificationConditionCheck);
Responses[StatusCodes.Status404NotFound] = Constants.SwaggerSummary.DialogNotFound;
}
}

0 comments on commit f480ce0

Please sign in to comment.