diff --git a/docs/schema/V1/schema.verified.graphql b/docs/schema/V1/schema.verified.graphql index 3e214b904..e914f1029 100644 --- a/docs/schema/V1/schema.verified.graphql +++ b/docs/schema/V1/schema.verified.graphql @@ -16,10 +16,16 @@ type Activity { extendedType: URL type: ActivityType! relatedActivityId: UUID - performedBy: String + performedBy: Actor! description: [Localization!]! } +type Actor { + actorType: ActorType + actorId: String + actorName: String +} + type ApiAction { id: UUID! action: String! @@ -173,8 +179,7 @@ type SearchDialogsPayload { type SeenLog { id: UUID! seenAt: DateTime! - endUserIdHash: String! - endUserName: String + seenBy: Actor! isCurrentEndUser: Boolean! } @@ -224,6 +229,11 @@ enum ActivityType { FORWARDED } +enum ActorType { + PARTY_REPRESENTATIVE + SERVICE_OWNER +} + enum ApplyPolicy { BEFORE_RESOLVER AFTER_RESOLVER diff --git a/docs/schema/V1/swagger.verified.json b/docs/schema/V1/swagger.verified.json index 663b3a5f6..120307510 100644 --- a/docs/schema/V1/swagger.verified.json +++ b/docs/schema/V1/swagger.verified.json @@ -319,7 +319,11 @@ "ExtendedType": null, "Type": 3, "RelatedActivityId": null, - "PerformedBy": "Some performed", + "PerformedBy": { + "ActorType": 2, + "ActorName": null, + "ActorId": null + }, "Description": [ { "Value": "Some description", @@ -1787,15 +1791,27 @@ "type": "string", "format": "date-time" }, - "endUserIdHash": { - "type": "string" + "seenBy": { + "$ref": "#/components/schemas/SearchDialogSeenLogActorDtoSO" }, - "endUserName": { - "type": "string", + "isViaServiceOwner": { + "type": "boolean", "nullable": true } } }, + "SearchDialogSeenLogActorDtoSO": { + "type": "object", + "additionalProperties": false, + "properties": { + "actorName": { + "type": "string" + }, + "actorId": { + "type": "string" + } + } + }, "ProblemDetails": { "type": "object", "additionalProperties": false, @@ -1867,15 +1883,27 @@ "type": "string", "format": "date-time" }, - "endUserIdHash": { - "type": "string" + "seenBy": { + "$ref": "#/components/schemas/GetDialogSeenLogActorDtoSO" }, - "endUserName": { - "type": "string", + "isViaServiceOwner": { + "type": "boolean", "nullable": true } } }, + "GetDialogSeenLogActorDtoSO": { + "type": "object", + "additionalProperties": false, + "properties": { + "actorName": { + "type": "string" + }, + "actorId": { + "type": "string" + } + } + }, "UpdateDialogRequest": { "type": "object", "additionalProperties": false, @@ -2281,8 +2309,7 @@ "nullable": true }, "performedBy": { - "type": "string", - "nullable": true + "$ref": "#/components/schemas/UpdateDialogDialogActivityActorDto" }, "description": { "type": "array", @@ -2312,6 +2339,35 @@ "Forwarded" ] }, + "UpdateDialogDialogActivityActorDto": { + "type": "object", + "additionalProperties": false, + "properties": { + "actorType": { + "$ref": "#/components/schemas/DialogActorType_Values" + }, + "actorName": { + "type": "string", + "nullable": true + }, + "actorId": { + "type": "string", + "nullable": true + } + } + }, + "DialogActorType_Values": { + "type": "string", + "description": "", + "x-enumNames": [ + "PartyRepresentative", + "ServiceOwner" + ], + "enum": [ + "PartyRepresentative", + "ServiceOwner" + ] + }, "PaginatedListOfSearchDialogDtoSO": { "type": "object", "additionalProperties": false, @@ -2363,6 +2419,11 @@ "format": "int32", "nullable": true }, + "guiAttachmentCount": { + "type": "integer", + "format": "int32", + "nullable": true + }, "extendedStatus": { "type": "string", "nullable": true @@ -2388,6 +2449,14 @@ "status": { "$ref": "#/components/schemas/DialogStatus_Values" }, + "latestActivity": { + "nullable": true, + "oneOf": [ + { + "$ref": "#/components/schemas/SearchDialogDialogActivityDtoSO" + } + ] + }, "content": { "type": "array", "items": { @@ -2402,6 +2471,60 @@ } } }, + "SearchDialogDialogActivityDtoSO": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "format": "guid" + }, + "createdAt": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "extendedType": { + "type": "string", + "format": "uri", + "nullable": true + }, + "type": { + "$ref": "#/components/schemas/DialogActivityType_Values" + }, + "relatedActivityId": { + "type": "string", + "format": "guid", + "nullable": true + }, + "performedBy": { + "$ref": "#/components/schemas/SearchDialogDialogActivityActorDtoSO" + }, + "description": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LocalizationDto" + } + } + } + }, + "SearchDialogDialogActivityActorDtoSO": { + "type": "object", + "additionalProperties": false, + "properties": { + "actorType": { + "$ref": "#/components/schemas/DialogActorType_Values" + }, + "actorName": { + "type": "string", + "nullable": true + }, + "actorId": { + "type": "string", + "nullable": true + } + } + }, "SearchDialogContentDtoSO": { "type": "object", "additionalProperties": false, @@ -2425,20 +2548,31 @@ "type": "string", "format": "guid" }, - "createdAt": { + "seenAt": { "type": "string", "format": "date-time" }, - "endUserIdHash": { - "type": "string" + "seenBy": { + "$ref": "#/components/schemas/SearchDialogDialogSeenLogActorDtoSO" }, - "endUserName": { - "type": "string", + "isViaServiceOwner": { + "type": "boolean", "nullable": true }, "isCurrentEndUser": { - "type": "boolean", - "nullable": true + "type": "boolean" + } + } + }, + "SearchDialogDialogSeenLogActorDtoSO": { + "type": "object", + "additionalProperties": false, + "properties": { + "actorName": { + "type": "string" + }, + "actorId": { + "type": "string" } } }, @@ -2765,8 +2899,7 @@ "nullable": true }, "performedBy": { - "type": "string", - "nullable": true + "$ref": "#/components/schemas/GetDialogDialogActivityActorDtoSO" }, "description": { "type": "array", @@ -2776,6 +2909,23 @@ } } }, + "GetDialogDialogActivityActorDtoSO": { + "type": "object", + "additionalProperties": false, + "properties": { + "actorType": { + "$ref": "#/components/schemas/DialogActorType_Values" + }, + "actorName": { + "type": "string", + "nullable": true + }, + "actorId": { + "type": "string", + "nullable": true + } + } + }, "GetDialogDialogSeenLogDtoSO": { "type": "object", "additionalProperties": false, @@ -2784,20 +2934,27 @@ "type": "string", "format": "guid" }, - "createdAt": { + "seenAt": { "type": "string", "format": "date-time" }, - "endUserIdHash": { - "type": "string" + "seenBy": { + "$ref": "#/components/schemas/GetDialogDialogSeenLogActorDtoSO" }, - "endUserName": { - "type": "string", - "nullable": true + "isViaServiceOwner": { + "type": "boolean" + } + } + }, + "GetDialogDialogSeenLogActorDtoSO": { + "type": "object", + "additionalProperties": false, + "properties": { + "actorName": { + "type": "string" }, - "isCurrentEndUser": { - "type": "boolean", - "nullable": true + "actorId": { + "type": "string" } } }, @@ -3084,8 +3241,7 @@ "nullable": true }, "performedBy": { - "type": "string", - "nullable": true + "$ref": "#/components/schemas/CreateDialogDialogActivityActorDto" }, "description": { "type": "array", @@ -3095,6 +3251,23 @@ } } }, + "CreateDialogDialogActivityActorDto": { + "type": "object", + "additionalProperties": false, + "properties": { + "actorType": { + "$ref": "#/components/schemas/DialogActorType_Values" + }, + "actorName": { + "type": "string", + "nullable": true + }, + "actorId": { + "type": "string", + "nullable": true + } + } + }, "CreateDialogActivityRequest": { "type": "object", "additionalProperties": false, @@ -3123,8 +3296,7 @@ "nullable": true }, "performedBy": { - "type": "string", - "nullable": true + "$ref": "#/components/schemas/UpdateDialogDialogActivityActorDto" }, "description": { "type": "array", @@ -3195,18 +3367,29 @@ "type": "string", "format": "date-time" }, - "endUserIdHash": { - "type": "string" + "seenBy": { + "$ref": "#/components/schemas/SearchDialogSeenLogSeenByDto" }, - "endUserName": { - "type": "string", - "nullable": true + "isViaServiceOwner": { + "type": "boolean" }, "isCurrentEndUser": { "type": "boolean" } } }, + "SearchDialogSeenLogSeenByDto": { + "type": "object", + "additionalProperties": false, + "properties": { + "actorName": { + "type": "string" + }, + "actorId": { + "type": "string" + } + } + }, "GetDialogSeenLogDto": { "type": "object", "additionalProperties": false, @@ -3219,18 +3402,29 @@ "type": "string", "format": "date-time" }, - "endUserIdHash": { - "type": "string" + "seenBy": { + "$ref": "#/components/schemas/GetDialogSeenLogActorDto" }, - "endUserName": { - "type": "string", - "nullable": true + "isViaServiceOwner": { + "type": "boolean" }, "isCurrentEndUser": { "type": "boolean" } } }, + "GetDialogSeenLogActorDto": { + "type": "object", + "additionalProperties": false, + "properties": { + "actorName": { + "type": "string" + }, + "actorId": { + "type": "string" + } + } + }, "PaginatedListOfSearchDialogDto": { "type": "object", "additionalProperties": false, @@ -3352,8 +3546,7 @@ "nullable": true }, "performedBy": { - "type": "string", - "nullable": true + "$ref": "#/components/schemas/SearchDialogDialogActivityActorDto" }, "description": { "type": "array", @@ -3363,6 +3556,23 @@ } } }, + "SearchDialogDialogActivityActorDto": { + "type": "object", + "additionalProperties": false, + "properties": { + "actorType": { + "$ref": "#/components/schemas/DialogActorType_Values" + }, + "actorName": { + "type": "string", + "nullable": true + }, + "actorId": { + "type": "string", + "nullable": true + } + } + }, "SearchDialogContentDto": { "type": "object", "additionalProperties": false, @@ -3390,11 +3600,11 @@ "type": "string", "format": "date-time" }, - "endUserIdHash": { - "type": "string" + "seenBy": { + "$ref": "#/components/schemas/SearchDialogDialogSeenLogActorDto" }, - "endUserName": { - "type": "string", + "isViaServiceOwner": { + "type": "boolean", "nullable": true }, "isCurrentEndUser": { @@ -3402,6 +3612,18 @@ } } }, + "SearchDialogDialogSeenLogActorDto": { + "type": "object", + "additionalProperties": false, + "properties": { + "actorName": { + "type": "string" + }, + "actorId": { + "type": "string" + } + } + }, "GetDialogDto": { "type": "object", "additionalProperties": false, @@ -3712,8 +3934,7 @@ "nullable": true }, "performedBy": { - "type": "string", - "nullable": true + "$ref": "#/components/schemas/GetDialogDialogActivityActorDto" }, "description": { "type": "array", @@ -3723,6 +3944,23 @@ } } }, + "GetDialogDialogActivityActorDto": { + "type": "object", + "additionalProperties": false, + "properties": { + "actorType": { + "$ref": "#/components/schemas/DialogActorType_Values" + }, + "actorName": { + "type": "string", + "nullable": true + }, + "actorId": { + "type": "string", + "nullable": true + } + } + }, "GetDialogDialogSeenLogDto": { "type": "object", "additionalProperties": false, @@ -3735,11 +3973,11 @@ "type": "string", "format": "date-time" }, - "endUserIdHash": { - "type": "string" + "seenBy": { + "$ref": "#/components/schemas/GetDialogDialogSeenLogActorDto" }, - "endUserName": { - "type": "string", + "isViaServiceOwner": { + "type": "boolean", "nullable": true }, "isCurrentEndUser": { @@ -3747,6 +3985,18 @@ } } }, + "GetDialogDialogSeenLogActorDto": { + "type": "object", + "additionalProperties": false, + "properties": { + "actorName": { + "type": "string" + }, + "actorId": { + "type": "string" + } + } + }, "Operation": { "type": "object", "additionalProperties": false, diff --git a/src/Digdir.Domain.Dialogporten.Application/ApplicationExtensions.cs b/src/Digdir.Domain.Dialogporten.Application/ApplicationExtensions.cs index dad0484c8..6c506d501 100644 --- a/src/Digdir.Domain.Dialogporten.Application/ApplicationExtensions.cs +++ b/src/Digdir.Domain.Dialogporten.Application/ApplicationExtensions.cs @@ -39,7 +39,6 @@ public static IServiceCollection AddApplication(this IServiceCollection services .AddScoped() // Transient - .AddTransient() .AddTransient() .AddTransient() .AddTransient() diff --git a/src/Digdir.Domain.Dialogporten.Application/Common/IStringHasher.cs b/src/Digdir.Domain.Dialogporten.Application/Common/IStringHasher.cs deleted file mode 100644 index c9a6e04d6..000000000 --- a/src/Digdir.Domain.Dialogporten.Application/Common/IStringHasher.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Security.Cryptography; -using System.Text; - -namespace Digdir.Domain.Dialogporten.Application.Common; - -internal interface IStringHasher -{ - [return: NotNullIfNotNull(nameof(personIdentifier))] - string? Hash(string? personIdentifier); -} - -internal class PersistentRandomSaltStringHasher : IStringHasher -{ - public const int StringLength = 10; - private const int SaltSize = 16; - private readonly Lazy _lazySalt = new(() => RandomNumberGenerator.GetBytes(SaltSize)); - - public string? Hash(string? personIdentifier) - { - if (string.IsNullOrWhiteSpace(personIdentifier)) - { - return null; - } - - var identifierBytes = Encoding.UTF8.GetBytes(personIdentifier); - Span buffer = stackalloc byte[identifierBytes.Length + _lazySalt.Value.Length]; - identifierBytes.CopyTo(buffer); - _lazySalt.Value.CopyTo(buffer[identifierBytes.Length..]); - - var hashBytes = SHA256.HashData(buffer); - - return BitConverter.ToString(hashBytes, 0, StringLength / 2).Replace("-", "").ToLowerInvariant(); - } -} diff --git a/src/Digdir.Domain.Dialogporten.Application/Common/IUserRegistry.cs b/src/Digdir.Domain.Dialogporten.Application/Common/IUserRegistry.cs index c70d95bd9..7dfdfcd48 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Common/IUserRegistry.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Common/IUserRegistry.cs @@ -2,6 +2,7 @@ using Digdir.Domain.Dialogporten.Application.Common.Extensions; using Digdir.Domain.Dialogporten.Application.Externals; using Digdir.Domain.Dialogporten.Application.Externals.Presentation; +using Digdir.Domain.Dialogporten.Domain.Parties; using UserIdType = Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogUserType.Values; namespace Digdir.Domain.Dialogporten.Application.Common; @@ -16,6 +17,14 @@ public sealed class UserId { public required UserIdType Type { get; set; } public required string ExternalId { get; init; } + public string ExternalIdWithPrefix => (Type switch + { + UserIdType.Person or UserIdType.ServiceOwnerOnBehalfOfPerson => NorwegianPersonIdentifier.PrefixWithSeparator, + UserIdType.SystemUser => SystemUserIdentifier.PrefixWithSeparator, + UserIdType.ServiceOwner => NorwegianOrganizationIdentifier.PrefixWithSeparator, + UserIdType.Unknown or UserIdType.LegacySystemUser => string.Empty, + _ => throw new UnreachableException("Unknown UserIdType") + }) + ExternalId; } public sealed class UserInformation @@ -27,14 +36,14 @@ public sealed class UserInformation public class UserRegistry : IUserRegistry { private readonly IUser _user; - private readonly IPersonNameRegistry _personNameRegistry; + private readonly IPartyNameRegistry _partyNameRegistry; public UserRegistry( IUser user, - IPersonNameRegistry personNameRegistry) + IPartyNameRegistry partyNameRegistry) { _user = user ?? throw new ArgumentNullException(nameof(user)); - _personNameRegistry = personNameRegistry ?? throw new ArgumentNullException(nameof(personNameRegistry)); + _partyNameRegistry = partyNameRegistry ?? throw new ArgumentNullException(nameof(partyNameRegistry)); } public UserId GetCurrentUserId() @@ -57,7 +66,7 @@ public async Task GetCurrentUserInformation(CancellationToken c { case UserIdType.Person: case UserIdType.ServiceOwnerOnBehalfOfPerson: - name = await _personNameRegistry.GetName(userId.ExternalId, cancellationToken); + name = await _partyNameRegistry.GetName(userId.ExternalIdWithPrefix, cancellationToken); break; case UserIdType.LegacySystemUser: diff --git a/src/Digdir.Domain.Dialogporten.Application/Common/IdentifierMasker.cs b/src/Digdir.Domain.Dialogporten.Application/Common/IdentifierMasker.cs new file mode 100644 index 000000000..70daf3a81 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Application/Common/IdentifierMasker.cs @@ -0,0 +1,36 @@ + +using System.Security.Cryptography; +using System.Text; +using Digdir.Domain.Dialogporten.Domain.Parties; + +namespace Digdir.Domain.Dialogporten.Application.Common; + +public static class IdentifierMasker +{ + public const int StringLength = 10; + private const int SaltSize = 16; + private static readonly byte[] Salt = RandomNumberGenerator.GetBytes(SaltSize); + + private static string? Hash(string? plaintext) + { + if (string.IsNullOrWhiteSpace(plaintext)) + { + return null; + } + + var identifierBytes = Encoding.UTF8.GetBytes(plaintext); + Span buffer = stackalloc byte[identifierBytes.Length + Salt.Length]; + identifierBytes.CopyTo(buffer); + Salt.CopyTo(buffer[identifierBytes.Length..]); + + var hashBytes = SHA256.HashData(buffer); + + return BitConverter.ToString(hashBytes, 0, StringLength / 2).Replace("-", "").ToLowerInvariant(); + } + + public static string? GetMaybeMaskedIdentifier(string? identifier) => + // We only care about masking norwegian person identifiers + identifier is null || !identifier.StartsWith(NorwegianPersonIdentifier.Prefix, StringComparison.Ordinal) + ? identifier + : NorwegianPersonIdentifier.HashPrefixWithSeparator + Hash(identifier); +} diff --git a/src/Digdir.Domain.Dialogporten.Application/Externals/IPartyNameRegistry.cs b/src/Digdir.Domain.Dialogporten.Application/Externals/IPartyNameRegistry.cs new file mode 100644 index 000000000..9cee0aace --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Application/Externals/IPartyNameRegistry.cs @@ -0,0 +1,6 @@ +namespace Digdir.Domain.Dialogporten.Application.Externals; + +public interface IPartyNameRegistry +{ + Task GetName(string externalIdWithPrefix, CancellationToken cancellationToken); +} diff --git a/src/Digdir.Domain.Dialogporten.Application/Externals/IPersonNameRegistry.cs b/src/Digdir.Domain.Dialogporten.Application/Externals/IPersonNameRegistry.cs deleted file mode 100644 index 284bb9226..000000000 --- a/src/Digdir.Domain.Dialogporten.Application/Externals/IPersonNameRegistry.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Digdir.Domain.Dialogporten.Application.Externals; - -public interface IPersonNameRegistry -{ - Task GetName(string personalIdentificationNumber, CancellationToken cancellationToken); -} diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogActivities/Queries/Get/GetDialogActivityDto.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogActivities/Queries/Get/GetDialogActivityDto.cs index 4b5e32fbf..78024dea1 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogActivities/Queries/Get/GetDialogActivityDto.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogActivities/Queries/Get/GetDialogActivityDto.cs @@ -1,5 +1,6 @@ using Digdir.Domain.Dialogporten.Application.Features.V1.Common.Localizations; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors; namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.DialogActivities.Queries.Get; @@ -13,6 +14,13 @@ public sealed class GetDialogActivityDto public Guid? RelatedActivityId { get; set; } - public string? PerformedBy { get; set; } + public GetDialogActivityActorDto PerformedBy { get; set; } = null!; public List Description { get; set; } = []; } + +public sealed class GetDialogActivityActorDto +{ + public DialogActorType.Values ActorType { get; set; } + public string? ActorName { get; set; } + public string? ActorId { get; set; } +} diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogActivities/Queries/Get/GetDialogActivityQuery.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogActivities/Queries/Get/GetDialogActivityQuery.cs index 1c71c9c2f..c73218bac 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogActivities/Queries/Get/GetDialogActivityQuery.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogActivities/Queries/Get/GetDialogActivityQuery.cs @@ -40,6 +40,9 @@ public async Task Handle(GetDialogActivityQuery request { var dialog = await _dbContext.Dialogs .Include(x => x.Activities.Where(x => x.Id == request.ActivityId)) + .ThenInclude(x => x.PerformedBy) + .Include(x => x.Activities.Where(x => x.Id == request.ActivityId)) + .ThenInclude(x => x.Description!.Localizations) .IgnoreQueryFilters() .FirstOrDefaultAsync(x => x.Id == request.DialogId, cancellationToken: cancellationToken); diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogActivities/Queries/Get/MappingProfile.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogActivities/Queries/Get/MappingProfile.cs index 0d686fade..a27a1e20a 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogActivities/Queries/Get/MappingProfile.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogActivities/Queries/Get/MappingProfile.cs @@ -1,5 +1,7 @@ using AutoMapper; +using Digdir.Domain.Dialogporten.Application.Common; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors; namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.DialogActivities.Queries.Get; @@ -9,5 +11,9 @@ public MappingProfile() { CreateMap() .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.TypeId)); + + CreateMap() + .ForMember(dest => dest.ActorType, opt => opt.MapFrom(src => src.ActorTypeId)) + .ForMember(dest => dest.ActorId, opt => opt.MapFrom(src => IdentifierMasker.GetMaybeMaskedIdentifier(src.ActorId))); } } diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Get/GetDialogSeenLogDto.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Get/GetDialogSeenLogDto.cs index 8d5def578..a6a6130a6 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Get/GetDialogSeenLogDto.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Get/GetDialogSeenLogDto.cs @@ -1,13 +1,17 @@ namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.DialogSeenLogs.Queries.Get; -public class GetDialogSeenLogDto +public sealed class GetDialogSeenLogDto { public Guid Id { get; set; } public DateTimeOffset SeenAt { get; set; } + public GetDialogSeenLogActorDto SeenBy { get; set; } = null!; - public string EndUserIdHash { get; set; } = null!; - - public string? EndUserName { get; set; } - + public bool IsViaServiceOwner { get; set; } public bool IsCurrentEndUser { get; set; } } + +public sealed class GetDialogSeenLogActorDto +{ + public string ActorName { get; set; } = null!; + public string ActorId { get; set; } = null!; +} diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Get/GetDialogSeenLogQuery.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Get/GetDialogSeenLogQuery.cs index 2ae343806..bdf5da5d4 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Get/GetDialogSeenLogQuery.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Get/GetDialogSeenLogQuery.cs @@ -24,20 +24,17 @@ internal sealed class GetDialogSeenLogQueryHandler : IRequestHandler Handle(GetDialogSeenLogQuery request, var dialog = await _dbContext.Dialogs .AsNoTracking() .Include(x => x.SeenLog.Where(x => x.Id == request.SeenLogId)) + .ThenInclude(x => x.SeenBy) .IgnoreQueryFilters() .FirstOrDefaultAsync(x => x.Id == request.DialogId, cancellationToken: cancellationToken); @@ -80,8 +78,7 @@ public async Task Handle(GetDialogSeenLogQuery request, } var dto = _mapper.Map(seenLog); - dto.IsCurrentEndUser = currentUserInformation.UserId.ExternalId == seenLog.EndUserId; - dto.EndUserIdHash = _stringHasher.Hash(seenLog.EndUserId); + dto.IsCurrentEndUser = currentUserInformation.UserId.ExternalIdWithPrefix == seenLog.SeenBy.ActorId; return dto; } diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Get/MappingProfile.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Get/MappingProfile.cs index 1bfcae98f..426c0d858 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Get/MappingProfile.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Get/MappingProfile.cs @@ -1,5 +1,7 @@ using AutoMapper; +using Digdir.Domain.Dialogporten.Application.Common; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors; namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.DialogSeenLogs.Queries.Get; @@ -9,5 +11,9 @@ public MappingProfile() { CreateMap() .ForMember(dest => dest.SeenAt, opt => opt.MapFrom(src => src.CreatedAt)); + + CreateMap() + .ForMember(dest => dest.ActorId, + opt => opt.MapFrom(src => IdentifierMasker.GetMaybeMaskedIdentifier(src.ActorId))); } } diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Search/MappingProfile.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Search/MappingProfile.cs index f57b3f427..1c4b6e82c 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Search/MappingProfile.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Search/MappingProfile.cs @@ -1,5 +1,7 @@ using AutoMapper; +using Digdir.Domain.Dialogporten.Application.Common; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors; namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.DialogSeenLogs.Queries.Search; @@ -9,5 +11,8 @@ public MappingProfile() { CreateMap() .ForMember(dest => dest.SeenAt, opt => opt.MapFrom(src => src.CreatedAt)); + + CreateMap() + .ForMember(dest => dest.ActorId, opt => opt.MapFrom(src => IdentifierMasker.GetMaybeMaskedIdentifier(src.ActorId))); } } diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Search/SearchDialogSeenLogDto.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Search/SearchDialogSeenLogDto.cs index a09d24123..22e62bfbc 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Search/SearchDialogSeenLogDto.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Search/SearchDialogSeenLogDto.cs @@ -1,13 +1,18 @@ namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.DialogSeenLogs.Queries.Search; -public class SearchDialogSeenLogDto +public sealed class SearchDialogSeenLogDto { public Guid Id { get; set; } public DateTimeOffset SeenAt { get; set; } - public string EndUserIdHash { get; set; } = null!; - - public string? EndUserName { get; set; } + public SearchDialogSeenLogSeenByDto SeenBy { get; set; } = null!; + public bool IsViaServiceOwner { get; set; } public bool IsCurrentEndUser { get; set; } } + +public sealed class SearchDialogSeenLogSeenByDto +{ + public string ActorName { get; set; } = null!; + public string ActorId { get; set; } = null!; +} diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Search/SearchDialogSeenLogQuery.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Search/SearchDialogSeenLogQuery.cs index eefd261f3..92b211caa 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Search/SearchDialogSeenLogQuery.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Search/SearchDialogSeenLogQuery.cs @@ -24,20 +24,17 @@ internal sealed class SearchDialogSeenLogQueryHandler : IRequestHandler Handle(SearchDialogSeenLogQuery request, CancellationToken cancellationToken) @@ -47,7 +44,7 @@ public async Task Handle(SearchDialogSeenLogQuery req var dialog = await _db.Dialogs .AsNoTracking() .Include(x => x.SeenLog) - .ThenInclude(x => x.Via!.Localizations) + .ThenInclude(x => x.SeenBy) .IgnoreQueryFilters() .FirstOrDefaultAsync(x => x.Id == request.DialogId, cancellationToken: cancellationToken); @@ -76,8 +73,7 @@ public async Task Handle(SearchDialogSeenLogQuery req .Select(x => { var dto = _mapper.Map(x); - dto.IsCurrentEndUser = x.EndUserId == currentUserInformation.UserId.ExternalId; - dto.EndUserIdHash = _stringHasher.Hash(x.EndUserId); + dto.IsCurrentEndUser = currentUserInformation.UserId.ExternalIdWithPrefix == x.SeenBy.ActorId; return dto; }) .ToList(); diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/GetDialogDto.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/GetDialogDto.cs index b394c14ed..83b0153b7 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/GetDialogDto.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/GetDialogDto.cs @@ -2,6 +2,7 @@ using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Attachments; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content; using Digdir.Domain.Dialogporten.Domain.Http; @@ -38,18 +39,23 @@ public sealed class GetDialogDto } -public class GetDialogDialogSeenLogDto +public sealed class GetDialogDialogSeenLogDto { public Guid Id { get; set; } public DateTimeOffset SeenAt { get; set; } - public string EndUserIdHash { get; set; } = null!; - - public string? EndUserName { get; set; } + public GetDialogDialogSeenLogActorDto SeenBy { get; set; } = null!; + public bool? IsViaServiceOwner { get; set; } public bool IsCurrentEndUser { get; set; } } +public sealed class GetDialogDialogSeenLogActorDto +{ + public string ActorName { get; set; } = null!; + public string ActorId { get; set; } = null!; +} + public sealed class GetDialogContentDto { public DialogContentType.Values Type { get; set; } @@ -67,10 +73,17 @@ public sealed class GetDialogDialogActivityDto public Guid? RelatedActivityId { get; set; } - public string? PerformedBy { get; set; } + public GetDialogDialogActivityActorDto PerformedBy { get; set; } = null!; public List Description { get; set; } = []; } +public sealed class GetDialogDialogActivityActorDto +{ + public DialogActorType.Values ActorType { get; set; } + public string? ActorName { get; set; } + public string? ActorId { get; set; } +} + public sealed class GetDialogDialogApiActionDto { public Guid Id { get; set; } diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/GetDialogQuery.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/GetDialogQuery.cs index 06c2b03c9..01abb0fe7 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/GetDialogQuery.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/GetDialogQuery.cs @@ -29,7 +29,6 @@ internal sealed class GetDialogQueryHandler : IRequestHandler Handle(GetDialogQuery request, CancellationToken cancellationToken) @@ -73,9 +70,11 @@ public async Task Handle(GetDialogQuery request, CancellationTo .Include(x => x.ApiActions.OrderBy(x => x.CreatedAt).ThenBy(x => x.Id)) .ThenInclude(x => x.Endpoints.OrderBy(x => x.CreatedAt).ThenBy(x => x.Id)) .Include(x => x.Activities).ThenInclude(x => x.Description!.Localizations) + .Include(x => x.Activities).ThenInclude(x => x.PerformedBy) .Include(x => x.SeenLog .Where(x => x.CreatedAt >= x.Dialog.UpdatedAt) .OrderBy(x => x.CreatedAt)) + .ThenInclude(x => x.SeenBy) .Where(x => !x.VisibleFrom.HasValue || x.VisibleFrom < _clock.UtcNowOffset) .IgnoreQueryFilters() .FirstOrDefaultAsync(x => x.Id == request.DialogId, cancellationToken); @@ -101,7 +100,10 @@ public async Task Handle(GetDialogQuery request, CancellationTo // TODO: What if name lookup fails // https://github.com/digdir/dialogporten/issues/387 - dialog.UpdateSeenAt(currentUserInformation.UserId.ExternalId, currentUserInformation.UserId.Type, currentUserInformation.Name); + dialog.UpdateSeenAt( + currentUserInformation.UserId.ExternalIdWithPrefix, + currentUserInformation.UserId.Type, + currentUserInformation.Name); var saveResult = await _unitOfWork .WithoutAuditableSideEffects() @@ -118,8 +120,7 @@ public async Task Handle(GetDialogQuery request, CancellationTo .Select(log => { var logDto = _mapper.Map(log); - logDto.IsCurrentEndUser = log.EndUserId == currentUserInformation.UserId.ExternalId; - logDto.EndUserIdHash = _stringHasher.Hash(log.EndUserId); + logDto.IsCurrentEndUser = currentUserInformation.UserId.ExternalIdWithPrefix == log.SeenBy.ActorId; return logDto; }) .ToList(); diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/MappingProfile.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/MappingProfile.cs index b226cfe6b..ef9064dea 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/MappingProfile.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/MappingProfile.cs @@ -1,7 +1,9 @@ using AutoMapper; +using Digdir.Domain.Dialogporten.Application.Common; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Attachments; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content; @@ -19,9 +21,16 @@ public MappingProfile() CreateMap() .ForMember(dest => dest.SeenAt, opt => opt.MapFrom(src => src.CreatedAt)); + CreateMap() + .ForMember(dest => dest.ActorId, opt => opt.MapFrom(src => IdentifierMasker.GetMaybeMaskedIdentifier(src.ActorId))); + CreateMap() .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.TypeId)); + CreateMap() + .ForMember(dest => dest.ActorType, opt => opt.MapFrom(src => src.ActorTypeId)) + .ForMember(dest => dest.ActorId, opt => opt.MapFrom(src => IdentifierMasker.GetMaybeMaskedIdentifier(src.ActorId))); + CreateMap(); CreateMap() diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Search/MappingProfile.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Search/MappingProfile.cs index 674b67960..aadfd38c2 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Search/MappingProfile.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Search/MappingProfile.cs @@ -1,6 +1,8 @@ using AutoMapper; +using Digdir.Domain.Dialogporten.Application.Common; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Attachments; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content; @@ -30,10 +32,16 @@ public MappingProfile() .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.TypeId)); CreateMap() - .ForMember(dest => dest.SeenAt, opt => opt.MapFrom(src => src.CreatedAt)) - .ForMember(dest => dest.EndUserIdHash, opt => opt.MapFrom(src => src.EndUserId)); + .ForMember(dest => dest.SeenAt, opt => opt.MapFrom(src => src.CreatedAt)); + + CreateMap() + .ForMember(dest => dest.ActorId, opt => opt.MapFrom(src => IdentifierMasker.GetMaybeMaskedIdentifier(src.ActorId))); CreateMap() .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.TypeId)); + + CreateMap() + .ForMember(dest => dest.ActorType, opt => opt.MapFrom(src => src.ActorTypeId)) + .ForMember(dest => dest.ActorId, opt => opt.MapFrom(src => IdentifierMasker.GetMaybeMaskedIdentifier(src.ActorId))); } } diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Search/SearchDialogDto.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Search/SearchDialogDto.cs index 9d42dabe1..f605efaef 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Search/SearchDialogDto.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Search/SearchDialogDto.cs @@ -1,6 +1,7 @@ using Digdir.Domain.Dialogporten.Application.Features.V1.Common.Localizations; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content; namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.Search; @@ -27,18 +28,23 @@ public sealed class SearchDialogDto public List SeenSinceLastUpdate { get; set; } = []; } -public class SearchDialogDialogSeenLogDto +public sealed class SearchDialogDialogSeenLogDto { public Guid Id { get; set; } public DateTimeOffset SeenAt { get; set; } - public string EndUserIdHash { get; set; } = null!; - - public string? EndUserName { get; set; } + public SearchDialogDialogSeenLogActorDto SeenBy { get; set; } = null!; + public bool? IsViaServiceOwner { get; set; } public bool IsCurrentEndUser { get; set; } } +public sealed class SearchDialogDialogSeenLogActorDto +{ + public string ActorName { get; set; } = null!; + public string ActorId { get; set; } = null!; +} + public sealed class SearchDialogContentDto { public DialogContentType.Values Type { get; set; } @@ -55,6 +61,13 @@ public sealed class SearchDialogDialogActivityDto public Guid? RelatedActivityId { get; set; } - public string? PerformedBy { get; set; } + public SearchDialogDialogActivityActorDto PerformedBy { get; set; } = null!; public List Description { get; set; } = []; } + +public sealed class SearchDialogDialogActivityActorDto +{ + public DialogActorType.Values ActorType { get; set; } + public string? ActorName { get; set; } + public string? ActorId { get; set; } +} diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Search/SearchDialogQuery.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Search/SearchDialogQuery.cs index 27e31e817..fe5723a0d 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Search/SearchDialogQuery.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Search/SearchDialogQuery.cs @@ -116,22 +116,19 @@ internal sealed class SearchDialogQueryHandler : IRequestHandler Handle(SearchDialogQuery request, CancellationToken cancellationToken) @@ -176,9 +173,7 @@ public async Task Handle(SearchDialogQuery request, Cancella foreach (var seenLog in paginatedList.Items.SelectMany(x => x.SeenSinceLastUpdate)) { - // Before we hash the end user id, check if the seen log entry is for the current user - seenLog.IsCurrentEndUser = currentUserInfo.UserId.ExternalId == seenLog.EndUserIdHash; - seenLog.EndUserIdHash = _stringHasher.Hash(seenLog.EndUserIdHash); + seenLog.IsCurrentEndUser = IdentifierMasker.GetMaybeMaskedIdentifier(currentUserInfo.UserId.ExternalIdWithPrefix) == seenLog.SeenBy.ActorId; } return paginatedList; diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogActivities/Queries/Get/GetDialogActivityDto.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogActivities/Queries/Get/GetDialogActivityDto.cs index 6474b3b75..bffc2eaa7 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogActivities/Queries/Get/GetDialogActivityDto.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogActivities/Queries/Get/GetDialogActivityDto.cs @@ -1,5 +1,6 @@ using Digdir.Domain.Dialogporten.Application.Features.V1.Common.Localizations; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors; namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.DialogActivities.Queries.Get; @@ -15,6 +16,13 @@ public sealed class GetDialogActivityDto public Guid? RelatedActivityId { get; set; } - public string? PerformedBy { get; set; } + public GetDialogActivityActorDto PerformedBy { get; set; } = null!; public List Description { get; set; } = []; } + +public sealed class GetDialogActivityActorDto +{ + public DialogActorType.Values ActorType { get; set; } + public string? ActorName { get; set; } + public string? ActorId { get; set; } +} diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogActivities/Queries/Get/GetDialogActivityQuery.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogActivities/Queries/Get/GetDialogActivityQuery.cs index d16d49e86..f394d0452 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogActivities/Queries/Get/GetDialogActivityQuery.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogActivities/Queries/Get/GetDialogActivityQuery.cs @@ -39,6 +39,9 @@ public async Task Handle(GetDialogActivityQuery request var dialog = await _dbContext.Dialogs .Include(x => x.Activities.Where(x => x.Id == request.ActivityId)) + .ThenInclude(x => x.PerformedBy) + .Include(x => x.Activities.Where(x => x.Id == request.ActivityId)) + .ThenInclude(x => x.Description!.Localizations) .IgnoreQueryFilters() .Where(x => resourceIds.Contains(x.ServiceResource)) .FirstOrDefaultAsync(x => x.Id == request.DialogId, diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogActivities/Queries/Get/MappingProfile.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogActivities/Queries/Get/MappingProfile.cs index affc6eac0..a816e018d 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogActivities/Queries/Get/MappingProfile.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogActivities/Queries/Get/MappingProfile.cs @@ -1,5 +1,6 @@ using AutoMapper; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors; namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.DialogActivities.Queries.Get; @@ -10,5 +11,9 @@ public MappingProfile() CreateMap() .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.TypeId)) .ForMember(dest => dest.DeletedAt, opt => opt.MapFrom(src => src.Dialog.DeletedAt)); + + CreateMap() + .ForMember(dest => dest.ActorType, opt => opt.MapFrom(src => src.ActorTypeId)); + } } diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogSeenLogs/Queries/Get/GetDialogSeenLogDto.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogSeenLogs/Queries/Get/GetDialogSeenLogDto.cs index 698e28a46..06c037a4f 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogSeenLogs/Queries/Get/GetDialogSeenLogDto.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogSeenLogs/Queries/Get/GetDialogSeenLogDto.cs @@ -1,11 +1,17 @@ namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.DialogSeenLogs.Queries.Get; -public class GetDialogSeenLogDto +public sealed class GetDialogSeenLogDto { public Guid Id { get; set; } public DateTimeOffset SeenAt { get; set; } - public string EndUserIdHash { get; set; } = null!; + public GetDialogSeenLogActorDto SeenBy { get; set; } = null!; - public string? EndUserName { get; set; } + public bool? IsViaServiceOwner { get; set; } +} + +public sealed class GetDialogSeenLogActorDto +{ + public string ActorName { get; set; } = null!; + public string ActorId { get; set; } = null!; } diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogSeenLogs/Queries/Get/GetDialogSeenLogQuery.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogSeenLogs/Queries/Get/GetDialogSeenLogQuery.cs index d57eb6591..70713af0f 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogSeenLogs/Queries/Get/GetDialogSeenLogQuery.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogSeenLogs/Queries/Get/GetDialogSeenLogQuery.cs @@ -22,18 +22,15 @@ internal sealed class GetDialogSeenLogQueryHandler : IRequestHandler Handle(GetDialogSeenLogQuery request, var dialog = await _dbContext.Dialogs .AsNoTracking() .Include(x => x.SeenLog.Where(x => x.Id == request.SeenLogId)) + .ThenInclude(x => x.SeenBy) .IgnoreQueryFilters() .Where(x => resourceIds.Contains(x.ServiceResource)) .FirstOrDefaultAsync(x => x.Id == request.DialogId, @@ -62,7 +60,6 @@ public async Task Handle(GetDialogSeenLogQuery request, } var dto = _mapper.Map(seenLog); - dto.EndUserIdHash = _stringHasher.Hash(seenLog.EndUserId); return dto; } diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogSeenLogs/Queries/Get/MappingProfile.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogSeenLogs/Queries/Get/MappingProfile.cs index 617328809..3af80c4af 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogSeenLogs/Queries/Get/MappingProfile.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogSeenLogs/Queries/Get/MappingProfile.cs @@ -1,5 +1,6 @@ using AutoMapper; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors; namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.DialogSeenLogs.Queries.Get; @@ -9,5 +10,7 @@ public MappingProfile() { CreateMap() .ForMember(dest => dest.SeenAt, opt => opt.MapFrom(src => src.CreatedAt)); + + CreateMap(); } } diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogSeenLogs/Queries/Search/MappingProfile.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogSeenLogs/Queries/Search/MappingProfile.cs index 76442824a..1d3ad4da1 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogSeenLogs/Queries/Search/MappingProfile.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogSeenLogs/Queries/Search/MappingProfile.cs @@ -1,5 +1,6 @@ using AutoMapper; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors; namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.DialogSeenLogs.Queries.Search; @@ -9,5 +10,7 @@ public MappingProfile() { CreateMap() .ForMember(dest => dest.SeenAt, opt => opt.MapFrom(src => src.CreatedAt)); + + CreateMap(); } } diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogSeenLogs/Queries/Search/SearchDialogSeenLogDto.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogSeenLogs/Queries/Search/SearchDialogSeenLogDto.cs index a68f7a7dc..4e27c357b 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogSeenLogs/Queries/Search/SearchDialogSeenLogDto.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogSeenLogs/Queries/Search/SearchDialogSeenLogDto.cs @@ -1,11 +1,17 @@ namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.DialogSeenLogs.Queries.Search; -public class SearchDialogSeenLogDto +public sealed class SearchDialogSeenLogDto { public Guid Id { get; set; } public DateTimeOffset SeenAt { get; set; } - public string EndUserIdHash { get; set; } = null!; + public SearchDialogSeenLogActorDto SeenBy { get; set; } = null!; - public string? EndUserName { get; set; } + public bool? IsViaServiceOwner { get; set; } +} + +public sealed class SearchDialogSeenLogActorDto +{ + public string ActorName { get; set; } = null!; + public string ActorId { get; set; } = null!; } diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogSeenLogs/Queries/Search/SearchDialogSeenLogQuery.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogSeenLogs/Queries/Search/SearchDialogSeenLogQuery.cs index c6937053c..a74a94f6c 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogSeenLogs/Queries/Search/SearchDialogSeenLogQuery.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/DialogSeenLogs/Queries/Search/SearchDialogSeenLogQuery.cs @@ -21,18 +21,15 @@ internal sealed class SearchDialogSeenLogQueryHandler : IRequestHandler Handle(SearchDialogSeenLogQuery req var dialog = await _db.Dialogs .AsNoTracking() .Include(x => x.SeenLog) - .ThenInclude(x => x.Via!.Localizations) + .ThenInclude(x => x.SeenBy) .IgnoreQueryFilters() .Where(x => resourceIds.Contains(x.ServiceResource)) .FirstOrDefaultAsync(x => x.Id == request.DialogId, @@ -58,7 +55,6 @@ public async Task Handle(SearchDialogSeenLogQuery req .Select(x => { var dto = _mapper.Map(x); - dto.EndUserIdHash = _stringHasher.Hash(x.EndUserId); return dto; }) .ToList(); diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Create/CreateDialogCommand.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Create/CreateDialogCommand.cs index de9014d49..4ccb7fd14 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Create/CreateDialogCommand.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Create/CreateDialogCommand.cs @@ -29,6 +29,7 @@ internal sealed class CreateDialogCommandHandler : IRequestHandler Handle(CreateDialogCommand request, CancellationToken cancellationToken) @@ -71,6 +74,24 @@ public async Task Handle(CreateDialogCommand request, Cancel return new ValidationError(ProgressValidationFailure); } + foreach (var activity in request.Activities) + { + if (activity.PerformedBy.ActorId is null) + { + continue; + } + + activity.PerformedBy.ActorName = await _partyNameRegistry.GetName(activity.PerformedBy.ActorId, cancellationToken); + + if (!string.IsNullOrWhiteSpace(activity.PerformedBy.ActorName)) + { + continue; + } + + var domainFailure = new DomainFailure(nameof(activity.PerformedBy.ActorId), $"Unable to look up name for actor id: {activity.PerformedBy.ActorId}"); + return new DomainError(domainFailure); + } + var dialog = _mapper.Map(request); dialog.ServiceResourceType = serviceResourceType; diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Create/CreateDialogCommandValidator.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Create/CreateDialogCommandValidator.cs index 6cba21c9f..3a50fee6e 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Create/CreateDialogCommandValidator.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Create/CreateDialogCommandValidator.cs @@ -4,6 +4,7 @@ using Digdir.Domain.Dialogporten.Domain; using Digdir.Domain.Dialogporten.Domain.Common; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content; using Digdir.Domain.Dialogporten.Domain.Http; using FluentValidation; @@ -258,7 +259,8 @@ public CreateDialogDialogApiActionEndpointDtoValidator() internal sealed class CreateDialogDialogActivityDtoValidator : AbstractValidator { public CreateDialogDialogActivityDtoValidator( - IValidator> localizationsValidator) + IValidator> localizationsValidator, + IValidator actorValidator) { RuleFor(x => x.Id) .NotEqual(default(Guid)); @@ -273,9 +275,32 @@ public CreateDialogDialogActivityDtoValidator( .NotEqual(x => x.Id) .When(x => x.RelatedActivityId.HasValue); RuleFor(x => x.PerformedBy) - .MaximumLength(Constants.DefaultMaxStringLength); + .NotNull() + .SetValidator(actorValidator); RuleFor(x => x.Description) .NotEmpty() .SetValidator(localizationsValidator); } } + +internal sealed class CreateDialogDialogActivityActorDtoValidator : AbstractValidator +{ + public CreateDialogDialogActivityActorDtoValidator() + { + RuleFor(x => x.ActorType) + .IsInEnum(); + + RuleFor(x => x.ActorId) + .Must((dto, value) => value is null || dto.ActorName is null) + .WithMessage("Only one of 'ActorId' or 'ActorName' can be set, but not both."); + + RuleFor(x => x.ActorType) + .Must((dto, value) => (value == DialogActorType.Values.ServiceOwner && dto.ActorId is null && dto.ActorName is null) || + (value != DialogActorType.Values.ServiceOwner && (dto.ActorId is not null || dto.ActorName is not null))) + .WithMessage("If 'ActorType' is 'ServiceOwner', both 'ActorId' and 'ActorName' must be null. Otherwise, one of them must be set."); + + RuleFor(x => x.ActorId!) + .IsValidPartyIdentifier() + .When(x => x.ActorId is not null); + } +} diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Create/CreateDialogDto.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Create/CreateDialogDto.cs index 332958113..e541da881 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Create/CreateDialogDto.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Create/CreateDialogDto.cs @@ -3,6 +3,7 @@ using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Attachments; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content; using Digdir.Domain.Dialogporten.Domain.Http; @@ -54,10 +55,17 @@ public sealed class CreateDialogDialogActivityDto public Guid? RelatedActivityId { get; set; } - public string? PerformedBy { get; set; } + public CreateDialogDialogActivityActorDto PerformedBy { get; set; } = null!; public List Description { get; set; } = []; } +public sealed class CreateDialogDialogActivityActorDto +{ + public DialogActorType.Values ActorType { get; set; } + public string? ActorName { get; set; } + public string? ActorId { get; set; } +} + public sealed class CreateDialogDialogApiActionDto { public string Action { get; set; } = null!; diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Create/MappingProfile.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Create/MappingProfile.cs index 0a0fc63cb..ec3808eec 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Create/MappingProfile.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Create/MappingProfile.cs @@ -2,6 +2,7 @@ using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Attachments; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content; @@ -39,6 +40,10 @@ public MappingProfile() .ForMember(dest => dest.Type, opt => opt.Ignore()) .ForMember(dest => dest.TypeId, opt => opt.MapFrom(src => src.Type)); + CreateMap() + .ForMember(dest => dest.ActorType, opt => opt.Ignore()) + .ForMember(dest => dest.ActorTypeId, opt => opt.MapFrom(src => src.ActorType)); + CreateMap() .ForMember(dest => dest.Type, opt => opt.Ignore()) .ForMember(dest => dest.TypeId, opt => opt.MapFrom(src => src.Type)); diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Update/MappingProfile.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Update/MappingProfile.cs index 6c2cbe8b2..eb04503e6 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Update/MappingProfile.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Update/MappingProfile.cs @@ -4,6 +4,7 @@ using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Attachments; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content; @@ -63,6 +64,10 @@ public MappingProfile() .ForMember(dest => dest.Type, opt => opt.Ignore()) .ForMember(dest => dest.TypeId, opt => opt.MapFrom(src => src.Type)); + CreateMap() + .ForMember(dest => dest.ActorType, opt => opt.Ignore()) + .ForMember(dest => dest.ActorTypeId, opt => opt.MapFrom(src => src.ActorType)); + // =========================================== // ================== Patch ================== // =========================================== @@ -72,6 +77,7 @@ public MappingProfile() .ForMember(dest => dest.Activities, opt => opt.Ignore()); CreateMap(); CreateMap(); + CreateMap(); CreateMap(); CreateMap(); CreateMap(); diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Update/UpdateDialogCommandValidator.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Update/UpdateDialogCommandValidator.cs index 0bab7d84a..8a41548de 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Update/UpdateDialogCommandValidator.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Update/UpdateDialogCommandValidator.cs @@ -4,6 +4,7 @@ using Digdir.Domain.Dialogporten.Domain; using Digdir.Domain.Dialogporten.Domain.Common; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content; using Digdir.Domain.Dialogporten.Domain.Http; using FluentValidation; @@ -257,7 +258,8 @@ public UpdateDialogDialogApiActionEndpointDtoValidator() internal sealed class UpdateDialogDialogActivityDtoValidator : AbstractValidator { public UpdateDialogDialogActivityDtoValidator( - IValidator> localizationsValidator) + IValidator> localizationsValidator, + IValidator actorValidator) { RuleFor(x => x.Id) .NotEqual(default(Guid)); @@ -272,9 +274,32 @@ public UpdateDialogDialogActivityDtoValidator( .NotEqual(x => x.Id) .When(x => x.RelatedActivityId.HasValue); RuleFor(x => x.PerformedBy) - .MaximumLength(Constants.DefaultMaxStringLength); + .NotNull() + .SetValidator(actorValidator); RuleFor(x => x.Description) .NotEmpty() .SetValidator(localizationsValidator); } } + +internal sealed class UpdateDialogDialogActivityActorDtoValidator : AbstractValidator +{ + public UpdateDialogDialogActivityActorDtoValidator() + { + RuleFor(x => x.ActorType) + .IsInEnum(); + + RuleFor(x => x.ActorId) + .Must((dto, value) => value is null || dto.ActorName is null) + .WithMessage("Only one of 'ActorId' or 'ActorName' can be set, but not both."); + + RuleFor(x => x.ActorType) + .Must((dto, value) => (value == DialogActorType.Values.ServiceOwner && dto.ActorId is null && dto.ActorName is null) || + (value != DialogActorType.Values.ServiceOwner && (dto.ActorId is not null || dto.ActorName is not null))) + .WithMessage("If 'ActorType' is 'ServiceOwner', both 'ActorId' and 'ActorName' must be null. Otherwise, one of them must be set."); + + RuleFor(x => x.ActorId!) + .IsValidPartyIdentifier() + .When(x => x.ActorId is not null); + } +} diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Update/UpdateDialogDto.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Update/UpdateDialogDto.cs index 7dc597130..42495c44a 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Update/UpdateDialogDto.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Update/UpdateDialogDto.cs @@ -3,6 +3,7 @@ using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Attachments; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content; using Digdir.Domain.Dialogporten.Domain.Http; @@ -52,10 +53,17 @@ public class UpdateDialogDialogActivityDto public Guid? RelatedActivityId { get; set; } - public string? PerformedBy { get; set; } + public UpdateDialogDialogActivityActorDto PerformedBy { get; set; } = null!; public List Description { get; set; } = []; } +public sealed class UpdateDialogDialogActivityActorDto +{ + public DialogActorType.Values ActorType { get; set; } + public string? ActorName { get; set; } + public string? ActorId { get; set; } +} + public sealed class UpdateDialogDialogApiActionDto { public Guid? Id { get; set; } diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Get/GetDialogDto.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Get/GetDialogDto.cs index 0ff5b9a44..d8009eb16 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Get/GetDialogDto.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Get/GetDialogDto.cs @@ -2,6 +2,7 @@ using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Attachments; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content; using Digdir.Domain.Dialogporten.Domain.Http; @@ -39,16 +40,20 @@ public sealed class GetDialogDto public List SeenSinceLastUpdate { get; set; } = []; } -public class GetDialogDialogSeenLogDto +public sealed class GetDialogDialogSeenLogDto { public Guid Id { get; set; } - public DateTimeOffset CreatedAt { get; set; } + public DateTimeOffset SeenAt { get; set; } - public string EndUserIdHash { get; set; } = null!; + public GetDialogDialogSeenLogActorDto SeenBy { get; set; } = null!; - public string? EndUserName { get; set; } + public bool IsViaServiceOwner { get; set; } +} - public bool? IsCurrentEndUser { get; set; } +public sealed class GetDialogDialogSeenLogActorDto +{ + public string ActorName { get; set; } = null!; + public string ActorId { get; set; } = null!; } public sealed class GetDialogContentDto @@ -72,10 +77,17 @@ public sealed class GetDialogDialogActivityDto public Guid? RelatedActivityId { get; set; } - public string? PerformedBy { get; set; } + public GetDialogDialogActivityActorDto PerformedBy { get; set; } = null!; public List Description { get; set; } = []; } +public sealed class GetDialogDialogActivityActorDto +{ + public DialogActorType.Values ActorType { get; set; } + public string? ActorName { get; set; } + public string? ActorId { get; set; } +} + public sealed class GetDialogDialogApiActionDto { public Guid Id { get; set; } diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Get/GetDialogQuery.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Get/GetDialogQuery.cs index da6c67986..2ba826e71 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Get/GetDialogQuery.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Get/GetDialogQuery.cs @@ -22,18 +22,15 @@ internal sealed class GetDialogQueryHandler : IRequestHandler Handle(GetDialogQuery request, CancellationToken cancellationToken) @@ -59,9 +56,11 @@ public async Task Handle(GetDialogQuery request, CancellationTo .Include(x => x.ApiActions.OrderBy(x => x.CreatedAt).ThenBy(x => x.Id)) .ThenInclude(x => x.Endpoints.OrderBy(x => x.CreatedAt).ThenBy(x => x.Id)) .Include(x => x.Activities).ThenInclude(x => x.Description!.Localizations) + .Include(x => x.Activities).ThenInclude(x => x.PerformedBy) .Include(x => x.SeenLog .Where(x => x.CreatedAt >= x.Dialog.UpdatedAt) .OrderBy(x => x.CreatedAt)) + .ThenInclude(x => x.SeenBy) .IgnoreQueryFilters() .AsNoTracking() // TODO: Remove when #386 is implemented .Where(x => resourceIds.Contains(x.ServiceResource)) @@ -82,8 +81,7 @@ public async Task Handle(GetDialogQuery request, CancellationTo { var logDto = _mapper.Map(log); // TODO: Set when #386 is implemented - // logDto.IsAuthenticatedUser = log.EndUserId == userPid; - logDto.EndUserIdHash = _stringHasher.Hash(log.EndUserId); + // logDto.IsCurrentEndUser = log.EndUserId == userPid; return logDto; }) .ToList(); diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Get/MappingProfile.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Get/MappingProfile.cs index 9b5d3d3bd..e762f361d 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Get/MappingProfile.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Get/MappingProfile.cs @@ -2,6 +2,7 @@ using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Attachments; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content; @@ -16,11 +17,17 @@ public MappingProfile() .ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.StatusId)) .ForMember(dest => dest.SeenSinceLastUpdate, opt => opt.Ignore()); - CreateMap(); + CreateMap() + .ForMember(dest => dest.SeenAt, opt => opt.MapFrom(src => src.CreatedAt)); + + CreateMap(); CreateMap() .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.TypeId)); + CreateMap() + .ForMember(dest => dest.ActorType, opt => opt.MapFrom(src => src.ActorTypeId)); + CreateMap(); CreateMap() diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Search/MappingProfile.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Search/MappingProfile.cs index 580cf9552..342db6f01 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Search/MappingProfile.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Search/MappingProfile.cs @@ -1,5 +1,8 @@ using AutoMapper; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Attachments; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content; namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Queries.Search; @@ -9,19 +12,35 @@ internal sealed class MappingProfile : Profile public MappingProfile() { CreateMap() + .ForMember(dest => dest.LatestActivity, opt => opt.MapFrom(src => src.Activities + .Where(activity => activity.TypeId != DialogActivityType.Values.Forwarded) + .OrderByDescending(activity => activity.CreatedAt).ThenByDescending(activity => activity.Id) + .FirstOrDefault() + )) .ForMember(dest => dest.SeenSinceLastUpdate, opt => opt.MapFrom(src => src.SeenLog .Where(x => x.CreatedAt >= x.Dialog.UpdatedAt) .OrderByDescending(x => x.CreatedAt) )) + .ForMember(dest => dest.GuiAttachmentCount, opt => opt.MapFrom(src => src.Attachments + .Count(x => x.Urls + .Any(url => url.ConsumerTypeId == DialogAttachmentUrlConsumerType.Values.Gui)))) .ForMember(dest => dest.Content, opt => opt.MapFrom(src => src.Content.Where(x => x.Type.OutputInList))) .ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.StatusId)); + CreateMap() + .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.TypeId)); + CreateMap() - .ForMember(dest => dest.EndUserIdHash, opt => opt.MapFrom(src => src.EndUserId)); + .ForMember(dest => dest.SeenAt, opt => opt.MapFrom(src => src.CreatedAt)); - CreateMap(); + CreateMap() + .ForMember(dest => dest.ActorId, opt => opt.MapFrom(src => src.ActorId)); - CreateMap() + CreateMap() .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.TypeId)); + + CreateMap() + .ForMember(dest => dest.ActorType, opt => opt.MapFrom(src => src.ActorTypeId)) + .ForMember(dest => dest.ActorId, opt => opt.MapFrom(src => src.ActorId)); } } diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Search/SearchDialogDto.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Search/SearchDialogDto.cs index dc94f12b2..af640f31a 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Search/SearchDialogDto.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Search/SearchDialogDto.cs @@ -1,5 +1,7 @@ using Digdir.Domain.Dialogporten.Application.Features.V1.Common.Localizations; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content; namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Queries.Search; @@ -13,6 +15,7 @@ public sealed class SearchDialogDto public string Party { get; set; } = null!; public string? EndUserId { get; set; } public int? Progress { get; set; } + public int? GuiAttachmentCount { get; set; } public string? ExtendedStatus { get; set; } public DateTimeOffset CreatedAt { get; set; } public DateTimeOffset UpdatedAt { get; set; } @@ -21,19 +24,27 @@ public sealed class SearchDialogDto public DialogStatus.Values Status { get; set; } + public SearchDialogDialogActivityDto? LatestActivity { get; set; } + public List Content { get; set; } = []; public List SeenSinceLastUpdate { get; set; } = []; } -public class SearchDialogDialogSeenLogDto +public sealed class SearchDialogDialogSeenLogDto { public Guid Id { get; set; } - public DateTimeOffset CreatedAt { get; set; } + public DateTimeOffset SeenAt { get; set; } - public string EndUserIdHash { get; set; } = null!; + public SearchDialogDialogSeenLogActorDto SeenBy { get; set; } = null!; + + public bool? IsViaServiceOwner { get; set; } + public bool IsCurrentEndUser { get; set; } +} - public string? EndUserName { get; set; } - public bool? IsCurrentEndUser { get; set; } +public sealed class SearchDialogDialogSeenLogActorDto +{ + public string ActorName { get; set; } = null!; + public string ActorId { get; set; } = null!; } public sealed class SearchDialogSearchTagDto @@ -46,3 +57,24 @@ public sealed class SearchDialogContentDto public DialogContentType.Values Type { get; set; } public List Value { get; set; } = []; } + +public sealed class SearchDialogDialogActivityDto +{ + public Guid Id { get; set; } + public DateTimeOffset? CreatedAt { get; set; } + public Uri? ExtendedType { get; set; } + + public DialogActivityType.Values Type { get; set; } + + public Guid? RelatedActivityId { get; set; } + + public SearchDialogDialogActivityActorDto PerformedBy { get; set; } = null!; + public List Description { get; set; } = []; +} + +public sealed class SearchDialogDialogActivityActorDto +{ + public DialogActorType.Values ActorType { get; set; } + public string? ActorName { get; set; } + public string? ActorId { get; set; } +} diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Search/SearchDialogQuery.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Search/SearchDialogQuery.cs index 14b7cec05..be3241622 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Search/SearchDialogQuery.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Search/SearchDialogQuery.cs @@ -10,6 +10,7 @@ using Digdir.Domain.Dialogporten.Application.Externals.AltinnAuthorization; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; using Digdir.Domain.Dialogporten.Domain.Localizations; +using Digdir.Domain.Dialogporten.Domain.Parties; using MediatR; using Microsoft.EntityFrameworkCore; using OneOf; @@ -122,20 +123,17 @@ internal sealed class SearchDialogQueryHandler : IRequestHandler Handle(SearchDialogQuery request, CancellationToken cancellationToken) @@ -180,14 +178,12 @@ public async Task Handle(SearchDialogQuery request, Cancella .ProjectTo(_mapper.ConfigurationProvider) .ToPaginatedListAsync(request, cancellationToken: cancellationToken); - foreach (var seenRecord in paginatedList.Items.SelectMany(x => x.SeenSinceLastUpdate)) + if (request.EndUserId is not null) { - if (request.EndUserId is not null) + foreach (var seenRecord in paginatedList.Items.SelectMany(x => x.SeenSinceLastUpdate)) { - seenRecord.IsCurrentEndUser = seenRecord.EndUserIdHash == request.EndUserId; + seenRecord.IsCurrentEndUser = seenRecord.SeenBy.ActorId == request.EndUserId; } - - seenRecord.EndUserIdHash = _stringHasher.Hash(seenRecord.EndUserIdHash); } return paginatedList; diff --git a/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/Activities/DialogActivity.cs b/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/Activities/DialogActivity.cs index 9784f0d42..71779671d 100644 --- a/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/Activities/DialogActivity.cs +++ b/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/Activities/DialogActivity.cs @@ -1,4 +1,5 @@ -using Digdir.Domain.Dialogporten.Domain.Dialogs.Events.Activities; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Events.Activities; using Digdir.Domain.Dialogporten.Domain.Localizations; using Digdir.Library.Entity.Abstractions.Features.Aggregate; using Digdir.Library.Entity.Abstractions.Features.EventPublisher; @@ -27,7 +28,7 @@ public class DialogActivity : IImmutableEntity, IAggregateCreatedHandler, IEvent public DialogActivityDescription? Description { get; set; } [AggregateChild] - public string? PerformedBy { get; set; } + public DialogActor PerformedBy { get; set; } = null!; public List RelatedActivities { get; set; } = []; diff --git a/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/Actors/DialogActor.cs b/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/Actors/DialogActor.cs new file mode 100644 index 000000000..3391b3072 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/Actors/DialogActor.cs @@ -0,0 +1,12 @@ +using Digdir.Library.Entity.Abstractions.Features.Identifiable; + +namespace Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors; + +public class DialogActor : IIdentifiableEntity +{ + public Guid Id { get; set; } + public string? ActorId { get; set; } + public DialogActorType ActorType { get; set; } = null!; + public DialogActorType.Values ActorTypeId { get; set; } + public string? ActorName { get; set; } = null!; +} diff --git a/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/Actors/DialogActorType.cs b/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/Actors/DialogActorType.cs new file mode 100644 index 000000000..6fed58389 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/Actors/DialogActorType.cs @@ -0,0 +1,15 @@ +using Digdir.Library.Entity.Abstractions.Features.Lookup; + +namespace Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors; + +public class DialogActorType : AbstractLookupEntity +{ + public DialogActorType(Values id) : base(id) { } + public override DialogActorType MapValue(Values id) => new(id); + + public enum Values + { + PartyRepresentative = 1, + ServiceOwner = 2 + } +} diff --git a/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/DialogEntity.cs b/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/DialogEntity.cs index 2ca9acd1a..9ac8adff3 100644 --- a/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/DialogEntity.cs +++ b/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/DialogEntity.cs @@ -1,5 +1,6 @@ using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Attachments; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content; using Digdir.Domain.Dialogporten.Domain.Dialogs.Events; @@ -84,7 +85,7 @@ public void OnDelete(AggregateNode self, DateTimeOffset utcNow) public void UpdateSeenAt(string endUserId, DialogUserType.Values userTypeId, string? endUserName) { var lastSeenAt = SeenLog - .Where(x => x.EndUserId == endUserId) + .Where(x => x.SeenBy.ActorId == endUserId) .MaxBy(x => x.CreatedAt) ?.CreatedAt ?? DateTimeOffset.MinValue; @@ -96,9 +97,13 @@ public void UpdateSeenAt(string endUserId, DialogUserType.Values userTypeId, str SeenLog.Add(new() { - EndUserId = endUserId, EndUserTypeId = userTypeId, - EndUserName = endUserName + SeenBy = new DialogActor + { + ActorTypeId = DialogActorType.Values.PartyRepresentative, + ActorId = endUserId, + ActorName = endUserName + } }); _domainEvents.Add(new DialogSeenDomainEvent(Id, ServiceResource, Party)); diff --git a/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/DialogSeenLog.cs b/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/DialogSeenLog.cs index bb4fcab49..65508deab 100644 --- a/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/DialogSeenLog.cs +++ b/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/DialogSeenLog.cs @@ -1,5 +1,4 @@ -using Digdir.Domain.Dialogporten.Domain.Localizations; -using Digdir.Library.Entity.Abstractions.Features.Aggregate; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors; using Digdir.Library.Entity.Abstractions.Features.Immutable; namespace Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; @@ -9,12 +8,9 @@ public class DialogSeenLog : IImmutableEntity public Guid Id { get; set; } public DateTimeOffset CreatedAt { get; set; } - public string EndUserId { get; set; } = null!; + public DialogActor SeenBy { get; set; } = null!; - public string? EndUserName { get; set; } - - [AggregateChild] - public DialogSeenLogVia? Via { get; set; } + public bool? IsViaServiceOwner { get; set; } // === Dependent relationships === public Guid DialogId { get; set; } @@ -23,9 +19,3 @@ public class DialogSeenLog : IImmutableEntity public DialogUserType.Values EndUserTypeId { get; set; } public DialogUserType EndUserType { get; set; } = null!; } - -public class DialogSeenLogVia : LocalizationSet -{ - public DialogSeenLog DialogSeenLog { get; set; } = null!; - public Guid DialogSeenLogId { get; set; } -} diff --git a/src/Digdir.Domain.Dialogporten.Domain/Parties/NorwegianPersonIdentifier.cs b/src/Digdir.Domain.Dialogporten.Domain/Parties/NorwegianPersonIdentifier.cs index 645b5703a..54c32c518 100644 --- a/src/Digdir.Domain.Dialogporten.Domain/Parties/NorwegianPersonIdentifier.cs +++ b/src/Digdir.Domain.Dialogporten.Domain/Parties/NorwegianPersonIdentifier.cs @@ -10,8 +10,11 @@ public class NorwegianPersonIdentifier : IPartyIdentifier private static readonly int[] SocialSecurityNumberWeights1 = [3, 7, 6, 1, 8, 9, 4, 5, 2, 1]; private static readonly int[] SocialSecurityNumberWeights2 = [5, 4, 3, 2, 7, 6, 5, 4, 3, 2, 1]; - public static string Prefix => "urn:altinn:person:identifier-no"; + private static string PersonPrefix => "urn:altinn:person:identifier-"; + public static string Prefix => PersonPrefix + "no"; public static string PrefixWithSeparator => Prefix + PartyIdentifier.Separator; + public static string HashPrefix => PersonPrefix + "ephemeral"; + public static string HashPrefixWithSeparator => HashPrefix + PartyIdentifier.Separator; public string FullId { get; } public string Id { get; } diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/Common/MappingProfile.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/Common/MappingProfile.cs index 8924c36d8..756771323 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/Common/MappingProfile.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/Common/MappingProfile.cs @@ -12,7 +12,10 @@ public MappingProfile() CreateMap(); CreateMap(); + CreateMap(); + CreateMap(); + CreateMap(); CreateMap() .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.Type)); @@ -21,7 +24,12 @@ public MappingProfile() CreateMap() .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.Type)); + CreateMap() + .ForMember(dest => dest.ActorType, opt => opt.MapFrom(src => src.ActorType)); + CreateMap() .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.Type)); + CreateMap() + .ForMember(dest => dest.ActorType, opt => opt.MapFrom(src => src.ActorType)); } } diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/Common/ObjectTypes.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/Common/ObjectTypes.cs index 313dc1d45..dfc761928 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/Common/ObjectTypes.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/Common/ObjectTypes.cs @@ -28,9 +28,7 @@ public sealed class SeenLog public Guid Id { get; set; } public DateTimeOffset SeenAt { get; set; } - public string EndUserIdHash { get; set; } = null!; - - public string? EndUserName { get; set; } + public Actor SeenBy { get; set; } = null!; public bool IsCurrentEndUser { get; set; } } @@ -45,10 +43,24 @@ public sealed class Activity public Guid? RelatedActivityId { get; set; } - public string? PerformedBy { get; set; } + public Actor PerformedBy { get; set; } = null!; + public List Description { get; set; } = []; } +public sealed class Actor +{ + public ActorType? ActorType { get; set; } + public string? ActorId { get; set; } + public string? ActorName { get; set; } +} + +public enum ActorType +{ + PartyRepresentative = 1, + ServiceOwner = 2 +} + public enum ActivityType { [GraphQLDescription("Refers to a submission made by a party that has been received by the service provider.")] diff --git a/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/Authorization/DecisionRequestHelper.cs b/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/Authorization/DecisionRequestHelper.cs index 3b744d849..36a26cdce 100644 --- a/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/Authorization/DecisionRequestHelper.cs +++ b/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/Authorization/DecisionRequestHelper.cs @@ -1,6 +1,5 @@ using Altinn.Authorization.ABAC.Xacml.JsonProfile; using System.Security.Claims; -using System.Text.Json; using Digdir.Domain.Dialogporten.Application.Common.Authorization; using Digdir.Domain.Dialogporten.Application.Common.Extensions; using Digdir.Domain.Dialogporten.Application.Externals.AltinnAuthorization; diff --git a/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/NameRegistry/PersonNameRegistryClient.cs b/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/NameRegistry/PartyNameRegistryClient.cs similarity index 53% rename from src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/NameRegistry/PersonNameRegistryClient.cs rename to src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/NameRegistry/PartyNameRegistryClient.cs index 804fa9497..f3fb5d52d 100644 --- a/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/NameRegistry/PersonNameRegistryClient.cs +++ b/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/NameRegistry/PartyNameRegistryClient.cs @@ -1,11 +1,12 @@ using System.Text.Json; using System.Text.Json.Serialization; using Digdir.Domain.Dialogporten.Application.Externals; +using Digdir.Domain.Dialogporten.Domain.Parties; using ZiggyCreatures.Caching.Fusion; namespace Digdir.Domain.Dialogporten.Infrastructure.Altinn.NameRegistry; -internal class PersonNameRegistryClient : IPersonNameRegistry +internal class PartyNameRegistryClient : IPartyNameRegistry { private readonly IFusionCache _cache; private readonly HttpClient _client; @@ -16,29 +17,41 @@ internal class PersonNameRegistryClient : IPersonNameRegistry DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault }; - public PersonNameRegistryClient(HttpClient client, IFusionCacheProvider cacheProvider) + public PartyNameRegistryClient(HttpClient client, IFusionCacheProvider cacheProvider) { _client = client ?? throw new ArgumentNullException(nameof(client)); _cache = cacheProvider.GetCache(nameof(NameRegistry)) ?? throw new ArgumentNullException(nameof(cacheProvider)); } - public async Task GetName(string personalIdentificationNumber, CancellationToken cancellationToken) + public async Task GetName(string externalIdWithPrefix, CancellationToken cancellationToken) { return await _cache.GetOrSetAsync( - $"Name_{personalIdentificationNumber}", - ct => GetNameFromRegister(personalIdentificationNumber, ct), + $"Name_{externalIdWithPrefix}", + ct => GetNameFromRegister(externalIdWithPrefix, ct), token: cancellationToken); } - private async Task GetNameFromRegister(string personalIdentificationNumber, CancellationToken cancellationToken) + private async Task GetNameFromRegister(string externalIdWithPrefix, CancellationToken cancellationToken) { const string apiUrl = "register/api/v1/parties/nameslookup"; + var nameLookupParty = new NameLookupParty(); + if (NorwegianPersonIdentifier.TryParse(externalIdWithPrefix, out var personIdentifier)) + { + nameLookupParty.Ssn = personIdentifier.Id; + } + else if (NorwegianOrganizationIdentifier.TryParse(externalIdWithPrefix, out var organizationIdentifier)) + { + nameLookupParty.OrgNo = organizationIdentifier.Id; + } + else + { + return null; + } + var nameLookup = new NameLookup { - Parties = [ - new() { Ssn = personalIdentificationNumber } - ] + Parties = [nameLookupParty] }; var nameLookupResult = await _client.PostAsJsonEnsuredAsync( @@ -52,17 +65,18 @@ public PersonNameRegistryClient(HttpClient client, IFusionCacheProvider cachePro private sealed class NameLookup { - public List Parties { get; set; } = null!; + public List Parties { get; set; } = null!; } private sealed class NameLookupResult { - public List PartyNames { get; set; } = null!; + public List PartyNames { get; set; } = null!; } - private sealed class NameLookupSsn + private sealed class NameLookupParty { public string Ssn { get; set; } = null!; + public string OrgNo { get; set; } = null!; public string? Name { get; set; } } } diff --git a/src/Digdir.Domain.Dialogporten.Infrastructure/InfrastructureExtensions.cs b/src/Digdir.Domain.Dialogporten.Infrastructure/InfrastructureExtensions.cs index 87ea2fa3f..1f2b19985 100644 --- a/src/Digdir.Domain.Dialogporten.Infrastructure/InfrastructureExtensions.cs +++ b/src/Digdir.Domain.Dialogporten.Infrastructure/InfrastructureExtensions.cs @@ -153,7 +153,7 @@ public static IServiceCollection AddInfrastructure(this IServiceCollection servi client.BaseAddress = services.GetRequiredService>().Value.AltinnCdn.BaseUri) .AddPolicyHandlerFromRegistry(PollyPolicy.DefaultHttpRetryPolicy); - services.AddMaskinportenHttpClient( + services.AddMaskinportenHttpClient( infrastructureConfigurationSection, x => x.ClientSettings.ExhangeToAltinnToken = true) .ConfigureHttpClient((services, client) => diff --git a/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/DialogDbContext.cs b/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/DialogDbContext.cs index 58f02c296..344c24458 100644 --- a/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/DialogDbContext.cs +++ b/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/DialogDbContext.cs @@ -12,6 +12,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using System.Linq.Expressions; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Attachments; namespace Digdir.Domain.Dialogporten.Infrastructure.Persistence; @@ -33,6 +34,7 @@ public DialogDbContext(DbContextOptions options) : base(options public DbSet DialogSeenLog => Set(); public DbSet DialogContentTypes => Set(); public DbSet DialogContent => Set(); + public DbSet DialogActors => Set(); public DbSet OutboxMessages => Set(); public DbSet OutboxMessageConsumers => Set(); diff --git a/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Migrations/20240720163626_AddActor.Designer.cs b/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Migrations/20240720163626_AddActor.Designer.cs new file mode 100644 index 000000000..a78cfd1c5 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Migrations/20240720163626_AddActor.Designer.cs @@ -0,0 +1,1388 @@ +// +using System; +using Digdir.Domain.Dialogporten.Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Digdir.Domain.Dialogporten.Infrastructure.Persistence.Migrations +{ + [DbContext(typeof(DialogDbContext))] + [Migration("20240720163626_AddActor")] + partial class AddActor + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions.DialogApiAction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AuthorizationAttribute") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.Property("DialogId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.HasKey("Id"); + + b.HasIndex("DialogId"); + + b.ToTable("DialogApiAction"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions.DialogApiActionEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("ActionId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.Property("Deprecated") + .HasColumnType("boolean"); + + b.Property("DocumentationUrl") + .HasMaxLength(1023) + .HasColumnType("character varying(1023)"); + + b.Property("HttpMethodId") + .HasColumnType("integer"); + + b.Property("RequestSchema") + .HasMaxLength(1023) + .HasColumnType("character varying(1023)"); + + b.Property("ResponseSchema") + .HasMaxLength(1023) + .HasColumnType("character varying(1023)"); + + b.Property("SunsetAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(1023) + .HasColumnType("character varying(1023)"); + + b.Property("Version") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("ActionId"); + + b.HasIndex("HttpMethodId"); + + b.ToTable("DialogApiActionEndpoint"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions.DialogGuiAction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AuthorizationAttribute") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.Property("DialogId") + .HasColumnType("uuid"); + + b.Property("HttpMethodId") + .HasColumnType("integer"); + + b.Property("IsDeleteDialogAction") + .HasColumnType("boolean"); + + b.Property("PriorityId") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(1023) + .HasColumnType("character varying(1023)"); + + b.HasKey("Id"); + + b.HasIndex("DialogId"); + + b.HasIndex("HttpMethodId"); + + b.HasIndex("PriorityId"); + + b.ToTable("DialogGuiAction"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions.DialogGuiActionPriority", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.ToTable("DialogGuiActionPriority"); + + b.HasData( + new + { + Id = 1, + Name = "Primary" + }, + new + { + Id = 2, + Name = "Secondary" + }, + new + { + Id = 3, + Name = "Tertiary" + }); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities.DialogActivity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.Property("DialogId") + .HasColumnType("uuid"); + + b.Property("ExtendedType") + .HasMaxLength(1023) + .HasColumnType("character varying(1023)"); + + b.Property("PerformedById") + .HasColumnType("uuid"); + + b.Property("RelatedActivityId") + .HasColumnType("uuid"); + + b.Property("TypeId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("DialogId"); + + b.HasIndex("PerformedById"); + + b.HasIndex("RelatedActivityId"); + + b.HasIndex("TypeId"); + + b.ToTable("DialogActivity"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities.DialogActivityType", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.ToTable("DialogActivityType"); + + b.HasData( + new + { + Id = 1, + Name = "Submission" + }, + new + { + Id = 2, + Name = "Feedback" + }, + new + { + Id = 3, + Name = "Information" + }, + new + { + Id = 4, + Name = "Error" + }, + new + { + Id = 5, + Name = "Closed" + }, + new + { + Id = 7, + Name = "Forwarded" + }); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors.DialogActor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("ActorId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ActorName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ActorTypeId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ActorTypeId"); + + b.ToTable("DialogActor"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors.DialogActorType", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.ToTable("DialogActorType"); + + b.HasData( + new + { + Id = 1, + Name = "PartyRepresentative" + }, + new + { + Id = 2, + Name = "ServiceOwner" + }); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Attachments.DialogAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.Property("DialogId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.HasKey("Id"); + + b.HasIndex("DialogId"); + + b.ToTable("DialogAttachment"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Attachments.DialogAttachmentUrl", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("ConsumerTypeId") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.Property("DialogAttachmentId") + .HasColumnType("uuid"); + + b.Property("MediaType") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(1023) + .HasColumnType("character varying(1023)"); + + b.HasKey("Id"); + + b.HasIndex("ConsumerTypeId"); + + b.HasIndex("DialogAttachmentId"); + + b.ToTable("DialogAttachmentUrl"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Attachments.DialogAttachmentUrlConsumerType", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.ToTable("DialogAttachmentUrlConsumerType"); + + b.HasData( + new + { + Id = 1, + Name = "Gui" + }, + new + { + Id = 2, + Name = "Api" + }); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content.DialogContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.Property("DialogId") + .HasColumnType("uuid"); + + b.Property("MediaType") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TypeId") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.HasKey("Id"); + + b.HasIndex("TypeId"); + + b.HasIndex("DialogId", "TypeId") + .IsUnique(); + + b.ToTable("DialogContent"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content.DialogContentType", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("AllowedMediaTypes") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("MaxLength") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("OutputInList") + .HasColumnType("boolean"); + + b.Property("Required") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("DialogContentType"); + + b.HasData( + new + { + Id = 1, + AllowedMediaTypes = new[] { "text/plain" }, + MaxLength = 255, + Name = "Title", + OutputInList = true, + Required = true + }, + new + { + Id = 2, + AllowedMediaTypes = new[] { "text/plain" }, + MaxLength = 255, + Name = "SenderName", + OutputInList = true, + Required = false + }, + new + { + Id = 3, + AllowedMediaTypes = new[] { "text/plain" }, + MaxLength = 255, + Name = "Summary", + OutputInList = true, + Required = true + }, + new + { + Id = 4, + AllowedMediaTypes = new[] { "text/html", "text/plain", "text/markdown" }, + MaxLength = 1023, + Name = "AdditionalInfo", + OutputInList = false, + Required = false + }, + new + { + Id = 5, + AllowedMediaTypes = new[] { "text/plain" }, + MaxLength = 20, + Name = "ExtendedStatus", + OutputInList = true, + Required = false + }, + new + { + Id = 6, + AllowedMediaTypes = new[] { "application/vnd.dialogporten.frontchannelembed+json;type=markdown" }, + MaxLength = 1023, + Name = "MainContentReference", + OutputInList = false, + Required = false + }); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.Property("Deleted") + .HasColumnType("boolean"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DueAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExtendedStatus") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ExternalReference") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Org") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .UseCollation("C"); + + b.Property("Party") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .UseCollation("C"); + + b.Property("Progress") + .HasColumnType("integer"); + + b.Property("Revision") + .IsConcurrencyToken() + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("ServiceResource") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .UseCollation("C"); + + b.Property("ServiceResourceType") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("StatusId") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.Property("VisibleFrom") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DueAt"); + + b.HasIndex("Org"); + + b.HasIndex("Party"); + + b.HasIndex("ServiceResource"); + + b.HasIndex("StatusId"); + + b.HasIndex("UpdatedAt"); + + b.ToTable("Dialog", (string)null); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogSearchTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.Property("DialogId") + .HasColumnType("uuid"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.HasKey("Id"); + + b.HasIndex("DialogId", "Value") + .IsUnique(); + + b.ToTable("DialogSearchTag"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogSeenLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.Property("DialogId") + .HasColumnType("uuid"); + + b.Property("EndUserTypeId") + .HasColumnType("integer"); + + b.Property("IsViaServiceOwner") + .HasColumnType("boolean"); + + b.Property("SeenById") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("DialogId"); + + b.HasIndex("EndUserTypeId"); + + b.HasIndex("SeenById"); + + b.ToTable("DialogSeenLog"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogStatus", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.ToTable("DialogStatus"); + + b.HasData( + new + { + Id = 1, + Name = "New" + }, + new + { + Id = 2, + Name = "InProgress" + }, + new + { + Id = 3, + Name = "Waiting" + }, + new + { + Id = 4, + Name = "Signing" + }, + new + { + Id = 5, + Name = "Cancelled" + }, + new + { + Id = 6, + Name = "Completed" + }); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogUserType", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.ToTable("DialogUserType"); + + b.HasData( + new + { + Id = 0, + Name = "Unknown" + }, + new + { + Id = 1, + Name = "Person" + }, + new + { + Id = 2, + Name = "LegacySystemUser" + }, + new + { + Id = 3, + Name = "SystemUser" + }, + new + { + Id = 4, + Name = "ServiceOwner" + }, + new + { + Id = 5, + Name = "ServiceOwnerOnBehalfOfPerson" + }); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Http.HttpVerb", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.ToTable("HttpVerb"); + + b.HasData( + new + { + Id = 1, + Name = "GET" + }, + new + { + Id = 2, + Name = "POST" + }, + new + { + Id = 3, + Name = "PUT" + }, + new + { + Id = 4, + Name = "PATCH" + }, + new + { + Id = 5, + Name = "DELETE" + }, + new + { + Id = 6, + Name = "HEAD" + }, + new + { + Id = 7, + Name = "OPTIONS" + }, + new + { + Id = 8, + Name = "TRACE" + }, + new + { + Id = 9, + Name = "CONNECT" + }); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Localizations.Localization", b => + { + b.Property("LocalizationSetId") + .HasColumnType("uuid"); + + b.Property("LanguageCode") + .HasMaxLength(15) + .HasColumnType("character varying(15)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(4095) + .HasColumnType("character varying(4095)"); + + b.HasKey("LocalizationSetId", "LanguageCode"); + + b.ToTable("Localization"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Localizations.LocalizationSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.ToTable("LocalizationSet"); + + b.HasDiscriminator().HasValue("LocalizationSet"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Outboxes.OutboxMessage", b => + { + b.Property("EventId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CorrelationId") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.Property("EventPayload") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("EventType") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("EventId"); + + b.ToTable("OutboxMessage"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Outboxes.OutboxMessageConsumer", b => + { + b.Property("EventId") + .HasColumnType("uuid"); + + b.Property("ConsumerName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("EventId", "ConsumerName"); + + b.ToTable("OutboxMessageConsumer"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions.DialogGuiActionPrompt", b => + { + b.HasBaseType("Digdir.Domain.Dialogporten.Domain.Localizations.LocalizationSet"); + + b.Property("GuiActionId") + .HasColumnType("uuid"); + + b.HasIndex("GuiActionId") + .IsUnique(); + + b.ToTable("LocalizationSet", t => + { + t.Property("GuiActionId") + .HasColumnName("DialogGuiActionPrompt_GuiActionId"); + }); + + b.HasDiscriminator().HasValue("DialogGuiActionPrompt"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions.DialogGuiActionTitle", b => + { + b.HasBaseType("Digdir.Domain.Dialogporten.Domain.Localizations.LocalizationSet"); + + b.Property("GuiActionId") + .HasColumnType("uuid"); + + b.HasIndex("GuiActionId") + .IsUnique(); + + b.HasDiscriminator().HasValue("DialogGuiActionTitle"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities.DialogActivityDescription", b => + { + b.HasBaseType("Digdir.Domain.Dialogporten.Domain.Localizations.LocalizationSet"); + + b.Property("ActivityId") + .HasColumnType("uuid"); + + b.HasIndex("ActivityId") + .IsUnique(); + + b.HasDiscriminator().HasValue("DialogActivityDescription"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Attachments.AttachmentDisplayName", b => + { + b.HasBaseType("Digdir.Domain.Dialogporten.Domain.Localizations.LocalizationSet"); + + b.Property("AttachmentId") + .HasColumnType("uuid"); + + b.HasIndex("AttachmentId") + .IsUnique(); + + b.HasDiscriminator().HasValue("AttachmentDisplayName"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content.DialogContentValue", b => + { + b.HasBaseType("Digdir.Domain.Dialogporten.Domain.Localizations.LocalizationSet"); + + b.Property("DialogContentId") + .HasColumnType("uuid"); + + b.HasIndex("DialogContentId") + .IsUnique(); + + b.HasDiscriminator().HasValue("DialogContentValue"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions.DialogApiAction", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogEntity", "Dialog") + .WithMany("ApiActions") + .HasForeignKey("DialogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Dialog"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions.DialogApiActionEndpoint", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions.DialogApiAction", "Action") + .WithMany("Endpoints") + .HasForeignKey("ActionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Digdir.Domain.Dialogporten.Domain.Http.HttpVerb", "HttpMethod") + .WithMany() + .HasForeignKey("HttpMethodId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Action"); + + b.Navigation("HttpMethod"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions.DialogGuiAction", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogEntity", "Dialog") + .WithMany("GuiActions") + .HasForeignKey("DialogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Digdir.Domain.Dialogporten.Domain.Http.HttpVerb", "HttpMethod") + .WithMany() + .HasForeignKey("HttpMethodId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions.DialogGuiActionPriority", "Priority") + .WithMany() + .HasForeignKey("PriorityId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Dialog"); + + b.Navigation("HttpMethod"); + + b.Navigation("Priority"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities.DialogActivity", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogEntity", "Dialog") + .WithMany("Activities") + .HasForeignKey("DialogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors.DialogActor", "PerformedBy") + .WithMany() + .HasForeignKey("PerformedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities.DialogActivity", "RelatedActivity") + .WithMany("RelatedActivities") + .HasForeignKey("RelatedActivityId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities.DialogActivityType", "Type") + .WithMany() + .HasForeignKey("TypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Dialog"); + + b.Navigation("PerformedBy"); + + b.Navigation("RelatedActivity"); + + b.Navigation("Type"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors.DialogActor", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors.DialogActorType", "ActorType") + .WithMany() + .HasForeignKey("ActorTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ActorType"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Attachments.DialogAttachment", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogEntity", "Dialog") + .WithMany("Attachments") + .HasForeignKey("DialogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Dialog"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Attachments.DialogAttachmentUrl", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Attachments.DialogAttachmentUrlConsumerType", "ConsumerType") + .WithMany() + .HasForeignKey("ConsumerTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Attachments.DialogAttachment", "DialogAttachment") + .WithMany("Urls") + .HasForeignKey("DialogAttachmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ConsumerType"); + + b.Navigation("DialogAttachment"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content.DialogContent", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogEntity", "Dialog") + .WithMany("Content") + .HasForeignKey("DialogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content.DialogContentType", "Type") + .WithMany() + .HasForeignKey("TypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Dialog"); + + b.Navigation("Type"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogEntity", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogStatus", "Status") + .WithMany() + .HasForeignKey("StatusId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Status"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogSearchTag", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogEntity", "Dialog") + .WithMany("SearchTags") + .HasForeignKey("DialogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Dialog"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogSeenLog", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogEntity", "Dialog") + .WithMany("SeenLog") + .HasForeignKey("DialogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogUserType", "EndUserType") + .WithMany() + .HasForeignKey("EndUserTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors.DialogActor", "SeenBy") + .WithMany() + .HasForeignKey("SeenById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Dialog"); + + b.Navigation("EndUserType"); + + b.Navigation("SeenBy"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Localizations.Localization", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Localizations.LocalizationSet", "LocalizationSet") + .WithMany("Localizations") + .HasForeignKey("LocalizationSetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LocalizationSet"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Outboxes.OutboxMessageConsumer", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Outboxes.OutboxMessage", "OutboxMessage") + .WithMany("OutboxMessageConsumers") + .HasForeignKey("EventId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OutboxMessage"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions.DialogGuiActionPrompt", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions.DialogGuiAction", "GuiAction") + .WithOne("Prompt") + .HasForeignKey("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions.DialogGuiActionPrompt", "GuiActionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GuiAction"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions.DialogGuiActionTitle", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions.DialogGuiAction", "GuiAction") + .WithOne("Title") + .HasForeignKey("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions.DialogGuiActionTitle", "GuiActionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GuiAction"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities.DialogActivityDescription", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities.DialogActivity", "Activity") + .WithOne("Description") + .HasForeignKey("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities.DialogActivityDescription", "ActivityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Activity"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Attachments.AttachmentDisplayName", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Attachments.DialogAttachment", "Attachment") + .WithOne("DisplayName") + .HasForeignKey("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Attachments.AttachmentDisplayName", "AttachmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Attachment"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content.DialogContentValue", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content.DialogContent", "DialogContent") + .WithOne("Value") + .HasForeignKey("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content.DialogContentValue", "DialogContentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DialogContent"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions.DialogApiAction", b => + { + b.Navigation("Endpoints"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions.DialogGuiAction", b => + { + b.Navigation("Prompt"); + + b.Navigation("Title"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities.DialogActivity", b => + { + b.Navigation("Description"); + + b.Navigation("RelatedActivities"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Attachments.DialogAttachment", b => + { + b.Navigation("DisplayName"); + + b.Navigation("Urls"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content.DialogContent", b => + { + b.Navigation("Value") + .IsRequired(); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogEntity", b => + { + b.Navigation("Activities"); + + b.Navigation("ApiActions"); + + b.Navigation("Attachments"); + + b.Navigation("Content"); + + b.Navigation("GuiActions"); + + b.Navigation("SearchTags"); + + b.Navigation("SeenLog"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Localizations.LocalizationSet", b => + { + b.Navigation("Localizations"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Outboxes.OutboxMessage", b => + { + b.Navigation("OutboxMessageConsumers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Migrations/20240720163626_AddActor.cs b/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Migrations/20240720163626_AddActor.cs new file mode 100644 index 000000000..11061c86a --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Migrations/20240720163626_AddActor.cs @@ -0,0 +1,218 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace Digdir.Domain.Dialogporten.Infrastructure.Persistence.Migrations +{ + /// + public partial class AddActor : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql(""" + DELETE FROM public."DialogActivity" + """); + migrationBuilder.Sql(""" + DELETE FROM public."DialogSeenLog" + """); + + migrationBuilder.DropForeignKey( + name: "FK_LocalizationSet_DialogSeenLog_DialogSeenLogId", + table: "LocalizationSet"); + + migrationBuilder.DropIndex( + name: "IX_LocalizationSet_DialogSeenLogId", + table: "LocalizationSet"); + + migrationBuilder.DropColumn( + name: "DialogSeenLogId", + table: "LocalizationSet"); + + migrationBuilder.DropColumn( + name: "EndUserId", + table: "DialogSeenLog"); + + migrationBuilder.DropColumn( + name: "EndUserName", + table: "DialogSeenLog"); + + migrationBuilder.DropColumn( + name: "PerformedBy", + table: "DialogActivity"); + + migrationBuilder.AddColumn( + name: "IsViaServiceOwner", + table: "DialogSeenLog", + type: "boolean", + nullable: true); + + migrationBuilder.AddColumn( + name: "SeenById", + table: "DialogSeenLog", + type: "uuid", + nullable: false); + + migrationBuilder.AddColumn( + name: "PerformedById", + table: "DialogActivity", + type: "uuid", + nullable: false); + + migrationBuilder.CreateTable( + name: "DialogActorType", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false), + Name = table.Column(type: "character varying(255)", maxLength: 255, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DialogActorType", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "DialogActor", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), + ActorId = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), + ActorTypeId = table.Column(type: "integer", nullable: false), + ActorName = table.Column(type: "character varying(255)", maxLength: 255, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_DialogActor", x => x.Id); + table.ForeignKey( + name: "FK_DialogActor_DialogActorType_ActorTypeId", + column: x => x.ActorTypeId, + principalTable: "DialogActorType", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.InsertData( + table: "DialogActorType", + columns: new[] { "Id", "Name" }, + values: new object[,] + { + { 1, "PartyRepresentative" }, + { 2, "ServiceOwner" } + }); + + migrationBuilder.CreateIndex( + name: "IX_DialogSeenLog_SeenById", + table: "DialogSeenLog", + column: "SeenById"); + + migrationBuilder.CreateIndex( + name: "IX_DialogActivity_PerformedById", + table: "DialogActivity", + column: "PerformedById"); + + migrationBuilder.CreateIndex( + name: "IX_DialogActor_ActorTypeId", + table: "DialogActor", + column: "ActorTypeId"); + + migrationBuilder.AddForeignKey( + name: "FK_DialogActivity_DialogActor_PerformedById", + table: "DialogActivity", + column: "PerformedById", + principalTable: "DialogActor", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_DialogSeenLog_DialogActor_SeenById", + table: "DialogSeenLog", + column: "SeenById", + principalTable: "DialogActor", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_DialogActivity_DialogActor_PerformedById", + table: "DialogActivity"); + + migrationBuilder.DropForeignKey( + name: "FK_DialogSeenLog_DialogActor_SeenById", + table: "DialogSeenLog"); + + migrationBuilder.DropTable( + name: "DialogActor"); + + migrationBuilder.DropTable( + name: "DialogActorType"); + + migrationBuilder.DropIndex( + name: "IX_DialogSeenLog_SeenById", + table: "DialogSeenLog"); + + migrationBuilder.DropIndex( + name: "IX_DialogActivity_PerformedById", + table: "DialogActivity"); + + migrationBuilder.DropColumn( + name: "IsViaServiceOwner", + table: "DialogSeenLog"); + + migrationBuilder.DropColumn( + name: "SeenById", + table: "DialogSeenLog"); + + migrationBuilder.DropColumn( + name: "PerformedById", + table: "DialogActivity"); + + migrationBuilder.AddColumn( + name: "DialogSeenLogId", + table: "LocalizationSet", + type: "uuid", + nullable: true); + + migrationBuilder.AddColumn( + name: "EndUserId", + table: "DialogSeenLog", + type: "character varying(255)", + maxLength: 255, + nullable: false, + defaultValue: ""); + + migrationBuilder.AddColumn( + name: "EndUserName", + table: "DialogSeenLog", + type: "character varying(255)", + maxLength: 255, + nullable: true); + + migrationBuilder.AddColumn( + name: "PerformedBy", + table: "DialogActivity", + type: "character varying(255)", + maxLength: 255, + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_LocalizationSet_DialogSeenLogId", + table: "LocalizationSet", + column: "DialogSeenLogId", + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_LocalizationSet_DialogSeenLog_DialogSeenLogId", + table: "LocalizationSet", + column: "DialogSeenLogId", + principalTable: "DialogSeenLog", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Migrations/DialogDbContextModelSnapshot.cs b/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Migrations/DialogDbContextModelSnapshot.cs index 224989321..39a624a1f 100644 --- a/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Migrations/DialogDbContextModelSnapshot.cs +++ b/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Migrations/DialogDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("ProductVersion", "8.0.7") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -222,9 +222,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(1023) .HasColumnType("character varying(1023)"); - b.Property("PerformedBy") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); + b.Property("PerformedById") + .HasColumnType("uuid"); b.Property("RelatedActivityId") .HasColumnType("uuid"); @@ -236,6 +235,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("DialogId"); + b.HasIndex("PerformedById"); + b.HasIndex("RelatedActivityId"); b.HasIndex("TypeId"); @@ -290,6 +291,58 @@ protected override void BuildModel(ModelBuilder modelBuilder) }); }); + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors.DialogActor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("ActorId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ActorName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ActorTypeId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ActorTypeId"); + + b.ToTable("DialogActor"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors.DialogActorType", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.ToTable("DialogActorType"); + + b.HasData( + new + { + Id = 1, + Name = "PartyRepresentative" + }, + new + { + Id = 2, + Name = "ServiceOwner" + }); + }); + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Attachments.DialogAttachment", b => { b.Property("Id") @@ -643,24 +696,23 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("DialogId") .HasColumnType("uuid"); - b.Property("EndUserId") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("EndUserName") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - b.Property("EndUserTypeId") .HasColumnType("integer"); + b.Property("IsViaServiceOwner") + .HasColumnType("boolean"); + + b.Property("SeenById") + .HasColumnType("uuid"); + b.HasKey("Id"); b.HasIndex("DialogId"); b.HasIndex("EndUserTypeId"); + b.HasIndex("SeenById"); + b.ToTable("DialogSeenLog"); }); @@ -870,7 +922,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("LocalizationSet"); - b.HasDiscriminator("Discriminator").HasValue("LocalizationSet"); + b.HasDiscriminator().HasValue("LocalizationSet"); b.UseTphMappingStrategy(); }); @@ -992,19 +1044,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasDiscriminator().HasValue("DialogContentValue"); }); - modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogSeenLogVia", b => - { - b.HasBaseType("Digdir.Domain.Dialogporten.Domain.Localizations.LocalizationSet"); - - b.Property("DialogSeenLogId") - .HasColumnType("uuid"); - - b.HasIndex("DialogSeenLogId") - .IsUnique(); - - b.HasDiscriminator().HasValue("DialogSeenLogVia"); - }); - modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions.DialogApiAction", b => { b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogEntity", "Dialog") @@ -1070,6 +1109,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors.DialogActor", "PerformedBy") + .WithMany() + .HasForeignKey("PerformedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities.DialogActivity", "RelatedActivity") .WithMany("RelatedActivities") .HasForeignKey("RelatedActivityId") @@ -1083,11 +1128,24 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Dialog"); + b.Navigation("PerformedBy"); + b.Navigation("RelatedActivity"); b.Navigation("Type"); }); + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors.DialogActor", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors.DialogActorType", "ActorType") + .WithMany() + .HasForeignKey("ActorTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ActorType"); + }); + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Attachments.DialogAttachment", b => { b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogEntity", "Dialog") @@ -1173,9 +1231,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.Restrict) .IsRequired(); + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors.DialogActor", "SeenBy") + .WithMany() + .HasForeignKey("SeenById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + b.Navigation("Dialog"); b.Navigation("EndUserType"); + + b.Navigation("SeenBy"); }); modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Localizations.Localization", b => @@ -1255,17 +1321,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("DialogContent"); }); - modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogSeenLogVia", b => - { - b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogSeenLog", "DialogSeenLog") - .WithOne("Via") - .HasForeignKey("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogSeenLogVia", "DialogSeenLogId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("DialogSeenLog"); - }); - modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions.DialogApiAction", b => { b.Navigation("Endpoints"); @@ -1315,11 +1370,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("SeenLog"); }); - modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogSeenLog", b => - { - b.Navigation("Via"); - }); - modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Localizations.LocalizationSet", b => { b.Navigation("Localizations"); diff --git a/src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/ServiceOwner/Dialogs/Update/UpdateDialogSwaggerConfig.cs b/src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/ServiceOwner/Dialogs/Update/UpdateDialogSwaggerConfig.cs index f1096c891..d4088781d 100644 --- a/src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/ServiceOwner/Dialogs/Update/UpdateDialogSwaggerConfig.cs +++ b/src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/ServiceOwner/Dialogs/Update/UpdateDialogSwaggerConfig.cs @@ -4,6 +4,7 @@ using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content; using Digdir.Domain.Dialogporten.Domain.Http; using Digdir.Domain.Dialogporten.WebApi.Common; @@ -152,7 +153,10 @@ public static RouteHandlerBuilder SetDescription(RouteHandlerBuilder builder) => { Id = Guid.Parse("8b95d42d-d2b6-4c01-8ca0-a817a4b3c50d"), Type = DialogActivityType.Values.Information, - PerformedBy = "Some performed", + PerformedBy = new UpdateDialogDialogActivityActorDto + { + ActorType = DialogActorType.Values.ServiceOwner + }, Description = [ new LocalizationDto diff --git a/src/Digdir.Tool.Dialogporten.GenerateFakeData/DialogGenerator.cs b/src/Digdir.Tool.Dialogporten.GenerateFakeData/DialogGenerator.cs index ff5f69c2e..c70cc0498 100644 --- a/src/Digdir.Tool.Dialogporten.GenerateFakeData/DialogGenerator.cs +++ b/src/Digdir.Tool.Dialogporten.GenerateFakeData/DialogGenerator.cs @@ -5,6 +5,7 @@ using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actors; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Attachments; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content; using Digdir.Domain.Dialogporten.Domain.Http; @@ -120,10 +121,10 @@ public static string GenerateFakeResource() return ResourcePrefix + result.ToString("D4", CultureInfo.InvariantCulture); } - public static string GenerateRandomParty() + public static string GenerateRandomParty(bool forcePerson = false) { var r = new Randomizer(); - return r.Bool() ? $"urn:altinn:organization:identifier-no:{GenerateFakeOrgNo()}" : $"urn:altinn:person:identifier-no:{GenerateFakePid()}"; + return r.Bool() && !forcePerson ? $"urn:altinn:organization:identifier-no:{GenerateFakeOrgNo()}" : $"urn:altinn:person:identifier-no:{GenerateFakePid()}"; } private static readonly int[] SocialSecurityNumberWeights1 = [3, 7, 6, 1, 8, 9, 4, 5, 2]; @@ -215,7 +216,7 @@ public static List GenerateFakeDialogActivities(i .RuleFor(o => o.CreatedAt, f => f.Date.Past()) .RuleFor(o => o.ExtendedType, f => new Uri(f.Internet.UrlWithPath())) .RuleFor(o => o.Type, f => type ?? f.PickRandom()) - .RuleFor(o => o.PerformedBy, f => f.Name.FullName()) + .RuleFor(o => o.PerformedBy, f => new CreateDialogDialogActivityActorDto { ActorType = DialogActorType.Values.PartyRepresentative, ActorName = f.Name.FullName() }) .RuleFor(o => o.Description, f => GenerateFakeLocalizations(f.Random.Number(4, 8))) .Generate(count ?? new Randomizer().Number(1, 4)); } diff --git a/tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Common/DialogApplication.cs b/tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Common/DialogApplication.cs index eef0ef3f5..7adf3bc51 100644 --- a/tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Common/DialogApplication.cs +++ b/tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Common/DialogApplication.cs @@ -61,7 +61,7 @@ public async Task InitializeAsync() .AddScoped() .AddScoped() .AddScoped(_ => CreateServiceOwnerNameRegistrySubstitute()) - .AddScoped(_ => CreateNameRegistrySubstitute()) + .AddScoped(_ => CreateNameRegistrySubstitute()) .AddScoped>(_ => CreateApplicationSettingsSubstitute()) .AddScoped() .AddScoped() @@ -75,9 +75,9 @@ public async Task InitializeAsync() await BuildRespawnState(); } - private static IPersonNameRegistry CreateNameRegistrySubstitute() + private static IPartyNameRegistry CreateNameRegistrySubstitute() { - var nameRegistrySubstitute = Substitute.For(); + var nameRegistrySubstitute = Substitute.For(); nameRegistrySubstitute .GetName(Arg.Any(), Arg.Any()) diff --git a/tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Features/V1/EndUser/Dialogs/Queries/ActivityLogTests.cs b/tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Features/V1/EndUser/Dialogs/Queries/ActivityLogTests.cs new file mode 100644 index 000000000..4106b3be0 --- /dev/null +++ b/tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Features/V1/EndUser/Dialogs/Queries/ActivityLogTests.cs @@ -0,0 +1,95 @@ +using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.Get; +using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.Search; +using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.DialogActivities.Queries.Get; +using Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Commands.Create; +using Digdir.Domain.Dialogporten.Application.Integration.Tests.Common; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; +using Digdir.Domain.Dialogporten.Domain.Parties; +using Digdir.Tool.Dialogporten.GenerateFakeData; +using FluentAssertions; + +namespace Digdir.Domain.Dialogporten.Application.Integration.Tests.Features.V1.EndUser.Dialogs.Queries; + +[Collection(nameof(DialogCqrsCollectionFixture))] +public class ActivityLogTests(DialogApplication application) : ApplicationCollectionFixture(application) +{ + [Fact] + public async Task Get_Dialog_ActivityLog_Should_Not_Return_User_Ids_Unhashed() + { + var (_, createCommandResponse) = await GenerateDialogWithActivity(); + + // Act + var response = await Application.Send(new GetDialogQuery { DialogId = createCommandResponse.AsT0.Value }); + + // Assert + response.TryPickT0(out var result, out _).Should().BeTrue(); + result.Should().NotBeNull(); + + result.Activities + .Single() + .PerformedBy.ActorId + .Should() + .StartWith(NorwegianPersonIdentifier.HashPrefixWithSeparator); + + } + + [Fact] + public async Task Search_Dialog_LatestActivity_Should_Not_Return_User_Ids_Unhashed() + { + // Arrange + var (createDialogCommand, _) = await GenerateDialogWithActivity(); + + // Act + var response = await Application.Send(new SearchDialogQuery + { + ServiceResource = [createDialogCommand.ServiceResource] + }); + + // Assert + response.TryPickT0(out var result, out _).Should().BeTrue(); + result.Should().NotBeNull(); + + result.Items + .Single() + .LatestActivity! + .PerformedBy.ActorId + .Should() + .StartWith(NorwegianPersonIdentifier.HashPrefixWithSeparator); + } + + [Fact] + public async Task Get_ActivityLog_Should_Not_Return_User_Ids_Unhashed() + { + // Arrange + var (_, createCommandResponse) = await GenerateDialogWithActivity(); + + var getDialogResult = await Application.Send(new GetDialogQuery { DialogId = createCommandResponse.AsT0.Value }); + var activityId = getDialogResult.AsT0.Activities.First().Id; + + // Act + var response = await Application.Send(new GetDialogActivityQuery() + { + DialogId = createCommandResponse.AsT0.Value, + ActivityId = activityId + }); + + // Assert + response.TryPickT0(out var result, out _).Should().BeTrue(); + result.Should().NotBeNull(); + + result.PerformedBy.ActorId + .Should() + .StartWith(NorwegianPersonIdentifier.HashPrefixWithSeparator); + } + + private async Task<(CreateDialogCommand, CreateDialogResult)> GenerateDialogWithActivity() + { + var createDialogCommand = DialogGenerator.GenerateSimpleFakeDialog(); + var activity = DialogGenerator.GenerateFakeDialogActivity(type: DialogActivityType.Values.Information); + activity.PerformedBy.ActorId = DialogGenerator.GenerateRandomParty(forcePerson: true); + activity.PerformedBy.ActorName = null; + createDialogCommand.Activities.Add(activity); + var createCommandResponse = await Application.Send(createDialogCommand); + return (createDialogCommand, createCommandResponse); + } +} diff --git a/tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Features/V1/EndUser/Dialogs/Queries/SeenLogTests.cs b/tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Features/V1/EndUser/Dialogs/Queries/SeenLogTests.cs index d6ac9a305..37578216e 100644 --- a/tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Features/V1/EndUser/Dialogs/Queries/SeenLogTests.cs +++ b/tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Features/V1/EndUser/Dialogs/Queries/SeenLogTests.cs @@ -1,9 +1,9 @@ -using Digdir.Domain.Dialogporten.Application.Common; using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.Get; using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.Search; using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.DialogSeenLogs.Queries.Get; using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.DialogSeenLogs.Queries.Search; using Digdir.Domain.Dialogporten.Application.Integration.Tests.Common; +using Digdir.Domain.Dialogporten.Domain.Parties; using Digdir.Tool.Dialogporten.GenerateFakeData; using FluentAssertions; @@ -13,9 +13,9 @@ namespace Digdir.Domain.Dialogporten.Application.Integration.Tests.Features.V1.E public class SeenLogTests(DialogApplication application) : ApplicationCollectionFixture(application) { [Fact] - public async Task Get_Dialog_Should_Not_Return_User_Ids_Unhashed() + public async Task Get_Dialog_SeenLog_Should_Not_Return_User_Ids_Unhashed() { - // Arrange + var createDialogCommand = DialogGenerator.GenerateSimpleFakeDialog(); var createCommandResponse = await Application.Send(createDialogCommand); @@ -28,13 +28,14 @@ public async Task Get_Dialog_Should_Not_Return_User_Ids_Unhashed() result.SeenSinceLastUpdate .Single() - .EndUserIdHash + .SeenBy.ActorId .Should() - .HaveLength(PersistentRandomSaltStringHasher.StringLength); + .StartWith(NorwegianPersonIdentifier.HashPrefixWithSeparator); + } [Fact] - public async Task Search_Dialog_Should_Not_Return_User_Ids_Unhashed() + public async Task Search_Dialog_SeenLog_Should_Not_Return_User_Ids_Unhashed() { // Arrange var createDialogCommand = DialogGenerator.GenerateSimpleFakeDialog(); @@ -57,9 +58,9 @@ public async Task Search_Dialog_Should_Not_Return_User_Ids_Unhashed() .Single() .SeenSinceLastUpdate .Single() - .EndUserIdHash + .SeenBy.ActorId .Should() - .HaveLength(PersistentRandomSaltStringHasher.StringLength); + .StartWith(NorwegianPersonIdentifier.HashPrefixWithSeparator); } [Fact] @@ -83,9 +84,9 @@ public async Task Get_SeenLog_Should_Not_Return_User_Ids_Unhashed() response.TryPickT0(out var result, out _).Should().BeTrue(); result.Should().NotBeNull(); - result.EndUserIdHash + result.SeenBy.ActorId .Should() - .HaveLength(PersistentRandomSaltStringHasher.StringLength); + .StartWith(NorwegianPersonIdentifier.HashPrefixWithSeparator); } [Fact] @@ -109,8 +110,8 @@ public async Task Search_SeenLog_Should_Not_Return_User_Ids_Unhashed() result.Should().NotBeNull(); result.Single() - .EndUserIdHash + .SeenBy.ActorId .Should() - .HaveLength(PersistentRandomSaltStringHasher.StringLength); + .StartWith(NorwegianPersonIdentifier.HashPrefixWithSeparator); } } diff --git a/tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Features/V1/ServiceOwner/Dialogs/Queries/ActivityLogTests.cs b/tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Features/V1/ServiceOwner/Dialogs/Queries/ActivityLogTests.cs new file mode 100644 index 000000000..7ae32eb66 --- /dev/null +++ b/tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Features/V1/ServiceOwner/Dialogs/Queries/ActivityLogTests.cs @@ -0,0 +1,96 @@ +using Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Queries.Get; +using Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Queries.Search; +using Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.DialogActivities.Queries.Get; +using Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Commands.Create; +using Digdir.Domain.Dialogporten.Application.Integration.Tests.Common; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; +using Digdir.Domain.Dialogporten.Domain.Parties; +using Digdir.Tool.Dialogporten.GenerateFakeData; +using FluentAssertions; + +namespace Digdir.Domain.Dialogporten.Application.Integration.Tests.Features.V1.ServiceOwner.Dialogs.Queries; + +[Collection(nameof(DialogCqrsCollectionFixture))] +public class ActivityLogTests(DialogApplication application) : ApplicationCollectionFixture(application) +{ + [Fact] + public async Task Get_Dialog_ActivityLog_Should_Return_User_Ids_Unhashed() + { + // Arrange + var (_, createCommandResponse) = await GenerateDialogWithActivity(); + + // Act + var response = await Application.Send(new GetDialogQuery { DialogId = createCommandResponse.AsT0.Value }); + + // Assert + response.TryPickT0(out var result, out _).Should().BeTrue(); + result.Should().NotBeNull(); + + result.Activities + .Single() + .PerformedBy.ActorId + .Should() + .StartWith(NorwegianPersonIdentifier.PrefixWithSeparator); + + } + + [Fact] + public async Task Search_Dialog_LatestActivity_Should_Return_User_Ids_Unhashed() + { + // Arrange + var (createDialogCommand, _) = await GenerateDialogWithActivity(); + + // Act + var response = await Application.Send(new SearchDialogQuery + { + ServiceResource = [createDialogCommand.ServiceResource] + }); + + // Assert + response.TryPickT0(out var result, out _).Should().BeTrue(); + result.Should().NotBeNull(); + + result.Items + .Single() + .LatestActivity! + .PerformedBy.ActorId + .Should() + .StartWith(NorwegianPersonIdentifier.PrefixWithSeparator); + } + + [Fact] + public async Task Get_ActivityLog_Should_Return_User_Ids_Unhashed() + { + // Arrange + var (_, createCommandResponse) = await GenerateDialogWithActivity(); + + var getDialogResult = await Application.Send(new GetDialogQuery { DialogId = createCommandResponse.AsT0.Value }); + var activityId = getDialogResult.AsT0.Activities.First().Id; + + // Act + var response = await Application.Send(new GetDialogActivityQuery() + { + DialogId = createCommandResponse.AsT0.Value, + ActivityId = activityId + }); + + // Assert + response.TryPickT0(out var result, out _).Should().BeTrue(); + result.Should().NotBeNull(); + + result.PerformedBy.ActorId + .Should() + .StartWith(NorwegianPersonIdentifier.PrefixWithSeparator); + } + + private async Task<(CreateDialogCommand, CreateDialogResult)> GenerateDialogWithActivity() + { + var createDialogCommand = DialogGenerator.GenerateSimpleFakeDialog(); + var activity = DialogGenerator.GenerateFakeDialogActivity(type: DialogActivityType.Values.Information); + activity.PerformedBy.ActorId = DialogGenerator.GenerateRandomParty(forcePerson: true); + activity.PerformedBy.ActorName = null; + createDialogCommand.Activities.Add(activity); + var createCommandResponse = await Application.Send(createDialogCommand); + return (createDialogCommand, createCommandResponse); + } +} diff --git a/tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Features/V1/ServiceOwner/Dialogs/Queries/SeenLogTests.cs b/tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Features/V1/ServiceOwner/Dialogs/Queries/SeenLogTests.cs index f55925c03..c1471fe91 100644 --- a/tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Features/V1/ServiceOwner/Dialogs/Queries/SeenLogTests.cs +++ b/tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Features/V1/ServiceOwner/Dialogs/Queries/SeenLogTests.cs @@ -1,8 +1,8 @@ -using Digdir.Domain.Dialogporten.Application.Common; using Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Queries.Search; using Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.DialogSeenLogs.Queries.Get; using Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.DialogSeenLogs.Queries.Search; using Digdir.Domain.Dialogporten.Application.Integration.Tests.Common; +using Digdir.Domain.Dialogporten.Domain.Parties; using Digdir.Tool.Dialogporten.GenerateFakeData; using FluentAssertions; using GetDialogQueryEndUser = Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.Get.GetDialogQuery; @@ -14,7 +14,7 @@ namespace Digdir.Domain.Dialogporten.Application.Integration.Tests.Features.V1.S public class SeenLogTests(DialogApplication application) : ApplicationCollectionFixture(application) { [Fact] - public async Task Get_Dialog_Should_Not_Return_User_Ids_Unhashed() + public async Task Get_Dialog_SeenLog_Should_Return_User_Ids_Unhashed() { // Arrange var createDialogCommand = DialogGenerator.GenerateSimpleFakeDialog(); @@ -32,13 +32,13 @@ public async Task Get_Dialog_Should_Not_Return_User_Ids_Unhashed() result.SeenSinceLastUpdate .Single() - .EndUserIdHash + .SeenBy.ActorId .Should() - .HaveLength(PersistentRandomSaltStringHasher.StringLength); + .StartWith(NorwegianPersonIdentifier.PrefixWithSeparator); } [Fact] - public async Task Search_Dialog_Should_Not_Return_User_Ids_Unhashed() + public async Task Search_Dialog_SeenLog_Should_Return_User_Ids_Unhashed() { // Arrange var createDialogCommand = DialogGenerator.GenerateSimpleFakeDialog(); @@ -61,13 +61,13 @@ public async Task Search_Dialog_Should_Not_Return_User_Ids_Unhashed() .Single() .SeenSinceLastUpdate .Single() - .EndUserIdHash + .SeenBy.ActorId .Should() - .HaveLength(PersistentRandomSaltStringHasher.StringLength); + .StartWith(NorwegianPersonIdentifier.PrefixWithSeparator); } [Fact] - public async Task Get_SeenLog_Should_Not_Return_User_Ids_Unhashed() + public async Task Get_SeenLog_Should_Return_User_Ids_Unhashed() { // Arrange var createDialogCommand = DialogGenerator.GenerateSimpleFakeDialog(); @@ -87,13 +87,13 @@ public async Task Get_SeenLog_Should_Not_Return_User_Ids_Unhashed() response.TryPickT0(out var result, out _).Should().BeTrue(); result.Should().NotBeNull(); - result.EndUserIdHash + result.SeenBy.ActorId .Should() - .HaveLength(PersistentRandomSaltStringHasher.StringLength); + .StartWith(NorwegianPersonIdentifier.PrefixWithSeparator); } [Fact] - public async Task Search_SeenLog_Should_Not_Return_User_Ids_Unhashed() + public async Task Search_SeenLog_Should_Return_User_Ids_Unhashed() { // Arrange var createDialogCommand = DialogGenerator.GenerateSimpleFakeDialog(); @@ -113,8 +113,8 @@ public async Task Search_SeenLog_Should_Not_Return_User_Ids_Unhashed() result.Should().NotBeNull(); result.Single() - .EndUserIdHash + .SeenBy.ActorId .Should() - .HaveLength(PersistentRandomSaltStringHasher.StringLength); + .StartWith(NorwegianPersonIdentifier.PrefixWithSeparator); } } diff --git a/tests/Digdir.Domain.Dialogporten.Application.Unit.Tests/Features/V1/ServiceOwner/Dialogs/Commands/CreateDialogTests.cs b/tests/Digdir.Domain.Dialogporten.Application.Unit.Tests/Features/V1/ServiceOwner/Dialogs/Commands/CreateDialogTests.cs index dab9d8f2f..6093490f0 100644 --- a/tests/Digdir.Domain.Dialogporten.Application.Unit.Tests/Features/V1/ServiceOwner/Dialogs/Commands/CreateDialogTests.cs +++ b/tests/Digdir.Domain.Dialogporten.Application.Unit.Tests/Features/V1/ServiceOwner/Dialogs/Commands/CreateDialogTests.cs @@ -1,6 +1,7 @@ using AutoMapper; using Digdir.Domain.Dialogporten.Application.Common; using Digdir.Domain.Dialogporten.Application.Common.ResourceRegistry; +using Digdir.Domain.Dialogporten.Application.Externals; using Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Commands.Create; using Digdir.Tool.Dialogporten.GenerateFakeData; using NSubstitute; @@ -25,6 +26,7 @@ public async Task CreateDialogCommand_Should_Return_Forbidden_When_Scope_Is_Miss var domainContextSub = Substitute.For(); var userResourceRegistrySub = Substitute.For(); var userOrganizationRegistrySub = Substitute.For(); + var partyNameRegistrySub = Substitute.For(); var createCommand = DialogGenerator.GenerateSimpleFakeDialog(); @@ -37,7 +39,7 @@ public async Task CreateDialogCommand_Should_Return_Forbidden_When_Scope_Is_Miss var commandHandler = new CreateDialogCommandHandler(dialogDbContextSub, mapper, unitOfWorkSub, domainContextSub, userResourceRegistrySub, - userOrganizationRegistrySub); + userOrganizationRegistrySub, partyNameRegistrySub); // Act var result = await commandHandler.Handle(createCommand, CancellationToken.None); @@ -63,6 +65,7 @@ public async Task CreateDialogCommand_Should_Return_ValidationError_When_Progres var domainContextSub = Substitute.For(); var userResourceRegistrySub = Substitute.For(); var userOrganizationRegistrySub = Substitute.For(); + var partyNameRegistrySub = Substitute.For(); var createCommand = DialogGenerator.GenerateSimpleFakeDialog(); @@ -77,7 +80,7 @@ public async Task CreateDialogCommand_Should_Return_ValidationError_When_Progres var commandHandler = new CreateDialogCommandHandler(dialogDbContextSub, mapper, unitOfWorkSub, domainContextSub, userResourceRegistrySub, - userOrganizationRegistrySub); + userOrganizationRegistrySub, partyNameRegistrySub); // Act var result = await commandHandler.Handle(createCommand, CancellationToken.None);